根据网上资料和自己理解整合而成,参考文章和代码链接在文章结尾。
FIFO和RAM
1、 FIFO
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO可以用于:(1)跨时钟域多bit传输:读写可以由不同的时钟控制,使用异步FIFO可以在两个不同时钟系统之间快速方便的传输数据。(2)数据匹配:对于不同宽度的数据接口可以使用FIFO,比如写入数据宽度为8bit,读取数据宽度为16bit,通过FIFO数据缓存器就可以达到数据匹配。(3)低延迟内存缓冲:当需要处理串行输入数据时,可以利用FIFO缓存原始数据,直到数据处理完毕。
一、 类型
根据FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
Xilinx的FIFO IP配置如下:
根据存储器的不同,有四种同步FIFO(Common clock),三种异步FIFO(independent clocks)。block RAM是FPGA中定制的ram资源,而distribute RAM则是由LUT构成的RAM资源。由此区别表明,当FIFO较大时应选择block RAM,当FIFO较小时,选择distribute RAM.另外一个很重要的就是block RAM支持读写不同宽度,而distribute不支持。在这里为了更全面的了解FIFO,选择block RAM以拥有非对称方向速率的特性,内嵌FIFO(Builtin FIFO)在5以上的FPGA芯片中才存在。
其中,不同的存储器的FIFO对应的配置也有些许不同。例如:只有Block RAM资源的FIFO可以变换位宽,其他三种都不可以;异步FIFO中,只有Block RAM的Enable Safety Circuit配置可选,其他两种都不可选。
二、 基本代码操作写法
最基本的要求:在状态“满”时,不能进行写操作;在状态“空”时,不能进行读操作。
1.同步FIFO
同步FIFO一般用作数据缓存和位宽变换,根据缓存的数据量和位宽变换情况改变FIFO的深度和位宽。其中高位宽转换为低位宽,高位先输出;低位宽转高位宽,先进的在高位。
1) module fifo_w18d1024
2) (
3) input clk,
4) input rst_n,
5) input [18-1:0]din,
6) input din_vld,
7) input dout_vld,
8)
9) output fifo_valid,
10) output [18-1:0]dout
11) );
12) //-----------------------------------------------
13)
14) wire fifo_rd_en;
15) wire fifo_wr_en;
16) wire fifo_empty;
17) wire fifo_full;
18) //-----------------------------------------------
19)
20) fifo_w18d1024 u_fifo_w18d1024 (
21) .clk(clk), // input wire clk
22) .srst(~rst_n), // input wire srst
23)
24) .din(din), // input wire [17 : 0] din
25) .wr_en(fifo_wr_en), // input wire wr_en
26)
27) .rd_en(fifo_rd_en), // input wire rd_en
28) .dout(dout), // output wire [17 : 0] dout
29)
30) .full(fifo_full), // output wire full
31) .empty(fifo_empty), // output wire empty
32)
33) .valid(fifo_valid) // output wire valid
34) );
35)
36) assign fifo_wr_en = din_vld & (~fifo_full);
37) assign fifo_rd_en = dout_vld & (~fifo_empty);
38)
39) endmodule
2.异步FIFO
根据写时钟和读时钟的关系确定FIFO的深度,不能出现溢出的情况。fifo的复位需要一段时间,期间wr_rst_busy和rd_rst_busy信号为高电平,此时应禁止读写FIFO,否则会造成数据丢失。
module fifo_w18d32
2 (
3 input clk_120m,
4 input clk_240m,
5 input rst_n,
6 input [18-1:0]din,
7 input din_vld,
8 input dout_vld,
9
10 output fifo_valid,
11 output [18-1:0]dout
12 );
13 //-----------------------------------------------
14
15 wire fifo_rd_en;
16 wire fifo_wr_en;
17 wire fifo_empty;
18 wire fifo_full;
19 //-------------------------------------------
20
21 fifo_d128w32 u_fifo_d128w18 (
22 .rst(~rst), // input wire rst
23
24 .wr_clk(clk_240m), // input wire wr_clk
25 .rd_clk(clk_120m), // input wire rd_clk
26
27 .din(din), // input wire [31 : 0] din
28 .wr_en(fifo_wr_en), // input wire wr_en
29
30 .rd_en(fifo_rd_en), // input wire rd_en
31 .dout(dout), // output wire [31 : 0] dout
32
33 .full(fifo_full), // output wire full
34 .empty(fifo_empty), // output wire empty
35
36 .valid(fifo_valid), // output wire valid
37
38 .wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy
39 .rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
40 );
41
42 assign fifo_wr_en = din_vld & (~fifo_full) &(~wr_rst_busy);
43 assign fifo_rd_en = dout_vld & (~fifo_empty) &(~rd_rst_busy);
44
45 endmodule
2、 Ram
RAM 即随机存取存储器,与先入先出的FIFO不同,RAM读出的数据顺序跟写入数据顺序不一致,可以向RAM中的任意位置写入数据,也可以读取任意的位置的数据。
FIFO没有写地址和读地址,只能按顺序读写数据,而RAM具有读写地址,因此可以读写任意地址。
FIFO常用于数据传输通道中,用于缓存数据或时钟域变换;RAM则常用于存储指令或者中间的数据。
一、 类型
Xilinx的block memory generator ip中的block ram有三种:单口RAM、简化双口RAM和真双口RAM。单口与双口的区别在于,单口只有一组数据线与地址线,因此读写不能同时进行。而双口有两组数据线与地址线,读写可同时进行。
当ena=1时,douta正常输出;
当ena=1 &wea=1时,dina正常输入;
双口RAM分伪双口RAM(Xilinx称为Simple two-dual RAM)与双口RAM(Xilinx称为true two-dual RAM)。
伪双口RAM,一个端口只读,另一个端口只写,且写入和读取的时钟可以不同,位宽比可以不是1:1;
而双口RAM两个端口都分别带有读写端口,可以在没有干扰的情况下进行读写,彼此互不干扰。
FIFO也是一个端口只读,另一个端口只写。FIFO与伪双口RAM的区别在于,FIFO为先入先出,没有地址线,不能对存储单元寻址;而伪双口RAM两个端口都有地址线,可以对存储单元寻址;
异步时钟域的缓存只要是双口器件都可以完成,但FIFO不需对地址进行控制,是最方便的。
二、 基本代码操作写法
根据Xilinx官方文档,不同的RAM主要应用如下:
• 单口 RAM:处理器暂存数据、查找表
• 简单双口 RAM:内容可寻址存储器、FIFO
• 真双口 RAM:多处理器存储
1、 单口RAM
//单口RAM用作数据缓存
2 spram_blk_w16d16 spram_blk_w16d16 (
3 .clka(clk), // input wire clka
4 .wea(1'b1), // input wire [0 : 0] wea
5
6 .addra(ram_addr), // input wire [3 : 0] addra
7 .dina(din), // input wire [15 : 0] dina
8 .douta(dout) // output wire [15 : 0] douta
9 );
10 always @(posedge clk or negedge rst_n)
11 begin
12 if(rst_n == 1’b0)
13 ram_addr <= 1’d0;
14 else if(ram_addr>=DLY_NUM)
15 ram_addr <= 1’d0;
16 else
17 ram_addr <= ram_addr + ’d1;
18 end
2、 双口RAM
双口RAM经常用于跨时钟域处理,且比FIFO灵活性更大。输入数据速率20MHz,输出数据速率100Mhz,使用双口RAM完成跨时钟域处理。一次传输的数据为1024个,假设数据位宽为8bit,使用两片宽度为8、深度为1024的双口RAM完成数据传输。
使用乒乓操作提高读写效率,写RAM1时,读取RAM2中的数据;写RAM2时,读取RAM1中的数据。数据读取速率为数据写入速率的5倍,因此写数据端可以一直保持数据写入,而读数据端按写入一组数据时间的1/5进行,使用out_valid信号表示读出的数据有效。
Vivado环境下,RAM使用Block Memory Generator IP核配置,存储类型选择为“True Dual Port RAM”。RAM在读取数据时根据配置参数会有一定的延迟,设计时要注意时序对齐(否则会容易丢掉开头或结尾的一些数据),代码如下:
1. 此处代码出处见结尾参考链接。
`timescale 1ns / 1ps
2.
3. module DualRAM
4. (
5. input clk_wr, //写时钟速率20Mhz
6. input clk_rd, //读时钟速率100Mhz
7. input rst_n,
8. input [7:0] din,
9. output reg out_valid,
10. output reg [7:0] dout
11. );
12.
13. reg [9:0] addr_wr, addr_rd;
14. reg en_wr1, en_wr2, we_wr1, we_wr2, en_rd1, en_rd2;
15. wire [7:0] dout1, dout2;
16.
17. dual_port_ram u1 (
18. .clka(clk_wr), //写端口
19. .ena(en_wr1),
20. .wea(we_wr1),
21. .addra(addr_wr),
22. .dina(din),
23. .douta(),
24. .clkb(clk_rd), //读端口
25. .enb(en_rd1),
26. .web(1'b0),
27. .addrb(addr_rd),
28. .dinb(8'd0),
29. .doutb(dout1)
30. );
31.
32. dual_port_ram u2 (
33. .clka(clk_wr), //写端口
34. .ena(en_wr2),
35. .wea(we_wr2),
36. .addra(addr_wr),
37. .dina(din),
38. .douta(),
39. .clkb(clk_rd), //读端口
40. .enb(en_rd2),
41. .web(1'b0),
42. .addrb(addr_rd),
43. .dinb(8'd0),
44. .doutb(dout2)
45. );
46.
47. //写端口乒乓操作
48. always @ (posedge clk_wr) //写地址信号控制0~1023
49. if (!rst_n) addr_wr <= 1023;
50. else addr_wr <= addr_wr + 1'b1;
51.
52. always @ (posedge clk_wr) //轮流写RAM1与RAM2
53. if (!rst_n) begin we_wr1 <= 1'b1; we_wr2 <= 1'b0;
54. en_wr1 <= 1'b1; en_wr2 <= 1'b0; end
55. else if (addr_wr == 1023) begin
56. we_wr1 <= ~we_wr1; we_wr2 <= ~we_wr2;
57. en_wr1 <= ~en_wr1; en_wr2 <= ~en_wr2;
58. end
59.
60. //读端口乒乓操作
61. always @ (posedge clk_rd) //读地址信号控制0~1023
62. if (!rst_n) addr_rd <= 1021; //匹配延迟
63. else addr_rd <= addr_rd + 1'b1;
64.
65. reg [15:0] cnt;
66. always @ (posedge clk_rd) //读时钟为写时钟的5倍
67. if (!rst_n) cnt <= 16'hFFFE; //匹配延迟
68. else if (cnt == 5119) cnt <= 0;
69. else cnt <= cnt + 1'b1;
70.
71. reg flag1, flag2;
72. always @ (posedge clk_rd) //读RAM标志,RAM1或RAM2
73. if (!rst_n) begin flag1 <= 1'b1; flag2 <= 1'b0; end
74. else if (cnt == 5119) begin flag1 = ~flag1; flag2 = ~flag2; end
75. else begin flag1 <= flag1; flag2 <= flag2; end
76.
77. always @ (posedge clk_rd) //读RAM使能,选择cnt的前1/5时间读取
78. if (!rst_n) begin en_rd1 <= 1'b1; en_rd2 <= 1'b0; end
79. else if (cnt < 1024) begin en_rd1 <= flag1; en_rd2 <= flag2; end
80. else begin en_rd1 <= 1'b0; en_rd2 <= 1'b0; end
81.
82. reg en_rd1_reg, en_rd2_reg;
83. always @ (posedge clk_rd) //延迟一级,匹配时序
84. if (!rst_n) begin en_rd1_reg <= 0; en_rd1_reg <= en_rd1_reg; end
85. else begin en_rd1_reg <= en_rd1; en_rd2_reg <= en_rd2; end
86.
87. always @ (posedge clk_rd) //输出选择,RAM1或RAM2;控制输出使能信号
88. if (!rst_n) begin dout <= 0; out_valid <= 0; end
89. else if (en_rd1_reg) begin dout <= dout1; out_valid <= 1; end
90. else if (en_rd2_reg) begin dout <= dout2; out_valid <= 1; end
91. else begin dout <= 0; out_valid <= 0; end
92.
93. endmodule
NOTE1:
注意一下:RAM的操作模式(Operation mode)选项
每个端口的操作模式决定了此端口的读和写之间关系。端口 A 和 B 可以独立配置为以 下三种模式中任一模式:写优先模式,读优先模式,不改变模式。这些模式详解见下面。当 A 和 B 端口地址有冲突时,操作模式就会影响 A 和 B 口之间关系。
1.写优先模式(write first mode):
在写优先模式中,输入数据被自动写入存储器件中,并且出现在数据输出端口。时序见下图。这种传输模式增强了在同一端口写操作时使用数据输出总线的灵活性。(即输入数据的同时自动写进存储器和驱动数据到数据输出端)
2.读优先模式(read first mode):
在读优先模式中,预先存储在写地址中的数据会被输出,而输入数据被存入存储器件中。这种模式见下图。(即以前写进当前写地址的数据出现在数据输出端,此时输入的数据被保存到存储器中)
3.不改变模式(no-change mode):
在不改变模式中,输出锁存在写操作时候保持不变,见下图。在同一端口 的写操作不会对数据输出端口产生影响,输出仍然是以前的读数据。(即输出锁存器保持不改变在写操作期间。说明在写期间输出端不会输出写期间地址的数据,不管以前保存数据还是现在的输入数据)。
NOTE2:
Xilinx官方文档里利用寄存器构建单双口RAM的Verilog代码。
1、单口RAM
1. // Single-Port Block RAM Write-First Mode (recommended template)
2. // File: rams_02a.v
3. //
4. module v_rams_02a (clk, we, en, addr, di, do);
5. input clk;
6. input we;
7. input en;
8. input [9:0] addr;
9. input [15:0] di;
10. output [15:0] do;
11. reg[15:0] RAM [1023:0];
12. reg[15:0] do;
13. always @(posedge clk)
14. begin
15. if (en)
16. begin
17. if (we)
18. begin
19. RAM[addr] <= di;
20. do <= di;
21. end
22. else
23. do <= RAM[addr];
24. end
25. end
26. endmodule
2、 真双口RAM
// Dual-Port Block RAM with Two Write Ports
2 // File: rams_16.v
3
4 module v_rams_16 (clka,clkb,ena,enb,wea,web,addra,addrb,dia,dib,doa,dob);
5
6 input clka,clkb,ena,enb,wea,web;
7 input [9:0] addra,addrb;
8 input [15:0] dia,dib;
9 output [15:0] doa,dob;
10 reg[15:0] ram [1023:0];
11 reg[15:0] doa,dob;
12
13 always @(posedge clka) begin if (ena)
14 begin
15 if (wea)
16 ram[addra] <= dia;
17 doa <= ram[addra];
18 end
19 end
20
21 always @(posedge clkb) begin if (enb)
22 begin
23 if (web)
24 ram[addrb] <= dib;
25 dob <= ram[addrb];
26 end
27 end
28
29 Endmodule
3、简单双口RAM
// Simple Dual-Port Block RAM with One Clock
2 // File: simple_dual_one_clock.v
3 module simple_dual_one_clock (clk,ena,enb,wea,addra,addrb,dia,dob);
4 input clk,ena,enb,wea;
5 input [9:0] addra,addrb;
6 input [15:0] dia;
7 output [15:0] dob;
8 reg[15:0] ram [1023:0];
9 reg[15:0] doa,dob;
10 always @(posedge clk) begin
11 if (ena) begin
12 if (wea)
13 ram[addra] <= dia;
14 end
15 end
16 always @(posedge clk) begin
17 if (enb)
18 dob <= ram[addrb];
19 end
20 endmodule
3、参考资料
https://www.cnblogs.com/xianyufpga/p/11073798.html
https://www.cnblogs.com/chengqi521/p/6831439.html
https://blog.csdn.net/zengaliang/article/details/78765159
https://blog.csdn.net/zengaliang/article/details/78765159
https://www.xilinx.com/support/documentation/sw_manuals/xilinx2014_4/ug901-vivado-synthesis.pdf
http://doc.embedfire.com/fpga/altera/ep4ce10_pro/zh/latest/code/pingpang.html
https://blog.csdn.net/FPGADesigner/article/details/83689811