上节内容讲解了ROM的生成、配置与使用,本节内容讲解利用FPGA内部的memory(可以是block memory 或是distribute memory),并举例说明单口RAM的使用。
1. 单口RAM的生成
- 新建工程,工程名s_port_ram,顶层设计实体名s_port_ram, 器件选择 cyclone LP, 10CL006YE144C8G, EAD tools仿真(simulation)选择Modelsim-Altera. Format 选择Verilog
- 在IP catalog选择单口RAM, IP Catalog–>Install IP–>Library–>Basic Functions–>On chip Memory–>RAM:1 PORT , 如图1

图1
双击RAM: 1 PORT ,在跳出的对话框中输入将要生成的单口RAM名称 — s_port_ram, 如 图2,

图2

图3
- Widths/Blk Type/Clks选择

图4
-
- 选择时钟的驱动方式, 可以选择单时钟,即输入、输出共享一个时钟。也可以选择双时钟,即输入、输出的时钟是分开的。但从图4左上角的示意图可以看出,由于地址线(address),data, wren等都是与inclock同步,只有q与outclock同步,因此选择双时钟的意义不大(即使外部使用RAM的程序是双时钟的)。
- 选择需要寄存的端口,如图5, 由于写入端口(data, wren, address)是必须寄存的,只有q可以选择是否寄存,如果选择非寄存方式,当然也就没有outclock的选择。选择寄存方式可以提升时序收敛效果。
- 创建时钟使能,可以为每一个寄存的信号创建使能(create one clock enable signal for each clock signal),如address,data,wren,q等,在没有读写需求时,clock信号不能穿透这些信号,同时这些信号也不会反转,因此在没有读写需求时,可以降低功耗。达到低功耗的设计效果。
- 创建异步清零端,由于data,address ,wren ,rden等信号都是锁存信号,因此在初始化时,它们的值是随机的,提供异步清零可以在初始化时处在一个确定的状态。注意不是对data,address,wren,rden等本身信号清零,而是对寄存器的输出清零。
- 创建读使能,由于是单口RAM,因此输入输出共享地址address, 如果没有读使能,在写入时,由于地址的变化,也会引起输出端数值的变化,即使外部此时没有读需求,也会引起功耗的上升,因此建议创建rden端,如果在应用程序中不用该使能控制,在例化的程序中将该端直接设为1’b1即可。

图5

图6
图7

图8

图9
-
- 如图10, 选择生成的文件,并将生成的文件加入项目中

图10

图11
2. 编写测试文件
- 编写testbench测试文件,并例化生成的单端口RAM IP核
新建Verilog文件,实现单端口RAM的测试, 测试代码如下:
|
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 |
`timescale 1 ns / 1 ps module test_ram ( ); parameter PERIOD = 20; reg clk; reg rst; reg [7:0] address; reg wren; reg rden; reg [7:0] data_in; wire [7:0] data_out; reg [2:0] rd_wr_st; initial begin clk = 0; rst = 1'b1; #50 rst = 1'b0; end always #(PERIOD/2) clk = ~clk; always@(posedge clk or posedge rst) if(rst) begin address <= 0; wren <= 0; rden <= 0; data_in <= 0; rd_wr_st <= 0; end else begin case (rd_wr_st) 0: begin address <= 0; wren <= 0; rden <= 1'b1; data_in <= 0; rd_wr_st <= 1; end 1: begin rden <= 1'b1; wren <= 0; if(address == 255) begin data_in <= address + 2; address <= 0; rden <= 1'b0; wren <= 1'b1; rd_wr_st <= 2; end else address <= address + 1; end 2: begin rden <= 1'b0; wren <= 1'b1; data_in <= address + 2; if(address == 255) begin address <= 0; rden <= 1'b1; wren <= 1'b0; rd_wr_st <= 1; end else address <= address + 1; end default: rd_wr_st <= 0; endcase end s_port_ram s_port_ram_inst ( .address (address), .clock (clk), .data (data_in), .rden (rden), .wren (wren), .q (data_out) ); endmodule |
仿真波形如图12,13,14,15

图12 初始化读

图13 写数据-1

图14 写数据2,写后读
从图14,可以看出,由于数据、地址是触发器类型,而且从图5选择输出q也有一级寄存器,因此给出地址address,rden后要延时2个时钟才会有相应的数据输出。
练习题:
- 上面的测试例子采用非阻塞式赋值语句,用阻塞式赋值语句改写,观察读写过程。
- 将输出q修改为非寄存模式,观察写后读数据过程,与寄存模式有何不同。
对应视频:
