上节内容讲解了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选择
- 端口宽度选择,原则上1~256 bits 任意选择, 选择后数据输入data与数据输出q的宽度相同,如果选择M9K结构的片上存储器类型,宽度设成9的整数倍(如1,9,18,27,36等),内部的M9K利用率最高. 这里选择8bit进行测试.
- 深度选择,选择memory的深度,选择256进行测试
- 类型: 这里有两种类型可供选择:M9K,LCs, M9K 是FPGA内部的存储器块,在本教材资料部分有专门针对M9K等片上存储器类型的介绍.LC是逻辑单元,实际上就是利用逻辑单元的查找表(也就是常说的分布式存储器),在这里选择Auto类型,让系统工具帮助选择最佳方案.
图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
-
- 在同一个地址上出现了写数据的同时又要读数据,可以提供两种选择:一种是读旧数据(old data)如图6,一种是读取新数据,也就是将写入的数据直接复制到数据输出端口q。这种使用方式在单口RAM作为CPU内存使用时提供了便捷的数据复制方式,如图7。读取新数据时如果提供了MASK信号(当多个字节读时),被屏蔽的信号(字节)以X(无关或未知)值输出。
图6
图7
-
- 图8,选择是否加载初始化文件*.mif或*.hex文件,这项设置于ROM的设置相同,可以参照ROM的初始化文件设置。如果没有加载初始化文件,在上电后直接读取RAM中的内容将是0000,或XXXX(如果选择了Initialize memory content data to XXXX on power up in simulation)
图8
-
- 为了能正确仿真RAM的功能,需要使用Altera_mf库文件。
图9
-
- 如图10, 选择生成的文件,并将生成的文件加入项目中
图10
图11
2. 编写测试文件
- 编写testbench测试文件,并例化生成的单端口RAM IP核
新建Verilog文件,实现单端口RAM的测试, 测试代码如下:
`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修改为非寄存模式,观察写后读数据过程,与寄存模式有何不同。
对应视频: