1、BRAM配置測(cè)試
我們知道Vivado中BRAM大小分為18K和36K兩種莉撇,這兩種BRAM在何種配置下會(huì)如何分配資源呢蛤,需要進(jìn)行一定的考量。由于Vivado可以配置生成任意bit數(shù)的IO位寬棍郎,所以我對(duì)BRAM配置進(jìn)行了簡(jiǎn)單的實(shí)驗(yàn)其障,結(jié)果如下所示。
可以看到,18bit位寬辜荠,1K深度可以正常使用18Kb的小BRAM汽抚。
如果使用16bit訪存位寬造烁,1152深度,依然是18Kb大小來(lái)生成BRAM,會(huì)導(dǎo)致資源無(wú)法映射到18Kb的BRAM中惭蟋,而是使用了36Kb大小的BRAM苗桂,這導(dǎo)致了一半的BRAM被浪費(fèi)。
上面兩張圖可以看到便锨,如果使用較低的8bit位寬實(shí)現(xiàn)18Kb大小的RAM,也無(wú)法調(diào)用18K BRAM我碟。但是如果將深度縮減為2K(經(jīng)過(guò)測(cè)試2049深度也是調(diào)用了36K BRAM放案,所以深度必須是2K以下),即RAM大小減小為16Kb怎囚,則可以調(diào)用18K BRAM卿叽,減少資源浪費(fèi),這就很有意思了恳守。
根據(jù)上述思路考婴,我又對(duì)16bit位寬進(jìn)行測(cè)試,設(shè)定深度為1K催烘,則調(diào)用了18K BRAM沥阱,并且深度也是必須小于等于1K才會(huì)調(diào)用,否則就是36K BRAM伊群,即RAM大小必須小于等于16Kb考杉。依此類推,4bit位寬如果想使用18K BRAM舰始,則必須深度小于等于4K崇棠,即RAM小于等于16Kb。
從官方文檔中找到了對(duì)上述內(nèi)容的解釋丸卷,其實(shí)BRAM有16K×1枕稀、8K×2、4K×4谜嫉、2K×9萎坷、1K×18、512×36等6種原型方案沐兰,所有的BRAM配置方案均在這些原型的基礎(chǔ)上進(jìn)行疊加拼接得到哆档。所以說(shuō)如果使用了1152×16的配置方案,則需要至少2塊1K×18串聯(lián)來(lái)滿足深度要求住闯,或者兩塊2K×9并聯(lián)(4個(gè)4K×4并聯(lián)等其他方案也可瓜浸,但太浪費(fèi)BRAM)來(lái)滿足位寬要求澳淑,所以必須要占用36K BRAM。并且如果有更大的深度或者位寬出現(xiàn)時(shí)斟叼,可能會(huì)有很多種不同的解決方案偶惠,Vivado中也提供了相應(yīng)的三種方案,Minimum Area Algorithm朗涩、Low Power Algorithm、Fixed Primitive Algorithm绑改,幫助生成最適合項(xiàng)目需求的RAM形式谢床。詳見(jiàn)文檔pg058-blk-mem-gen,42~45頁(yè)厘线。
2识腿、BRAM讀寫(xiě)時(shí)序
在本文中主要使用了寫(xiě)優(yōu)先模式,以保證讀出數(shù)據(jù)為最新造壮《伤希可以從時(shí)序圖中看到,輸入數(shù)據(jù)耳璧、數(shù)據(jù)地址成箫、Enable信號(hào)等需要在時(shí)鐘上升沿之前就到達(dá)接口位置。
3旨枯、ARM Memory Compiler SRAM時(shí)序
與ASIC實(shí)現(xiàn)中使用Memory Compiler生成的SRAM時(shí)序進(jìn)行對(duì)比蹬昌,可以發(fā)現(xiàn)該SRAM中控制信號(hào)和數(shù)據(jù)寫(xiě)入基本與Xilinx的BRAM是一致的,均需要在時(shí)鐘上升沿之前到來(lái)地址和數(shù)據(jù)攀隔。但是MC SRAM數(shù)據(jù)讀出速度要更快皂贩,在一定的時(shí)間延遲后即可得到有效輸出數(shù)據(jù)嗎,而B(niǎo)RAM則至少有1~3個(gè)周期的延遲才能獲得讀出數(shù)據(jù)昆汹。
4明刷、讀寫(xiě)沖突
轉(zhuǎn)自Xilinx之RAM使用指南。
BRAM讀寫(xiě)時(shí)對(duì)端口位寬大小的單元進(jìn)行操作满粗,例如dout位寬為32bit辈末,則每次讀寫(xiě)均為32bit,BRAM深度大小就是有幾個(gè)32bit的數(shù)據(jù)败潦。
因此讀寫(xiě)沖突的可能僅存在于同時(shí)讀寫(xiě)同一個(gè)32bit數(shù)據(jù)本冲,即同時(shí)讀寫(xiě)一個(gè)地址的數(shù)據(jù)。所以我們?cè)谶M(jìn)行OBUF實(shí)現(xiàn)時(shí)劫扒,應(yīng)該不會(huì)有讀寫(xiě)沖突的出現(xiàn)檬洞。下圖中所示為三種端口模式下的讀寫(xiě)沖突情況,其中寫(xiě)優(yōu)先模式的讀寫(xiě)沖突沟饥,是由于前一周期B端口在讀添怔,本周期A端口在寫(xiě)湾戳,導(dǎo)致本周期B端口應(yīng)當(dāng)讀到的舊數(shù)據(jù)被覆蓋。從數(shù)據(jù)讀邏輯上來(lái)說(shuō)广料,只要有一個(gè)端口在本周期寫(xiě)數(shù)據(jù)砾脑,本周期讀到的數(shù)據(jù)必定是亞穩(wěn)態(tài),如果是字節(jié)寫(xiě)模式(有寫(xiě)Mask)艾杏,則寫(xiě)入部分地址的數(shù)據(jù)輸出為亞穩(wěn)態(tài)韧衣。如果是本周期A端口寫(xiě)入、B端口讀出购桑,則可以在下周期讀出最新寫(xiě)入的數(shù)據(jù)畅铭。(本周期讀的數(shù)據(jù)在下周期獲得是在BRAM配置時(shí)設(shè)定的輸出寄存器個(gè)數(shù)決定的,延遲周期數(shù)可以為1~3)
各種RAM(雙端RAM勃蜘、DRAM)HDL寫(xiě)法詳見(jiàn)Vivado使用技巧(27):RAM編寫(xiě)技巧尝抖。
5炊林、雙端RAM Ping-Pong Buffer讀寫(xiě)控制
轉(zhuǎn)自FPGA基礎(chǔ)設(shè)計(jì)(7)雙口RAM乒乓操作滩援。
這里的雙端RAM兩個(gè)口接的時(shí)鐘頻率不一樣破衔,寫(xiě)端口CLKA為20MHz,讀端口CLKB為100MHz阳惹,也就是說(shuō)讀速度為寫(xiě)速度的5倍谍失。
`timescale 1ns / 1ps
module DualRAM
(
input clk_wr, //寫(xiě)時(shí)鐘速率20Mhz
input clk_rd, //讀時(shí)鐘速率100Mhz
input rst_n,
input [7:0] din,
output reg out_valid,
output reg [7:0] dout
);
reg [9:0] addr_wr, addr_rd;
reg en_wr1, en_wr2, we_wr1, we_wr2, en_rd1, en_rd2;
wire [7:0] dout1, dout2;
dual_port_ram u1 (
.clka(clk_wr), //寫(xiě)端口
.ena(en_wr1),
.wea(we_wr1),
.addra(addr_wr),
.dina(din),
.douta(),
.clkb(clk_rd), //讀端口
.enb(en_rd1),
.web(1'b0),
.addrb(addr_rd),
.dinb(8'd0),
.doutb(dout1)
);
dual_port_ram u2 (
.clka(clk_wr), //寫(xiě)端口
.ena(en_wr2),
.wea(we_wr2),
.addra(addr_wr),
.dina(din),
.douta(),
.clkb(clk_rd), //讀端口
.enb(en_rd2),
.web(1'b0),
.addrb(addr_rd),
.dinb(8'd0),
.doutb(dout2)
);
//寫(xiě)端口乒乓操作
always @ (posedge clk_wr) //寫(xiě)地址信號(hào)控制0~1023
if (!rst_n) addr_wr <= 1023;
else addr_wr <= addr_wr + 1'b1;
always @ (posedge clk_wr) //輪流寫(xiě)RAM1與RAM2
if (!rst_n) begin we_wr1 <= 1'b1; we_wr2 <= 1'b0;
en_wr1 <= 1'b1; en_wr2 <= 1'b0; end
else if (addr_wr == 1023) begin
we_wr1 <= ~we_wr1; we_wr2 <= ~we_wr2;
en_wr1 <= ~en_wr1; en_wr2 <= ~en_wr2;
end
//讀端口乒乓操作
always @ (posedge clk_rd) //讀地址信號(hào)控制0~1023
if (!rst_n) addr_rd <= 1021; //匹配延遲
else addr_rd <= addr_rd + 1'b1;
reg [15:0] cnt;
always @ (posedge clk_rd) //讀時(shí)鐘為寫(xiě)時(shí)鐘的5倍
if (!rst_n) cnt <= 16'hFFFE; //匹配延遲
else if (cnt == 5119) cnt <= 0;
else cnt <= cnt + 1'b1;
reg flag1, flag2;
always @ (posedge clk_rd) //讀RAM標(biāo)志,RAM1或RAM2
if (!rst_n) begin flag1 <= 1'b1; flag2 <= 1'b0; end
else if (cnt == 5119) begin flag1 = ~flag1; flag2 = ~flag2; end
else begin flag1 <= flag1; flag2 <= flag2; end
always @ (posedge clk_rd) //讀RAM使能穆端,選擇cnt的前1/5時(shí)間讀取
if (!rst_n) begin en_rd1 <= 1'b1; en_rd2 <= 1'b0; end
else if (cnt < 1024) begin en_rd1 <= flag1; en_rd2 <= flag2; end
else begin en_rd1 <= 1'b0; en_rd2 <= 1'b0; end
reg en_rd1_reg, en_rd2_reg;
always @ (posedge clk_rd) //延遲一級(jí)袱贮,匹配時(shí)序
if (!rst_n) begin en_rd1_reg <= 0; en_rd1_reg <= en_rd1_reg; end
else begin en_rd1_reg <= en_rd1; en_rd2_reg <= en_rd2; end
always @ (posedge clk_rd) //輸出選擇,RAM1或RAM2体啰;控制輸出使能信號(hào)
if (!rst_n) begin dout <= 0; out_valid <= 0; end
else if (en_rd1_reg) begin dout <= dout1; out_valid <= 1; end
else if (en_rd2_reg) begin dout <= dout2; out_valid <= 1; end
else begin dout <= 0; out_valid <= 0; end
endmodule
上面的代碼中有例化BRAM模塊攒巍,不過(guò)這些端口不一定全部需要,根據(jù)本項(xiàng)目特點(diǎn)荒勇,使用Simple dual-port BRAM就可以柒莉,因此A口寫(xiě),B口讀沽翔,A口沒(méi)有douta信號(hào)兢孝,B口沒(méi)有dinb和web信號(hào),例化的時(shí)候要注意仅偎,如果不清楚可以到Vivado中打開(kāi)diagram看一下跨蟹。
6、對(duì)比DNN Weaver RAM與BRAM數(shù)據(jù)接口與時(shí)序差別
下面是DNN Weaver RAM模塊代碼橘沥,該RAM使用在IBUF和BBUF中窗轩。與BRAM模塊接口對(duì)比,該RAM的讀寫(xiě)使能信號(hào)分開(kāi)座咆,并且讀通道與寫(xiě)通道分開(kāi)痢艺,可以同時(shí)讀寫(xiě)仓洼,但沒(méi)有解決讀寫(xiě)沖突問(wèn)題,說(shuō)明IBUF和BBUF不會(huì)出現(xiàn)該問(wèn)題堤舒,并且輸出均有1個(gè)寄存器的延遲(例化模塊時(shí)設(shè)定OUTPUT_REG=1)色建。總之使用BRAM對(duì)該模塊可以進(jìn)行很好的代替舌缤,因?yàn)楣δ苌蟻?lái)說(shuō)該RAM是BRAM的子集箕戳。
除了IBUF和BBUF,DNN Weaver中還有個(gè)OBUF友驮。由于OBUF需要大量的讀寫(xiě)漂羊,OBUF設(shè)計(jì)比IBUF等邏輯復(fù)雜很多,并且有兩套讀寫(xiě)接口卸留,模塊名稱為banked_ram。學(xué)姐當(dāng)時(shí)建議使用DRAM(Distributed RAM)實(shí)現(xiàn)OBUF椭豫,不知道會(huì)不會(huì)在綜合的時(shí)候RAM邏輯過(guò)大耻瑟,導(dǎo)致片上資源不足,或者導(dǎo)致綜合時(shí)間過(guò)長(zhǎng)的問(wèn)題赏酥。
`timescale 1ns/1ps
module ram
#(
parameter integer DATA_WIDTH = 10,
parameter integer ADDR_WIDTH = 12,
parameter integer OUTPUT_REG = 0
)
(
input wire clk,
input wire reset,
input wire s_read_req,
input wire [ ADDR_WIDTH -1 : 0 ] s_read_addr,
output wire [ DATA_WIDTH -1 : 0 ] s_read_data,
input wire s_write_req,
input wire [ ADDR_WIDTH -1 : 0 ] s_write_addr,
input wire [ DATA_WIDTH -1 : 0 ] s_write_data
);
reg [ DATA_WIDTH -1 : 0 ] mem [ 0 : 1<<ADDR_WIDTH ];
always @(posedge clk)
begin: RAM_WRITE
if (s_write_req)
mem[s_write_addr] <= s_write_data;
end
generate
if (OUTPUT_REG == 0)
assign s_read_data = mem[s_read_addr];
else begin
reg [DATA_WIDTH-1:0] _s_read_data;
always @(posedge clk)
begin
if (reset)
_s_read_data <= 0;
else if (s_read_req)
_s_read_data <= mem[s_read_addr];
end
assign s_read_data = _s_read_data;
end
endgenerate
endmodule