# SOC study journal ## Introduction ## Lab 1 ### Brief introduction about the overall system * **HLS flow** 選擇PYNQ board,用C/C++ design並作C simulation,設定clock period 為 10 ns作合成,合成完後作cosimulation並觀察結果以及waveform,輸出IP。 * **Vivado Flow** 加入ZYNQ7 processing system(ps),並run block automation以及connection automation,連接PS以及PL,產生AXI,並產出bitstream、hwh file。 * **PYNQ Flow** 將bitstream、hwh file上傳到jupyter notebook,用python include進來,即可以在FPGA上執行,測試結果正確性。 ### C simulation 用C/C++ design以及寫testbench作simulation,可以下一些breakpoint,印出debug message ![](https://hackmd.io/_uploads/BJir7xOfp.png) ### C synthesis 作HLS synthesis,產生3個Verilog module file: ![](https://hackmd.io/_uploads/BycUmgufp.png) 由multip_2num_control_s_axi定義AXI的interface,用於PS與PL之間進行溝通,包括一些控制訊號、參數傳遞……等。 Register logic部分: 可以看到參數: pn32ResOut, n32In2, n32In1 ![](https://hackmd.io/_uploads/HyeOme_z6.png) multip_2num是wrapper個,有FSM的設計。 ![](https://hackmd.io/_uploads/ryWt7l_fa.png) ### Screen dump * ### Performance ![](https://hackmd.io/_uploads/S1_nmgdza.png) * ### Utilization ![](https://hackmd.io/_uploads/HyJ6mxufa.png) ![](https://hackmd.io/_uploads/HJ6aXedzT.png) * ### Interface 可以看到interface中axi形式 ![](https://hackmd.io/_uploads/Syi07e_fp.png) ### Co-simulation 將C與RTL作simulation,並測試其正確性 * ### Co-simulation transcript Simulation結果記錄在multip_2num_csim.log中,可以看到所有測資都pass ![](https://hackmd.io/_uploads/BJ0rNguzT.png) * ### Co-simulation waveform ![](https://hackmd.io/_uploads/HkGPElOfT.png) 從waveform中pn32ResOut_a_vld == 1可以知道,每個乘法運算需要花 13 cycle完成 ### Export RTL 用vitis_hls將RTL design匯出ip ### Block Design 將ip匯入,並connect PS與PL,其中PS的GP要連接kernel function的AXI Lite (slave),如下圖。 ![](https://hackmd.io/_uploads/H1NhVxOzp.png) ### Jupyter Notebook execution results 借onlinefpga,並將bitstream及.hwh file上傳到Jupyter notebook,即可以在FPGA上執行。 * .bit: program進FPGA的邏輯 * .hwh: 關於PS及PL間interface描述 ![](https://hackmd.io/_uploads/SJyWHgOMT.png) ## Lab 2 ### Overview Lab 2 中用HLS 去design FIR filter的IP,且要用2種不同interface: AXI Master以及Stream,完成後上傳到OnlineFPGA測試結果。 ### What is observed & learned * ### Three types AXI interface * #### AXI4 用於高性能memory-mapped 需求,以實現高帶寬、低延遲的data傳輸。 * #### AXI4-Stream 用於high-speed streaming data傳輸,例如video處理、音頻處理和數據流處理器等。 * #### AXI4-Lite 用於簡單、低throughput的memory-mapped communication,例如to and from control and status registers的讀寫操作。是AXI4的一種子集,適用於對memory-mappedregister進行配置和監視的應用。 * #### Directive 這次與lab 1不同,使用directive進行設定,這個方法可以多組參數去對同一份code作實驗。 ### Screen dump * ### Co-simulation report 將C與RTL作simulation,並測試其正確性。 Simulation結果記錄在fir_n11_maxi/stream_csim.log中,可以看到所有測資都pass ![](https://hackmd.io/_uploads/ryu2clOz6.png) * ### Performance ![](https://hackmd.io/_uploads/B1jMjgdGa.png) * ### Utilization ![](https://hackmd.io/_uploads/ryR7se_fa.png) ![](https://hackmd.io/_uploads/BJvSig_Gp.png) * ### Interface ![](https://hackmd.io/_uploads/S1DvjgdGa.png) ### Jupyter Notebook execution results ![](https://hackmd.io/_uploads/BkwHnluM6.png) ### Troubleshooting 1. Co-simulation出現error,經了解是depth設定錯誤,需要depth >= data size。 ![](https://hackmd.io/_uploads/ByJPhldGp.png) 2. C o simulation 時出現 m pfr_srcptr has not been declared 的e rror ![](https://hackmd.io/_uploads/S1RxpluzT.png) 參考官方網站留言: https://support.xilinx.com/s/question/0D52E00006xoQLMSA2/using-apinth-in-my-design-causes-many-crtl-cosimulation-errors-mainly-in-mpfrh?language=en_US ![](https://hackmd.io/_uploads/r1tWTe_M6.png) 在FIR h 中加入紅框中2 行,c o simulation 順利沒出現error 。 ![](https://hackmd.io/_uploads/SkONyW_za.png) ## Lab 3 ### Overview Lab 3 中用 Verilog 去 design FIR filter的 module,且要用 2種不同 interface: AXI Lite以及 Stream 並熟悉 control signal以及 BRAM的使用 。 **1. Block diagram** ![](https://hackmd.io/_uploads/r1KyVWdGa.png) **2. FIR filter computation** 在fir計算中每次輸入一筆新的 data 會與對應的coefficient作相乘直到計算完11個相乘輸出該次計算結果 由於BRAM中較難實行shift data也觀察到說計算上的規律可以用 pointer去指定對應的位置以下為計算步驟以及圖示 a. data_ptr與 tap_ptr初始化位置分別為data與tap位址 = 0 b. 每經過 1 cycle ptr各指到相應的位置 (橘色表示作相乘、累加計算) c. 經過 11 個 cycle 輸出該次fir計算結果 d. 圖三所示 輸入新的data在1的位置, 此時對應的tap則是在0的位置重複 b-c步驟完成 fir filter運算 ![](https://hackmd.io/_uploads/rJo5E-_zp.png) ![](https://hackmd.io/_uploads/SyUeHWuMT.png) **3. Resource usage** 圖四~圖六為 FIR filter消耗的資源系統的控制與計算由LUT的方法進行。圖五可以看到有使用DSP應為 convolution時的乘法、加法運算時使用。 ![](https://hackmd.io/_uploads/r1sB8bufp.png) ![](https://hackmd.io/_uploads/r13UIbuM6.png) ![](https://hackmd.io/_uploads/SJ3DIZOfT.png) **4. Timing Summary** 總計從ap_start ~ ap_done花費8400 cycles合成時使用 Clock cycle: 6 ns 可以從timing report看到setup slack: 1.261 ns,如圖七。 ![](https://hackmd.io/_uploads/BkBnUb_zT.png) 圖八可以觀察到max delay path的 source 以及 destination critical path發生在read back的部分 slack: 1.261 ns。 ![](https://hackmd.io/_uploads/HJsIvb_MT.png) **5. Simulation Waveform** * Coefficient(Tap BRAM write) 在tap BRAM write中 coefficient由 AXI lite interface輸入直接寫入Tap BRAM。可以從圖九觀察到 當 awready與 awvalid同時為1時Tap BRAM會收到要寫入的 address 這時當wready與 wvalid同時為1且BRAM的WE為4’b1111時在下一個cycle時data會寫入BRAM(指定位置的值)。 ![](https://hackmd.io/_uploads/rkvav-_z6.png) * Coefficient Read back(Tap BRAM read back) 圖十可以看到從tap BRAM中將coefficient由AXI interface讀出。遵循protocal 當 arvalid與 arready為1時tap BRAM會收到要讀出的address當rvalid為1時為coefficient已經放入rdata上 可讀出。 ![](https://hackmd.io/_uploads/Sk99dW_M6.png) * Data in stream in 圖十可以看到從tap BRAM中將coefficient由AXI interface讀出。遵循protocal 當 arvalid與 arready為1時tap BRAM會收到要讀出的address當rvalid為1時為coefficient已經放入rdata上 可讀出。 ![](https://hackmd.io/_uploads/HkLo_ZdGT.png) * Data out stream out 圖十可以看到從tap BRAM中將coefficient由AXI interface讀出。遵循protocal 當 arvalid與 arready為1時tap BRAM會收到要讀出的address當rvalid為1時為coefficient已經放入rdata上 可讀出。 ![](https://hackmd.io/_uploads/Hyf2uWuM6.png) ### Troubleshooting 1. 合成無法順利執行 將BRAM不包在design裡面合成即可順利執行。 ## Lab 4-1 ### Overview 在lab 4-1中,我們設計controller去控制BRAM與wishbone bus,並設計FIR filter的firmware code,再運用建立好的Caravel SoC環境去模擬電路行為。 **1. Explanation of your firmware code** 如下圖,fir.c運作步驟如下: 1. 初始化outputsignal 2. 運用2 for loop去進行FIR filter,並用shift register去將對應的inputsignal與tap做運算。outputsignal在每次iteration得到該點的partial sum,並累加起來儲存,完整運算完後會reutrn回counter_la_fir.c中的fir function。 ![image](https://hackmd.io/_uploads/BkJTktxP6.png) 如下圖,fir.h中設tap以及inputsignal data ![image](https://hackmd.io/_uploads/Hk8pkYxPa.png) **2. Explanation of your assembly code** * **Apply the configuration** * Assembly code ![image](https://hackmd.io/_uploads/BkOWeFgvT.png) * counter_fir_la.c ![image](https://hackmd.io/_uploads/H1EQlFgvp.png) * **與對應到的address進行寫入** * Assembly code ![image](https://hackmd.io/_uploads/H1MUeYeDa.png) * counter_fir_la.c ![image](https://hackmd.io/_uploads/Hy7wltxv6.png) * **呼叫fir function並將結果寫入reg_mprj_datal指定的address** * Assembly code ![image](https://hackmd.io/_uploads/ryNYeYlw6.png) * counter_fir_la.c ![image](https://hackmd.io/_uploads/BJMcgtewp.png) * **fir.c中作fir filter的運算** * Assembly code ![image](https://hackmd.io/_uploads/Hyh2gtlvT.png) ![image](https://hackmd.io/_uploads/ByZpxYewT.png) * fir.c ![image](https://hackmd.io/_uploads/SkDpxKxva.png) **3. How does it execute a multiplication in assembly code?** 將乘法拆成不斷累加做計算。counter_la_fir.out中顯示了fir.c中做multiplication的assembly code,如下圖。 ![image](https://hackmd.io/_uploads/Bk1Cltlv6.png) a0及a1將要做multiplication的data送進來,a0 assign 給a2,再將a0設為0作為後續累加。當a3為不為0時,進行a0+a2的累加運算。當a3為0時,跳過累加部分,直接進到將a1右移一位的指令,後將a2左移一位作乘積的進位,再去判斷如果a1為0時為完成累加的運算,反之則繼續回到38000008的位子進行運算。 **4. What address allocate for user project and how many space is required to allocate to firmware code** 從section.lds可以看到user project的original address為0x3800000。 ![image](https://hackmd.io/_uploads/S1JyZFgDT.png) 從counter_la_fir.out可以看到fir filter計算的空間為0x38000000~0x38000164,總計需要356 bytes (Hex 164)。 ![image](https://hackmd.io/_uploads/SJoJ-Fewa.png) ![image](https://hackmd.io/_uploads/r1LQ-tgDa.png) ![image](https://hackmd.io/_uploads/rktXbFxDa.png) **5. Interface between BRAM and wishbone interface** A. Waveform from xsim * **Read data from BRAM(wbs_we_i = 1)** 下圖為從BRAM read data的waveform,可以看到wbs_cyc_i與wbs_stb_i都為1時,表示BRAM的資料可以被讀出(valid),當wbs_ack_0為1時,Do0上的data被讀出。 ![image](https://hackmd.io/_uploads/rynBWKgDT.png) * **Write data into BRAM (wbs_we_i = 1)** 下圖為write data進BRAM的waveform,可以看到wbs_cyc_i與wbs_stb_i都為1時,表示wishbone的data可以被BRAM(valid),當wbs_ack_0為1時,data寫入BRAM。 ![image](https://hackmd.io/_uploads/HygI-KxPT.png) **B. Design FSM** * RESET: 初始狀態 * IDLE: 閒置狀態 * DATA: Delay 10 cycle後準備output結果 ![image](https://hackmd.io/_uploads/HyeqZKgv6.png) **6. Synthesis report** clock period = 10.547 (ns) input delay = 1.299 (ns) output delay = 2.167 (ns) ![image](https://hackmd.io/_uploads/HkmsWYlwa.png) ## Lab 4-2 **1. Design block diagram –datapath, control-path** ![image](https://hackmd.io/_uploads/Bk0HGtlPT.png) **2. The interface protocol between firmware, user project and testbench** ![image](https://hackmd.io/_uploads/rJIgQKlvT.png) **firmware與user project:** firware與user project之間是透過wishbone的protocol來連接,如上圖的紅色路徑。下面的code是firware中控制兩者傳輸的部分,reg_fir_x與reg_fir_y分別是指到0x3000_0080,與0x3000_0084的指標,因為這個address的區域是屬於user project,所以會生成對應的讀或寫的wishbone cycle到user project,而我們的design即可以抓到這些cycle並做出對應的output送到0x3000_0084並回應firware發到0x3000_0084這個位置的讀取wishbone cycle。 ![image](https://hackmd.io/_uploads/H1GzmKxPT.png) **firmware與testbench:** firware是透過wishbone與GPIO連接,如上圖藍色路線所示。一開始firware code會configure GPIO,讓他的[31:16]bits為management的output,之後firmware會寫透過寫入0X2600_0000C (GPIO 31 to 0)即可將data傳到testbench。 **3. Waveform and analysis of the hardware/software behavior.** **Startup code:** 系統一開始會執行左邊的init code,這個code會產生到0x3800_0000的write cycle到wishbone上面,user bram即可以將data抓出來並存入。 ![image](https://hackmd.io/_uploads/Syd7mYgPa.png) **Fir calculation:** **AXILite:** 將data length傳入。 傳入tap的value。 傳入ap_start。 **AXI Stream:** 交替的產生讀寫的cycle將x寫入fir並讀出y。 ![image](https://hackmd.io/_uploads/B1FP7Fevp.png) **4. What is the FIR engine theoretical throughput, i.e. data rate? Actually measured throughput?** 在lab 3中我們設計的fir在fully pipelined的情況下可以做到11個cycles一個output。在lab 4中我們將design接上wishbone,因為系統發出wishbone cycle到user project有一定的間隔,換句話說無法作到fully pipelined的設計,造成理論上的throughput變為1 output/ 14 cycles。波形圖如下所示,因為讀data bram有固定的3 cycles的delay,原本可以pipeline來避免但現在不行。 ![image](https://hackmd.io/_uploads/HJcdXKlwp.png) 實際上的throughput如下圖所示,為476 cycles per output。 ![image](https://hackmd.io/_uploads/SJ1KXYewa.png) 參考report後面優化的部分,我們最後做到27 cycles per output ![image](https://hackmd.io/_uploads/ByzKmFgwp.png)   **5. What is latency for firmware to feed data? ** 要測量這個latency我們要找到cpu讀取組語中對應的sw指令到cpu發出帶著資料的wishbone cycle間的latency。我們以傳送data length這件事情為例,我們可以發現對應的sw是在位置0x3800_0018的地方。 ![image](https://hackmd.io/_uploads/BJdY7txD6.png) 而我們的data length則是定義在0x3000_0010的位置。 ![image](https://hackmd.io/_uploads/rJ2FQtgvT.png) 以下波型可以看到cpu先發出read cycle,讀取在0x3800_0018這個位置的指令的,也就是讀sw那條指令,經過user bram的12個cycle的delay cpu收到,再經過35個cycle看到cpu送出到0x3800_0010的write cycle。 ![image](https://hackmd.io/_uploads/H1xc7tewT.png) 所以firmware feed data的latency為47 cycles。 **6. What techniques used to improve the throughput?** * **Does bram12 give better performance, in what way?** 我們在Lab3的設計沒有使用bram12。 * **Can you suggest other method to improve the performance?** 分析整個系統的波型圖可以發現,兩筆資料間需要476 clock cycles(下圖A到C,中間部分有省略),而實際計算FIR的cycle數只有14(下圖A到B),佔比不高,其他時間看波型得知有許多wishbone cycle,其wbs_adr_i[31:0]大部分是0x38開頭,也就是在抓firmware code。所以我們認為做FIR本身優化幫助並不大,考慮到整個系統才是最重要的。 ![image](https://hackmd.io/_uploads/ryOp7YlPp.png) 我們想到三個可以提升throughput的方式。首先是整體的clock period,在假設系統clock的bottleneck在FIR的前提下(畢竟我們只有針對User Project做合成),想辦法優化FIR的critical path對整個系統會有非常大的幫助。我們將原先沒有切pipeline的版本做合成,clock cycle最低為16.451ns。而在運算單元前後擋好register後,clock period可以壓到10.547ns,前後如下面兩張圖。 ![image](https://hackmd.io/_uploads/H17CQteDa.png) ![image](https://hackmd.io/_uploads/rJDRXtlwa.png) 接著,我們觀察到因為在做FIR運算時,CPU是IDLE的,也就是要等到我們14個cycles算完回wbs_ack_o,系統才會繼續讀firmware code。於是我們想說利用存在data_Bram的資料,做到CPU和FIR的平行。做法是先算完前十筆已存在data_Bram的資料,再拉起ss_ready收最後一筆data,運算完後即拉起sm_valid傳出結果,同時回wbs_ack_o讓CPU繼續執行下一筆firmware code,這個間隔估計會從3+11=14 cycles減少到3+1=4 cycles。但因為兩筆資料間隔了許多firmware code(476 cycles),所以效果不會太大(僅省了10/476=2.1%)。而這部分的優化要改動fir.v,但因為時間的關係並沒有實作,且在最後一個優化方式之後因為data之間沒有firmware code的wishbone cycle的空檔(見7. Any other insights?),所以沒辦法做這個優化。 最後,也是優化最多的部分,我們針對開頭提到的,兩筆資料間佔多數時間的是wishbone cycle執行數條firmware code,而非FIR運算。儘管我們的fir.c已經寫得很精簡,我們覺得展開for loop應該會有不錯的效果,我們嘗試在run_sim中修改riscv32-unknown-elf-gcc的參數,加上-O3,讓compiler幫助我們做assembly code的簡化。最後效果非常明顯兩筆data間CPU甚至沒有讀0x38 firmware code而是不斷接續的傳資料,從476個cycles降低到27個cycles,總cycle數從31059降低到2093,如圖。 ![image](https://hackmd.io/_uploads/S18bNFgDT.png) ![image](https://hackmd.io/_uploads/B1zeVKxDa.png) ![image](https://hackmd.io/_uploads/HyqxNtewa.png) **7. Any other insights ?** 我們開-O3後看波型圖遇到一個問題,在兩筆資料間CPU甚至沒有去拿firmware code,可以看到下圖沒有0x38開頭的wbs_adr_i。我們原本想說至少會有一個12 cycle delay的時間去拿firmware code但看起來是沒有。 ![image](https://hackmd.io/_uploads/ryuM4Kgwp.png) 於是我們去檢查生出來的assembly code,發現的確有相較前一版更簡潔,也有bne指令判斷for迴圈的跳出條件(data傳64個之後會跳出for loop),但在波型上只有第一筆資料有執行114->118->11c->120->128,之後直接離開for loop去到12c,直到所有data算完才繼續拿剩下的firmware code(0x38),我們不太清楚為什麼第二筆開始CPU不用去0x38讀114而可以直接發0x30000080去送資料,推測是第一次抓code後將他存到其他地方像是cache。 ![image](https://hackmd.io/_uploads/HyN74tgvT.png) **8. Final Score** ● #-of-clock: 2093 ● clock_period: 10.547 ns ● gate-resource (LUT+FF): 374+396=770 ![image](https://hackmd.io/_uploads/S1wVNKxD6.png) ● Score = 2093*10.547*1e-9*770=16997650.67*1e-9=0.01699765067 ## Lab 5 **1. Block diagram** ![image](https://hackmd.io/_uploads/H1skUKgDa.png) **2. FPGA utilization** ### **User project counter with clk 50M** ![image](https://hackmd.io/_uploads/HkQSLtgPp.png) * **processing_system7_0** ![image](https://hackmd.io/_uploads/ryiS8Yewp.png) * **caravel_0** ![image](https://hackmd.io/_uploads/HJXUUFxDp.png) * **caravel_ps_0** ![image](https://hackmd.io/_uploads/rJ8UUYgvp.png) * **output_pin_0** ![image](https://hackmd.io/_uploads/rJt8IYlwT.png) * **read_romcode_0** ![image](https://hackmd.io/_uploads/SyhU8txw6.png) * **spiflash_0** ![image](https://hackmd.io/_uploads/ByC8UFlD6.png)   ### **User project gcd with clk 10M** ![image](https://hackmd.io/_uploads/BJ-qLKxP6.png) * **processing_system7_0** ![image](https://hackmd.io/_uploads/HJ_cLKlvp.png) * **caravel_0** ![image](https://hackmd.io/_uploads/r1jcUFgv6.png) * **caravel_ps_0** ![image](https://hackmd.io/_uploads/rk0qIKxDT.png)   * **output_pin_0** ![image](https://hackmd.io/_uploads/Hy4oIteDT.png) * **read_romcode_0** ![image](https://hackmd.io/_uploads/r1Pj8YeDp.png) * **spiflash_0** ![image](https://hackmd.io/_uploads/By9iIKlDT.png)   **3. Explain the function of IP in this design** **HLS** * **read_romcode** 用來把code從PS端搬到PL端bram的module。python會先在PS端allocate一個numpy array放入compile好的code。接著python會透過axi_lite的介面,把這個numpy array的地址及長度傳給ip知道(利用mmio 0x10~14及0x1c傳入)。之後python會送入ap_start,ip便會開始透過axi_master的介面抓global memory的資料並寫入bram。此時python端會有一個while等待ap_idle拉起來代表ip要做的事情已經完成。 * **ResetControl** PS端用axi_lite介面的outpin_ctrl去控制outpin,可寫入值為0去assert reset或用1去release reset。 * **caravel_ps** 主要目的為讓PS端可以透過axi_lite去拿到mprj pin的output/en值,或寫入mprj pin的input。對於每個mprj pin,若其為output enable,input就做一個從output接過來的loop-back,反之則從PS端的ps_mprj_in餵進來。 **Verilog** * **spiflash** Caravel SOC內的SPI master會向spiflash(SPI slave)讀先前read_romcode拿進來存在bram的code。而spiflash就負責處理之間的interface,其一是對bram的interface(包含Addr, EN, WEN, DIN, DOUT, Clk, Rst),二是對SPI master的interface(包含SCK(spiclk), SS(csb), MISO(io1), MOSI(io0)),並只支援SPI master read的指令,SPI master不能write。 架構主要是兩個8bit的shift register,一個負責吃MOSI(spiclk pos edge),另一個負責給出MISO(spiclk neg edge) 一筆32bit(4byte)資料會需要8byte的長度傳輸,包含前四個byte分別是第0個byte讀master的read command,第1~3個byte讀master傳的addr,和後四個byte資料傳輸,即從bram抓的一行32bit code data。   **4. Screenshot of Execution result on all workload** * counter_wb.hex ![image](https://hackmd.io/_uploads/BJqzwtevp.png)   * counter_la.hex ![image](https://hackmd.io/_uploads/B1Z7DKgv6.png) * gcd_la.hex ![image](https://hackmd.io/_uploads/BkVXDtxPa.png) ## Lab 6 **1. How do you verify your answer from notebook** 我們修改gpio的configuration如下,加入mprj_io[15:12]作為caravel的management input讓pynq PS可以跟firmware溝通。 ![image](https://hackmd.io/_uploads/HyZQwZED6.png) 假設要傳遞3筆data,AB55, AB34,及AB12,每筆資料傳遞的步驟1~4如下。 ![image](https://hackmd.io/_uploads/B13mvW4P6.png) 1. firmware利用mprj_io[31:16]送出運算的output。 2. pynq收到data後將mprj_io[12]設為1,讓firmware知道資料已經被收集。 3. pynq將mprj_io[8]設為1,此時pynq可以deassert mprj_io[12]。 4. 確定pynq已經deassert掉mprj_io[12]後,firmware可以繼續輸出下一個output。 為了達成這樣的implementation我們修改了助教給的block design以及HLS的caravel_ps IP。下面逐一說明修改處。 **1. block design:** 原本的caravel_ps的output pin mprj_in並沒有接上caravel的mprj_i,我們新增了一些slice block將我們作為input的mprj_io[15:12]接上去。 ![image](https://hackmd.io/_uploads/r13UwZEva.png) **2. caravel_ps HLS IP:** 我們發現原本的hls在implement上有點問題,mprj_en應該是作為input的enable,因為當我們把gpio的configuration設為input的時候對應pin的mprj_en會變成1,這時候才應該要讓ps_mprj_in傳入。修改完後我們整個系統才能正常運行。 jupyter notebook驗證結果: 我們修改原本的caravel_start(),新增讀取不同task output的部分。sync()則是剛剛說明的與firmware溝通的部分。修改的code及output如下,可以看到運算結果的正確性以及hello字串的傳回。 ![image](https://hackmd.io/_uploads/BkmuvWNvp.png) ![image](https://hackmd.io/_uploads/r1wuDbEvp.png) **2. Block design** ![image](https://hackmd.io/_uploads/ryK6w-EDT.png) **3. Timing report/ resource report after synthesis** * **Timing report** 如下圖,可以看到slack都在正常範圍的值 ![image](https://hackmd.io/_uploads/Hy0W_-EPT.png) * **Utilization** ![image](https://hackmd.io/_uploads/B1gUFbNP6.png) * **processing_system7_0** ![image](https://hackmd.io/_uploads/B1AUKbNwT.png) * **caravel_0** ![image](https://hackmd.io/_uploads/HkYPFZEvT.png)   * **caravel_ps_0** ![image](https://hackmd.io/_uploads/HJp_KWNP6.png) * **output_pin_0** ![image](https://hackmd.io/_uploads/SJlYKWVDp.png)   * **read_romcode_0** ![image](https://hackmd.io/_uploads/S1VYF-Vv6.png) * **spiflash_0** ![image](https://hackmd.io/_uploads/HyDtF-EvT.png)   * **axi-uartlite_0** ![image](https://hackmd.io/_uploads/S1gxcZNPa.png) * **axi_intc_0** ![image](https://hackmd.io/_uploads/HJmgqWNDp.png)   **4. Latency for a character loop back using UART** ![image](https://hackmd.io/_uploads/r10b9Z4wp.png) ![image](https://hackmd.io/_uploads/SJuf9ZEwa.png) 在Uart write的時候紀錄每次開始時間,Uart read的時候紀錄每次結束時間,相減後得到該次latency,最終得到6個 character的latency,平均為0.0037654 sec。   **5. Suggestion for improving latency for UART loop back** ![image](https://hackmd.io/_uploads/HyVVq-NPp.png) 我們假設要從 PS 端傳送數個字元,經過測試後發現兩個字元間的傳送幾乎沒間差(平均0.08毫秒),而一個字元傳進去到讀出的時間則是3.7毫秒左右。這段時間包含: 1. Uart_rx 以 baud rate=9600 收八位元資料。39579 cycles 2. Caravel Uart 硬體收到資料後,Uart_rx 發送 irq 至 isr 處理完的時間(包含佔大部分時間的 trap_entry,和佔少部分的 firmware 的 uart_read() 和 uart_write())。22994 cycles 3. uart_write() 指令開始後,Uart 硬體將 8bit data 以同樣 baud rate-9600 傳出至 PS 端。41661 cycles 觀察波型後,我們想到的優化方式有: 1. **增加 baud rate**,因為 tx 和 rx 共佔約總時長的 65%,所以可以所短這部分所需 cycle 數。 2. 假設 baud rate 維持不變,可以發現因為 tx 和 rx 所佔時間長,若是能重疊兩者,可以優化非常多的時間。我們的想法是當 **tx 在送出資料時 rx 可以先接收下一筆資料**,這會是一個優化方向。 3. 如果 baud rate 增加,isr 的時間就會變得重要。因為大部分的時間是在 trap_entry,這部份可能無法優化,我們能做的是**減少發送 irq 的次數**。可以這樣是因為大部分情況會是傳一段字串,若每個字元都發 irq 會顯得沒有效率,所以我們認為可以先從 PS config 要傳的字串長度,並在硬體用一個固定大小的 buffer 存收到的若干字元後,再發送一個 irq 去回傳那些字元。   **6. What else do you observe** 在firmware code中,如果把irq在最後才enable,會接收不到uart rx interrupt,所以我們將irq移至firmware code最上面,讓他越早開始越好。 ![image](https://hackmd.io/_uploads/ryPK5bNvp.png) ### Troubleshooting 起初,下圖為synthesis後的Timing report,其中Total hold slack為負值。 ![image](https://hackmd.io/_uploads/Hk769WVP6.png) 經過設定implementation的strategy為performance_NetDelay_high後,Total hold slack為0,如下圖。 ![image](https://hackmd.io/_uploads/ryIT5WNvp.png) ## Final project 1. Block Diagram a. Whole Design: 我們設計FIR以及MM的加速器,並與Caravel以及user project的SDRAM整合。FIR的部分,我們採用AXIS的介面,並加入input的DMA以及output的DMA。MM的部分則是直接透過Wishbone介面access SDRAM。因為整個系統有4個master要access SDRAM,但SDRAM有single port的限制,所以我們設計了一個Arbiter來分配SDRAM的存取。 ![image](https://hackmd.io/_uploads/HyG-mvNuT.png) 2. Baseline 我們使用 LabD SDRAM 版本的 code 當作 baseline,並從下圖波形圖來檢視能優化的地方。可以看到 FIR 為本次 workload 的 bottleneck,詳細優化步驟列舉在第三部分。我們將 code 和 data 放在 mprjram 也就是 0x38 開頭的位置,修改方式為將原本送到dff的.data及.bss改成送到mprjram。因為 mprjram 的 _esram 會因為前面額外的 .data 及 .bss 資料而往後移,所以我們在 _esram 的地方再多加上 SIZEOF(.data) 以及 SIZEOF(.bss)。 ![image](https://hackmd.io/_uploads/HJFfmP4d6.png) ![image](https://hackmd.io/_uploads/BkCzmDE_a.png) 3. Optimization a. 在 SDRAM 中增加 Read Prefetch Buffer 因為從 Bram 中拿 code 或 data 需要 10T,而換成 SDRAM 每筆資料也需要 8T,所以針對這部分我們實作 read prefetch buffer,讓當 read address 連續時能夠花更少的時間拿到資料。 我們最一開始是設計成如果 prefetch buffer hit 的話可以直接在同個cycle 秒回 ack_o,如下面的波型(addr 38000024的位置存放data 10),我們有測試過這樣的設計是可以完成 LABD SDRAM 的 add function 並且執行正確。 ![image](https://hackmd.io/_uploads/S1fNmPVua.png) 但當我們使用這個design來跑這次的FIR時便發現cpu因為莫名原因而無法順利把讀到的資料再送到mprjio給tb檢查。如下圖可以看到結果的正確性,CPU讀取FIR運算後存在0x38開頭的位置output,而output的data也是FIR的第二筆及第三筆正確的結果,分別是-10及-29,也可以看到讀完資料後,CPU似乎有發出WB address到2600000C(mprjio的位置),不過我們的tb卻沒有驗到正確的答案。 ![image](https://hackmd.io/_uploads/SJKNmP4Op.png) 因為發現可能造成錯誤的原因是秒回ack_o,因此最後我們設計的prefetch buffer波型圖如下,可以看到就算prefetch buffer hit也會再等一個cycle才給ack_o及data。 ![image](https://hackmd.io/_uploads/B1JB7DV_a.png) I. 為什麼 code 和 data 使用同一個 prefetch buffer 仍有好效果? 我們設計上並沒有將code以及data分開在不同的bank並使用不同的prefetch buffer。雖然可以預期結果應該會提升,但相對我們認為給軟體的限制上有點太多,而且我們也發現因為CPU的cache關係,CPU每次都會一次要連續的8個指令,除了少部分跳去拿data的位置會被打斷以外,大部分都符合我們設計的讀取連續address的理念,因此我們的prefetch buffer還是可以發揮很好的作用。 II. 優化結果 下面波形圖可以看到每個 workload 基本上都減少了 40% 左右的 cycles 數,詳細各個優化的 cycles 數比較可以參考第四部分。 ![image](https://hackmd.io/_uploads/B14P7vVua.png) III. 後續改進 至此我們繼續觀察軟體能優化的部分,卻發現說除了讀 code 跟 data 之外,CPU 在執行乘法運算的效率也不高,每次要做乘法他會跳到下圖這個 assembly code 的 block 做累加,研究波形後發現 software computation 也佔去近一半的時間。而這是據我們所知無法靠任何軟體技巧優化的部分,所以若只優化 SDRAM 效果仍有限。 ![image](https://hackmd.io/_uploads/By-_XPVOa.png) 於是我們從軟體轉向去做硬體加速,並配合使用 DMA 給 FIR streaming 的 data,這樣做的好處是 firmware code 可以只要 config 一些 addr 和 length 之類的東西後發 ap_start,只要下達開始訊號後就由硬體開始掌管接下來的所有事情。而 firmware assembly code 行數少就表示 CPU 就不需要花太多時間去讀 code,且因為有 DMA去跟 SDRAM 要資料而非 CPU發 wishbone,所以可以自行設計跟 SDRAM 要資料的協定(但因為我們的 SDRAM 只有一個 port,每個 cycle 仍只能有一筆資料,我們仍使用 wishbone 協定。再者,照理說 CPU 在交由 DMA 搬資料後可以繼續做其他事,但因為我們 code 也應要求存放在同個 SDRAM 中,而這時已經被 DMA 佔用走,所以 CPU 這時仍無法執行接下來的 code )。 b. FIR hardware 因為 bottleneck 在 FIR,所以我們先從它開始優化。Lab 4-2 使用的設計是一個乘法和加法器,最快11個 cycles 可以吐一筆資料,且與 firmware 達到速度上的平衡。但因為有 SDRAM prefetch buffer 的設計,我們最快可以兩個 cycle 拿一筆資料,所以我們決定重新設計 FIR,我們使用11 個乘法器,且用兩個 DMA 協助讀取資料而非像 lab 4-2 一樣透過 CPU。理想上若 memory 夠快,將可以達到每個 cycle 吐一筆資料的 throughput。我們預期照這樣設計可以比 lab4-2 更快的算完 FIR。 I. Block diagram: 我們的 FIR 採用11個乘法器以及adder tree的設計。另外頭尾我們都有設計可以存4筆data的buffer,為了可以達到同時input output並且做到一定程度的資料緩衝。 ![image](https://hackmd.io/_uploads/B1bcQPN_6.png) 下圖是我們驗證 FIR 時產生的波型,可以看到因為有FIFO緩衝的設計所以 FIR 可以做到每個cycle吐出一筆資料。如果沒有,而又想每個cycle output一個yn,會形成combinational loop(FIR 用 ss_valid與sm_ready決定sm_valid,但接下去的design就不能用sm_valid直接決定sm_ready),而導致模擬失敗。 ![image](https://hackmd.io/_uploads/HJ1jXv4_6.png) 下面是我們將模擬ss_valid與sm_ready為隨機間隔0~1個cycle的情況,我們可藉此觀察fir運作的情形。 ![image](https://hackmd.io/_uploads/HyBa7wVO6.png) II. 使用 DMA_in 跟 DMA_out 來讀寫資料 我們參考 Lab2 HLS FIR 的設計,分別使用input及output的DMA來送stream的資料給FIR (見whole design block diagram)。我們在input DMA裡面implement一個FIFO,當FIFO還有空位,就持續向SDRAM發出wishbone cycle要資料,另一方面如果FIFO內有東西,就隨時將資料送出給FIR。output DMA 與input DMA 相似,如果FIFO有空位,就將FIR的運算結果收進來,反之,如果FIFO有資料,就發wishbone cycle給SDRAM要求寫入。此外,output DMA有設計configuration register ap_done給CPU讀,讓CPU知道資料的搬運是否已經完成並且確定SDRAM的結果可不可以取用。 下面為DMA_in在模擬wbs_ack_o與sm_ready間隔為隨機0~1 cycle的情況。 ![image](https://hackmd.io/_uploads/HJ60mwE_p.png) DMA limitations: 因為整個系統只有一個single port的SDRAM,CPU執行的code又需要來自SDRAM,因此事實上DMA在access SDRAM時,CPU是會因為抓不到指令而idle的,如果code放在其他的地方,例如cache或是原本的flash裡面,應該就可以做到平行運作。我們最後是將SDRAM的accessibility優先劃分給DMA (參考arbiter設計的部分),讓CPU卡在讀ap_done的迴圈,當DMA沒有request給sdram時,也就是資料已經搬完了,此時,CPU便可以抓到while迴圈的code並拿到ap_done,之後繼續執行接下來的其他指令。 IV. Arbiter design: 因為SDRAM只有一個port,但同時有多個master (CPU, DMA_in, DMA_out, MM) 要發wishbone cycle給SDRAM,因此我們需要設計arbiter來決定誰可以去access SDRAM。設計的原則如下,如果其中一位拿到grant,那他便可以一直使用直到他不需要為止,會這樣設計主要是考慮到SDRAM prefetch連續的位置的特性,又因為同個master access的位置幾乎都是連續的,讓同個master盡量連續的佔用SDRAM能更有效的使用到prefetch的特性。另外,我們設計成讓CPU的優先度最低,因為single port SDRAM的特性,我們希望DMA在使用的時候CPU不會來競爭使用,以最大程度提升performace。下圖為arbiter的finite state machine,為簡單的mearly machine架構,為求簡化就沒有將MM也畫進來。 ![image](https://hackmd.io/_uploads/Hk_GEvV_6.png) V. Caravel implementation: 我們在FIR以及DMA上定義了一些comfiguration registers,DMA需要被config讀的base address以及資料的長度(21~24行),FIR則是tap的values (26~29行)。如下圖我們將這些位置都program好後,分別給這三個module start的訊號,並持續偵測DMA_out的結束(36行)。 ![image](https://hackmd.io/_uploads/B1aXNDVOp.png) 最後下圖為waveform的部分,首先可以觀察最下面arbiter的訊號,req的4個bit分別是CPU、DMA_out、DMA_in、MM的wishbone cycle,bit越significant優先度越低,而gnt則是代表誰有使用的權利。可看到雖然CPU一直有要access code但是因為DMA有在使用所以在DMA結束之前grant都不會給到CPU,看到最上面可以看到CPU的cycle到運算結束才被serve。接著可以看到FIR、DMA_in以及DMA_out的部分,因為DMA以及FIR裡面有加起來16個data FIFO緩衝,所以最後data進出SDRAM的行為就變成以16筆資料為單位運作。另外可以觀察到FIR 的 stall 訊號頻繁發生,這是因為兩個DMA要輪流向 SDRAM 讀寫資料,且雖然SDRAM 有 read prefetch,但其他時間並不是每個 cycle 處理一筆讀寫,簡單來說是因為 memory bound 讓 FIR 不能不停的輸出資料。 ![image](https://hackmd.io/_uploads/S1uEEDEOa.png) VI. 優化結果 從原本軟體的 173113 cycles 進步到只需花 557 cycles,也確實比 lab 4-2 的極限還要更快。波形圖如下,mm 加速的部分請見 c. 部分。而下圖運算以外的其他時間則是 CPU 要資料後傳回 0x260000C 給 tb 去檢查是否正確的時間。 ![image](https://hackmd.io/_uploads/B1Fr4DVd6.png) 後續改進 當 FIR 有非常顯著的提升之後,MM 變成新的 bottleneck,且我們認為 MM 相對 QS 更好實作硬體,所以先從這部份下手。 c. MM hardware 首先因為 MM 不像 FIR 適合用 streaming 的方式實作,所以我們用 wishbone 去讀寫 SDRAM 並做矩陣乘法。細部請參考 whole design block diagram。 CPU 會先用 wishbone config A, B, C array 的 base address 和矩陣的 size。然後下達 ap_start 後讓 MM 取得對 SDRAM 的主導權。MM使用 wishbone 向 SDRAM 要資料並做運算,運算完後會將 ap_done 拉起給 CPU 讀到並結束。而運算的部分我們考量到我們的 SDRAM prefetch buffer 只有一個,如果輪流讀 A 跟 B 的話 address 會不連續而沒有 prefetch 效果,所以我們先讀四筆 A 存到 row buffer,再讀四筆 B 同時乘加,而後輸出一筆 C。架構圖如下:
 I. Block Diagram ![image](https://hackmd.io/_uploads/Sy68BPNup.png) II. Firmware Code ![image](https://hackmd.io/_uploads/B1FvBvNd6.png) III. 優化結果 我們總共實驗了三種方式,第一種是沒有將 B 矩陣 transpose ,所以 B 在讀的時候 prefetch 沒有幫助。第二種是先在 firmware 讓矩陣 B transpose,這樣 A 跟 B 都有得到 prefetch 的效果。最後一種是有 transpose 且有利用A每個 row 會重複的特性而不重複讀同個 A 的 row。三種方式的結果如下表: ![image](https://hackmd.io/_uploads/Sk7NID4dp.png) 4. Performace Summary: ![image](https://hackmd.io/_uploads/BkWbUDE_6.png) 5. Configuration Registers Address: ![image](https://hackmd.io/_uploads/H1TTHvEOT.png) ![image](https://hackmd.io/_uploads/ByMRrvN_p.png) ## Extra lab SDRAM 1. SDRAM Controller Design. 我們將controller的FSM停在READ的時間延長,將原本用在wait tCASL的時間改成持續的pipeline的讀出連續的位置的data,並存在prefetch buffer裡面。當下次讀的address在prefetch buffer內,會馬上回覆ack_o以及對應的data; 反之如果是write hit在prefetch buffer內,我們會使用write through的方式,同時更新prefetch buffer以及SDRAM。基本上只有優化到SDRAM讀取的速度,而SDRAM寫入則會與原本相同。 2. Introduce the Prefetch Scheme. 我們觀察到大部分的情況都是讀SDRAM中連續的位置,所以我們採用的方式是帶出要讀的連續4個位置,如下面波型。 ![image](https://hackmd.io/_uploads/rk5weD4dT.png) 圖上的state_q訊號可以看到停在5 (READ)比較久,因為要發出更多的read command及對應的address給SDRAM。第一筆data完後會進入2 (IDLE)等待下一個讀寫,因為下個讀取遇到prefetch buffer hit,所以進入8 (PREFETCH),直到沒有hit後,便會重新再讀取資料並且也同時更新prefetch buffer。 3. Introduce the Bank Interleave for Code and Data. 在還沒有設計prefetch之前bank interleave (code放bank0, data放bank1)應該不會帶來額外的好處,因為還有花時間去activate另一個bank。但如果設計複數個prefetch buffer給不同的bank,且code跟data放在不同bank,就不會因為執行的時候要在data跟code跳來跳去而無法hit prefetch buffer,可以更進一步提升performance。不過在這個lab我們只有設計一個prefetch buffer,因此所以把code跟data放在不同bank並不會帶來performance的提升。 4. Introduce how to modify the linker to load address/data in two different banks. ![image](https://hackmd.io/_uploads/HyYFxw4Oa.png) 我們的設計有額外修改address map,可以看到BA的address在13:12。所以我們在firmware/sections.lds檔案中調整memory的分配,讓原本的mprjram對應為第一個bank的範圍,另外再新增兩個bank1及bank2。 ![image](https://hackmd.io/_uploads/HJZqlP4dp.png) 最後我們修改sections.lds的59行以及74行,讓.data(input array A and B)送到bank1,.bss(output array result)送到bank2,可以看到右邊dump.out有成功把data移過去。此外,本來的execution code還是一樣送到bank0。 ![image](https://hackmd.io/_uploads/Bk_qeD4u6.png) 5. Observe SDRAM access conflicts with SDRAM refresh. 觀查下面的波形可以看到refresh flag已經拉起來了,在此同時controller還在讀data的階段,當這個data讀完後,就會開始做refresh的一連串動作(先precharge再refresh)之後state又會回到2 (IDLE),並繼續接下來的讀寫。 ![image](https://hackmd.io/_uploads/SJ6ogwN_T.png)