一、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教程