当前位置:   article > 正文

【原创】Verilog实现常见数据结构计划(一)顺序线性表_顺序链表 verilog

顺序链表 verilog

引言

数据结构不仅仅是一门软件基础课,同时也是一门硬件基础课,可以说是数字设计的入门课。数据结构里详细介绍了链表、队列、堆栈、哈希等基本底层操作,这些底层操作在一些高级语言中都被封装好了,但是在做硬件设计时都是需要考虑的,如内存分配和存储问题,在硬件设计中是没有编译器来帮我们完成的,都需要一步一步设计来完成。
这里将分几期来演示使用Verilog实现常用数据结构的操作,本文实现Verilog实现顺序线性表的插入操作


一、顺序线性表

顺序表的定义是用一段地址连续的存储单元一次存储线性表的数据元素,而C语言中的数组本质上也是存储器中的一片地址连续的空间,所以顺序表在C中常用一个一维数组实现(这里指非变长数组)。
描述顺序表需要三个属性:存储空间的首地址线性表实时长度存储空间最大容量。

顺序表的 i 位置插入数据的步骤:
1. 从最后一个数据向前遍历到第 i 个位置,将它们一个一个向后移动 1 位。
2. 将要插入的数据插进 i 处。
3. 表长自增 1 。

二、顺序线性表插入操作模块设计

Verilog代码如下:

  1. `timescale 1ns/1ns
  2. module seqlist(
  3. input clk,
  4. input rst_n,
  5. // 顺序表插入操作
  6. input [9:0] wr_addr, // 插入地址
  7. input [7:0] wr_data, // 插入数据
  8. input wr_valid, // 数据有效信号
  9. output reg wr_done, // 插入完成信号
  10. // 顺序表读取操作
  11. input [9:0] rd_addr, // 读地址
  12. input rd_valid, // 读使能
  13. output reg [7:0] rd_data, // 读数据
  14. output [9:0] list_length // 线性表长度
  15. );
  16. // parameter define
  17. localparam IDLE = 2'b01;
  18. localparam SHIFT = 2'b10;
  19. reg [7:0] dram [1023:0]; // 内存空间
  20. reg [9:0] cnt; // 移位计数器
  21. reg [9:0] length; // 线性表长度
  22. reg [1:0] state; // 状态寄存器
  23. assign list_length = length;
  24. // 内存读取
  25. always @(*) begin
  26. if( rd_valid == 1'b1 )
  27. rd_data <= dram[rd_addr];
  28. else
  29. rd_data <= rd_data;
  30. end
  31. // 顺序表插入操作
  32. integer i;
  33. always @(posedge clk or negedge rst_n) begin
  34. if (!rst_n) begin
  35. wr_done <= 1'b0;
  36. cnt <= 10'd256 - 1'b1;
  37. length <= 10'd256; // 线性表初始长度为256
  38. state <= IDLE;
  39. // 内存空间初始化
  40. for( i=0; i<1024; i=i+1 )
  41. dram[i] <= i[7:0];
  42. end
  43. else begin
  44. case( state )
  45. IDLE : begin
  46. wr_done <= 1'b0;
  47. cnt <= length - 1'b1;
  48. if( wr_valid == 1'b1 )
  49. state <= SHIFT;
  50. else
  51. state <= IDLE;
  52. end
  53. SHIFT : begin
  54. if( cnt >= wr_addr ) begin
  55. cnt <= cnt - 1'b1;
  56. dram[cnt+1] <= dram[cnt];
  57. end
  58. else begin
  59. length <= length + 1'b1;
  60. dram[wr_addr] <= wr_data;
  61. state <= IDLE;
  62. wr_done <= 1'b1;
  63. end
  64. end
  65. default : ;
  66. endcase
  67. end
  68. end
  69. endmodule

此模块定义了两个状态:空闲态(IDLE) 和 移位状态(SHIFT)。
1. 在空闲态中,等待插入操作启动信号 wr_valid 有效。此信号有效后进入移位状态。
2. 在移位状态中, 从最后一个数据向前遍历到第 wr_addr 个位置,将它们一个一个向后移动 1 位。wr_addr 就是要插入数据的地址。等待移位过程结束后将待插入数据插入地址 wr_addr 中,表长加一,并且拉高状态信号 wr_done 一个时钟周期,表示插入操作完成。

三、testbench设计

testbench代码如下:

  1. `timescale 1ns/1ns
  2. module seqlist_tb;
  3. // input signal
  4. reg clk;
  5. reg rst_n;
  6. reg [9:0] wr_addr;
  7. reg [7:0] wr_data;
  8. reg wr_valid;
  9. reg [9:0] rd_addr;
  10. reg rd_valid;
  11. // output signal
  12. wire wr_done;
  13. wire [7:0] rd_data;
  14. wire [9:0] list_length;
  15. // reg define
  16. reg [3:0] tb_state;
  17. // 复位
  18. initial begin
  19. rst_n = 1;
  20. #50 rst_n = 0;
  21. #100 rst_n = 1;
  22. end
  23. // 生成时钟
  24. initial begin
  25. clk = 0;
  26. forever #5 clk = ~clk;
  27. end
  28. // 信号初始化 打开dat文件
  29. integer handle;
  30. initial begin
  31. wr_addr = 10'd0;
  32. wr_data = 8'd0;
  33. wr_valid = 0;
  34. rd_addr = 10'd0;
  35. rd_valid = 0;
  36. tb_state = 4'b0001;
  37. // 打开dat文件
  38. handle = $fopen("C:/Users/xumingwei/Desktop/seqlist/sim/data_out.dat");
  39. $fdisplay(handle,"数据 地址 表长\n");
  40. end
  41. // 激励文件状态机
  42. always @(posedge clk or negedge rst_n) begin
  43. if( !rst_n ) begin
  44. wr_addr <= 10'd0;
  45. wr_data <= 8'd0;
  46. wr_valid <= 0;
  47. rd_addr <= 10'd0;
  48. rd_valid <= 0;
  49. tb_state <= 4'b0001;
  50. end
  51. else begin
  52. wr_valid <= 1'b0;
  53. rd_valid <= 1'b0;
  54. case( tb_state )
  55. // 向顺序表中插入数据
  56. 4'b0001 : begin
  57. wr_addr <= 10'd5;
  58. wr_data <= 8'd123;
  59. wr_valid <= 1'b1;
  60. tb_state <= 4'b0010;
  61. end
  62. // 等待数据插入完毕
  63. 4'b0010 : begin
  64. if( wr_done == 1'b1 ) begin
  65. tb_state <= 4'b0100;
  66. rd_valid <= 1; // 读出地址0的数据
  67. end
  68. else
  69. tb_state <= 4'b0010;
  70. end
  71. // 读出并打印内存空间的数据
  72. 4'b0100 : begin
  73. if( rd_addr < 10'd1023 ) begin
  74. rd_addr <= rd_addr + 1'b1;
  75. rd_valid <= 1;
  76. // 将内存空间中的数据写入dat文件中
  77. $fdisplay(handle,"%d %d %d",rd_data,rd_addr,list_length);
  78. end
  79. else if( rd_addr == 10'd1023 ) begin
  80. tb_state <= 4'b1000;
  81. rd_addr <= 10'd0;
  82. $fdisplay(handle,"%d %d %d",rd_data,rd_addr,list_length);
  83. end
  84. end
  85. 4'b1000 : ; // 仿真停止
  86. default : ;
  87. endcase
  88. end
  89. end
  90. // top例化
  91. seqlist tb_seqlist(
  92. .clk ( clk ),
  93. .rst_n ( rst_n ),
  94. .wr_addr ( wr_addr ),
  95. .wr_data ( wr_data ),
  96. .wr_valid ( wr_valid ),
  97. .wr_done ( wr_done ),
  98. .rd_addr ( rd_addr ),
  99. .rd_valid ( rd_valid ),
  100. .rd_data ( rd_data ),
  101. .list_length( list_length )
  102. );
  103. endmodule

在 testbench 中操作顺序表模块向表的地址 10'd5 中插入数据 8'd123 ,并将插入后的整个内存空间中的数据打印至文件 data_out.dat 中,便于直观地观察实验结果。

四、仿真结果

编写自动化仿真脚本,在Modelsim中启动仿真,可见写入后的 data_out.dat 文件如下图所示。在地址 5 的位置成功插入了数据 123 ,且表长自增为257(初始表长设为256)

在仿真波形中也可以看到,当 rd_valid (读存储器使能)拉高后,顺序读出存储空间中的新数据,可见在地址 5 处插入了数据 123 。


顺序表的插入和删除需要移动大量数据,此时时间复杂度是O(n)。即使在硬件中可以并行移动所有数据,但也会消耗大量硬件资源,尤其当存储空间很大时。
由此看来,无论硬件软件,顺序表的缺点都是一样的,不适合进行插入和删除操作,会浪费大量时间或硬件面积。 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/952454
推荐阅读
相关标签
  

闽ICP备14008679号