# 將 Linux 執行於 FPGA 為基礎 RISC-V 處理器-詳細實驗過程 ### 簡述 本篇為[將 Linux 執行於 FPGA 為基礎 RISC-V 處理器](https://hackmd.io/@sysprog/S1jNiYgr2)實驗步驟的額外補充。 ## 1.產生 Bitstream file和Peripherals 的腳位連接 * 從 [這裡](https://github.com/yutongshen/RISC-V_SoC) 取得 `RISC-V_SoC`原始程式碼,點擊`Download ZIP`,下載`RISC-V_SoC-master.zip`並解壓縮。 ![RISC-V_Soc下載畫面](https://hackmd.io/_uploads/SyKqU1HcR.jpg) * 之後用 vivado 將其開啟,選擇 File → Project → Open… ![image](https://hackmd.io/_uploads/SJ4YsJSqA.png) * 選擇剛下載的`RISC-V_SoC-master`目錄,打開目錄下的`vivado/soc/soc.xpr`,打開後選擇左側選單最下方的`generate bitsteam` ![螢幕擷取畫面 2024-08-10 213814](https://hackmd.io/_uploads/SyWjpJrcC.jpg) * 之後一路按 OK 到底,等它全部跑完後( 約20min),選擇File → Export → Export Hardware…,進入畫面後按 Next。 ![image](https://hackmd.io/_uploads/BkmkeeS9A.png) * 選擇 `Include Bitstream` 再按 Next。 ![image](https://hackmd.io/_uploads/r1FglxS90.png) * 設定好要輸出的目錄和檔名。 ![image](https://hackmd.io/_uploads/H15ZgxBcA.png) * 然後一路 Next 到底後按下 Finish 即可產生對應的 xsa 檔案。 * 之後從 `RISC-V_SoC-master/vivado` 目錄中,找到`soc.hwh` 以及 `soc_wrapper.bit`。為了方便後續使用,因此將 `soc_wrapper.bit` 更名為 `soc_xsa.bit`。 * 準備好 bit stream file 後,接下來要將所需的各個 periperal 接起來。 * 關於Peripherals 的腳位連接,參考下方的接線圖,分別將 Jtag-USB(CJMCU-232H)模組, Uart-USB(CP2102)模組 以及 spi-microsd模組分別與 PYNQ-Z2開發板 進行連結即可。 ![image](https://hackmd.io/_uploads/ByG2pQLcR.png) ## 2.建立 SD 卡分區 * 從 [這裡](https://drive.google.com/drive/folders/1U_mz91qeFlM4RmhGj2KTosD3d-gEyJ4v) 下載`rootfs.tar.gz` 、 `vmlinux` 和 `bbl` 等必備檔案。 * 在 SD 卡上建立兩個分區,第一個分區用來存放 `bbl` 和 `vmlinux` ,第二個分區用來存放 `rootfs`資料夾。 * 透過[Oracle VM VirtualBox](https://www.virtualbox.org/)進入Linux作業系統,在終端上使用`lsblk`命令查詢從外部讀取的SD卡名稱。 ![螢幕擷取畫面 2024-08-12 105046](https://hackmd.io/_uploads/ry_-Kgvq0.jpg) * 如果VirtualBox沒有成功讀取到SD卡可以參考下面步驟(待補) 在Oracle VM VirtualBox中,選擇要使用的虛擬機,點選 設定->USB,將SD卡的裝置名稱加入進去即可。 * 接著使用`fdisk`命令建立分區。 ``` Linux commands = sudo fdisk /dev/sdb #fdisk:管理和操作磁碟的分割區 ``` * 使用命令`d`刪除舊有的分區,以便建立新分區。 * 然後建立第一個分區,按照終端上的操作,建立一個大小為50M,part type為W95 FAT32 LBA的分區。 ``` Linux= Command (m for help): n Partition type p primary (0 primary, 0 extended, 4 free) e extended (container for logical partitions) Select (default p): p Partition number (1-4, default 1): 1 First sector (2048- 60440575, default 2048): 2048 Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048- 60440575, default 60440575): +50M Created a new partition 1 of type 'Linux' and of size 50 MiB. Partition #1 contains a vfat signature. Do you want to remove the signature? [Y]es/[N]o: Y The signature will be removed by a write command. Command (m for help): t Selected partition 1 Hex code or alias (type L to list all): 0c Changed type of partition 'Linux' to 'W95 FAT32 (LBA)'. ``` * 再來建立第二個分區,將SD卡剩餘的可用空間都劃分給它。 ```Linux= Command (m for help): n Partition type p primary (1 primary, 0 extended, 3 free) e extended (container for logical partitions) Select (default p): p Partition number (2-4, default 2): 2 First sector (104448-60440575, default 104448): 104448 Last sector, +/-sectors or +/-size{K,M,G,T,P} (104448-60440575, default 60440575): press enter Created a new partition 2 of type 'Linux' and of size 28.9 GiB. Partition #2 contains a vfat signature. Do you want to remove the signature? [Y]es/[N]o: Y The signature will be removed by a write command. ``` 指令註解 `n` : 創建新的分區。 `p` : 選擇建立的分區。 `t` : 切換分區類型。 `w` : 儲存建立好的分區到磁碟並退出fdisk。 `q` : 不儲存直接退出fdisk。 * 完成後,輸入命令`p`,確認建立好的分區資訊。 ```Linux commands = Device Boot Start End Sectors Size Id Type /dev/sdb1 2048 104447 102400 50M c W95 FAT32 (LBA) /dev/sdb2 104448 60440575 60336128 28.8G 83 Linux ``` * 使用命令`w` 將 partition 資訊寫入SD卡並退出fdisk。 * 使用`mkfs`命令,將兩分區格式化,並設定成指定的檔案系統格式。 ```Linux commands = sudo mkfs.fat /dev/sdb1 #FAT格式適用於小型存儲設備 sudo mkfs.ext3 /dev/sdb2 #EXT3格式適用於Linux系統 ``` * 接著將檔案分別放到對應的目錄下。由於檔案下載後通常放置在`Downloads`上,建議終端上的操作在`Downloads`上進行,方便檔案的移動。 * 將 `vmlinux` 和 `bbl` 複製到 `sdb1`裝置的目錄底下,以下為操作步驟: + 尋找存取裝置的目錄名稱 進入存取裝置的目錄,開啟終端找到目錄路徑 ![螢幕擷取畫面 2024-08-20 172041](https://hackmd.io/_uploads/Hy9fbJMjC.png) + 複製檔案 ```Linux commands = sudo cp vmlinux bbl /media/elijah/D4A2-86FD ``` * 再來將rootfs目錄的所有檔案複製到`sdb2`裝置的目錄底下,把`rootfs.tar.gz`解壓縮成`rootfs`,進行以下操作: + 尋找存取裝置的目錄名稱 進入存取裝置的目錄,開啟終端找到目錄路徑 ![螢幕擷取畫面 2024-08-20 172706](https://hackmd.io/_uploads/BkKob1MoC.png) + 複製檔案 ```Linux commands = sudo cp rootfs /media/elijah/d33989bc-ee99-4675-9786-e068ee8c028a ``` + 移動rootfs的檔案到目錄外 ```Linux commands = sudo mv * .. ``` * 步驟二結束 ## 3.使用 RISC-V CPU 驗證連接的 Peripherals 運作正常 * 準備另外一塊 8GB 以上的 micro SD卡並將FPGA板對應的 [pynq_z2_v2.7.0 SDcard image](https://github.com/Xilinx/PYNQ/releases) 下載解壓縮後,燒錄到該 micro SD卡中。以下為燒錄的操作步驟: + 下載[Win32diskimager](https://sourceforge.net/projects/win32diskimager/) 並進行安裝。 + 進入該工具的使用介面後,選擇要燒錄的image檔案和燒錄進去的micro SD卡裝置,點擊"寫入"將image檔案燒錄進micro SD卡。 ![image](https://hackmd.io/_uploads/BkGmUgwcR.png) + 由於image檔案較大,燒錄時間大約需要5分鐘左右。燒錄完成後會出現一個視窗,說明寫入成功。 * 把 PYNQ-Z2開發板 右上角的跳線設置為 sd (圖中步驟 1 的藍色區域)以及將左下角的跳線設置為 USB (圖中步驟 2 的藍色區域),之後將存有 image檔案 的 micro SD卡 放入 PYNQ 下方的插槽(圖中步驟 3 ) ,將 usb 傳輸線(圖中步驟 4 )連上電腦並將乙太網路線(圖中步驟 5 )接上自己的電腦後即可打開電源(圖中步驟 6 ),系統啟動時,FPGA板上會有**兩個藍色LED燈**和**四個綠色LED燈**同時閃爍,當藍色LED燈關閉,**綠色LED燈持續亮燈**,表示系統已啟動完成。 ![image](https://hackmd.io/_uploads/rkSJEyIcC.png) * 圖中步驟 4 插入的 usb 傳輸線,這裡使用 [Putty](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) 作為UART連線軟體。開啟電腦上的裝置管理員,從上面的連線埠(COM和LPT) ,找到 USB 傳輸線 和 外接的 `Uart-USB`模組 各自的裝置名稱。 ![螢幕擷取畫面 2024-08-12 113152](https://hackmd.io/_uploads/SJq98GvqR.jpg) * 我個人的COM7 被作為 FPGA端的UART接口,COM3 則作為 RISC-V soc的 UART接口。將 Baud rate 設為 115200,點選 Open 即可建立連線。 ![image](https://hackmd.io/_uploads/ryk0hNj9R.png) * 連上之後我們就能進入 FPGA ARM 運行的Linux終端。使用`ifconfig`命令可以看到,分配給開發板的靜態ip位址為 `192.168.2.99`。 ![螢幕擷取畫面 2024-08-12 130757](https://hackmd.io/_uploads/HJj1Fzv5C.jpg) * 電腦端通訊 需要修改電腦端的IP位址,才能與 開發板 進行通訊,進入 PYNQ 上的 jupyter 伺服器。進入電腦上的設定 -> 網路和網際網路的`進階網路設定` -> 編輯`乙太網路(無法辨識)`的介面卡選項 -> 修改`網際網路通訊協定第4板(TCP/IPv4)`內容 -> 將IP位址設定成`192.168.2.x`,讓 電腦端IP 與 開發板IP 處於同一網段就可以了 ![螢幕擷取畫面 2024-08-21 134230](https://hackmd.io/_uploads/By-FBb7oC.jpg) * 接著打開網頁瀏覽器(Chrome、Edge..),在網址中輸入剛剛 `ifconfig` 指令出現的 ip 位址,並指定 port = 9090 ( jupyter kernel server 的預設 port ),即輸入 `192.168.2.99:9090` 到網址列中,第一次他會提示需要輸入密碼,密碼為 `xilinx` ,如此我們便能進入 PYNQ 上的 jupyter 伺服器。 ![螢幕擷取畫面 2024-08-12 131147](https://hackmd.io/_uploads/HklH9zDqA.jpg) 參考資料 : https://blog.csdn.net/weixin_41258131/article/details/129872294 https://medium.com/%E9%AB%94%E9%A9%97%E4%BA%BA%E7%94%9F-touch-life/pynq-z2-intro-bbd1c64355c9 https://blog.csdn.net/Mculover666/article/details/83019579 https://blog.csdn.net/qq_53144843/article/details/127270996 --- * 接著將 [riscv.ipynb](https://gist.github.com/fennecJ/5f7af384e79c9aa8d201aaebfc40c1ee)下載並解壓縮,將 `riscv.ipynb`與前面產生 Bit stream file 得到的 `soc_xsa.bit` 及 `soc_xsa.hwh` 透過 upload 按鈕上傳到板子上。 * 開啟 riscv.ipynb 後,執行Overlay便能藉由 RISC-V CPU 測試硬體和各個 Peripheral 是否能運作正常(挑選以下必要的測試說明)。 + **Overlay** : 將 RISC-V_SoC 所產生的 Bitstream 燒錄到開發板上的可程式化硬體(PL)中。 ```python = from pynq import Overlay riscv = Overlay("./soc_xsa.bit") ``` 同時,由於bitstream file 中包含目標硬體設計的各種基本資訊,可以透過 Overlay 列出這些資訊。 ![image](https://hackmd.io/_uploads/BkwN8BUqA.png) * **UART Test** : 測試 `UART-USB` 模組是否運作正常。 ```python = //RISC-V至PC端測試 for i in "Hello".encode(): riscv_base.write(UART_TXFIFO, i) ``` 執行以上程式後在Putty上可以看到從RISC-V傳送至PC端的 `Hello` 字樣。 ![image](https://hackmd.io/_uploads/B1PwAs-sC.png) ```python = //至PC端至RISC-V測試 for _ in range(5): char = riscv_base.read(UART_RXFIFO) while char & 0x80000000: char = riscv_base.read(UART_RXFIFO) print("Receive {:x} ({:c})".format(char, char)) ``` 另外以上程式需在Putty上輸入5次文字,測試PC端傳送至RISC-V是否正常。 ![image](https://hackmd.io/_uploads/Hy7D12bsA.png) * **SPI Test** : 主要用來測試 micro sd-SPI 模組是否運作正常。 ```python = # Initialize SD card via SPI (stat, sd_type) = sd_init(2) # SPI baud rate is 45.45MHz / 2 sd_showstat(stat) sd_showtype(sd_type) # print sector 0 sect = 0 (stat, buff) = sd_readblk(sect) if stat == 1: print("Read Success") print_byte_sd(sect*512, buff) else: print("Read Fail") print() # print sector 0 sect = 0 (stat, buff) = sd_readblk(sect) if stat == 1: print("Read Success") print_byte_sd(sect*512, buff) else: print("Read Fail") print_byte_sd(sect*512, buff) print() ``` 結果中必須出現`Read Success`,表示透過 micro SD卡可正常存取資料。 ## 4.1 啟動方法1-直接在JupyterNotebook上啟動(推薦) 1. 將[rom.bin](https://drive.google.com/drive/folders/1U_mz91qeFlM4RmhGj2KTosD3d-gEyJ4v)上傳至JupyterNotebook。 2. 把以下程式新增到`riscv.ipynb` 上。 ```python = # Read rom.bin file def read_file_as_hex(file_path): with open(file_path, 'rb') as file: file_content = file.read() hex_content = file_content.hex() # Group the hex content into 4-byte chunks (8 hex digits each) hex_chunks = [hex_content[i:i+8] for i in range(0, len(hex_content), 8)] return hex_chunks # Function to convert hex chunks to unsigned integers def hexstr_to_int(s): if len(s) < 8: s = s.ljust(8, '0') s = s[6:8] + s[4:6] + s[2:4] + s[0:2] return int(s, 16) boot = read_file_as_hex('rom.bin') print('Write rom.bin to ROM') for i, b in enumerate(boot): riscv_base.write(i*4, hexstr_to_int(b)) print('Write done') ``` 3. 新增位置必須是在啟動程式之前(如下)。 ```python = CFGREG_RSTN = 0x4000000 riscv_base.write(CFGREG_RSTN, 0) riscv_base.write(CFGREG_RSTN, 1) ``` 4. Run all cell後進入RISC-V Linux終端___實驗結束。 ## 4.2 啟動方法2-使用 Jtag debugger 啟動系統 * 確認所有 perepheral 接到開發板上,包含 `Jtag-USB` 、 `Uart-USB` 、 以及 `spi-microsd` ,同時將之前燒錄好 `vmlinux` 、 `bbl` 以及 `rootfs` 的 micro SD卡 插入 `spi-microsd` 模組中。 * 將 UART-USB 接到電腦上,使用Putty進行連線, baud rate 設為 115200。 * 下載[rom.bin](https://drive.google.com/drive/folders/1U_mz91qeFlM4RmhGj2KTosD3d-gEyJ4v),並將[debugger](https://github.com/yutongshen/RISC-V-Debugger)下載解壓縮,開啟`debugger/bin/Debug`目錄下的`debugger.exe`,便會看到下面視窗。 ![螢幕擷取畫面 2024-08-11 181717](https://hackmd.io/_uploads/HyjRaMU5R.jpg) * 首先點選 Setup, Jtag 便會將 ROM 中的資料呈現出來。 ![image](https://hackmd.io/_uploads/HJN5LQL9C.png) * 接著按下 Download ROM ,選擇 rom.bin載入到 ROM 中。 * 最後點右上WORD鍵,並在右上角輸入 4000000 然後按下 Goto ,接著在圖中左上紅框處點兩下,輸入 1 後 便會開始執行 Linux 開機。 ![image](https://hackmd.io/_uploads/SyfI8Q890.png) ## 5.問題與解決辦法 * 執行RISC-V_SoC專案時,無法順利產生bitstream file。 * IP位址的設定出現差錯,導致無法進入 PYNQ 的 jupyterNootbook 。 * 建立sd分區時,找不到讀取的SD卡設備。 * 操作`debugger.exe`啟動系統時,無法從putty上看到 RVLinux 的歡迎畫面。 ![image](https://hackmd.io/_uploads/rk1nOXL5R.png) 解決辦法-https://blog.csdn.net/qq_21383969/article/details/135497700?utm_medium=distribute.pc_relevant.none-task-blog-2 ## 6.心得 Ethan : 這次關於將 Linux執行於 FPGA上的 RISC-V處理器實驗給了我許多實踐的經驗,特別是在硬體設計與軟體整合的過程上,也額外增加了我對除錯上的經驗,實驗過程會遇到一些不那麼直覺可以解決的問題,但是答案通常都藏在細節裡,對此我的解決問題辦法是來自網路上過來人的經驗與更仔細的觀察是否存在我遺漏的細節或資訊。嵌入式系統開發需要的不只是技術上的積累,更需要人的耐心與靈感。