# FPGA 第六組 Lab3 結報
[文章網址](https://hackmd.io/jy8ZP-5BTEitzMMWkC-8Ow)
## 組員
| 姓名 | 學號 |
|:------:|:---------:|
| 劉永勝 | F94089032 |
| 蔡宗瑾 | E14083137 |
| 李宇洋 | E24099025 |
## Problem 1 - RGB LED [[影片連結]](https://www.youtube.com/shorts/wLFn9Q1GFNA)
### Block Design

不同於上次實驗,這次是要以c code控制led燈的亮燈順序。不像verilog有平行處理的概念,c code是序項執行。因此分成以下3個步驟,依序執行,完成這此實作。
1. Set LED color
設定三個參數,R、G、B,分別代表該顏色的16進為數值。例如紫色的16進制為`0x7f_1f_ff`,則分別指定參數 R = `0x7f`、G = `0x1f`、B = `0xff`。
2. Bright
- 根據PWM的觀念,以256個clk cycle作為1個週期,控制R、G、B各別亮燈時間。則步驟一set color的參數就能作為亮燈時間的最大值。見下方程式說明,第一圈for loop表示一個顏色停留的時間,第二圈則表示PWM以256個cycle為一周期。
- 其中較特別的是第3、6、9行,rgb_data會透過XGpio函數,寫到實體LED的port上。由於線寬為 3-bit,由高位元到低位元各別代表B、G、R的開關,紅燈表示 `3'b001`、綠燈表示 `3'b010`、藍燈表示 `3'b100`。藉由不斷開關R、G、B三個燈的,欺騙人眼的方式實現PWM效果。 (但之後發現用c code的arithmetic operator `&`、`|` 會更好)
``` c=
for (int Delay = 0; Delay < LED_DELAY; Delay++){
for (int count = 0; count < 256; count ++) {
rgb_data = (count < R)? 1 : 0;
XGpio_DiscreteWrite(&RGB_Gpio, 1, rgb_data);
rgb_data = (count < G)? 2 : 0;
XGpio_DiscreteWrite(&RGB_Gpio, 1, rgb_data);
rgb_data = (count < B)? 4 : 0;
XGpio_DiscreteWrite(&RGB_Gpio, 1, rgb_data);
}
}
```
3. Change color
題目要求6個顏色的燈要依序閃爍,則使用的個counter,每閃完一個燈號則加一,對此參數取 `mod6`,則可再步驟一set color時控制顏色。
## Problem 2 - Sorting
### Algorithm
此專案為單純讓c code跑在processing system上,並沒有使用到AXI介面資料傳輸,因此沒有考慮sorting電路的寫法。我們採用quick sort演算法,quicksort是一種遞迴式的排序演算法,其pseudo code如下所示:


PARTITION的功能即是將array最右端的值A[r]與array其他值做比較,並且將array分成兩部分,左半部比A[r]小,右半部比A[r]大。

### 實作結果
需先輸入要排序的數字總數(key numbers),再依序輸入每一筆數字(key)。
- switch為0: acsending order

- switch為1: descending order

## Problem 3
### Block Design

### 電路設計說明
#### Problem3-1
##### AXI interface

>slv_reg0 -> operand1
slv_reg1 -> operand2
slv_reg2 -> operator
slv_reg3 -> inValid
slv_reg4 -> dataResponse
slv_reg5 -> outData
slv_reg6 -> outValid
slv_reg7 -> overflow

| state | 說明 |
|:------:|:----------:|
|NOP |閒置狀態,不進行任何操作,並等待inValid訊號。|
|ADD |進行加法運算,並且在處理完成後拉高calcDone。|
|SUB |進行減法運算,並且在處理完成後拉高calcDone。|
|MUL |進行乘法運算,並且在處理完成後拉高calcDone。|
|DONE |將outValid設為1,並等待PS發回dataResponse訊號 **(確認已收到Output data)** 後,回到NOP state等待下一次操作。|
##### 設計邏輯
- **PS端(main.c)**:
```cpp=
/*--------------------------INPUT--------------------------*/
printf("Please choose calculation mode(1: add, 2: sub, 3: mul):\n\r");
scanf("%d", &opeartor);
while(opeartor > 3 || opeartor < 1){
...
}
printf("%d\r\n", opeartor);
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 8, opeartor);
// operand1
printf("Please input operand1 (Which is smaller than 127, and bigger -128):\n\r");
scanf("%d", &operand1);
while(operand1 > 127 || operand1 < -128){
...
}
printf("%d\r\n", operand1);
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 0, operand1);
// operand2
printf("Please input operand2 (Which is smaller than 127, and bigger -128):\n\r");
scanf("%d", &operand2);
while(operand2 > 127 || operand2 < -128){
...
}
printf("%d\r\n", operand2);
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 4, operand2);
```
上方程式碼用於確認使用者的input是否在允許範圍內,並且將value寫入對應的AXI slave register存取的位置。
```cpp=
// inValid high
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 12, 1);
// Wait until the calculation is done
while(ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 24) == 0){
...
if(ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 24) == 1){
// inValid low
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 12, 0);
}
}
if(ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 24) == 1){
// inValid low
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 12, 0);
}
printf("Calculation is done!\r\n");
```
上方程式碼會將Processor hang在while迴圈直到確認Arithmetic module已經做完運算(如果已經完成會發回outValid訊號)。
```cpp=
// ! Output
switch (opeartor)
{
case 1:
printf("Result of %d + %d: %d\n",operand1, operand2, (int8_t)ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 20));
printf("Overflow: %d\n", ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 28));
break;
case 2:
printf("Result of %d - %d: %d\n",operand1, operand2, (int8_t)ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 20));
printf("Overflow: %d\n", ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 28));
break;
case 3:
printf("Result of %d * %d: %d\n",operand1, operand2, (int8_t)ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 20));
printf("Overflow: %d\n", ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 28));
break;
default:
break;
}
// Told PL the data is received
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 16, 1);
printf("====================================\n");
// Reset
for(int i=0;i<9;i++){
printf("Reset slave register %d: %d\n",i,ARITHMETIC_mReadReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 4*i));
ARITHMETIC_mWriteReg(XPAR_ARITHMETIC_0_S00_AXI_BASEADDR, 4*i, 0);
}
printf("====================================\n");
```
最後會將從PL得到的Output結果print出來,告知PL已經收到data(將dataResponse對應的slave register拉高),並且將AXI slave register的data進行重置,進行下一次操作。
- **PL端(Arithmetic.v)**:
```verilog=
// Registers
reg calcDone, overflow_reg;
reg signed [7:0] operand1_reg, operand2_reg;
reg signed [15:0] outData_reg; // The redudant bits are for overflow detection
// Wires
assign outData = outData_reg[7:0];
...
// --------------------- //
// outData //
// --------------------- //
always @(posedge clk) begin
case (state)
`ADD: begin
outData_reg <= operand1_reg + operand2_reg;
calcDone <= 1'b1;
end
`SUB: begin
outData_reg <= operand1_reg - operand2_reg;
calcDone <= 1'b1;
end
`MUL: begin
outData_reg <= operand1_reg * operand2_reg;
calcDone <= 1'b1;
end
`DONE: begin
outData_reg <= outData_reg;
calcDone <= 1'b1;
end
default: begin
outData_reg <= 32'd0;
calcDone <= 1'b0;
end
endcase
end
```
`outData_reg`會在運算的state將已經儲存好的operand進行對應的運算,並且在`DONE`時將output值維持住,直到PS回傳dataResponse的訊號後回到`NOP`。
```verilog=
// Registers
reg calcDone, overflow_reg;
reg signed [15:0] outData_reg; // The redudant bits are for overflow detection
// Wires
assign overflow = overflow_reg;
...
// --------------------- //
// overflow //
// --------------------- //
always @(posedge clk) begin
if(state == `DONE) begin
if(outData_reg > 127 || outData_reg < -128)
overflow_reg <= 1'b1;
else
overflow_reg <= 1'b0;
end
else
overflow_reg <= 1'b0;
end
```
`outData_reg`因為有預留好多餘的8-bit空間,所以可以透過其在`DONE`時的value來判斷是否為overflow。
#### Problem3-2
##### Algorithm
本問題採用insertion sort的演算法來進行電路撰寫,insertion sort的pseudo code如下所示:

##### AXI interface

>slv_reg0 -> inData
slv_reg1 -> inEN
slv_reg2 -> outData
##### State Diagram

| state | 說明 |
|:------:|:----------:|
|INIT |等待PL端接收到PS傳來的input enable訊號,收到後即跳至LOAD|
|LOAD |將slv_reg0的輸入資料一筆一筆讀進sorting array A中|
|COMPARE|判斷while_condition,確認 j>=0 and A[j]>key此條件|
|SWAP |執行上方pseudo code的第6以及第7行|
|LAST |執行上方pseudo code的第8行|
##### 設計邏輯
- **PS端(main.c)**:
```cpp=
/*--------------------------INPUT--------------------------*/
printf("Please input 8 numbers:\n\r");
for(i = 0; i < 8; i++)
{
printf("Input No.%d key: \r\n", i+1);
scanf("%d", &key);
while(key > 15 || key < 0){
printf("%d\r\n", key);
printf("Your input is out of range!\n\r");
printf("Input No.%d key: \r\n", i+1);
scanf("%d", &key);
}
printf("%d\r\n", key);
inData = inData + ((key & 0xf) << (4*i));
}
SORTING_mWriteReg(XPAR_SORTING_0_S00_AXI_BASEADDR, 0, inData);
SORTING_mWriteReg(XPAR_SORTING_0_S00_AXI_BASEADDR, 4, 1);
outData = SORTING_mReadReg(XPAR_SORTING_0_S00_AXI_BASEADDR, 8);
/*--------------------------OUTPUT--------------------------*/
printf("Sorted numbers are: \r\n");
for(i = 0; i < 8; i++)
{
output = ((outData >> (4*i)) & 0xf);
printf("%d\r\n", output);
}
```
上方程式碼的第13行透過for loop將所有輸入的8筆4-bit資料全部存入inData,並將其透過第15行寫入slave_register_0,再透過第16行將input enable的訊號寫入slave_register_1。
第20行則是去讀取PL端輸出的資料,從slave_register_2讀取。
- **PL端(sort.v)**:
在LOAD狀態,將從PS端傳來的slv_reg0內的inData給一筆一筆存入至array中。
等待電路經過i=1至n-1次COMPARE,SWAP以及LAST的循環後,array將會被排序完成,而outData會一直將array的每一個元素給串接起來,直到FSM跑至DONE,outData結果才是正確的,PS屆時讀取slv_reg2的值才會是正確的輸出。
```verilog=
// --------------------- //
// array //
// --------------------- //
always@(posedge clk) begin
if(rstn == 1'b0) begin
array[0] <= 4'd0;
...
end
else begin
if(curState == `LOAD) begin
case(index)
5'd0: array[index] <= inData[3:0];
5'd1: array[index] <= inData[7:4];
...
endcase
end
else if(curState == `SWAP) array[j+5'd1] <= array[j];
else if(curState == `LAST) array[j+5'd1] <= key;
else begin
array[0] <= array[0];
...
end
end
end
// --------------------- //
// outData //
// --------------------- //
assign outData = {array[7], array[6], array[5], array[4], array[3], array[2], array[1], array[0]};
```
##### 運行流程

上方圖中輸入8筆資料先後由左到右依序為<3,15,1,4,14,6,0,9>,可以看到input顯示906e41f3,為將8筆資料存入同一個32-bit的u32暫存空間的結果,從LSB至MSB以16進制方式表示與輸入序列相吻合,代表輸入成功寫入slv_reg0。
#### Problem3-3
##### AXI interface

>slv_reg0 -> type
slv_reg1 -> inValid
slv_reg2 -> inData
slv_reg3 -> dataResponse
slv_reg4 -> result
slv_reg5 -> outValid
##### State Diagram

| state | 說明 |
|:------:|:----------:|
|NOP |閒置狀態,不進行任何操作,並等待inValid訊號。|
|HANDLE|正在處理parity運算,並且在處理完成後拉高calcDone。|
|DONE |將outValid設為1,並等待PS發回dataResponse訊號 **(確認已收到Output data)** 後,回到NOP state等待下一次操作。|
##### 設計邏輯
- **PS端(main.c)**:
```cpp=
// pairty mode
printf("Please choose pairty mode(1: odd, 2: even):\n\r");
scanf("%d", &type);
while(type < 1 || type > 2){
...
}
printf("%d\r\n", type);
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 0, type);
// input data
printf("Please input data (Support number to 2147483647(2^31) at most):\n\r");
scanf("%d", &inData);
printf("%d\r\n", inData);
printf("Also equal to 0x%x\r\n", inData);
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 4, inData);
```
上方程式碼用於確認使用者的input是否在允許範圍內,並且將value寫入對應的AXI slave register存取的位置。
```cpp=
// inValid high
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 8, 1);
// Wait until the calculation is done
while(PARITYGENERATOR_mReadReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 20) == 0){
...
if(PARITYGENERATOR_mReadReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 20) == 1){
// inValid low
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 8, 0);
}
}
if(PARITYGENERATOR_mReadReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 20) == 1){
// inValid low
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 8, 0);
}
printf("Calculation is done!\r\n");
```
上方程式碼會將Processor hang在while迴圈直到確認ParityGenerator module已經做完運算(如果已經完成會發回outValid訊號)。
```cpp=
// ! Output
switch (type)
{
case 1:
printf("Odd pairty of 0x%x: %d\n", inData, PARITYGENERATOR_mReadReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 16));
break;
case 2:
printf("Even pairty of 0x%x: %d\n", inData, PARITYGENERATOR_mReadReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 16));
break;
default:
break;
}
// Told PL the data is received
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 12, 1);
printf("====================================\n");
// Reset
for(int i=0;i<7;i++){
printf("Reset slave register %d: %d\n",i,PARITYGENERATOR_mReadReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 4*i));
PARITYGENERATOR_mWriteReg(XPAR_PARITYGENERATOR_0_S00_AXI_BASEADDR, 4*i, 0);
}
printf("====================================\n");
```
最後會將從PL得到的Output結果print出來,告知PL已經收到data(將dataResponse對應的slave register拉高),並且將AXI slave register的data進行重置,進行下一次操作。
- **PL端(ParityGenerator.v)**:
``` verilog=
// Registers
reg calcDone;
reg calc_reg, result_reg;
// Wires
assign result = result_reg;
assign outValid = (state == `DONE) ? 1'b1 : 1'b0;
integer counter;
...
// --------------------- //
// outData //
// --------------------- //
always @(posedge clk) begin
case (state)
`NOP: begin
calc_reg <= 32'd0;
result_reg <= 32'd0;
calcDone <= 1'b0;
inData_part_reg <= 0;
end
`HANDLE: begin
if(counter >= 31) begin
calc_reg <= calc_reg;
calcDone <= 1'b1;
end
else begin
calc_reg <= calc_reg ^ inData[counter];
inData_part_reg <= inData[counter];
end
end
`DONE: begin
if(type == 1) begin
result_reg <= ~calc_reg;
end
else begin
result_reg <= calc_reg;
end
calcDone <= 1'b1;
end
default: begin
calc_reg <= 32'd0;
result_reg <= 32'd0;
calcDone <= 1'b0;
end
endcase
end
// --------------------- //
// counter //
// --------------------- //
always @(posedge clk) begin
if(state == `HANDLE) begin
counter <= counter + 1;
end
else
counter <= 0;
end
```
`counter`和`calc_reg`為在`HANDLE`時主要作為運算的兩個register:
- `counter`會在每個cycle遞增直到大於31。
- `calc_reg`在`NOP`會重置為0,並且在`HANDLE`時和input data對應counter的bit進行XOR運算,最後在`DONE`根據不同的parity type將值傳給`result_reg `進行output。
### 完整運行結果
1. **透過ZYNQ Processor上運行的main.c選擇進行哪種運算**
*(1: Arithmetic, 2: Sorting, 3: Parity Generator)*

2. **Arithmetic**:
- Check if mode is valid

- Add result

- Sub result

- Mul result

3. **Sorting**:
- Check if mode is valid

- result

4. **Parity Generator**:
- Check if mode is valid

- result
