FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证
前言
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] = 8'h00;
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] : 8'b0;
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系统任务可以用来初始化块存储器。有关更多信息,请参见:
使用readmemb二进制和readmemh十六进制表示。为了避免XST和模拟器行为之间可能的差异,Xilinx®建议您在这些系统任务中使用索引参数。请参见以下编码示例。
$readmemb("rams_20c.data",ram, 0, 7);
因此,对于存储器的初始化,这样做是没问题的。
- 分享
- 举报
-
浏览量:6948次2021-01-31 01:07:50
-
浏览量:8125次2021-02-06 22:29:19
-
浏览量:6675次2021-03-14 02:34:44
-
浏览量:10485次2020-12-13 19:59:59
-
浏览量:6269次2021-01-30 01:09:16
-
浏览量:19151次2020-12-06 18:59:36
-
浏览量:8240次2020-12-27 18:30:21
-
浏览量:10704次2021-01-17 00:16:33
-
浏览量:8574次2021-01-09 02:07:52
-
浏览量:13285次2021-01-01 02:53:29
-
浏览量:6999次2021-03-15 23:18:36
-
浏览量:7057次2021-01-30 23:18:56
-
浏览量:12272次2021-01-02 00:02:19
-
浏览量:6888次2021-01-23 18:45:14
-
浏览量:4933次2021-03-18 22:57:24
-
浏览量:5477次2021-03-17 23:24:53
-
浏览量:4492次2021-06-20 20:10:03
-
浏览量:7777次2021-02-07 00:59:28
-
浏览量:5071次2021-05-23 23:50:14
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
李锐博恩
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明