上节内容讲解了双口RAM的生成步骤以及各选项的细节讲解,本节着重介绍双口RAM的测试及仿真。由于双口RAM作为FPGA片上RAM应用极其广泛,使用相对灵活,是FPGA学习中应重点掌握的内容。而且每个使用方案对应的时序也不尽相同,只有多练、多操作、多观察和思考才能掌握各种使用技巧。按照上节内容生成双口RAM,例化并按照如下步骤进行仿真与测试。
1.新建verilog文件 dpram.v(模块名dpram,与工程的顶层实体名一致)
2.例化新生成的双口RAM并例化
关于双端口RAM IP核的生成,参考文章Quartus 双口RAM例化详解。
将a,b端口重新整理,代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
module dpram ( //==================================a side input inclka, input [8:0] address_a, input [15:0] data_a, input wren_a, input rden_a, output [15:0] q_a, //==================================b side input inclkb, input [8:0] address_b, input [15:0] data_b, input wren_b, input rden_b, output [15:0] q_b ); d_p_ram d_p_ram_inst ( .clock_a (inclka), .address_a (address_a), .data_a (data_a), //data --write to memory .rden_a (rden_a), .wren_a (wren_a), .q_a (q_a), //read, data out from memory .clock_b (inclkb), .address_b (address_b), .data_b (data_b), .rden_b (rden_b), .wren_b (wren_b), .q_b (q_b) ); endmodule |
新生成的工程中双口RAM的示意图如图1

图1
3.双口RAM测试内容
为了能比较容易观察写入和读出数据,本测试设置写入的数据为16-bit宽度,为了观察a,b端口数据的输入与输出以及数据的综合应用,设计a,b端口对数据包的操作如下:
- 同步时钟下,输出端口非寄存输出的情况下,观察a端口写入,a端口读出延时。a端口写入,b端口读出的延时。注:读出时选择读出旧数据(old data),如图2,
- 在状态1(st==1)时,在地址为1的单元写入16’h55aa;

图2
测试程序如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
`timescale 1ns /1ps module addab ( ); parameter PERIOD = 20; reg inclka; wire inclkb; reg rst; reg [8:0] address_a; reg [15:0] data_a; wire [15:0] q_a; reg rden_a; reg wren_a; reg [8:0] address_b; reg [15:0] data_b; wire [15:0] q_b; reg rden_b; reg wren_b; reg [2:0] st; assign inclkb = inclka; initial begin rst = 1'b1; inclka = 0; #50 rst = 0; end always #(PERIOD/2) inclka = ~inclka; always@(posedge inclka or posedge rst) if(rst) begin st <= 0; address_a <= 0; address_b <= 0; rden_a <= 1'b0; rden_b <= 1'b0; wren_a <= 1'b0; wren_b <= 1'b0; data_a <= 0; data_b <= 0; end else begin case(st) 0: begin address_a <= 0; address_b <= 0; rden_a <= 1'b1; rden_b <= 1'b1; wren_a <= 1'b0; wren_b <= 1'b0; data_a <= 0; data_b <= 0; st <= 1; end 1: begin address_a <= 1; address_b <= 1; wren_a <= 1'b1; data_a <= 16'h55aa; st <= 2; end 2: begin wren_a <= 1'b0; st <= 3; end 3: begin wren_a <= 1'b0; st <= 4; end 4: begin wren_a <= 1'b0; st <= 5; end 5: begin wren_a <= 1'b0; st <= 5; end default: st <= 0; endcase end dpram dpram_inst ( .inclka (inclka), .address_a (address_a), .data_a (data_a), //data --write to memory .rden_a (rden_a), .wren_a (wren_a), .q_a (q_a), //read, data out from memory .inclkb (inclkb), .address_b (address_b), .data_b (data_b), .rden_b (rden_b), .wren_b (wren_b), .q_b (q_b) ); endmodule |
仿真波形如图3:

图3
- 波形分析:从图3可以看出,在状态机st为1时对a端口进行赋值,在状态机st=2时,地址address_a,数据data_a,写使能wren_a都得到相应的数据,但双口RAM内部对地址address_a,数据data_a,写使能wren_a都进行了锁存,因此写入到RAM中将会再延迟一个时钟,所以真正将数据写入到RAM中是在状态机st=2结束时的时钟上升沿时刻,最终将数据写到RAM并且数据稳定是在状态机st=3时,但是由于在生成双口RAM时选择了读取旧数据(old data),因此在状态机st=3并没有及时读出,而实际读出数据是在状态机st=4。
- 修改a端口和b端口的读出数据都为新数据( new data),如图4,再观察仿真结果。

图4
测试条件不变,仿真波形如图5

图5
从图5的波形可以看出,由于在读出端口设置了读新数据,因此a端口读出的值比图3提前了一个时钟,但b端口输出并没有因为改成新数据提前一个时钟。注意在输入、输出为异步时钟时,读取新数据并不稳定,在使用新数据读取时一定要注意异步时钟的亚稳态效应。
- 同步时钟,a端口。b端口输出端口寄存的情况下,观察a端口写入,a端口读出延时。a端口写入,b端口读出的延时,此时采用读取新数据(new data)。如图6,

图6
测试条件不变,仿真结果如图7,

图7
从图7中可以看出,尽管读取新数据,但由于数据的数据采用寄存器寄存,因此又延时了一个时钟,a端口读取数据又推迟到了状态4。b端口读取数据推迟到状态5。
图8,是在其它测试条件不变的情况下,读取端更改为a端口读取旧数据,b端口读取新数据,观察输入、输出

图8
仿真数据如图9,

图9
从图9可以看出,读取数据是否是新数据还是旧数据只影响a端口的延迟,而b端口读取新、旧数据的延迟一致,这一点在使用双口RAM时一定要注意。
对应视频: