FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证

举报
李锐博恩 发表于 2021/07/14 23:26:13 2021/07/14
【摘要】 前言 本文首发:FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证 RAM以及ROM在FPGA中的实现大体有两种方式,一种是使用IP核定制,一种是RTL设计。 也许有人会反驳,那原语呢? 我不喜欢讨论这个问题,原语你去使用吗?如果你真的喜欢,请自便。 下面我们讨论这两种实现方式: 首先是RTL的设计,这种方式中,我们重点在于实现逻辑设计。 在I...

前言

本文首发: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原理图:
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,…
list内容
因此,仿真内容也符合预期。

给出综合后的原理图:

综合后原理图

证明可综合!

最后,大家可能会有疑问?说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系统任务可以用来初始化块存储器。有关更多信息,请参见:

从外部文件初始化RAM的示例

使用readmemb二进制和readmemh十六进制表示。为了避免XST和模拟器行为之间可能的差异,Xilinx®建议您在这些系统任务中使用索引参数。请参见以下编码示例。

$readmemb("rams_20c.data",ram, 0, 7);

  
 
  • 1

因此,对于存储器的初始化,这样做是没问题的。

文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。

原文链接:reborn.blog.csdn.net/article/details/113449637

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。