前言
RAM以及ROM在FPGA中的实现大体有两种方式,一种是使用IP核定制,一种是RTL设计。
也许有人会反驳,那原语呢? 我不喜欢讨论这个问题,原语你去使用吗?如果你真的喜欢,请自便。
下面我们讨论这两种实现方式:
首先是RTL的设计,这种方式中,我们重点在于实现逻辑设计。
在IP核的定制中,我们将分别定制一种简单的RAM和ROM的IP核,并讨论它们使用中的一些参数注意事项。(这种方式,下一节讨论)
RAM的RTL设计
RAM的实现分类在RAM的实现中,我们根据数据是否与时钟同步,分为同步RAM以及异步RAM,如果继续细分地话,我们可以将RAM分为同步读同步写,同步读异步写,异步读,异步写等等组合,但这就没什么意思了,我会给出同步以及异步示例。
同步RAM我们这里的同步RAM,就是RAM的读写和时钟同步,为了和后面使用IP核定制的方式尽量一致,我们本篇文章统统使用一种位宽,一种深度,端口信号也尽量一致(这包括数量以及命名)。
双端口同步读写 `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Engineer: 李锐博恩 // Create Date: 2021/01/31 02:46:06 // Module Name: dual_ram ////////////////////////////////////////////////////////////////////////////////// module dual_ram #( parameter WIDTH = 16, parameter DEPTH = 4 )( //a input wire clka, input wire rst, input wire ena, input wire wea, input wire [DEPTH – 1 : 0] addra, input wire [WIDTH – 1 : 0] dina, output reg [WIDTH – 1 : 0] douta, //b input wire clkb, input wire enb, input wire web, input wire [DEPTH – 1 : 0] addrb, input wire [WIDTH – 1 : 0] dinb, output reg [WIDTH – 1 : 0] doutb ); //双端口RAM reg [WIDTH – 1 : 0] dual_ram[DEPTH – 1 : 0]; //写 integer i; always@(posedge clka or posedge rst) begin if(rst) begin for(i = 0; i <= DEPTH – 1; i = i + 1) begin: initial_a dual_ram[i] <= d0; end end else if(ena && wea) begin dual_ram[addra] <= dina; end else if(enb && web) begin dual_ram[addrb] <= dinb; end else begin //保持 end end //a端口读 always@(posedge clka) begin if(ena && ~wea) begin douta <= dual_ram[addra]; end else begin douta <= d0; end end always@(posedge clkb) begin if(enb && ~web) begin doutb <= dual_ram[addrb]; end else begin doutb <= d0; end end endmodule这种写法简单易懂,且在平时练习的过程中尽量使用参数化的方式,养成习惯,不要怕麻烦,这样会让你在以后的工作中受益!
验证则尽量简单化:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Engineer: 李锐博恩 // Create Date: 2021/01/31 02:46:06 // Module Name: dual_ram_tb ////////////////////////////////////////////////////////////////////////////////// module dual_ram_tb( ); parameter WIDTH = 16; parameter DEPTH = 4; parameter PERIOD_A = 4; parameter PERIOD_B = 5; reg clka; reg rst; reg ena; reg wea; reg [DEPTH – 1 : 0] addra; reg [WIDTH – 1 : 0] dina; wire [WIDTH – 1 : 0] douta; //b reg clkb; reg enb; reg web; reg [DEPTH – 1 : 0] addrb; reg [WIDTH – 1 : 0] dinb; wire [WIDTH – 1 : 0] doutb; initial begin clka = 0; forever begin # (PERIOD_A/2) clka = ~clka; end end initial begin clkb = 0; forever begin # (PERIOD_B/2) clkb = ~clkb; end end initial begin rst = 1; ena = 0; enb = 0; wea = 0; web = 0; addra = 0; addrb = 0; dina = 0; dinb = 0; repeat(15); @(posedge clka); rst = #(0.1 * PERIOD_A) 0; //a端口连续写两个数据 repeat(10); @(posedge clka); addra = #(0.1 * PERIOD_A) d0; dina = #(0.1 * PERIOD_A) $random; @(posedge clka); ena = #(0.1 * PERIOD_A) 1; wea = #(0.1 * PERIOD_A) 1; @(posedge clka); addra = #(0.1 * PERIOD_A) d1; dina = #(0.1 * PERIOD_A) $random; @(posedge clka); //b端口读两个数据 repeat(10); @(posedge clkb); addrb = #(0.1 * PERIOD_B) d0; @(posedge clkb); enb = #(0.1 * PERIOD_B) 1; web = #(0.1 * PERIOD_B) 0; @(posedge clkb); addrb = #(0.1 * PERIOD_B) d1; end dual_ram#( .WIDTH ( WIDTH ), .DEPTH ( DEPTH ) )u_dual_ram( .clka ( clka ), .rst ( rst ), .ena ( ena ), .wea ( wea ), .addra ( addra ), .dina ( dina ), .douta ( douta ), .clkb ( clkb ), .enb ( enb ), .web ( web ), .addrb ( addrb ), .dinb ( dinb ), .doutb ( doutb ) ); endmodule如下是仿真时序图:
RTL原理图:
综合原理图:
表明可综合设计。
异步RAM异步RAM,意思就是不需要时钟同步的RAM,给出RTL设计:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Engineer: 李锐博恩 // Create Date: 2021/01/31 02:46:06 // Module Name: asy_ram ////////////////////////////////////////////////////////////////////////////////// module asy_ram#( parameter DATA_WIDTH = 16, parameter RAM_DEPTH = 4 )( //a input wire ena, input wire wea, input wire [RAM_DEPTH – 1 : 0] addra, input wire [DATA_WIDTH – 1 : 0] dina, output reg [DATA_WIDTH – 1 : 0] douta, //b input wire enb, input wire web, input wire [RAM_DEPTH – 1 : 0] addrb, input wire [DATA_WIDTH – 1 : 0] dinb, output reg [DATA_WIDTH – 1 : 0] doutb ); //————–Internal variables—————- reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1]; //initialization // synopsys_translate_off integer i; initial begin for(i=0; i < RAM_DEPTH; i = i + 1) begin mem[i] = 8h00; end end // synopsys_translate_on //————–Code Starts Here—————— // Memory Write Block // Write Operation : When we_0 = 1, cs_0 = 1 always @ (*) begin : MEM_WRITE if ( ena && wea ) begin mem[addra] = dina; end else if (enb && web) begin mem[addrb] = dinb; end end // Memory Read Block // Read Operation : When we_0 = 0, oe_0 = 1, cs_0 = 1 always @ (*) begin : MEM_READ_a if (ena && ~wea) begin douta = mem[addra]; end else begin douta = 0; end end //Second Port of RAM always @ (*) begin : MEM_READ_b if (enb && ~web) begin doutb = mem[addrb]; end else begin doutb = 0; end end endmodule综合后的原理图: 表明可综合。
仿真就利用同步RAM的仿真,改下例化,时钟只是一个时间尺度,可以不拉出来:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Engineer: 李锐博恩 // Create Date: 2021/01/31 02:46:06 // Module Name: dual_ram_tb ////////////////////////////////////////////////////////////////////////////////// module dual_ram_tb( ); parameter WIDTH = 16; parameter DEPTH = 4; parameter PERIOD_A = 4; parameter PERIOD_B = 5; reg clka; reg rst; reg ena; reg wea; reg [DEPTH – 1 : 0] addra; reg [WIDTH – 1 : 0] dina; wire [WIDTH – 1 : 0] douta; //b reg clkb; reg enb; reg web; reg [DEPTH – 1 : 0] addrb; reg [WIDTH – 1 : 0] dinb; wire [WIDTH – 1 : 0] doutb; initial begin clka = 0; forever begin # (PERIOD_A/2) clka = ~clka; end end initial begin clkb = 0; forever begin # (PERIOD_B/2) clkb = ~clkb; end end initial begin rst = 1; ena = 0; enb = 0; wea = 0; web = 0; addra = 0; addrb = 0; dina = 0; dinb = 0; repeat(15); @(posedge clka); rst = #(0.1 * PERIOD_A) 0; //a端口连续写两个数据 repeat(10); @(posedge clka); addra = #(0.1 * PERIOD_A) d0; dina = #(0.1 * PERIOD_A) $random; @(posedge clka); ena = #(0.1 * PERIOD_A) 1; wea = #(0.1 * PERIOD_A) 1; @(posedge clka); addra = #(0.1 * PERIOD_A) d1; dina = #(0.1 * PERIOD_A) $random; @(posedge clka); //b端口读两个数据 repeat(10); @(posedge clkb); addrb = #(0.1 * PERIOD_B) d0; @(posedge clkb); enb = #(0.1 * PERIOD_B) 1; web = #(0.1 * PERIOD_B) 0; @(posedge clkb); addrb = #(0.1 * PERIOD_B) d1; end asy_ram#( .DATA_WIDTH ( 16 ), .RAM_DEPTH ( 4 ) )u_asy_ram( .ena ( ena ), .wea ( wea ), .addra ( addra ), .dina ( dina ), .douta ( douta ), .enb ( enb ), .web ( web ), .addrb ( addrb ), .dinb ( dinb ), .doutb ( doutb ) ); endmodule仿真波形图:
ROM的RTL设计
ROM的设计就更简单了,不用考虑写,一次性写入,剩下的都是读的问题了。
给出RTL设计:
module Rom_RTL( input [7:0] address , // Address input output [7:0] data , // Data output input read_en , // Read Enable input ce // Chip Enable ); reg [7:0] mem [0:255] ; assign data = (ce && read_en) ? mem[address] : 8b0; initial begin $readmemb(“F:/Prj_blog/vivado_csdn/prj_mem/prj_mem.srcs/sources_1/new/memory.list”, mem); // memory_list is memory file end endmodule仿真平台:
`timescale 1ns / 1ps module rom_using_file_tb; reg [7:0] address; reg read_en, ce; wire [7:0] data; integer i; initial begin address = 0; read_en = 0; ce = 0; //#10 $monitor (“address = %h, data = %h, read_en = %b, ce = %b”, address, data, read_en, ce); for (i = 0; i < 256; i = i + 1 )begin #5 address = i; read_en = 1; ce = 1; #5 read_en = 0; ce = 0; address = 0; end end Rom_RTL u_Rom_RTL( .address ( address ), .data ( data ), .read_en ( read_en ), .ce ( ce ) ); endmodule由于,memory.list文件内容是0,1,2,3,… 因此,仿真内容也符合预期。
给出综合后的原理图:
证明可综合!
最后,大家可能会有疑问?说ROM的设计中用到了一个系统函数:readmemb,这东西能综合?
其实,这还真是要取决于综合工具,我找出了一个解释:
Altera的“推荐的HDL编码样式”指南包括示例10-31(第10-38页),该示例演示了从中推断出的ROM $readmemb(如下所示):
module dual_port_rom ( input [(addr_width-1):0] addr_a, addr_b, input clk, output reg [(data_width-1):0] q_a, q_b ); parameter data_width = 8; parameter addr_width = 8; reg [data_width-1:0] rom[2**addr_width-1:0]; initial // Read the memory contents in the file // dual_port_rom_init.txt. begin $readmemb(“dual_port_rom_init.txt”, rom); end always @ (posedge clk) begin q_a <= rom[addr_a]; q_b <= rom[addr_b]; end endmodule同样,Xilinx的XST用户指南指出:
该readmemb和readmemh系统任务可以用来初始化块存储器。有关更多信息,请参见:
从外部文件初始化RAM的示例
使用readmemb二进制和readmemh十六进制表示。为了避免XST和模拟器行为之间可能的差异,Xilinx®建议您在这些系统任务中使用索引参数。请参见以下编码示例。
$readmemb(“rams_20c.data”,ram, 0, 7);因此,对于存储器的初始化,这样做是没问题的。
免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证 https://www.yhzz.com.cn/a/13063.html