vivado 亚稳态_【DNN Weaver FPGA实现】Vivado BRAM资源使用
1、BRAM配置測試
我們知道Vivado中BRAM大小分為18K和36K兩種,這兩種BRAM在何種配置下會如何分配資源,需要進行一定的考量。由于Vivado可以配置生成任意bit數的IO位寬,所以我對BRAM配置進行了簡單的實驗,結果如下所示。
18bit位寬,1K深度,共18Kb
可以看到,18bit位寬,1K深度可以正常使用18Kb的小BRAM。
16bit位寬,1152深度,共18Kb
如果使用16bit訪存位寬,1152深度,依然是18Kb大小來生成BRAM,會導致資源無法映射到18Kb的BRAM中,而是使用了36Kb大小的BRAM,這導致了一半的BRAM被浪費。
8bit位寬,2304深度,共18Kb
8bit位寬,2048深度,共16Kb
上面兩張圖可以看到,如果使用較低的8bit位寬實現18Kb大小的RAM,也無法調用18K BRAM。但是如果將深度縮減為2K(經過測試2049深度也是調用了36K BRAM,所以深度必須是2K以下),即RAM大小減小為16Kb,則可以調用18K BRAM,減少資源浪費,這就很有意思了。
根據上述思路,我又對16bit位寬進行測試,設定深度為1K,則調用了18K BRAM,并且深度也是必須小于等于1K才會調用,否則就是36K BRAM,即RAM大小必須小于等于16Kb。依此類推,4bit位寬如果想使用18K BRAM,則必須深度小于等于4K,即RAM小于等于16Kb。
官方文檔給出的BRAM原型分配
從官方文檔中找到了對上述內容的解釋,其實BRAM有16K×1、8K×2、4K×4、2K×9、1K×18、512×36等6種原型方案,所有的BRAM配置方案均在這些原型的基礎上進行疊加拼接得到。所以說如果使用了1152×16的配置方案,則需要至少2塊1K×18串聯來滿足深度要求,或者兩塊2K×9并聯(4個4K×4并聯等其他方案也可,但太浪費BRAM)來滿足位寬要求,所以必須要占用36K BRAM。并且如果有更大的深度或者位寬出現時,可能會有很多種不同的解決方案,Vivado中也提供了相應的三種方案,Minimum Area Algorithm、Low Power Algorithm、Fixed Primitive Algorithm,幫助生成最適合項目需求的RAM形式。詳見文檔pg058-blk-mem-gen,42~45頁。
Examples of the Minimum Area Algorithm
Examples of the Low Power Algorithm
Examples of the Fixed Primitive Algorithm
2、BRAM讀寫時序
寫優先
在本文中主要使用了寫優先模式,以保證讀出數據為最新。可以從時序圖中看到,輸入數據、數據地址、Enable信號等需要在時鐘上升沿之前就到達接口位置。
3、ARM Memory Compiler SRAM時序
Memory Compiler SRAM時序
與ASIC實現中使用Memory Compiler生成的SRAM時序進行對比,可以發現該SRAM中控制信號和數據寫入基本與Xilinx的BRAM是一致的,均需要在時鐘上升沿之前到來地址和數據。但是MC SRAM數據讀出速度要更快,在一定的時間延遲后即可得到有效輸出數據嗎,而BRAM則至少有1~3個周期的延遲才能獲得讀出數據。
4、讀寫沖突
BRAM讀寫時對端口位寬大小的單元進行操作,例如dout位寬為32bit,則每次讀寫均為32bit,BRAM深度大小就是有幾個32bit的數據。
因此讀寫沖突的可能僅存在于同時讀寫同一個32bit數據,即同時讀寫一個地址的數據。所以我們在進行OBUF實現時,應該不會有讀寫沖突的出現。下圖中所示為三種端口模式下的讀寫沖突情況,其中寫優先模式的讀寫沖突,是由于前一周期B端口在讀,本周期A端口在寫,導致本周期B端口應當讀到的舊數據被覆蓋。從數據讀邏輯上來說,只要有一個端口在本周期寫數據,本周期讀到的數據必定是亞穩態,如果是字節寫模式(有寫Mask),則寫入部分地址的數據輸出為亞穩態。如果是本周期A端口寫入、B端口讀出,則可以在下周期讀出最新寫入的數據。(本周期讀的數據在下周期獲得是在BRAM配置時設定的輸出寄存器個數決定的,延遲周期數可以為1~3)
BRAM讀寫沖突
各種RAM(雙端RAM、DRAM)HDL寫法詳見Vivado使用技巧(27):RAM編寫技巧。
5、雙端RAM Ping-Pong Buffer讀寫控制
轉自FPGA基礎設計(7)雙口RAM乒乓操作。
這里的雙端RAM兩個口接的時鐘頻率不一樣,寫端口CLKA為20MHz,讀端口CLKB為100MHz,也就是說讀速度為寫速度的5倍。
`timescale 1ns / 1ps
module DualRAM
(
input clk_wr, //寫時鐘速率20Mhz
input clk_rd, //讀時鐘速率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), //寫端口
.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), //寫端口
.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)
);
//寫端口乒乓操作
always @ (posedge clk_wr) //寫地址信號控制0~1023
if (!rst_n) addr_wr <= 1023;
else addr_wr <= addr_wr + 1'b1;
always @ (posedge clk_wr) //輪流寫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) //讀地址信號控制0~1023
if (!rst_n) addr_rd <= 1021; //匹配延遲
else addr_rd <= addr_rd + 1'b1;
reg [15:0] cnt;
always @ (posedge clk_rd) //讀時鐘為寫時鐘的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標志,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時間讀取
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) //延遲一級,匹配時序
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;控制輸出使能信號
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模塊,不過這些端口不一定全部需要,根據本項目特點,使用Simple dual-port BRAM就可以,因此A口寫,B口讀,A口沒有douta信號,B口沒有dinb和web信號,例化的時候要注意,如果不清楚可以到Vivado中打開diagram看一下。
仿真結果
6、對比DNN Weaver RAM與BRAM數據接口與時序差別
下面是DNN Weaver RAM模塊代碼,該RAM使用在IBUF和BBUF中。與BRAM模塊接口對比,該RAM的讀寫使能信號分開,并且讀通道與寫通道分開,可以同時讀寫,但沒有解決讀寫沖突問題,說明IBUF和BBUF不會出現該問題,并且輸出均有1個寄存器的延遲(例化模塊時設定OUTPUT_REG=1)。總之使用BRAM對該模塊可以進行很好的代替,因為功能上來說該RAM是BRAM的子集。
除了IBUF和BBUF,DNN Weaver中還有個OBUF。由于OBUF需要大量的讀寫,OBUF設計比IBUF等邏輯復雜很多,并且有兩套讀寫接口,模塊名稱為banked_ram。學姐當時建議使用DRAM(Distributed RAM)實現OBUF,不知道會不會在綜合的時候RAM邏輯過大,導致片上資源不足,或者導致綜合時間過長的問題。
`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<
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
總結
以上是生活随笔為你收集整理的vivado 亚稳态_【DNN Weaver FPGA实现】Vivado BRAM资源使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何点亮QQ邮箱图标 - 龙 炫家族特权
- 下一篇: FPGA学习笔记1