# FPGA 第六組 Lab5 結報
[文章網址](https://hackmd.io/-VSODjKNQuuPo5jIQL7hPg)
## 組員
| 姓名 | 學號 |
|:------:|:---------:|
| 劉永勝 | F94089032 |
| 蔡宗瑾 | E14083137 |
| 李宇洋 | E24099025 |
## Problem1 - Simple Computing System

### 電路設計說明
- 本次實作的目的是要學習如何操作BRAM和DSP,將BRAM讀出的資料送進DSP中進行運算,再將算出的結果存入BRAM的指定地址。
- 透過Controller將PS端傳輸進來的instruction解碼,並傳至BRAM0,BRAM1,以及DSP中進行相對應運作。

- Spec規定BRAM的data width是32bit,且可存取的範圍只有32*32Kb,因此對於BRAM讀取寫入的address bus寬度只需要5bit。
- BRAM0的B port需將資料送進DSP port A,BRAM1的port B除了需將資料送進DSP port B之外,還須將DSP的運算結果寫入其中,因此假如欲完成 ***BRAM1[3] <= BRAM0[0] * BRAM1[2]*** 運算會需要兩個instruction來完成,一個是對BRAM1的port B進行讀取的指令,此時Execute為0,當DSP運算完後,第二筆指令用來寫入BRAM1的port B,此時Execute為1。
#### BRAM0
- 下表為RAMB36E1針對不同port data width所對應的address bus長度,可以看到port data width為36(32 data + 4 parity)所對應的ADDR Bus只有[14:5]是有效地址。
- ADDR Bus[4:0]可為5'b11111或5'b00000,根據助教解釋,後5bit全為1為省電模式,再者,根據實驗結果,全為1或0並不會影響BRAM存取行為。


1. Port A(**可以讀以及寫**)與CPU進行溝通,Port B(**單純讀不能寫**)則是將instruction傳進來的地址(bram0_raddr)讀出資料(**DOBDO**)至DSP的A port。
2. 雖然本次實作沒有要求BRAM0的port A可以讀值,但因為方便偵錯,所以還是將DOADO接到BRAM controller中,使得CPU可以讀取BRAM0的內部資料。
3. 經過實驗得知,需要將DOA以及DOB的output register關掉,DSP的input port才會讀到正確且立即性的值。
4. ADDRARDADDR為從processer進來的地址,因為在PS端與PL端溝通的方式都是透過***XPAR_AXI_BRAM_CTRL_NUM_S_AXI_BASEADDR + 4*i** 這行指令進行,其地址的增長是每四個增加,為byte addressing mode,因此只須取ADDRARDADDR[10:2],而忽略ADDRARDADDR[1:0]。
5. ADDRBWRADDR則是從instruction端進來的地址,是word addressing mode,因此不能省略任何部分,為ADDRBWRADDR[4:0]。
``` verilog=
// Attributes
RAMB36E1 #(
...
// must turn off output registers for immediate read operation for DSP.
.DOA_REG(0),
.DOB_REG(0),
...
// Initial data setup
.INIT_00(256'h00000000_00000531_00000000_00000000_00000000_00001201_00000001_00000023),
...
// BRAM mode
.RAM_MODE("TDP"),
...
);
// Instances
RAMB36E1_inst (
/*-------------------------PORT A-------------------------------*/
// Port A Data: 32-bit (each) output: Port A data
.DOADO(DOADO), // 32-bit output: A port data/LSB data -> output to CPU
.ADDRARDADDR({1'b0, 1'b0, ADDRARDADDR[10:2], 5'b11111}), // 16-bit input: A port address/Read address
.ENARDEN(ENARDEN), // 1-bit input: A port enable/Read enable
.REGCEAREGCE(1'b1), // 1-bit input: A port register enable/Register enable
.WEA(WEA), // 4-bit input: A port write enable
// Port A Data: 32-bit (each) input: Port A data
.DIADI(DIADI), // 32-bit input: A port data/LSB data
/*-------------------------PORT B-------------------------------*/
// Port B Data: 32-bit (each) output: Port B data
.DOBDO(DOBDO), // 32-bit output: B port data -> output to DSP port A
.ADDRBWRADDR({1'b0, 5'b00000, ADDRBWRADDR[4:0], 5'b11111}), // 16-bit input: B port address/Write address
.ENBWREN(1'b1), // 1-bit input: B port enable/Write enable
.WEBWE(), // 8-bit input: B port write enable/Write enable
);
```
#### BRAM1

1. Port A(**可以讀以及寫**)與CPU進行溝通。Port B(**可以讀可以寫**),其讀與寫的憑據WEB是來自instruction的execute,讀則是將instruction傳進來的地址(bram1_addr)讀出資料(**DOBDO**)至DSP的B port;寫則是將DSP的P port寫入BRAM1的bram1_addr位置。
2. ADDRARDADDR 與 ADDRBWRADDR的概念和BRAM0雷同。
3. DIBDI 是來自DSP P port的運算結果。
``` verilog=
// Attributes
RAMB36E1 #(
...
// must turn off output registers for immediate read operation for DSP.
.DOA_REG(0),
.DOB_REG(0),
...
// Initial data setup
.INIT_00(256'h00000000_00000531_00000000_00000000_00000000_00001201_00000001_00000023),
...
// BRAM mode
.RAM_MODE("TDP"),
...
);
// Instances
RAMB36E1_inst (
/*-------------------------PORT A-------------------------------*/
// Port A Data: 32-bit (each) output: Port A data
.DOADO(DOADO), // 32-bit output: A port data/LSB data -> output to CPU
.ADDRARDADDR({1'b0, 1'b0, ADDRARDADDR[10:2], 5'b11111}), // 16-bit input: A port address/Read address
.ENARDEN(ENARDEN), // 1-bit input: A port enable/Read enable
.REGCEAREGCE(1'b1), // 1-bit input: A port register enable/Register enable
.WEA(WEA), // 4-bit input: A port write enable
// Port A Data: 32-bit (each) input: Port A data
.DIADI(DIADI), // 32-bit input: A port data/LSB data
/*-------------------------PORT B-------------------------------*/
// Port B Data: 32-bit (each) output: Port B data
.DOBDO(DOBDO), // 32-bit output: B port data -> output to DSP port A
.ADDRBWRADDR({1'b0, 5'b00000, ADDRBWRADDR[4:0], 5'b11111}), // 16-bit input: B port address/Write address
.ENBWREN(1'b1), // 1-bit input: B port enable/Write enable
.WEBWE({4'd0, WEB, WEB, WEB, WEB}), // 8-bit input: B port write enable/Write enable
.DIBDI(DIBDI), // 32-bit input: B port data/MSB data
);
```
#### DSP
- 本次實作沒有用到 MASK,PATTERN 比對和 pre-adder功能,因此相對應的register以及attribute暫不詳細討論,但可以注意到DREG為pre-adder的暫存,設為0。我們也沒有用到cascade的功能。
- Input port A,B以及C均選用一個register,以及DSP的OPMODE,ALUMODE,INMODE都會使用一個register,有選用到的register,在instance中的clk enable都需將其開啟。
- ACASCREG 和 BCASCREG 為A端以及B端cascade 路徑上的輸入register數量,必須跟AREG以及BREG數量相同。
- 依照題目要求,P port的register必須開啟,因此我們一個procedure分成兩個instruction執行,第一個階段DSP算出的結果暫存在register中,第二階段會將計算結果**feedback回DPS,執行加0的加法**,再將P register中的值寫入BRAM1中,若沒有feedback的動作,則DSP又會從input A、B的地方取到錯的值計算。
``` verilog=
DSP48E1 #(
// Feature Control Attributes: Data Path Selection
.A_INPUT("DIRECT"), // Selects A input source, "DIRECT" (A port) or "CASCADE" (ACIN port)
.B_INPUT("DIRECT"), // Selects B input source, "DIRECT" (B port) or "CASCADE" (BCIN port)
.USE_DPORT("FALSE"), // Select D port usage (TRUE or FALSE)
.USE_MULT("MULTIPLY"), // Select multiplier usage ("MULTIPLY", "DYNAMIC", or "NONE")
...
// Register Control Attributes: Pipeline Register Configuration
.ACASCREG(1), // Number of pipeline stages between A/ACIN and ACOUT (0, 1 or 2)
.ADREG(0), // Number of pipeline stages for pre-adder (0 or 1)
.ALUMODEREG(1), // Number of pipeline stages for ALUMODE (0 or 1)
.AREG(1), // Number of pipeline stages for A (0, 1 or 2)
.BCASCREG(1), // Number of pipeline stages between B/BCIN and BCOUT (0, 1 or 2)
.BREG(1), // Number of pipeline stages for B (0, 1 or 2)
.CARRYINREG(0), // Number of pipeline stages for CARRYIN (0 or 1)
.CARRYINSELREG(0), // Number of pipeline stages for CARRYINSEL (0 or 1)
.CREG(1), // Number of pipeline stages for C (0 or 1)
.DREG(0), // Number of pipeline stages for D (0 or 1)
.INMODEREG(1), // Number of pipeline stages for INMODE (0 or 1)
.MREG(1), // Number of multiplier pipeline stages (0 or 1)
.OPMODEREG(1), // Number of pipeline stages for OPMODE (0 or 1)
.PREG(1) // Number of pipeline stages for P (0 or 1)
)
DSP48E1_inst(
.P(P), // 48-bit output: Primary data output
// Cascade: 30-bit (each) input: Cascade Ports
...
// Control: 4-bit (each) input: Control Inputs/Status Bits
.ALUMODE(ALUMODE), // 4-bit input: ALU control input
.CARRYINSEL(0), // 3-bit input: Carry select input
.CLK(clk), // 1-bit input: Clock input
.INMODE(INMODE), // 5-bit input: INMODE control input
.OPMODE(OPMODE), // 7-bit input: Operation mode input
// Data: 30-bit (each) input: Data Ports
.A(A), // 30-bit input: A data input
.B(B), // 18-bit input: B data input
.C(48'h0000_0009_5514), // 48-bit input: C data input
.CARRYIN(0), // 1-bit input: Carry input signal
.D(), // 25-bit input: D data input
// Reset/Clock Enable: 1-bit (each) input: Reset/Clock
.CEA1(1), // 1-bit input: Clock enable input for 1st stage AREG
.CEA2(0), // 1-bit input: Clock enable input for 2nd stage AREG
.CEAD(0), // 1-bit input: Clock enable input for ADREG
.CEALUMODE(1), // 1-bit input: Clock enable input for ALUMODE
...
);
```
- ALUMODE(4bit)是在決定X,Y以及Z muxes出來output的運算模式。
- OPMODE(7bit)是在決定X,Y,以Z muxes的output。
- INMODE(5bit)的[3:0]是在決定 Dual A,D and pre-adder logics,使得multiplier A port 有不同的輸出。INMODE[4]是在決定multipler B port的輸出。詳情可以參見[DSP48E1 user guide notes](https://hackmd.io/Ao7HJne5Qg2GfZF2kQdLWg)。
- 根據不同的operation可以歸類出以下表格:
| Operation | ALUMODE | OPMODE|Z output|
|:------:|:---------:|:---------:|:---------:|
| A*B = Z+X+Y | 0000 | 000_01_01 |0|
| A*B+C = Z+X+Y | 0000 | 011_01_01|C|
| C-A*B = Z-(X+Y)| 0011 | 011_01_01 |C|
| A*B-C-1 = -Z-1+(X+Y)| 0001 | 011_01_01 |C|
#### PS C-code
一個procedure分為兩個階段,第一個是DSP計算階段,第二個是BRAM1寫入階段。在第一階段計算結果存放在PREG中,我們須在第二階段將PREG裡的值拉回X mux輸出並和Y以及Z mux輸出的0相加後,將其結果寫入BRAM1中。
``` c++=
// In order to write into BRAM1
// OPMODE[1:0] = 2'b10 for X mux output P
// OPMODE[3:2] = 2'b00 for Y mux output 0
// OPMODE[6:4] = 3'b000 for Z mux output 0
/*--------------BRAM1[3] <= BRAM0[0] * BRAM1[2]-----------------*/
// ------------------DSP computing stage------------------------//
printf("---------------------------------\r\n");
printf("BRAM1[3] <= BRAM0[0] * BRAM1[2]\r\n"); // 27623 -> check
// E/ ALU/ OP / IN/ B1WR/ B1R / B0R
// inst = 0_0000_0000101_10001_00000_00010_00000
inst = 0b00000000010110001000000001000000;
Xil_Out32(XPAR_AXI_GPIO_0_BASEADDR, inst);
// DSP port A checking
...
// DSP port B checking
...
// DSP port P checking
...
// ------------------Writing BRAM1 stage------------------------//
// E/ ALU/ OP / IN/ B1WR/ B1R / B0R
// inst = 1_0000_0000010_00000_00011_00000_00000
inst = 0b10000000001000000000110000000000;
Xil_Out32(XPAR_AXI_GPIO_0_BASEADDR, inst);
// test for BRAM1 WEB
...
// test for BRAM1 ADDRBWRADDR
...
// test for BRAM! DIBDI
...
```
以上的程式運行結果如下圖所示:

可以看到BRAM1[3]的確被更新成DSP P port的輸出結果。

### Block Design

## Problem
1. PYNQ-Z2上共有多少個DSP48E1 Slice ?
由 PYNQ-Z2 的[user manual](https://www.mouser.com/datasheet/2/744/pynqz2_user_manual_v1_0-1525725.pdf)(下方截圖)可知DSP slice有220個。

## Reference
- [DSP48E1 user guide notes](https://hackmd.io/Ao7HJne5Qg2GfZF2kQdLWg)
- [7 Series FPGAs Memory Resources User Guide (UG473)
](https://docs.xilinx.com/v/u/en-US/ug473_7Series_Memory_Resources)-see page 29 to 30 for address bus discreption.