FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证
前言
本文首发: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
- 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
这种写法简单易懂,且在平时练习的过程中尽量使用参数化的方式,养成习惯,不要怕麻烦,这样会让你在以后的工作中受益!
验证则尽量简单化:
`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
- 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
如下是仿真时序图:
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
- 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
综合后的原理图:
表明可综合。
仿真就利用同步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
- 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
仿真波形图:
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
仿真平台:
`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
- 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
由于,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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
同样,Xilinx的XST用户指南指出:
该readmemb和readmemh系统任务可以用来初始化块存储器。有关更多信息,请参见:
使用readmemb二进制和readmemh十六进制表示。为了避免XST和模拟器行为之间可能的差异,Xilinx®建议您在这些系统任务中使用索引参数。请参见以下编码示例。
$readmemb("rams_20c.data",ram, 0, 7);
- 1
因此,对于存储器的初始化,这样做是没问题的。
文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。
原文链接:reborn.blog.csdn.net/article/details/113449637
- 点赞
- 收藏
- 关注作者
评论(0)