在 libvirt 中运行 RISC-V 虚拟机

背景 我在 libvirt 中跑了几个 KVM 加速的虚拟机,然后突发奇想,既然 libvirt 背后是 qemu,然后 qemu 是支持跨指令集的,那是否可以让 libvirt 来运行 RISC-V 架构的虚拟机?经过一番搜索,发现可以跑 ARM:How To: Running Fedora-ARM under QEMU,既然如此,我们也可以试试用 libvirt 来运行 RV64 虚拟机。 准备 rootfs 第一步是根据 Debian 的文档 Creating a riscv64 chroot 来创建 rootfs,然后再用 virt-make-fs 来打包。 首先是用 mmdebstrap 来生成一个 chroot: $ sudo mkdir -p /tmp/riscv64-chroot $ sudo apt install mmdebstrap qemu-user-static binfmt-support debian-ports-archive-keyring $ sudo mmdebstrap --architectures=riscv64 --include="debian-ports-archive-keyring" sid /tmp/riscv64-chroot "deb http://deb.debian.org/debian-ports sid main" "deb http://deb.debian.org/debian-ports unreleased main" 进入 chroot 以后,进行一些配置:

Read More

「教学」异步 SRAM 时序

背景 在一些场合里,我们会使用异步的(即没有时钟信号的)外部 SRAM 来存储数据,而我们经常使用的很多外部接口都是同步接口(即有时钟信号的接口),比如 SPI 和 I2C 等等,UART 虽然是异步,但是它速度很低,不怎么需要考虑时序的问题。所以在 FPGA 上编写一个正确的异步 SRAM 控制器是具有一定的挑战的。 寄存器时序 考虑到读者可能已经不记得寄存器的时序了,这里首先来复习一下 setup 和 hold 的概念。如果你已经比较熟悉了,可以直接阅读下一节。 寄存器在时钟的上升沿(下图的 a)进行采样,为了保证采样的稳定性,输入引脚 D 需要在时钟上升沿之前 \(t_{su}\) 的时刻(下图的 b)到时钟上升沿之后 \(t_h\) 的时刻(下图的 c)保持稳定,输出引脚 Q 会在时钟上升沿之后 \(t_{cko}\) 的时刻(下图的 d)变化: 接口 首先我们来看看异步 SRAM 的接口。下文中,采用 IS61WV102416BLL-10TLI 和 AS7C34098A-10TCN 作为例子: 可以看到,它有 20 位的地址,16 位的数据,若干个控制信号,同时只能进行读或者写(简称 1RW)。它没有时钟信号,所以是异步 SRAM。 时序 对于一个同步接口,我们通常只需要给一个满足时钟周期的时钟,然后通过约束文件保证 setup 和 hold 条件满足即可。但是对于异步接口,由于输出的时候没有时钟,我们需要更小心地完成这件事情。 读时序 首先来看一下比较简单的读时序: 可以看到地址和数据的关系:首先是地址需要稳定 \(t_{RC}\) 的时间,那么数据合法的范围是地址稳定的初始时刻加上 \(t_{AA}\),到地址稳定的结束时刻加上 \(t_{OH}\)。我们再来看一下这几个时间的范围: 首先可以看到读周期时间 \(t_{RC}\) 至少是 10ns,这对应了型号中最后的数字,这表示了这个 SRAM 最快的读写速度。比较有意思的是 \(t_{AA}\) 最多是 10ns,刚好和 \(t_{RC}\) 的最小值相等。

Read More

「教学」ACE 缓存一致性协议

背景 最近几天分析了 TileLink 的缓存一致性协议部分内容,见TileLink 总线协议分析,趁此机会研究一下之前尝试过研究,但是因为缺少一些基础知识而弃坑的 ACE 协议分析。 下面主要参考了 IHI0022E 的版本,也就是 AXI4 对应的 ACE 版本。 回顾 首先回顾一下一个缓存一致性协议需要支持哪些操作。对于较上一级 Cache 来说,它需要这么几件事情: 读或写 miss 的时候,需要请求这个缓存行的数据,并且更新自己的状态,比如读取到 Shared,写入到 Modified 等。 写入一个 valid && !dirty 的缓存行的时候,需要升级自己的状态,比如从 Shared 到 Modified。 需要 evict 一个 valid && dirty 的缓存行的时候,需要把 dirty 数据写回,并且降级自己的状态,比如 Modified -> Shared/Invalid。如果需要 evict 一个 valid && !dirty 的缓存行,可以选择通知,也可以选择不通知下一级。 收到 snoop 请求的时候,需要返回当前的缓存数据,并且更新状态。 需要一个方法来通知下一级 Cache/Interconnect,告诉它第一和第二步完成了。 如果之前看过我的 TileLink 分析,那么上面的这些操作对应到 TileLink 就是: 读或写 miss 的时候,需要请求这个缓存行的数据(发送 AcquireBlock,等待 GrantData),并且更新自己的状态,比如读取到 Shared,写入到 Modified 等。 写入一个 valid && !

Read More

向 Rocket Chip 添加自定义调试信号

背景 最近在尝试把核心作为一个 Tile 加到 Rocket System 中,所以想要把核心之前自定义的调试信号接到顶层上去。Rocket System 自带的支持是 trace,也就是输出每个周期 retire 的指令信息,但和自定义的不大一样,所以研究了一下怎么添加自定义的调试信号,并且连接到顶层。 分析 Trace 信号连接方式 首先,观察 Rocket Chip 自己使用的 Trace 信号是如何连接到顶层的。在顶层上,可以找到使用的是 testchipip.CanHaveTraceIO: trait CanHaveTraceIO { this: HasTiles => val module: CanHaveTraceIOModuleImp // Bind all the trace nodes to a BB; we'll use this to generate the IO in the imp val traceNexus = BundleBridgeNexusNode[Vec[TracedInstruction]]() val tileTraceNodes = tiles.flatMap { case ext_tile: WithExtendedTraceport => None case tile => Some(tile) }.map { _.

Read More

「教学」内存认证算法

背景 之前 @松 给我讲过一些内存认证(Memory Authentication)算法的内容,受益匪浅,刚好今天某硬件群里又讨论到了这个话题,于是趁此机会再学习和整理一下相关的知识。 内存认证计算的背景是可信计算,比如要做一些涉及重要数据的处理,从软件上,希望即使系统被攻击非法进入了,也可以保证重要信息不会泄漏;从硬件上,希望即使系统可以被攻击者进行一些物理的操作(比如导出或者修改内存等等),也可以保证攻击者无法读取或者篡改数据。 下面的内容主要参考了 Hardware Mechanisms for Memory Authentication: A Survey of Existing Techniques and Engines 这篇 2009 年的文章。 威胁模型 作为一个防御机制,首先要确定攻击方的能力。一个常见的威胁模型是认为,攻击者具有物理的控制,可以任意操控内存中的数据,但是无法读取或者修改 CPU 内部的数据。也就是说,只有 CPU 芯片内的数据是可信的,离开了芯片都是攻击者掌控的范围。一个简单的想法是让内存中保存的数据是加密的,那么怎样攻击者可以如何攻击加密的数据?下面是几个典型的攻击方法: Spoofing attack:把内存数据改成任意攻击者控制的数据;这种攻击可以通过签名来解决 Splicing or relocation attack:把某一段内存数据挪到另一部分,这样数据的签名依然是正确的;所以计算签名时需要把地址考虑进来,这样地址变了,验证签名就会失败 Replay attack:如果同一个地址的内存发生了改变,攻击者可以把旧的内存数据再写进去,这样签名和地址都是正确的;为了防止重放攻击,还需要引入计数器或者随机 nonce Authentication Primitives 为了防御上面几种攻击方法,上面提到的文章里提到了如下的思路: 一是 Hash Function,把内存分为很多个块,每一块计算一个密码学 Hash 保存在片内,那么读取数据的时候,把整块数据读取进来,计算一次 Hash,和片内保存的结果进行比对;写入数据的时候,重新计算一次修改后数据的 Hash,更新到片内的存储。这个方法的缺点是没有加密,攻击者可以看到内容,只不过一修改就会被 CPU 发现(除非 Hash 冲突),并且存储代价很大:比如 512-bit 的块,每一块计算一个 128-bit 的 Hash,那就浪费了 25% 的空间,而片内空间是十分宝贵的。 二是 MAC Function,也就是密码学的消息验证码,它需要一个 Key,保存在片内;由于攻击者不知道密码,根据 MAC 的性质,攻击者无法篡改数据,也无法伪造 MAC,所以可以直接把计算出来的 MAC 也保存到内存里。为了防御重放攻击,需要引入随机的 nonce,并且把 nonce 保存在片内,比如每 512-bit 的数据,保存 64-bit 的 nonce,这样片内需要保存 12.

Read More

TileLink 总线协议分析

背景 最近在研究一些支持缓存一致性的缓存的实现,比如 rocket-chip 的实现和 sifive 的实现,因此需要研究一些 TileLink 协议。本文讨论的时候默认读者具有一定的 AXI 知识,因此很多内容会直接参考 AXI。 信号 根据 TileLink Spec 1.8.0,TileLink 分为以下三种: TL-UL: 只支持读写,不支持 burst,类比 AXI-Lite TL-UH:支持读写,原子指令,预取,支持 burst,类比 AXI+ATOP(AXI5 引入的原子操作) TL-C:在 TL-UH 基础上支持缓存一致性协议,类比 AXI+ACE/CHI TileLink Uncached TileLink Uncached(TL-UL 和 TL-UH)包括了两个 channel: A channel: M->S 发送请求,类比 AXI 的 AR/AW/W D channel: S->M 发送响应,类比 AXI 的 R/W 因此 TileLink 每个周期只能发送读或者写的请求,而 AXI 可以同时在 AR 和 AW channel 上发送请求。 一些请求的例子: 读:M->S 在 A channel 上发送 Get,S->M 在 D channel 上发送 AccessAckData 写:M->S 在 A channel 上发送 PutFullData/PutPartialData,S->M 在 D channel 是发送 AccessAck 原子操作:M->S 在 A channel 上发送 ArithmeticData/LogicalData,S->M 在 D channel 上发送 AccessAckData 预取操作:M->S 在 A channel 上发送 Intent,S->M 在 D channel 上发送 AccessAck AXI4ToTL 针对 AXI4ToTL 模块的例子,来分析一下如何把一个 AXI4 Master 转换为 TileLink。

Read More

NUC11 ESXi 中 iGPU 直通虚拟机

背景 之前在 NUC11PAKi5 上装了 ESXI 加几个虚拟机系统,但是自带的 iGPU Intel Iris Xe Graphics(Tiger Lake GT-2) 没用上,感觉有些浪费。因此想要给 Windows 直通。在直通到 Windows 后发现会无限重启,最后直通到 Linux 中。 步骤 第一步是到 esxi 的设备设置的地方,把 iGPU 的 Passthrough 打开,这时候会提示需要重启,但是如果重启,会发现还是处于 Needs reboot 状态。网上进行搜索,发现是 ESXi 自己占用了 iGPU 的输出,解决方法如下: $ esxcli system settings kernel set -s vga -v FALSE 这样设置以后就不会在显卡输出上显示 dcui 了,这是一个比较大的缺点,但是平时也不用自带的显示输出,就无所谓了。 第二步,重启以后,这时候看设备状态就是 Active。回到 Windows 虚拟机,添加 PCI device,然后启动。这时候,我遇到了这样的错误: Module ‘DevicePowerOn’ power on failed Failed to register the device pciPassthru0 搜索了一番,解决方法是关掉 IOMMU。在虚拟机设计中关掉 IOMMU,就可以正常启动了。 第三步,进入 Windows,这时候就可以看到有一个新的未知设备了,VID=8086,PID=9a49;等待一段时间,Windows 自动安装好了驱动,就可以正常识别了。GPU-Z 中可以看到效果如下:

Read More

LoongArch64 工具链构建

最近因为龙芯杯的原因,想自己搞个 LoongArch64 的交叉编译工具链试试,结果遇到了很多坑,最后终于算是搞出来了。 一开始是想搞一个 newlib 的工具链,比较简单,而且之前做过一个仓库:jiegec/riscv-toolchain,就是构建的 riscv64-unknown-elf 工具链,照着 riscv-gnu-toolchain 就可以了。不过研究发现,newlib 还不支持 loongarch,目前只有 glibc 支持,只好硬着头皮上了。 于是我就在 riscv-toolchain 的基础上搞 loongarch64-unknown-linux-gnu,也就是带 glibc 的工具链,结果发现遇到很多坑。首先编译 libgcc 的时候就找不到头文件,于是先要从 glibc 和 linux 安装头文件到 sysroot 里面,对于 sysroot 里面的头文件路径到底是 include 还是 usr/include 也折腾了半天。然后编译 libgcc 又各种出问题,最后折腾了半天,结果是 gcc stage1 和 glibc 都没问题,gcc stage2 会报链接错误,但是不管它也能用,可以编译出正常的程序,毕竟 libc 是好的。 于是转念一想,要不要试试 crosstool-ng。克隆了一份上游的版本,照着 riscv 的部分抄了一份变成了 loongarch,然后把 config 里面的 linux/glibc/gcc/binutils-gdb 都替换为 custom location,这样我就可以用上游的最新版本了。中途还遇到了 crosstool-ng 对 gcc 12/13 不兼容的 bug,还好下面有人提出了解决方法。这些都搞定以后,终于构建出了一个完整的 loongarch64-unknown-linux-gnu 工具链。仓库地址是 jiegec/ct-ng-loongarch64,需要配合添加了 LoongArch 的 jiegec/crosstool-ng loongarch 分支 使用。

Read More

试用沁恒 CH32V307 评估板

背景 之前有一天看到朋友在捣鼓 CH32V307,因此自己也萌生了试用 CH32V307 评估板的兴趣,于是在沁恒官网申请样品,很快就接到电话了解情况,几天后就顺丰送到了,不过因为疫情原因直到现在才拿到手上,只能说疫情期间说不定货比人还快。 开箱 收到的盒子里有一个 CH32V307 评估板,和一个 WCH-Link,相关资料可以在 官网 或者 openwch/ch32v307 下载。在说明书中有如下的图示: 板子自带的跳线帽不是很多,建议自备一些,或者用杜邦线替代。比较重要的是 WCH-Link 子板上 CH549 和 CH2V307 连接的几个信号,和下面 BOOT0/1 的选择。 WCH-Link 可以看到评估板自带了一个 WCH-Link,所以不需要附赠的那一个,直接把 11 号 Type-C 连接到电脑上即可。这里还遇到一个小插曲,用 Type-C to Type-C 的线连电脑上不工作,连 PWR LED 都点不亮,换一根 Type-A to Type-C 的就可以,没有继续研究是什么原因。电脑上可以看到 WCH-Link 的设备:VID=1a86, PID=8010。比较有意思的是,在 RISC-V 模式(CON 灯不亮)的时候 PID 是 8010,ARM 模式(CON 灯亮)的时候 PID 是 8011,从 RISC-V 模式切换到 ARM 模式的方法是连接 TX 和 GND 后上电,反过来要用 MounRiver,详见 WCH-Link 使用说明 V1.0 V1.3 和原理图 V1.1。

Read More

导出 Vivado 下载 Bitstream 的 SVF 文件

背景 最近在研究如何实现一个远程 JTAG 的功能,目前实现在 jiegec/jtag-remote-server,实现了简单的 XVC 协议,底层用的是 libftdi 的 MPSSE 协议来操作 JTAG。但是,在用 Vivado 尝试的时候,SysMon 可以正常使用,但是下载 Bitstream 会失败,所以要研究一下 Vivado 都做了什么(目前已经修好,是最后一个字节的部分位读取的处理问题)。 SVF SVF 格式其实是一系列的 JTAG 上的操作。想到这个,也是因为在网上搜到了一个 dcfeb_v45.svf,里面描述的就是一段 JTAG 操作: // Created using Xilinx Cse Software [ISE - 12.4] // Date: Mon May 09 11:00:32 2011 TRST OFF; ENDIR IDLE; ENDDR IDLE; STATE RESET; STATE IDLE; FREQUENCY 1E6 HZ; //Operation: Program -p 0 -dataWidth 16 -rs1 NONE -rs0 NONE -bpionly -e -loadfpga TIR 0 ; HIR 0 ; TDR 0 ; HDR 0 ; TIR 0 ; HIR 0 ; HDR 0 ; TDR 0 ; //Loading device with 'idcode' instruction.

Read More