# FPGA 第六組 Lab3 結報 [文章網址](https://hackmd.io/jy8ZP-5BTEitzMMWkC-8Ow) ## 組員 | 姓名 | 學號 | |:------:|:---------:| | 劉永勝 | F94089032 | | 蔡宗瑾 | E14083137 | | 李宇洋 | E24099025 | ## Problem 1 - RGB LED [[影片連結]](https://www.youtube.com/shorts/wLFn9Q1GFNA) ### Block Design ![](https://i.imgur.com/z3zSR60.png) 不同於上次實驗,這次是要以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如下所示: ![](https://i.imgur.com/Kzn9CPk.png) ![](https://i.imgur.com/Eswj4XL.png) PARTITION的功能即是將array最右端的值A[r]與array其他值做比較,並且將array分成兩部分,左半部比A[r]小,右半部比A[r]大。 ![](https://i.imgur.com/uP0FMab.png) ### 實作結果 需先輸入要排序的數字總數(key numbers),再依序輸入每一筆數字(key)。 - switch為0: acsending order ![](https://i.imgur.com/mGwZjK8.png) - switch為1: descending order ![](https://i.imgur.com/B3uBnhE.png) ## Problem 3 ### Block Design ![](https://i.imgur.com/HdWOq3u.png) ### 電路設計說明 #### Problem3-1 ##### AXI interface ![](https://i.imgur.com/zBis0d1.png) >slv_reg0 -> operand1 slv_reg1 -> operand2 slv_reg2 -> operator slv_reg3 -> inValid slv_reg4 -> dataResponse slv_reg5 -> outData slv_reg6 -> outValid slv_reg7 -> overflow ![](https://i.imgur.com/5NM5QsW.png) | 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如下所示: ![](https://i.imgur.com/5lUa2gH.png) ##### AXI interface ![](https://i.imgur.com/T6ONtu5.png) >slv_reg0 -> inData slv_reg1 -> inEN slv_reg2 -> outData ##### State Diagram ![](https://i.imgur.com/vBqGeS7.png) | 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]}; ``` ##### 運行流程 ![](https://i.imgur.com/CAqkXXg.png) 上方圖中輸入8筆資料先後由左到右依序為<3,15,1,4,14,6,0,9>,可以看到input顯示906e41f3,為將8筆資料存入同一個32-bit的u32暫存空間的結果,從LSB至MSB以16進制方式表示與輸入序列相吻合,代表輸入成功寫入slv_reg0。 #### Problem3-3 ##### AXI interface ![](https://i.imgur.com/xdiPxzF.png) >slv_reg0 -> type slv_reg1 -> inValid slv_reg2 -> inData slv_reg3 -> dataResponse slv_reg4 -> result slv_reg5 -> outValid ##### State Diagram ![](https://i.imgur.com/DOz6AT4.png) | 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)* ![](https://i.imgur.com/mIKSBEe.png) 2. **Arithmetic**: - Check if mode is valid ![](https://i.imgur.com/Y8d3QUV.png) - Add result ![](https://i.imgur.com/VUE7b0k.png) - Sub result ![](https://i.imgur.com/xK8eHhK.png) - Mul result ![](https://i.imgur.com/Lrj0uzm.png) 3. **Sorting**: - Check if mode is valid ![](https://i.imgur.com/aeD74So.png) - result ![](https://i.imgur.com/UZhokJH.png) 4. **Parity Generator**: - Check if mode is valid ![](https://i.imgur.com/Abc5pnN.png) - result ![](https://i.imgur.com/GxGmsCH.png)