这是一个针对 Nexys-A7 FPGA 板的 RISC-V SoC 示例。它包含 lowRISC Ibex 内核以及以下功能:
- RISC-V 调试支持(使用 PULP RISC-V 调试模块)
- 串口(UART)
- 通用输入输出接口(GPIO)
- 脉宽调制(PWM)
- 定时器(Timer)
- SPI
- 用于将 ASCII 输出写入文件并停止软件模拟的基本外设
*注:该项目在2024年1月22日被完全更新了,以下手册只适用最新的0.02版本
(一)软件支持链接:
- CMAKE:
sudo apt install cmake
- rv32imc GCC toolchain:https://github.com/lowRISC/lowrisc-toolchains/releases/download/20220524-1/lowrisc-toolchain-gcc-rv32imcb-20220524-1.tar.xz
- python3:
sudo apt install python3.10-venv
- openocd 0.12.0:
sudo apt install openocd
- srecord: Download SRecord
- screen:用于在终端观测usb端口的输出
sudo apt install screen
(二)Container Guide
cd到ibex-demo-system-main文件夹,打开终端,运行命令
sudo apt install curl
curl -sSL https://get.docker.com/ | sudo sh
sudo docker build . -t ibex -f container/Dockerfile
运行结束后,docker创建完成,就可以运行命令:
sudo docker run -it --rm \
-p 6080:6080 \
-p 3333:3333 \
-v $(pwd):/home/dev/demo:Z \
ibex
不关闭终端,可以在http://localhost:6080/vnc.html中查看container内容。
点击Connect后会出现一个桌面:
(三)Native Python Environment
要使用 pip 安装 python 依赖项,您可能希望在虚拟环境中执行此操作,以避免干扰您当前的 python 设置(请注意,它使用 edalize 和 FuseSoC 的 lowRISC 分支,因此如果您已经使用这些,建议使用虚拟环境):
# Setup python venv python3 -m venv .venv source .venv/bin/activate # Install python requirements pip3 install -r python-requirements.txt
如果出现以下错误,您可能需要运行最后一个命令两次:
“ERROR: Failed building wheel for fusesoc”
第一句指令python3 -m venv .venv是在构建一个单独的虚拟的python环境,区别于系统安装好了python环境。因为这个项目所需要的一些内容是这个项目特有的,需要区别于系统的python环境。
第二句指令source .venv/bin/activate是将创建好的.venv文件夹作为当前终端下的python环境。之后的的install的内容都只会安装在当前目录下的.venv文件夹中。
第三句指令pip3 install -r python-requirements.txt是在该环境下安装了一些这个项目需要的东西。我们可以打开这个txt文档。可以看出来,前面是在安装一些所需要的package,而后面是在安装edalize和fusesoc两个工具。这两个工具在后面的simulation和其他步骤中会用到。
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
# Individual Python packages
anytree
hjson
mako
pyyaml
wheel
# Development version of edalize until all our changes are upstream
git+https://github.com/lowRISC/edalize.git@ot
# Development version with OT-specific changes
git+https://github.com/lowRISC/fusesoc.git@ot
注意!!!
1. 如果你在运行完上面的指令后,想在另一个终端中继续simulation及其后面的内容的话,请务必首先运行source .venv/bin/activate命令。如果当前终端已经挂载好虚拟环境,则如下图所示,前面会多一个(.venv)的标识。
(四)Building Software
C stack
首先必须构建软件。它可以加载到 FPGA 中以在合成的 Ibex 处理器上运行,或者传递到 verilator 仿真模型以在 PC 上进行仿真。但是在此之前,需要将之前下载好的rv32imc GCC toolchain编译器设置好。
cd /path/to/ibex-demo-system-main/ #记得改成自己的位置
sudo gedit sw/c/gcc_toolchain.cmake
打开文本编辑器,找到CMAKE_C_COMPILER后面的riscv32-unknown-elf-gcc,如下图所示。
将此修改为绝对路径,如set(CMAKE_C_COMPILER "/your_path/lowrisc-toolchain-gcc-rv32imcb-20220524-1/bin/riscv32-unknown-elf-gcc"),如图所示。
保存后关闭,运行cmake等命令。
mkdir sw/c/build
pushd sw/c/build
cmake ..
make
popd
如果出现以下错误:
运行以下指令sudo apt-get install srecord后,将srec_cat加载到环境变量中,默认地址是/usr/bin/srec_cat,所以vim ~/.bashrc后,把export PATH=$PATH:/usr/bin/srec_cat这一行加入到最后一行。(按i进入编辑模式,加入后按Esc退出编辑模式,按:wq保存并退出)。
如果修改后依然不行,则删除之前创建的sw/c/build文件夹,再重新运行上述指令。
出现以下内容则make成功:
Rust stack
首先安装必要的软件:
sudo apt install cargo
wget https://sh.rustup.rs -O - | sh
source "$HOME/.cargo/env"
rustup component add rust-src
运行指令
pushd sw/rust
cargo build --bin led
popd
稍等一段时间,出现以下内容后,此部分完成。请注意,FPGA 构建依赖于初始二进制文件 (blank.vmem) 的固定路径,因此如果您想在其他位置创建构建目录,则需要在ibex_demo_system.core文件中调整路径。
Finished dev [unoptimized + debuginfo] target(s) in 1m 16s
~/Desktop/ibex_demo_system/ibex-demo-system-main
(五)Building Simulation
sudo apt-get update
sudo apt-get install libelf-dev
sudo apt-get install verilator
fusesoc --cores-root=. run --target=sim --tool=verilator --setup --build lowrisc:ibex:demo_system
可能会出现的错误:
1. 找不到fusesoc指令:请回到(三)Native Python Environment这一步骤中。
2. ERROR: Conflicting requirements: Requirements: 'lowrisc_ibex_demo_system == 0-0' <- 'lowrisc_ibex_fpga_xilinx_shared >= 0-0' <- 'lowrisc_prim_ram_1p >= 0-0' ............:
请运行source .venv/bin/activate指令。
3. No module named 'yaml'等之类的找不到package的错误:打开文件夹,打开“Show Hidden Files”。找到.venv文件夹并删除,回到(三)Native Python Environment这一步骤中。
(六)Running the Simulator
在项目的根目录下运行以下指令:
./build/lowrisc_ibex_demo_system_0/sim-verilator/Vibex_demo_system \
-t --meminit=ram,./sw/c/build/demo/hello_world/demo
得到以下结果,记得按CTRL+C来终止仿真。
其中指令-t参数是为了产生fst文件,这是一个只能由GTKwave打开的文件。
(七)Building FPGA bitstream
由于我使用的FPGA开发板是NexysA7开发板,我需要更改一些文件来保证bitstream正确生成。
7.1 SystemVerilog顶层文件与约束文件
在项目根目录下,执行以下指令,以添加top_nexysa7.sv顶层文件。
gedit ./rtl/fpga/top_nexysa7.sv
在打开的页面中添加代码,根据不同的开发板自行修改。
// This is the top level SystemVerilog file that connects the IO on the board to the Ibex Demo System on Nexys-A7 board.
module top_nexysa7 (
// These inputs are defined in data/pins_nexysa7.xdc
input IO_CLK,//clk100mhz
input IO_RST_N,//cpu_resetn
input [ 7:0] SW,//SW
output [ 7:0] LED,//LED
output [ 5:0] RGB_LED,//[0:5] = led16_b/led16_g/led16_r/led17_b/led17_g/led17_r
input UART_RX,//uart_rxd_out
output UART_TX//uart_txd_in
// input SPI_RX,
// output SPI_TX,
// output SPI_SCK
);
parameter SRAMInitFile = "";
logic clk_sys, rst_sys_n;
// Instantiating the Ibex Demo System.
ibex_demo_system #(
.GpiWidth(8),
.GpoWidth(8),
.PwmWidth(6),
.SRAMInitFile(SRAMInitFile)
) u_ibex_demo_system (
//input
.clk_sys_i(clk_sys),
.rst_sys_ni(rst_sys_n),
.gp_i(SW),
.uart_rx_i(UART_RX),
//output
.gp_o(LED),
.pwm_o(RGB_LED),
.uart_tx_o(UART_TX),
.spi_rx_i(1'b0),
.spi_tx_o(),
.spi_sck_o()
);
// Generating the system clock and reset for the FPGA.
clkgen_xil7series clkgen(
.IO_CLK,
.IO_RST_N,
.clk_sys,
.rst_sys_n
);
endmodule
在添加约束文件时需要注意:
对于NexysA7开发板来说,用于Debug的FTDI芯片(该开发板中使用的是FT2232H芯片)同时也被用作了下载器,也就是说在不使用外接下载器的情况下,该开发板可以直接通过一跟SmartUSB的线,将bitstream从主机烧写到FPGA上,如Reference Manual中的下图所示(Nexys A7 Reference Manual - Digilent Reference):
此处的Artix7指的是该FPGA中的可编程模块(PL)的GPIO口,而通常这四个引脚(C4、D4、D3、E5)会被用作UART接口。而UART又和FTDI芯片连接到了一起,如下图所示。
这也解释了为什么在PynqZ2开发板上实现的时候,会用一根额外的接线来连接FTDI芯片和UART的TX端口(详见https://github.com/lowRISC/ibex-demo-system/pull/49/files)如下图所示。
因此,在约束文件中,我的端口UART_TX、UART_RX、UART_CTS和UART_RTS应该分别定义在之前提到的C4、D4、D3和E5中,如以下所示:
##USB-RS232 Interface
set_property -dict { PACKAGE_PIN C4 IOSTANDARD LVCMOS33 } [get_ports { UART_RX }]; #IO_L7P_T1_AD6P_35 Sch=uart_txd_in
set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { UART_TX }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out
#set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { UART_CTS }]; #IO_L12N_T1_MRCC_35 Sch=uart_cts
#set_property -dict { PACKAGE_PIN E5 IOSTANDARD LVCMOS33 } [get_ports { UART_RTS }]; #IO_L5N_T0_AD13N_35 Sch=uart_rts
当然,因为top文件中没有UART_CTS和UART_RTS,在本次实例中也用不到,所以注释掉了。执行以下指令,转到正确的目录位置后,添加约束文件
gedit ./data/pins_nexysa7.xdc
完整的约束文件如下:
## This file is a general .xdc for the Nexys A7-100T
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project
## Clock signal
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { IO_CLK }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {IO_CLK}];
##Switches
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { SW[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { SW[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { SW[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]
set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { SW[3] }]; #IO_L13N_T2_MRCC_14 Sch=sw[3]
set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { SW[4] }]; #IO_L12N_T1_MRCC_14 Sch=sw[4]
set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { SW[5] }]; #IO_L7N_T1_D10_14 Sch=sw[5]
set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { SW[6] }]; #IO_L17N_T2_A13_D29_14 Sch=sw[6]
set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { SW[7] }];
## LEDs
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { LED[0] }]; #IO_L18P_T2_A24_15 Sch=led[0]
set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { LED[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1]
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { LED[2] }]; #IO_L17N_T2_A25_15 Sch=led[2]
set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { LED[3] }]; #IO_L8P_T1_D11_14 Sch=led[3]
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { LED[4] }]; #IO_L7P_T1_D09_14 Sch=led[4]
set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { LED[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5]
set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { LED[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6]
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { LED[7] }];
## RGB LEDs
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { RGB_LED[0] }]; #IO_L5P_T0_D06_14 Sch=led16_b
set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { RGB_LED[1] }]; #IO_L10P_T1_D14_14 Sch=led16_g
set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports { RGB_LED[2] }]; #IO_L11P_T1_SRCC_14 Sch=led16_r
set_property -dict { PACKAGE_PIN G14 IOSTANDARD LVCMOS33 } [get_ports { RGB_LED[3] }]; #IO_L15N_T2_DQS_ADV_B_15 Sch=led17_b
set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { RGB_LED[4] }]; #IO_0_14 Sch=led17_g
set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports { RGB_LED[5] }]; #IO_L11N_T1_SRCC_14 Sch=led17_r
##CPU Reset Button
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { IO_RST_N }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn
##USB-RS232 Interface
set_property -dict { PACKAGE_PIN C4 IOSTANDARD LVCMOS33 } [get_ports { UART_RX }]; #IO_L7P_T1_AD6P_35 Sch=uart_txd_in
set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { UART_TX }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out
#set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { UART_CTS }]; #IO_L12N_T1_MRCC_35 Sch=uart_cts
#set_property -dict { PACKAGE_PIN E5 IOSTANDARD LVCMOS33 } [get_ports { UART_RTS }]; #IO_L5N_T0_AD13N_35 Sch=uart_rts
7.2 .core文件
gedit ./ibex_demo_system.core
打开.core文件后,找到filesets,添加如下内容:
files_xilinx_nexysa7:
depend:
- lowrisc:ibex:rv_timer
- lowrisc:ibex:fpga_xilinx_shared
files:
- rtl/fpga/top_nexysa7.sv
file_type: systemVerilogSource
找到files_constraints,添加如下内容:
files_constraints_nexysa7:
files:
- data/pins_nexysa7.xdc
file_type: xdc
找到synth_cw312a35:在parameter后面添加如下内容:
synth_nexysa7:
<<: *default_target
default_tool: vivado
filesets_append:
- files_xilinx_nexysa7
- files_constraints_nexysa7
toplevel: top_nexysa7
tools:
vivado:
part: "xc7a100tcsg324-1"
parameters:
- SRAMInitFile
- PRIM_DEFAULT_IMPL=prim_pkg::ImplXilinx
至此,core文件的依赖关系修改完毕。
7.3 util文件夹中的各个文件
这部分内容需要将FPGA板插入到电脑上,以获取正确的端口信息!!!
运行以下指令,修改load_demo_system.sh文件中的内容:
gedit ./util/load_demo_system.sh
修改为以下内容:
#!/bin/sh
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
if [ $# -ne 3 ]; then
echo "Usage $0 artya7|nexysa7 run|halt elf_file"
exit 1
fi
if [ ! -f $3 ]; then
echo "$3 does not exist"
exit 1
fi
if [ $2 != "halt" ] && [ $2 != "run" ]; then
echo "Second argument must be halt or run"
exit 1
fi
if [ $1 != "artya7" ] && [ $1 != "nexysa7" ]; then
echo "First argument must be artya7 or nexysa7"
exit 1
fi
EXIT_CMD=''
if [ $2 = "run" ]; then
EXIT_CMD='-c "exit"'
fi
SCRIPT_DIR="$(dirname "$(readlink -e "$0")")"
if [ $1 = "artya7" ]; then
SCRIPT_FILENAME="arty-a7-openocd-cfg.tcl"
elif [ $1 = "nexysa7" ]; then
SCRIPT_FILENAME="nexys-a7-openocd-cfg.tcl"
fi
openocd -f $SCRIPT_DIR/$SCRIPT_FILENAME -c "load_image $3 0x0" \
-c "verify_image $3 0x0" \
-c "echo \"Doing reset\"" \
-c "reset $2" \
$EXIT_CMD
运行以下指令,创建nexys-a7-openocd-cfg.tcl文件:
gedit ./util/nexys-a7-openocd-cfg.tcl
在弹出界面加入以下内容:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
adapter driver ftdi
transport select jtag
ftdi_vid_pid 0x0403 0x6010
ftdi_device_desc "Digilent USB Device"
#ftdi_serial 3
ftdi_channel 0
#ftdi_layout_init 0x0018 0x05fb
#ftdi_layout_signal SWD_EN -data 0
ftdi_layout_init 0x00e8 0x60eb
#ftdi_layout_signal nTRST -ndata 0x0010 -noe 0x0040
#ftdi_layout_signal nSRST -ndata 0x0020 -noe 0x0040
reset_config none
set _CHIPNAME riscv
set _EXPECTED_ID 0x13631093
jtag newtap $_CHIPNAME cpu -irlen 6 -expected-id $_EXPECTED_ID -ignore-version
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
riscv set_ir idcode 0x09
riscv set_ir dtmcs 0x22
riscv set_ir dmi 0x23
adapter speed 1000
riscv set_prefer_sba on
gdb_report_data_abort enable
gdb_report_register_access_error enable
gdb_breakpoint_override hard
reset_config none
init
halt
其中需要注意的是:
- ftdi_vid_pid:运行指令lsusb,得到以下输出,其中框选出来的就是ftdi_vid_pid的值。
- ftdi_device_desc:确认好硬件正确连接后,即lsusb输出有正确结果,运行指令sudo dmesg | grep usb,来输出usb端口的具体信息,找到以下结果,需要idVender和idProduct与上一点的ftdi_vid_pid相匹配且Detected的内容是FT2232H,找到的Product后面跟随的内容就是ftdi_device_desc的内容。
7.5 生成Bistream
至此,所有需要修改的文件已经修改完毕。
运行以下指令来生成nexysa7的bitstream文件。这个过程会很长,请耐心等待。
source .venv/bin/activate #以确保调用的fusesoc指令是该项目适用的特定的版本
fusesoc --cores-root=. run --target=synth_nexysa7 --run lowrisc:ibex:demo_system
如果以上指令不工作,可以运行以下指令
make -C ./build/lowrisc_ibex_demo_system_0/synth_nexysa7-vivado/ pgm
执行成功的话,会有以下输出:
(八)Loading an application to the programmed FPGA
与github上不同的是,因为在7.3部分已经修改了load_demo_system.sh文件,因此在运行应用时,一共需要3个参数。如以下指令所示
# Run demo
./util/load_demo_system.sh nexysa7 run ./sw/c/build/demo/hello_world/demo
./util/load_demo_system.sh nexysa7 run ./sw/c/build/demo/lcd_st7735/lcd_st7735
# Load demo and start halted awaiting a debugger
./util/load_demo_system.sh nexysa7 halt ./sw/c/build/demo/hello_world/demo
正确的话,会有以下输出:
执行halt而非run的话,程序就会停止,而不会直接结束,这个时候就可以接入gdb了。 halt的话会输出以下结果:
(九)Debugging an application
请正确安装openocd。
请注意,当之前的程序还在halt的时候,FT2232H芯片被占用,需要Ctrl+C停止后再运行openocd的指令:
openocd -f util/nexys-a7-openocd-cfg.tcl
有以下输出时,说明openocd正常工作:
打开另一个终端,运行riscv32的gdb:
riscv32-unknown-elf-gdb ./sw/c/build/demo/hello_world/demo
是得到以下输出结果说明riscv32-unknown-elf-gdb正常工作:
接入3333端口:
(gdb) target extended-remote localhost:3333
有以下输出时表示成功接入:
此时运行了openocd的终端会有收到的信息:
至此,整个ibex系统在FPGA上搭建完毕,并且debugger正常工作。
(十)终端上通过screen观测uart输出
在第(八)节中,有提到可以使用screen来观测usb端口的输出内容。
首先运行了以下指令打开了screen终端:
screen /dev/ttyUSB1 115200
其中ttyUSB1来自7.3节的sudo dmesg | grep usb的输出结果。
在C代码中可以得知,在hello_world项目的main.c文件中,该系统的所有输出都是通过自定义的puts函数来讲内容输出到uart端口的,而不是printf。具体puts函数是如何调用其他库中的函数,可以使用gdb的stepi指令来了解到。在hello_world的demo项目中,终端通过screen显示出来的内容如下图所示:
至此,在Nexys a7的ibex-demo-system的安装就完成了,接下来可以在以下链接中去完成各个实验:GitHub - lowRISC/ibex-demo-system-labs: Labs for the Ibex Demo System