Vivado 雙口RAM 的呼叫和實現
1.雙口RAM概述
雙口RAM(dual port RAM)在異構系統中應用廣泛,通過雙口RAM,不同硬體架構的晶片可以實現資料的互動,從而實現通訊。例如,一般情況下,ARM與DSP之間的通訊,可以利用雙口RAM實現,ARM通過EBI匯流排連線到雙口RAM的A口,DSP通過EMIF匯流排(也可以是uPP匯流排,取決於速度需求)連線到雙口RAM的B口,兩者對同一塊儲存區域進行操作,即可實現兩者的資料互動。
但是,因為雙口RAM的A口和B口都可以對相同的記憶體地址進行操作,這就引出了一個問題——假如通訊雙方在兩個埠對同一地址同時讀寫,就會引發衝突。要解決這個問題,辦法有二。一是通訊雙方在時序上保證不會同時讀寫同一地址,將ARM和DSP可寫地址範圍進行分割槽,無論任何一方寫完資料後都通過IO傳送中斷通知對方,對方進行資料讀取(乒乓RAM操作),這樣是比較可靠的;另外一個辦法就是在fpga裡設定寫busy訊號,實現兩端寫同步[]。在FPGA中,構建雙口RAM可以通過兩種方法,一種是利用distributed RAM構建,另一種是利用Block RAM構建,關於兩者的具體區別,可以參考這兩篇文章[][]。簡而言之,Block RAM是是使用FPGA中的整塊雙口RAM資源,而distributed RAM則是用FPGA中的邏輯資源拼湊形成的。一般的原則是,較大的儲存應用,建議用bram;零星的小ram,一般就用dram。
在Vivado中,RAM IP核在Memories & Strorage Elements\RAM & ROMs和RAM & ROMs & BRAM資料夾下,如圖所示,下面簡要介紹一下Vivado的雙口RAM IP核。
(圖1.1)
2.Vivado 雙口RAM IP核
2.1 Block Memory Generator概述
點選圖1.1的Block Memory Generator項,利用BRAM來構建雙口RAM。Block Memory Generator視窗如圖2.1所示。
圖中,第1部分,在IP symbol選項卡,點選"+"號可以展開埠具體訊號,如圖2.2所示。第2部分,Component Name可以設定IP核的名字。第3部分,Basic選項卡,在Memory Type下拉列表中,可以設定記憶體的型別,如圖2.3所示。Block Memory Gnerator一共可以產生5種不同型別的記憶體空間,其中block RAM有三種:單口RAM、簡化雙口RAM和真雙口RAM[]。單口RAM只有一個埠(A埠),可以對A埠進行讀寫。簡化雙口RAM有兩個埠(A和B埠),但是A埠只能進行寫入操作,不能進行讀出操作,而B埠則只能進行讀出操作,不能進行寫入操作。真雙口RAM有兩個埠(A和B埠),A和B埠都能進行讀寫操作[]。
(圖2.1)
(圖2.2)
(圖2.3)
2.2 真雙口RAM的設定
2.2.1 Basic設定
在Basic選項卡的Memory type選項中選擇真雙口RAM,IP Symbol如圖2.4所示。ECC Options為預設設定,Write Enable中也選擇預設設定,不使能位元組寫,Algorithm Options選擇預設設定。
(圖2.4)
2.2.2 Port設定
點選Port A Options選項卡,對A埠進行設定, 設定Write Width為16(即RAM單元為16位),Write Width為1024(即記憶體深度為1024,該埠可讀寫的RAM單元有1024個),Operating Mode(操作模式)一共有三種:Write First,Read First,No Change。在Write First模式中,在一個時鐘週期裡,寫入記憶體單元的資料被同步輸出到輸出資料匯流排上;在Read First模式中,在一個時鐘週期裡,寫入到記憶體單元的資料是當前輸入資料匯流排上的資料,而輸出到輸出資料匯流排上的資料則是上一個時鐘週期儲存在記憶體單元中的資料。細節可參考PG058的49到50頁4。Enable Port Type設定為Always Enabled,一直使能埠A。其它設定使用預設設定。如圖2.5所示。
(圖2.5)
埠B設定為與A一致。在Other Options選項卡中,保留預設設定。Load Init File設定是否用Coe檔案對記憶體區域初始化,這個在初始化ROM的時候會用到,這裡不勾選,保持預設。最後,在Summary選項卡會顯示消耗的資源。
3.雙口RAM例程
例程1,該例程是Altera官方例程[],採用暫存器構建雙口RAM,程式碼如下:
moduletrue_dpram_sclk
(
input [7:0] data_a, data_b,
input [5:0] addr_a, addr_b,
input we_a, we_b, clk,
outputreg [7:0] q_a, q_b
);
// Declare the RAM variable
reg [7:0] ram[63:0];
// Port A
always @ (posedge clk)
begin
if (we_a)
begin
ram[addr_a] <= data_a;
q_a <= data_a;
end
else
begin
q_a <= ram[addr_a];
end
end
// Port B
always @ (posedge clk)
begin
if (we_b)
begin
ram[addr_b] <= data_b;
q_b <= data_b;
end
else
begin
q_b <= ram[addr_b];
end
end
endmodule
例程2,該例程是Xilinx官方例程[],採用暫存器構建真雙口RAM,程式碼如下:
// Dual-Port Block RAM with Two Write Ports
// File: rams_16.v
modulev_rams_16 (clka,clkb,ena,enb,wea,web,addra,addrb,dia,dib,doa,dob);
input clka,clkb,ena,enb,wea,web;
input [9:0] addra,addrb;
input [15:0] dia,dib;
output [15:0] doa,dob;
reg[15:0] ram [1023:0];
reg[15:0] doa,dob;
always @(posedge clka) beginif (ena)
begin
if (wea)
ram[addra] <= dia;
doa <= ram[addra];
end
end
always @(posedge clkb) beginif (enb)
begin
if (web)
ram[addrb] <= dib;
dob <= ram[addrb];
end
end
endmodule
例程3,該例程是網友部落格中的例程[],程式碼如下:
moduleTOP(
input USER_CLK
)
`define DLY #1
reg FPGA_Enable=0;
reg[3:0] FPGA_Write_Enable=4'h0;
reg[31:0] FPGA_Address=0;
reg[31:0] FPGA_Write_Data=0;
reg[31:0] FPGA_Read_Data_reg=0;
wire[31:0] FPGA_Read_Data;
reg[10:0] count=0;
always @ (posedge USER_CLK)
begin
count <= count +1;
if(count<=100)
begin
FPGA_Enable <=0;
FPGA_Write_Enable <=4'h0;
end
elseif((count <=105)&&(count >100))
begin
FPGA_Enable <=1;
FPGA_Write_Enable <=4'hf;
FPGA_Address <= FPGA_Address +4;
FPGA_Write_Data <= FPGA_Write_Data +1;
end
elseif((count <=110)&&(count >105))
begin
FPGA_Enable <=0;
FPGA_Write_Enable <=4'h0;
FPGA_Address <=0;
FPGA_Write_Data <=0;
end
elseif((count <=117)&&(count >110))
begin
FPGA_Enable <=1;
FPGA_Write_Enable <=4'h0;
FPGA_Read_Data_reg <= FPGA_Read_Data;
FPGA_Address <= FPGA_Address +4;
end
elseif(count ==118)
begin
FPGA_Enable <=0;
count <= count;
end
end
BBBByour_instance_name (
.clka(USER_CLK), // input clka
.ena(FPGA_Enable), // input ena
.wea(FPGA_Write_Enable), // input [3 : 0] wea
.addra(FPGA_Address), // input [31 : 0] addra
.dina(FPGA_Write_Data), // input [31 : 0] dina
.douta(FPGA_Read_Data), // output [31 : 0] douta
.clkb(clkb), // input clkb
.enb(enb), // input enb
.web(web), // input [3 : 0] web
.addrb(addrb), // input [31 : 0] addrb
.dinb(dinb), // input [31 : 0] dinb
.doutb(doutb) // output [31 : 0] doutb
);
endmodule
該例程中,在count為101(>100)後開始往地址4到20寫入1-5,然後在count為111(>110)的時候讀出寫入的資料。
4.模擬
下面利用Modelsim和Vivado進行聯合模擬,關於vivado如何與modelsim進行聯合模擬可以參考這篇文章:
vivado與modelsim的關聯以及器件庫編譯
有一點要注意的是,我用的是Vivado2017.1版本,這個版本只支援Modelsim10.5及以上的版本,如果是低版本的Modelsim,在用Vivado2017.1編譯Modelsim的模擬庫時,會出錯。Modelsim10.5版本可以在這裡下載:
modelsim 10.5 適用vivado 2017.1
用Modelsim模擬時,會在sim_1/behav資料夾下產生3個.do檔案,分別是xx_compile.do,xx_simulate.do,xx _wave.do檔案。在設計的verilog檔案修改之後,如果在Modelsim中直接restart,模擬的其實還是沒有修改前的檔案,要使修改的.v檔案在Modelsim中生效,可以在Modelsim的命令視窗輸入do xx_compile.do檔案,對模擬的庫檔案以及設計檔案(.v檔案)重新編譯,然後在輸入do xx_simulate.do檔案,才能模擬修改後的檔案。輸入do xx_compile.do命令對設計檔案重新編譯的時候,Modelsim會強制退出,這時由最後一句force quit命令引起的,只要把它刪掉就行了。如果要儲存波形檔案,可以save format,另存為xx_wave.do檔案。
參考上面雙口RAM的例程3進行功能模擬,RAM IP使用Write First模式,設計檔案程式碼如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/12/09 22:36:48
// Design Name:
// Module Name: dual_port_ram_demo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
moduledual_port_ram_demo(
input USER_CLK
);
`define DLY #1
//Port A declaration
reg FPGA_Enable=0;
reg FPGA_Write_Enable=0;
reg[31:0] FPGA_Address=0;
reg[31:0] FPGA_Write_Data=0;
reg[31:0] FPGA_Read_Data_reg=0;
wire[31:0] FPGA_Read_Data;
//Port B declaration
reg enb=0;
reg[3:0] web=4'h0;
reg[31:0] addrb=0;
reg[31:0] dinb=0;
reg[31:0] doutb_reg=0;
wire[31:0] doutb=0;
reg[10:0] count=0;
always @ (posedge USER_CLK)
begin
count <= count +1;
if(count<=100)
begin
FPGA_Enable <=1;
FPGA_Write_Enable <=0;
end
elseif((count <=105)&&(count >100))
begin
FPGA_Enable <=1;
FPGA_Write_Enable <=1;
FPGA_Address <= FPGA_Address +4;
FPGA_Write_Data <= FPGA_Write_Data +1;
end
elseif((count <=110)&&(count >105))
begin
FPGA_Enable <=1;
FPGA_Write_Enable <=0;
FPGA_Address <=0;
FPGA_Write_Data <=0;
end
elseif((count <=117)&&(count >110))
begin
FPGA_Enable <=1;
FPGA_Write_Enable <=1;
FPGA_Read_Data_reg <= FPGA_Read_Data;
FPGA_Address <= FPGA_Address +4;
end
elseif(count ==118)
begin
FPGA_Enable <=0;
count <= count;
end
end
dpRAMu1 (
.clka(USER_CLK), // input clka
.ena(FPGA_Enable), // input ena
.wea(FPGA_Write_Enable), // input [3 : 0] wea
.addra(FPGA_Address), // input [31 : 0] addra
.dina(FPGA_Write_Data), // input [31 : 0] dina
.douta(FPGA_Read_Data), // output [31 : 0] douta
.clkb(USER_CLK), // input clkb
.enb(enb), // input enb
.web(web), // input [3 : 0] web
.addrb(addrb), // input [31 : 0] addrb
.dinb(dinb), // input [31 : 0] dinb
.doutb(doutb) // output [31 : 0] doutb
);
endmodule
testbench檔案如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/12/09 22:47:26
// Design Name:
// Module Name: simu
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
modulesimu(
);
//testbench 時鐘訊號
reg clk =0;
always # 10 clk < =~clk;
//呼叫dual_port_ram_demo模組
dual_port_ram_demodemo1(clk);
endmodule
模擬結果如下:
(圖4.1)
程式在1時刻準備好地址和要寫入RAM的資料,在2時刻寫入RAM中,在3時刻端口才會輸出2時刻寫入RAM的資料,注意與PG058的圖稍有不同。
(圖4.2)
4.後記
關於BRAM,推薦一個youtube視訊,裡面講的非常清晰易懂。
What is a Block RAM in an FPGA?