向 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

浅谈乱序执行 CPU(二)

背景 之前写过一个浅谈乱序执行 CPU,随着学习的深入,内容越来越多,页面太长,因此把后面的一部分内容独立出来,变成了这篇博客文章。之后也许会有(三)(四)等等。 内存访问 内存访问是一个比较复杂的操作,它涉及到缓存、页表、内存序等问题。在乱序执行中,要尽量优化内存访问对其他指令的延迟的影响,同时也要保证正确性。这里参考的是 BOOM 的 LSU 设计。 首先是正确性。一般来说可以认为,Load 是没有副作用的(实际上,Load 会导致 Cache 加载数据,这也引发了以 Meltdown 为首的一系列漏洞),因此可以很激进地预测执行 Load。但是,Store 是有副作用的,写出去的数据就没法还原了。因此,Store 指令只有在 ROB Head 被 Commit 的时候,才会写入到 Cache 中。 其次是性能,我们希望 Load 指令可以尽快地完成,这样可以使得后续的计算指令可以尽快地开始进行。当 Load 指令的地址已经计算好的时候,就可以去取数据,这时候,首先要去 Store Queue 里面找,如果有 Store 指令要写入的地址等于 Load 的地址,说明后面的 Load 依赖于前面的 Store,如果 Store 的数据已经准备好了,就可以直接把数据转发过来,就不需要从 Cache 中获取,如果数据还没准备好,就需要等待这一条 Store 完成;如果没有找到匹配的 Store 指令,再从内存中取。不过,有一种情况就是,当 Store 指令的地址迟迟没有计算出来,而后面的 Load 已经提前从 Cache 中获取数据了,这时候就会出现错误,所以当 Store 计算出地址的时候,需要检查后面的 Load 指令是否出现地址重合,如果出现了,就要把这条 Load 以及依赖这条 Load 指令的其余指令重新执行。POWER8 处理器微架构论文中对此也有类似的表述: The POWER8 IFU also implements mechanisms to mitigate performance degradation associated with pipeline hazards.

Read More

用 sv2v+yosys 把 fpnew 转为 verilog 网表

背景 FPnew 是一个比较好用的浮点计算单元,但它是 SystemVerilog 编写的,并且用了很多高级特性,虽然闭源软件是支持的,但是开源拖拉机经常会遇到这样那样的问题。所以一直想用 sv2v 把它翻译成 Verilog,但此时的 Verilog 还有很多复杂的结构,再用 yosys 转换为一个通用可综合的网表。 步骤 经过一系列踩坑,一个很重要的点是要用最新的 sv2v(v0.0.9-24-gf868f06) 和 yosys(0.15+70)。Debian 打包的 yosys 版本比较老,不能满足需求。 首先,用 verilator 进行预处理,把一堆 sv 文件合成一个: $ cat a.sv b.sv c.sv > test.sv $ verilator -E test.sv > merged.sv $ sed -i '/^`line/d' merged.sv 注意这里用 sed 去掉了无用的行号信息。然后,用 sv2v 进行转换: $ sv2v merged.sv > merge.v $ sed -i '/\$$fatal/d' merge.v 这里又用 sed 把不支持的 $fatal 去掉。最后,用 yosys 进行处理: $ yosys -p 'read_verilog -defer merge.

Read More

Synopsys Design Compiler 综合实践

工艺库 综合很重要的一步是把 HDL 的逻辑变成一个个单元,这些单元加上连接方式就成为了网表。那么,基本单元有哪些,怎么决定用哪些基本单元? 这个就需要工艺库了,工艺库定义了一个个单元,单元的引脚、功能,还有各种参数,这样 Design Compiler 就可以按照这些信息去找到一个优化的网表。 Liberty 格式 网上可以找到一些 Liberty 格式的工艺库,比如 Nangate45,它的设定是 25 摄氏度,1.10 伏,属于 TT(Typical/Typical)的 Process Corner。 在里面可以看到一些基本单元的定理,比如 AND2_X1,就是一个 drive strength 是 1 的二输入与门: cell (AND2_X1) { drive_strength : 1; pin (A1) { direction : input; } pin (A2) { direction : input; } pin (ZN) { direction : output; function : "(A1 & A2)"; } /* ... */ } 这样就定义了两个输入 pin,一个输出 pin,还有它实现的功能。还有很重要的一点是保存了时序信息,比如: lu_table_template (Timing_7_7) { variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1 ("0.

Read More