# FPGA 第六組 Lab4 結報 [文章網址](https://hackmd.io/nW5uC6ckRiu0B17S9jMT1Q?both) ## 組員 | 姓名 | 學號 | |:------:|:---------:| | 劉永勝 | F94089032 | | 蔡宗瑾 | E14083137 | | 李宇洋 | E24099025 | ## Problem1 - Block RAM Utilize ### 電路設計說明 這次實作的概念是,以verilog叫出內建的`RAMB36E1` IP。實作 **True dual port RAM** 則代表支援2 read、2 write、1 read/1 write 3種資料傳輸方式。 呼叫方式如下方code範例,分成attributes的基本設置和port descriptions的腳位定義。之後依序說明設置方式和理由。 ``` verilog= RAMB36E1 #( // Available Attributes ) RAMB36E1_inst ( // Port Descriptions ); ``` 1. Available Attributes 依照題目要求實作true dual port RAM,在.RAM_MODE設置為"TDP",下方分項說明各個設定的意義。 ``` verilog= RAMB36E1 #( . . . .RAM_MODE("TDP"), . . . ) ``` (1) Set output register 在memory data輸出的位置,有一個output register,將此暫存器開啟,暫存從memory讀取出來的數值,保證下一級電路能有一個完整的clk cycle取資料。 ``` verilog= RAMB36E1 #( . . . .DOA_REG(1), .DOB_REG(1), . . . ) ``` (2) Set initial value in RAM 依題目要求,每32 bit為一個word,並依照指定位置,設置RAM的初始值。其中每4個offset代表4 byte,亦即1 word。例如: offset=28,則將數值設置在第7個word上。 Offset = 0 : 0x2597 Offset = 4 : 0x6425 Offset = 28 : 0x5071 Offset = 64 : 0x8CF5 ``` verilog= RAMB36E1 #( . . . // INIT_00 to INIT_7F: Initial contents of the data memory array .INIT_00(256'h00005071_00000000_00000000_00000000_00000000_00000000_00006425_00002597), .INIT_01(256'h0804020108040201080402010804020108040201080402010804020108040201), .INIT_02(256'h08040201_08040201_08040201_08040201_08040201_08040201_08040201_00008CF5), . . . ) ``` (3) Define width of datapath 題目要求為32-bit datapath,但須設置為36-bit。4 bit作為parity bit。此外,因為實作true dual port,A、B兩個port的read、write皆須定義為36-bit。 ``` verilog= RAMB36E1 #( . . . .READ_WIDTH_A(36), // 0-72 .READ_WIDTH_B(36), // 0-36 .WRITE_WIDTH_A(36), // 0-36 .WRITE_WIDTH_B(36), // 0-72 . . . ) ``` 2. Port Descriptions 先看到dual port RAM的IO名稱,dual port的關係,A、B兩個port各腳位都是成對的。下方依序說明各腳位的功用。 ``` verilog= module bram( input clkA, input clkB, input enA, input enB, input [3:0] wen_A, input [3:0] wen_B, input [31:0] data_in_A, input [31:0] data_in_B, input [14:0] addr_A, input [14:0] addr_B, output [31:0] data_out_A, output [31:0] data_out_B ); ``` (1) Set clk port 在TDP mode中,A、B兩個port是可以獨立的,實現asynchronous讀寫,因此分別輸入不同的clk。此外,為了能用兩個`AXI BRAM controller`實現dual port,因此必須定義兩個clk接線。 ``` verilog= RAMB36E1_inst ( . . . .CLKARDCLK(clkA), .CLKBWRCLK(clkB), . . . ); ``` (2) Set enable output register 同前面設定output register,將A、B output register enable 設定為1'b1,表示兩個output register永遠開啟。 ``` verilog= RAMB36E1_inst ( . . . .REGCEAREGCE(1'b1), .REGCEB(1'b1), . . . ); ``` (3) Set enable RAM RAM的read、write enable由兩個訊號控制,分別為enable和write_enable。**並沒有所謂的read_enable訊號**。enable表示RAM目前可讀、寫,write_enable為high則表示可寫、low表示可讀。下方表格說明兩訊號關係。 |enable|write_enable|說明| |:----:|:----------:|:-:| |1'b0|1'b0|cannot read and write| |1'b0|1'b1|cannot read and write| |1'b1|1'b0|enable read only| |1'b1|1'b1|enable write only| 在TDP mode中,.ENARDEN()、.ENBWREN()表示enable訊號,.WEA()、.WEBWE()則表示write_enable訊號。 ``` verilog= RAMB36E1_inst ( . . . .ENARDEN(enA), .WEA(wen_A), .ENBWREN(enB), .WEBWE({4'd0, wen_B}), . . . ); ``` :::warning **<特別注意>** .WEA()、.WEBWE()為4-bit enable而非1-bit,原因是此RAM支援byte-wide write。我們的1 word為32-bit,等於4 byte,若我們今天要一次寫入32-bit則write_enable設定為4'b1111。若我們只想寫入32-bit中的LSB 8-bit,則write_enable設定為4'b0001。 ::: (4) Set input address 在TDP mode.ADDRARDADDR()、.ADDRBWRADDR()分別代表port A、B的讀,寫16-bit address。 - Reason about 16-bit address RAMB36E1共有36Kb的memory array儲存空間,其組成方式為32Kb的data memory array以及4Kb的parity memory array,因此實際上能夠儲存資料的空間只有$32Kb=2^{5+10}=2^{15}bit$ 那麼多,但address腳位卻給到16-bit,MSB的位元(**A15 pin**)供RAM cascade使用,而這次並沒有實作cascade,因此MSB設置為1'b0。 - Reason about LSB is 3'd0 在C program中,我們offset的最小單位為1 byte,即為8-bit。因此設定address最小單位為byte,則address的LSB為3'd0。 ``` verilog= RAMB36E1_inst ( . . . .ADDRARDADDR({1'b0,addr_A[11:0],3'd0}), .ADDRBWRADDR({1'b0,addr_B[11:0],3'd0}), . . . ); ``` (5) Set input/output datapath RAM input/output data皆為32-bit,而.DOADO()、.DOBDO()為output dual port,.DIADI()、.DIBDI()為input dual port。 ``` verilog= RAMB36E1_inst ( . . . .DOADO(data_out_A), .DIADI(data_in_A), .DOBDO(data_out_B), .DIBDI(data_in_B), . . . ); ``` ### Utilization of BRAM 根據[Product selection guide](https://docs.xilinx.com/v/u/en-US/zynq-7000-product-selection-guide),BRAM共有140個可使用,而我們這次dual port BRAM使用了其中的1個BRAM。因此從下方utilization report看到我們BRAM用了1/140 = 0.71%。 ![](https://i.imgur.com/v2oaWfX.png) ### Block Design截圖 ![](https://i.imgur.com/h3ObenV.png) - 為了實現 **真-dual port BRAM**,必須使用兩個`AXI BRAM controller`,分別連接到的BRAM port A、port B上,才能在C program測試時分別灌資料測試。 - 在接線時,會發現`AXI BRAM controller`只有一個**BRAM_PORTA**的接口,接不上自行設定的verilog template BRAM。無意中發現只要點開`AXI BRAM controller`最右邊的 **+** 就會出現controller詳細的接口,再手動連接到verilog template BRAM即可。 ### Testing ```cpp= // test initial value & port A read printf("Test initial value & port A read:\r\n"); read = Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 0); printf("[Port A: R] Offset = 0, Data = %x\r\n", read); read = Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 4); printf("[Port A: R] Offset = 4, Data = %x\r\n", read); read = Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 28); printf("[Port A: R] Offset = 28, Data = %x\r\n", read); read = Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 64); printf("[Port A: R] Offset = 64, Data = %x\r\n", read); printf("===================\n"); // test port A write printf("Test port A write:\r\n"); for (i = 0; i < 4; i++) { read = Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 4*i); printf("[Port A: W] Offset = %3d, Data = %x -> %x\r\n", 4*i, read, a[i]); Xil_Out32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 4*i, a[i]); printf("Check: MEM[%x] = %x\r\n", XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 4*i, Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 4*i)); printf("-------------------\n"); } printf("===================\n"); // test port B R/W printf("Test port B read & write:\r\n"); for (i = 0; i < 20; i++) { read = Xil_In32(XPAR_AXI_BRAM_CTRL_0_S_AXI_BASEADDR + 4*(i+1)); printf("[Port A: R] Offset = %3d, Data = %x\r\n", 4*(i+1), read); read2 = Xil_In32(XPAR_AXI_BRAM_CTRL_1_S_AXI_BASEADDR + 4*i); printf("[Port B: R] Offset = %3d, Data = %x\r\n", 4*i, read2); printf("[Port B: W] Offset = %3d, Data = %x -> %x\r\n", 4*i, read2, read); Xil_Out32(XPAR_AXI_BRAM_CTRL_1_S_AXI_BASEADDR + 4*i, read); printf("Check: MEM[%x] = %x\r\n", XPAR_AXI_BRAM_CTRL_1_S_AXI_BASEADDR + 4*i, Xil_In32(XPAR_AXI_BRAM_CTRL_1_S_AXI_BASEADDR + 4*i)); printf("-------------------\n"); } printf("===================\n"); ``` - **Port A test:** ![](https://hackmd.io/_uploads/rycwunrE2.png) - **Port B test:** ![](https://hackmd.io/_uploads/SkM2dhBV2.png) ![](https://hackmd.io/_uploads/BkWZt2S4n.png) ## Problem - <筆記> `Block memory - RAMB36E1` 分成parity mem、data mem,分別有16、128個word。每個word為256-bit。總共容量為 (16+128) * 256 = 36864,約為36Kb。 - <筆記>「mebibyte」是數字資訊中的一個位元組數單位。字首「mebi」等於$2^{20}$,1 mebibyte等於1,048,576位元組。 1. PYNQ-Z2上共有多少容量的Block RAM ? 根據[Product selection guide](https://docs.xilinx.com/v/u/en-US/zynq-7000-product-selection-guide)可得知Total Block RAM共有4.9Mb(**Mebibyte**)。一個Block RAM的容量有36Kb,又共有140個Block RAM,因此總容量為$(32_{data}+4_{parity})*1024*140=5,160,960(bits) = 4.921875(Mb)$ 2. 承上題,共有多少個RAMB36E1 ? 本實作板子為PYNQ-Z2,屬於zynq-7000系列Soc家庭,其processing system(PS)的型號為Z-7020,根據[Product selection guide](https://docs.xilinx.com/v/u/en-US/zynq-7000-product-selection-guide)可得知Total Block RAM(#36Kb blocks)有140個。 3. 若要將RAMB36E1 Configure成36Kb FIFO,該使用什麼Verilog Template ? 下方為實作32-bitwise的FIFO,將`RAMB36E1`在TDP mode操作,A port為read、B port為write。其中,以head、tail為FIFO的pointer,當head等於tail時,表示記憶體裡面是空的,則無法read data,write data則沒有考慮overflow,永遠都可以write。BRAM設定說明見程式碼註解 (17個設定說明)。 :::warning <特別注意> 程式碼第49行,ren需要加上inverter的邏輯,因為write_enable=0才是read data。 ::: ```verilog= module ( input clk, input rst, input [31:0] data_in, input [3:0] wen, // write enable input ren, // read enable output reg empty, full, // FIFO memory R/W enable output [31:0] data_out ); reg [12:0] head; reg [12:0] tail; // control head and tail always @(posedge clk or posedge rst) begin if (rst) begin head <= 13'd0; tail <= 13'd0; end else begin head <= (wen) ? head + 13'd1 : head; tail <= (ren) ? tail + 13'd1 : tail; end end // FIFO is empty cannot read always @(*) begin empty = !(head[12] ^ tail[12]) && (head[11:0] == tail[11:0]); full = (head[12] ^ tail[12]) && (head[11:0] == tail[11:0]); end RAMB36E1 #( // Available Attributes .RAM_MODE("TDP"), // set as true dual port mode .DOA_REG(1), // set A port output register .READ_WIDTH_A(36), // A port read 32-bit data .WRITE_WIDTH_A(0), // A port cannot write .READ_WIDTH_B(0), // B port cannot read .WRITE_WIDTH_B(36) // B port write 32-bit data ) RAMB36E1_inst ( // Port Descriptions .CLKARDCLK(clk), // A, B synchronize .CLKBWRCLK(clk), // A, B synchronize .REGCEAREGCE(1'b1), // enable A port output register .ENARDEN(!empty), // read enable, cannot read when FIFO is empty .WEA({~ren, ~ren, ~ren, ~ren}), // always not write .ENBWREN(!full), // cannot write when FIFO is full .WEBWE({4'd0, wen}), // write 4 byte at once, write_enable = 0 is read .ADDRARDADDR({1'b0, head[11:0], 3'd0}), // read address .ADDRBWRADDR({1'b0, tail[11:0], 3'd0}), // write address .DOADO(data_out), // 32-bit data read out .DIBDI(data_in), // 32-bit data write in ); endmodule ``` ## Reference [1] [Vivado Design Suite 7 Series FPGA and Zynq-7000 SoC Libraries Guide (UG953)](https://docs.xilinx.com/r/2021.2-English/ug953-vivado-7series-libraries/RAMB18E1)文檔搜尋: RAMB36E1 [2] [考古](https://hackmd.io/@H-L-Parker/Syad05yuO) [3] [腳位說明](https://blog.csdn.net/weixin_41445387/article/details/125676600) [4] [範例code](https://blog.csdn.net/shuchangsc/article/details/102542799)