一、Quartus
1.打开Quartus ii,点击Tools---MegaWizard Plug-In Manager
2.弹出创建页面,选择Creat a new custom megafunction variation,点Next
3.选择IP核,可以直接搜索ram,选择RAM:2-PORT,右上方选择器件型号,语言选成Verilog,再填写一下路径名字,点Next,后面就是参数设置了。
4.设置读写需要几个端口,深度计算按word还是bit。Next
5.设置深度,位宽,类型。Next
6.设置读写需要几个端口,深度计算按word还是bit,一般选word。Next
7.是否为输出添加一个寄存器(加了寄存器可以使RAM输出的数据更稳定)?本来ram的输出就是会慢1clk,勾选后又慢1clk,所以一般不勾选。Next
8.输出的是新数据还是老数据,一般是要新数据,所以I don't care就行。Next
9.是否添加初始化文件mif ? Next
10.告诉你此IP核的编译库是什么,Next
11.输出的文件列表,除了正常IP核,还可以选择例化文件,注意bb.v文件用不到,一般是不勾选的。之后点finish就生成IP核了。
二、ISE
1.创建ISE工程,IP核需要在ISE工程里面进行调用。点击Tools---Core Generator...
2.在新弹出来的界面中创建一个属于IP核的工程:file---new project,并填写文件存储位置和文件名称,一般为ipcore_dir文件夹,点击保存
3.弹出的Part处填写器件的系列、型号、封装以及速度等级,Generation处设置语言为Verilog,点击OK
4.点击文件夹,找到Memories & Storage Elements---RAMs & ROMs---Block Memory Generator,(也可以直接搜索)双击打开,进行参数设置
5.设置模块名称,Next
6.类型选择,一般选Single Dual RAM,该RAM为“a口负责写,b口负责读”,而对于真双口RAM来说,a和b都是可读可写。其他选项根据需要勾选。Next
7.RAM的位宽、深度、使能选择,Next
8.是否在B端添加一个寄存器(加了寄存器可以使RAM输出的数据更稳定)?本来ram的输出就慢1clk,勾选了又慢1clk,所以一般不勾选。是否需要初始化并加载初始化ceo文件,Next
9.是否对B口添加复位键,Next
10.总结页面,注意里面一句话:B口的读取有1clk的延迟,点击Generate即可生成RAM。大功告成!
三、单RAM读写操作(by威三学院FPGA教程)
本代码针对Quartus的RAM:2-PORT
1 //========================================================================== 2 // --- 名称 : ram_ctrl.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-23 5 // --- 描述 : 单个ram读写操作 6 //========================================================================== 7 8 module ram_ctrl 9 //---------------------<端口声明>------------------------------------------- 10 ( 11 input wire clk , //时钟,50Mhz 12 input wire rst_n , //复位,低电平有效 13 input wire [7:0] din , //写入的数据 14 output wire [7:0] dout //读出的数据 15 ); 16 //---------------------<信号定义>------------------------------------------- 17 reg wr_en ; //写使能 18 reg [7:0] wr_addr ; //写地址 19 reg [7:0] rd_addr ; //读地址 20 21 //-------------------------------------------------------------------------- 22 //-- ram例化 23 //-------------------------------------------------------------------------- 24 ram_256x8 u_ram_256x8 25 ( 26 .clock (clk ), 27 .wren (wr_en ), 28 .wraddress (wr_addr ), 29 .data (din ), 30 .rdaddress (rd_addr ), 31 .q (dout ) 32 ); 33 34 //-------------------------------------------------------------------------- 35 //-- 产生写使能 36 //-------------------------------------------------------------------------- 37 always @(posedge clk or negedge rst_n) begin 38 if(!rst_n) 39 wr_en <= 1'b1; 40 else if(wr_addr==8'd255) 41 wr_en <= 1'b0; 42 else if(rd_addr==8'd255) 43 wr_en <= 1'b1; 44 end 45 46 //-------------------------------------------------------------------------- 47 //-- 产生写地址 48 //-------------------------------------------------------------------------- 49 always @(posedge clk or negedge rst_n) begin 50 if(!rst_n) 51 wr_addr <= 8'd0; 52 else if(wr_en==1) 53 wr_addr <= wr_addr+1'd1; 54 else 55 wr_addr <= 8'd0; 56 end 57 58 //-------------------------------------------------------------------------- 59 //-- 产生读地址 60 //-------------------------------------------------------------------------- 61 always @(posedge clk or negedge rst_n) begin 62 if(!rst_n) 63 rd_addr <= 8'd0; 64 else if(wr_en==0) 65 rd_addr <= rd_addr+1'd1; 66 else 67 rd_addr <= 8'd0; 68 end 69 70 71 72 endmodule
四、双RAM乒乓操作(by威三学院FPGA教程)
RAM的简单读写比较简单,平常也用不太到,比较重要的是异步双口RAM实现乒乓操作。
本代码针对ise的Simple Dual PORT RAM
1 //========================================================================== 2 // --- 名称 : ram_pp.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-23 5 // --- 描述 : 异步双口ram乒乓操作 6 //========================================================================== 7 8 module ram_pp 9 //---------------------<端口声明>------------------------------------------- 10 ( 11 input wire clk , //时钟,50Mhz 12 input wire rst_n , //复位,低电平有效 13 input wire [7:0] din , //输入的数据 14 output wire [7:0] dout //输出的数据 15 ); 16 //---------------------<信号定义>------------------------------------------- 17 //ram_a 18 reg wr_en_a ; 19 reg [9:0] wr_addr_a ; 20 reg [9:0] rd_addr_a ; 21 wire [7:0] dout_a ; 22 //ram_b 23 reg wr_en_b ; 24 reg [9:0] wr_addr_b ; 25 reg [9:0] rd_addr_b ; 26 wire [7:0] dout_b ; 27 //缓一拍 28 reg wr_en_a_r0 ; 29 30 //-------------------------------------------------------------------------- 31 //-- ip核例化 32 //-------------------------------------------------------------------------- 33 //ram_a 34 ram_1024x8 u_ram_1024x8_a 35 ( 36 .clka (clk ), // input clka 37 .wea (wr_en_a ), // input [0 : 0] wea 38 .addra (wr_addr_a ), // input [9 : 0] addra 39 .dina (din ), // input [7 : 0] dina 40 .clkb (clk ), // input clkb 41 .addrb (rd_addr_a ), // input [9 : 0] addrb 42 .doutb (dout_a ) // output [7 : 0] doutb 43 ); 44 //ram_b 45 ram_1024x8 u_ram_1024x8_b 46 ( 47 .clka (clk ), // input clka 48 .wea (wr_en_b ), // input [0 : 0] wea 49 .addra (wr_addr_b ), // input [9 : 0] addra 50 .dina (din ), // input [7 : 0] dina 51 .clkb (clk ), // input clkb 52 .addrb (rd_addr_b ), // input [9 : 0] addrb 53 .doutb (dout_b ) // output [7 : 0] doutb 54 ); 55 56 //-------------------------------------------------------------------------- 57 //-- ram_a 58 //-------------------------------------------------------------------------- 59 // 写使能 60 always @(posedge clk or negedge rst_n) begin 61 if(!rst_n) 62 wr_en_a <= 1'b1; 63 else if(rd_addr_a=='d1023) 64 wr_en_a <= 1'b1; 65 else if(wr_addr_a=='d1023) 66 wr_en_a <= 1'b0; 67 end 68 69 // 写地址 70 always @(posedge clk or negedge rst_n) begin 71 if(!rst_n) 72 wr_addr_a <= 'd0; 73 else if(wr_addr_a=='d1023) 74 wr_addr_a <= 'd0; 75 else if(wr_en_a==1'b1) 76 wr_addr_a <= wr_addr_a + 1'b1; 77 end 78 79 // 读地址 80 always @(posedge clk or negedge rst_n) begin 81 if(!rst_n) 82 rd_addr_a <= 'd0; 83 else if(rd_addr_a=='d1023) 84 rd_addr_a <= 'd0; 85 else if(wr_en_a==1'b0) 86 rd_addr_a <= rd_addr_a + 1'b1; 87 end 88 89 //-------------------------------------------------------------------------- 90 //-- ram_b 91 //-------------------------------------------------------------------------- 92 // 写使能 93 always @(posedge clk or negedge rst_n) begin 94 if(!rst_n) 95 wr_en_b <= 1'b0; 96 else if(wr_addr_a=='d1023) //关键之处!!! 97 wr_en_b <= 1'b1; 98 else if(wr_addr_b=='d1023) 99 wr_en_b <= 1'b0; 100 end 101 102 // 写地址 103 always @(posedge clk or negedge rst_n) begin 104 if(!rst_n) 105 wr_addr_b <= 'd0; 106 else if(wr_addr_b=='d1023) 107 wr_addr_b <= 'd0; 108 else if(wr_en_b==1'b1) 109 wr_addr_b <= wr_addr_b + 1'b1; 110 end 111 112 // 读地址 113 always @(posedge clk or negedge rst_n) begin 114 if(!rst_n) 115 rd_addr_b <= 'd0; 116 else if(rd_addr_b=='d1023) 117 rd_addr_b <= 'd0; 118 else if(wr_en_b==1'b0) 119 rd_addr_b <= rd_addr_b + 1'b1; 120 end 121 122 //-------------------------------------------------------------------------- 123 //-- wr_en_a缓一拍时序才对齐 124 //-------------------------------------------------------------------------- 125 always @(posedge clk or negedge rst_n) begin 126 if(!rst_n) 127 wr_en_a_r0 <= 0; 128 else 129 wr_en_a_r0 <= wr_en_a; 130 end 131 132 //-------------------------------------------------------------------------- 133 //-- 数据输出 134 //-------------------------------------------------------------------------- 135 assign dout = (wr_en_a_r0==0) ? dout_a : dout_b; 136 137 138 139 endmodule
五、单口,伪双口,真双口的区别
1.单口:只有一组数据线和地址线,只生成一个addr,因此不能同时进行读写。
2.双口:拥有两组数据线和地址线,可以生成wr_addr和rd_addr,因此可以同时进行读写。
①伪双口:一个端口读,一个端口写。
②真双口:两个端口都能读能写。
ps:
无论是单口、伪双口还是真双口,他们都只使用一块Memory,真双口其实是两组地址对同一块Memory进行读写,如果真双口的两端口同时对同一地址进行写入数据,那实际情况是未知(仿真也不可信)。
六、ROM、RAM和FIFO的区别
1.ROM有地址,只能读而不能写。用初始化文件mif/ceo将内容存进去,读取不会使得数据减少消失。
2.RAM有地址,可以进行寻址读写,数据写进去后,读取不会使得数据减少消失。
3.FIFO没有地址,只能是先进先出,数据写进去后,读取会使得数据减少消失,读一个少一个。
参考资料:
[1]威三学院FPGA教程
[2]小梅哥FPGA教程