owned this note
owned this note
Published
Linked with GitHub
# 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%。

### Block Design截圖

- 為了實現 **真-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:**

- **Port B test:**


## 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)