Skip to content

lvna usage emu

Wonicon edited this page Dec 13, 2020 · 2 revisions

LvNA 使用文档(软件环境部分)

环境准备工作

操作系统: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

riscv-linux 镜像

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 即可。

NOHYPE 模式

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.scalaTestHarness 类下,在符号 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 启动日志。

通过 OPENOCD 访问标签化设施

目前我们通过扩展 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_readdmi_write 是对 dmi 读写命令的简单封装。dmi_read_fieldsdmi_write_fields 用于读写 dm 寄存器的具体字段,待写入的字段用 python 的关键字参数表示,要传入的 dm_reg 对象为 dm_reg.py 里的实例,字段的字符串表示也在 dm_reg.py 里描述。

其他 python 脚本基本就是具体控制平面功能的操作脚本,可以根据自己的需要来编写。

控制平面功能

如无特殊说明,以下 debug module register 均可读。

核心选择 HART_SEL

DM (debug module,下略)地址:0x42

根据核心的物理下标进行选择,主要用于在设置与核心相关的项目如内存划分和可编程 hart id 前指定要设置的核心。单独设置没有意义,其用法将和具体的设置项目一起展示。

DSID 选择 DSID_SEL

DM 地址:0x4e

根据 DSID 进行选择,主要用于与 DSID 相关的项目如令牌桶和 L2 cache way mask,用于在设置具体项目前指定 DSID 表项。单独设置没有意义,其用法将和具体的设置项目一起展示。

可编程 HART ID

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。

核心 DSID(标签)

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>

L2 Cache 路划分

DM 地址:0x4d

way mask 是按照 DSID 进行索引的,为 1 的位表示对应下标的 way 可以用于替换。

设置方法:

riscv dmi_write 0x4e <dsid> # select dsid
riscv dmi_write 0x4c <waymask>

令牌桶 L1 流量调节

名称 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>