-
Notifications
You must be signed in to change notification settings - Fork 41
lvna usage emu
操作系统:Ubuntu 18.04 或 20.04
执行以下命令安装需要的软件包并配置环境变量:
# Bash shell
# Required Ubuntu packages
sudo apt install -y build-essential git python3 python3-pip flex bison curl openjdk-8-jdk device-tree-compiler
sudo -H pip3 install pyfdt
# Pre-built RISC-V environment
wget https://github.com/LvNA-system/labeled-RISC-V/releases/download/v0.1.0/riscv-toolchain-2018.05.24.tar.gz
tar xf riscv-toolchain-2018.05.24.tar.gz
export RISCV=`realpath ./riscv-toolchain-2018.05.24`
export PATH=$RISCV/bin:$PATH
# Prepare labeled-RISC-V repo
git clone https://github.com/LvNA-system/labeled-RISC-V.git
cd ./labeled-RISC-V
git submodule update --init
在 label-RISC-V/fpga
目录下,执行以下命令:
mkdir build
make sw -j
该命令会创建 labeled-RISC-V/../sw
目录,在其中克隆经过定制修改的 boot loader 仓库 riscv-pk, linux 仓库 riscv-linux 和文件系统镜像 riscv-rootfs,并依次编译构建。
最终在在 build
目录下生成裸机二进制 linux 镜像 linux.bin
.
默认情况下会编译用于仿真的 linux 镜像,其大小约 1.5M,不含 initram 文件系统,不会进入控制台界面,仅用于在仿真环境下验证启动过程。
要切换不同的配置,可以在 sw/riscv-linux
目录下选择执行以下指令:
# For fpga
make ARCH=riscv fpga_defconfig
# For emu
make ARCH=riscv emu_defconfig
然后在 labeled-RISC-V/fpga
目录下重新执行 make sw -j
即可。
labeled-RISC-V 可以通过内存隔离映射,让不同的物理核心运行独立的系统,我们将其称为 Nohype。
要使用这一模式,首先在 sw/riscv-pk/machine/encoding.h
目录下,定义 NOHYPE 宏:
#define NOHYPE
需要删除 sw/riscv-pk/build
目录后再执行 make sw -j
才能生效。
硬件方面,虽然可以通过调试模块在系统运行时修改内存映射配置,但也可以通过静态配置引脚在复位时快速应用默认的 Nohype 设置。
在硬件代码 labeled-RISC-V/src/main/scala/system/TestHarness.scala
的 TestHarness
类下,在符号 dut
定义之后,追加以下代码:
dut.mem_part_en := true.B
dut.distinct_dsid_en := true.B
该代码将硬件的控制平面默认复位为 Nohype 的配置,可以在人工复位后直接按 Nohype 来启动系统。
在 labeled-RISC-V/fpga/emu
目录下执行 make emu -j
来编译硬件 RTL 代码和仿真器代码。
初次编译需要通过 sbt 下载 scala 依赖包,需要较长时间。此外还会 verilator 源码并编译,作为工具来生成最终的仿真器。
在 labeled-RISC-V/fpga/emu
目录下执行 make run-emu
开始仿真。在 labeled-RISC-V/fpga/build
目录下,有相关的日志文件:
-
emu.log
: 仿真器的运行日志,以及硬件代码的调试信息; -
serial60000000
: 模拟 uart 设备的输出,主要是 linux 的启动日志; -
serial60001000
: 第二个模拟 uart 设备的输出,在 nohype 模式下对应第二个物理核心独立运行的 linux 启动日志。
目前我们通过扩展 RISC-V 架构规定的 debug module register 来对外暴露控制平面的寄存器。RISCV 社区适配的 openocd 可以通过 jtag 接口读写这些寄存器。 适配的 openocd 可以通过源码单独编译安装:https://github.com/riscv/riscv-openocd,在 riscv-toolchains 里也已经包含。
要在仿真时使用调试模块和 openocd,需要执行命令 make run-emu DEBUG=1
,启动后仿真器会阻塞执行,侦听端口 4040,等待 openocd 接入(每个周期都需要接受一次来自 openocd 的数据包,所以仿真效率会极大降低)。
将以下配置文件保存为 emu.cfg
:
interface remote_bitbang
remote_bitbang_host <your-ps-ip>
remote_bitbang_port 4040
set _CHIPNAME riscv
jtag newtap $_CHIPNAME dap -irlen 5
set _CORE_0 $_CHIPNAME.cpu0
target create $_CORE_0 riscv -chain-position $_CHIPNAME.dap
bindto <your-host-ip> # Optional
init
执行命令 openocd -f emu.cfg
,仿真便会继续执行。
假设 openocd 绑定的 IP 为 127.0.0.1,则可以执行 telnet 127.0.0.1 4444
登录到 openocd 的命令行界面,我们主要使用以下命令:
> riscv dmi_read <dm_addr>
> riscv dmi_write <dm_addr> <data>
dm_addr
即 debug module register 的地址,寄存器描述与地址请参见 RISC-V External Debug Support 的 3.12 节。
控制平面相关寄存器也是在 debug module register 的地址空间里进行扩展的,具体定义请参见 src/main/scala/devices/debug/dm_registers.scala
中以 CP_
为前缀的符号。
除了在 telnet 里人工输入命令外,我们还可以通过 tcl 接口利用脚本来与 openocd 交互。在 labeled-RISC-V/fpga/openocd_rpc
目录,修改 openocd.py
里的 self.tclRpcIp
为可以访问到 openocd 的 IP 地址。
Openocd 类封装了通过 tcl 访问 openocd 的基本接口,通过 send
方法发送和 telnet 里一样的命令,其返回值(字符出)便是 telnet 里的输出。dmi_read
和 dmi_write
是对 dmi 读写命令的简单封装。dmi_read_fields
和 dmi_write_fields
用于读写 dm 寄存器的具体字段,待写入的字段用 python 的关键字参数表示,要传入的 dm_reg 对象为 dm_reg.py 里的实例,字段的字符串表示也在 dm_reg.py 里描述。
其他 python 脚本基本就是具体控制平面功能的操作脚本,可以根据自己的需要来编写。
如无特殊说明,以下 debug module register 均可读。
DM (debug module,下略)地址:0x42
根据核心的物理下标进行选择,主要用于在设置与核心相关的项目如内存划分和可编程 hart id 前指定要设置的核心。单独设置没有意义,其用法将和具体的设置项目一起展示。
DM 地址:0x4e
根据 DSID 进行选择,主要用于与 DSID 相关的项目如令牌桶和 L2 cache way mask,用于在设置具体项目前指定 DSID 表项。单独设置没有意义,其用法将和具体的设置项目一起展示。
DM 地址:0x4f
设置方法:
riscv dmi_write 0x42 <core-index> # hart sel
riscv dmi_write 0x4f <hart-id>
在 bbl 和 riscv-linux 的 SMP 代码中,一旦核心的 hart id 不为 0,就会陷入自旋等待 0 号核心完成初始化后来唤醒它们。为了实现 NOHYPE,即片上核心物理隔离,就需要让软件读取到的 hart id 为 0.
目前通过专门的 CSR 来访问可编程 hart id,CSR 的地址为 0x810,定义在 src/main/scala/rocket/Instructions.scala -> object Instructions -> val fakeHartId
。可编程 hart id 默认值为 0,对应所有核心物理隔离的 NOHYPE 场景,通过控制平面对可编程 hart id 和内存划分进行设定,则可以灵活配置 SMP 和独立核心的分配。
在 riscv-pk 仓库下,在 machine/encoding.h
里定义 NOHYPE 宏,即可使用新的 CSR 来获取 hart id,从而使得软件可以支持 NOHYPE。
DM 地址:0x41
设置方法:
riscv dmi_write 0x42 <core-index> # hart sel
riscv dmi_write 0x41 <hart-dsid>
核心 DSID 用于在总线上识别请求所来自的核心,它占据总 DSID 的低位。如果不想区分核心,则全部设置成 0 即可。
在 TLBroadcast (src/main/scala/tilelink/Broadcast.scala)
里,曾经会根据 DSID 屏蔽不必要的一致性嗅探(DSID 相同的才嗅探)。目前已经修改成可编译期配置项目,如果需要这一功能,请在 config 里加入 new WithCOHonDSID
(定义在 src/main/scala/tilelink/Broadcast.scala
)
名称 | DM 地址 |
---|---|
base_lo | 0x44 |
base_hi | 0x45 |
mask_lo | 0x46 |
mask_hi | 0x47 |
Rocketchip 内部地址总线宽度为 40 bits,而 dm register 长度只有 32 bits,所以需要拆分成 low 和 high 两部分。
地址映射计算方法为:mapped_addr = (raw_addr & mask) | base
. 如果在 device tree 里准确描述了划分后的 memory 大小,那么地址基本不可能超过 mask,所以一般来说 mask 为 1 就行。
设置时,必须先设置低位段,此时会把数据写入临时寄存器,等到高位段也被写入时,才会一起写入到真正的映射用寄存器里。
设置方法:
# base
riscv dmi_write 0x42 <core-index> # select hart
riscv dmi_write 0x44 <base-lo>
riscv dmi_write 0x45 <base-hi>
# mask
riscv dmi_write 0x42 <core-index> # select hart
riscv dmi_write 0x46 <mask-lo>
riscv dmi_write 0x47 <mask-hi>
DM 地址:0x4d
way mask 是按照 DSID 进行索引的,为 1 的位表示对应下标的 way 可以用于替换。
设置方法:
riscv dmi_write 0x4e <dsid> # select dsid
riscv dmi_write 0x4c <waymask>
名称 | DM 地址 |
---|---|
freq | 0x48 |
size | 0x49 |
inc | 0x4a |
freq 表示执行一次 inc 所需要等待的周期数,inc 表示一次增加的令牌数,size 表示令牌桶最大容量。
在目前的实现里,基本上一次 acquire 请求消耗一个令牌,因为访存的 acquire 来自 cache,所以总是固定大小的。
设置方法:
# base
riscv dmi_write 0x4e <dsid> # select dsid
riscv dmi_write 0x48 <freq>
riscv dmi_write 0x49 <size>
riscv dmi_write 0x4a <inc>