Post

RISCV

RISCV

1. 香山工具记录

1.1 基础工具介绍

香山处理器Tutorial

香山开源处理器用户手册

  1. 香山处理器使用 DiffTest 协同仿真框架进行仿真验证:对于根据riscv手册的两种实现, 给定相同的正确程序, 它 们的状态变化应当一致,其中一种是我们的CPU,另一种模拟器就可以了
  2. lightSSS,它可以在仿真进程出错时自动保存出错点附近的波形和debug信息
  3. Nexus-am,生成workload
  4. NEMU是参考模型,辅助香山比较和验证微架构,而XiangShan
  5. ChiselDB来将结构化数据存储在数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
#1)generate workload
cd xs-env/nexus-am/apps/xxx
make ARCH=riscv64-xs -j48
#2)using NEMU running workload
cd xs-env/NEMU
make clean && make defconfig riscv64-xs_defconfig && make -j48
$NEMU_HOME/build/riscv64-nemu-interpreter -b xxx.bin
#3)在香山核仿真程序上仿真运行 workload
##这个命令会将 NEMU 模拟器编译成动态链接库,将会在 build 目录下生成文件 interpreter-so ,从而接入到香山仿真差分测试中
make clean && make defconfig riscv64-xs-ref_defconfig && make -j48
#running xiangshan && NEMU to difftest
cd xs-env/XiangShan
./build/emu -i xxx.bin

香山VCS 验证框架–但不支持LightSSS 等验证工具

性能评估验证—还没看

1.2 workload生成(nexus-am/apps代码阅读)

1.3 gdb调试

1
2
3
4
5
6
7
qemu-riscv64 -g 1234 ./main.elf
# 另开一个终端
riscv64-unknown-linux-gnu-gdb main.elf
(gdb) target remote :1234
(gdb) continue
# 程序崩溃后:
(gdb) bt

1.4 qemu运行

qemu run risc-v ubuntu

qemu-risc-v环境

accelr-net/tvm-riscv-demo: Demonstrator on running TVM on RISC-V with RN18 and KWS examples

1
2
3
4
5
6
7
8
#1.启动
qemu-system-riscv64 -cpu rv64,v=true -machine virt -nographic -m 8192 -smp 8 -bios opensbi/build/platform/generic/firmware/fw_jump.bin -kernel u-boot/u-boot -device virtio-net-device,netdev=eth0 -netdev user,id=eth0,hostfwd=tcp::2222-:22 -device virtio-rng-pci -drive file=./ubuntu.img,format=raw,if=virtio

#2.ssh
ssh ubuntu@localhost -p 2222
#3.关机
sudo shutdown -h now
#当你想要关闭 QEMU 时,可以在 QEMU 的窗口(非SSH链接)中按 `Ctrl+A`,然后 `X`

2. RVV标准向量扩展介绍

RVV 拥有独立的32个vector寄存器和7个CSRs寄存器,向量指令:设置向量CSR+访存+计算

rvv入门学习中文文档-很全

RVV的tail agnostic,mask agnostic和vector masking

不可知(agnostic)意味着这部分元素不应成为源操作数
RVV规范也充分考虑了不同实现中如何处理非活跃元素(inactive,tail)更方便高效。
对非重命名向量寄存器的处理器来说,给非active元素应用undisturbed策略最经济。因为这类处理器往往VLEN超长,一次仅处理数个元素,跳过非活跃元素可以加速运算,而写1就无法加速运算。因此非重命名架构直接用undisturbed处理更简单。
而重命名架构VLEN较短,通过检查mask和vl,跳过个别元素的收益并不大。在重命名架构中,每个uop读写的寄存器都是经过重命名映射的物理寄存器,对于同一个vd,读和写分别是不同的物理寄存器。于是undisturbed模式就额外多了一个源操作数,发射队列需要多存一份旧vd对应的物理寄存器,也会给物理寄存器堆增加一个旧vd的读取需求,这样会增加物理实现开销。
所以在重命名架构中,解除指令对旧vd的数据依赖对设计是有优化,按写1实现agnostic更有利于性能。

香山(昆明湖架构)向量扩展的设计和实现

陆旭凡-RVV学习

RISC-V Vector寄存器图解

RISC-V “V” Vector Extension Version 1.0 -知乎

2.1 利用rvv指令的方式

编译器支持的话最好,不支持的话,目前intrinsics 函数编程是最容易的

2.x 矢量加载指令的地址模式

矢量元素个数为VLEN/EEW*EMUL

  • unit-stride:矢量中每个元素在内存的地址空间中是连续
  • stride:矢量中元素在内存的地址空间是不连续的,并且每个相邻元素的间距(stride)为固定的
  • indexed:矢量中元素在内存的地址空间是不连续的,同时每个相邻元素的间距是不固定的
  • indexed地址模式又可以细分为保序(ordered)和非保序(unordered)两种,而unit-stride以及strided均为非保序的。保序要求每个元素的访存行为按照其索引大小的顺序发生
1
2
3
4
5
6
7
8
9
10
#1)unit-stride:  vd 为目的矢量寄存器, rs1 为内存基地址, vm为掩码(v0.t or <missing>)
vle8.v vd, (rs1), vm # EEW = 8b

#2)strided: ,vd 为目的矢量寄存器, rs1 为内存基地址, rs2 为stride
vlse8.v vd, (rs1), rs2, vm # EEW = 8b

#3)indexed: vd 为目的矢量寄存器, rs1 为内存机地址, vs2 为offset

vluxei8.v vd, (rs1), vs2, vm # unordered, EEW = 8b
vloxei8.v vd, (rs1), vs2, vm # ordered, EEW = 8b
  • Minimum Vector Length Standard Extensions(Minimum VLEN 32~1024):用以拓展向量扩展的最小向量长度,有Zvl32b~Zvl1024b六种
  • zve扩展(Vector Extensions for Embedded Processors)

zve

2.3 汇编指令

以下都是根据xiangshan的nexus-am/apps中的代码经过GPT解读后生成的内容解释:

  • rvv中的向量load/store如下

1. vle8.v v0, (rs1)

  • 语义:把内存中以字节(8 bit)为单位的连续元素,逐一装入一个向量寄存器(v0)。

  • 执行过程

    1. 根据当前的 vl(Vector Length)值,一共加载 vl 个元素。

    2. 从地址 rs1 + 0*1rs1 + 1*1、…、rs1 + (vl−1)*1 依次取出 8 bit,每个元素零扩展(或符号扩展,取决于指令变体)到当前 SEW 宽度后写入 v0[i]


2. vlm.v v0, (rs1)

  • 语义:按当前 LMUL 配置,将内存中连续数据“一组一组”装入从 v0 开始的一个寄存器组。

  • 执行过程

    1. 计算寄存器组宽度:若 LMUL = k,则寄存器组包含 {v0,…,v(k−1)}

    2. 对每个向量寄存器 vj,按 vl 个元素从内存中连续读取 SEW 位宽的数据,依次填满 vj[0…vl−1]


3. vlse8.v v0, (rs1), stride

  • 语义:以字节为单位、指定步长,从内存加载散步长元素到 v0

  • 执行过程

    1. 根据 vl,一共加载 vl 个元素。

    2. 对第 i 个元素,从地址 rs1 + i*stride 处取 8 bit,扩展到 SEW 后写入 v0[i]


4. vloxei8.v v0, (rs1), v1

  • 语义:按索引向量 v1 中的每个字节偏移,从内存加载带散列表的元素到 v0

  • 执行过程

    1. v1[i] 给出第 i 个元素的字节偏移量;

    2. 从地址 rs1 + v1[i] 取 8 bit,扩展后写入 v0[i]


5. vle8ff.v v0, (rs1)

  • 语义:Fault-Only-First 变体的 vle8.v,只在第一次访问错时(i=0)发生异常,其后正常加载。

  • 执行过程

    1. 访问第 0 个元素:从 rs1+0 取 8 bit;若发生页错/越界,令 vl 更新为已成功加载的元素数(0 或更小),停止后续访问;

    2. 若未错或第一次成功,继续像 vle8.v 一样加载剩余元素。


6. vlseg2e8.v v0, (rs1)

  • 语义:按结构化段(unit-stride segment)加载,每次从内存连续取出 2 个 8 bit 字节,分别写入 v0v1

  • 执行过程

    1. 对第 i 段,从 rs1 + 2*i + 0rs1 + 2*i + 1 各取 8 bit;

    2. 写入寄存器组:v0[i] ← 第 0 个字节,v1[i] ← 第 1 个字节,共加载 vl 段。


7. vlseg2e8ff.v v0, (rs1)

  • 语义:Fault-Only-First 变体的 vlseg2e8.v,只在第一段访问错时生效。

  • 执行过程

    1. 对段 0 执行两次 8 bit 访问,若任一次失败,则根据已加载段数更新 vl,停止后续。

    2. 否则像 vlseg2e8.v 继续加载余下段。


8. vlsseg2e8.v v0, (rs1), stride

  • 语义:带步长的结构化段加载,每段内元素间隔 stride 字节。

  • 执行过程

    1. 对段 i:第 0 元素地址 rs1 + i*stride + 0,第 1 元素地址 rs1 + i*stride + 1

    2. 分别取 8 bit 写入 v0[i]v1[i],共 vl 段。


9. vloxseg2ei8.v v4, (rs1), v1

  • 语义:按索引向量 v1 加载结构化段,每段两元素,分别写入 v4v5

  • 执行过程

    1. 对第 i 段,偏移量 v1[2*i + 0] 给出第 0 元素地址,v1[2*i + 1] 给出第 1 元素地址;

    2. 各自加载 8 bit,并写入 v4[i]v5[i]


10. vl1re8.v v0, (rs1)

  • 语义:“whole load” 变体,按块大小一次性从内存加载一整组元素到 v0

  • 执行过程

    1. vl 个 8 bit 元素聚成一个连续块;

    2. rs1 开始一次性取出 vl 字节,分别写入 v0[0…vl−1]


11. vl4re8.v v0, (rs1)

  • 语义:同 vl1re8.v,但一次按 4×vl 字节对齐加载(跨 4 个连续向量寄存器)。

  • 执行过程

    1. 计算四倍元素块大小(4*vl);

    2. 从对齐地址 rs1 一次性读取该块,填充 v0…v3


12. vse8.v v0, (rs1)

  • 语义:与 vle8.v 对应的按元素存储,将 v0 中每个 8 bit 元素依次写到内存。

  • 执行过程

    1. 对第 i 个元素,将 v0[i] 的低 8 bit 写到地址 rs1 + i*1

    2. 共写 vl 次。


13. vsm.v v0, (rs1)

  • 语义:按掩码存储,多寄存器组存储,与 vlm.v 对应。

  • 执行过程

    1. 计算寄存器组 {v0…v(k−1)}

    2. 按掩码位(默认为全真)对每个寄存器依次写 vl 个元素到内存。


14. vsse8.v v0, (rs1), stride

  • 语义:带步长的按元素存储,将 v0[i] 写到 rs1 + i*stride

  • 执行过程

    1. 对第 i 个元素,写 8 bit 到地址 rs1 + i*stride

    2. 重复 vl 次。


15. vsoxei8.v v0, (rs1), v1

  • 语义:按索引向量 v1 存储,将 v0[i] 写到 rs1 + v1[i]

  • 执行过程

    1. 对第 i 个元素,计算目标地址 rs1 + v1[i]

    2. 写出该元素的低 8 bit。


16. vsseg2e8.v v0, (rs1)

  • 语义:单段结构化存储,将寄存器组 {v0,v1}vl 段分别写回内存连续两字节。

  • 执行过程

    1. 对第 i 段,写 v0[i]rs1+2*iv1[i]rs1+2*i+1

    2. 共写 vl 段。


17. vssseg2e8.v v0, (rs1), stride

  • 语义:带步长的分段存储,将 {v0,v1} 写到 rs1 + i*stride + {0,1}

  • 执行过程

    1. 对段 i,写 v0[i]rs1+i*stridev1[i]rs1+i*stride+1

    2. vl 段。


18. vsoxseg2ei8.v v4, (rs1), v1

  • 语义:按索引向量 v1 的分段存储,将两寄存器组的元素写回。

  • 执行过程

    1. 对段 i,偏移量由 v1[2*i+0]v1[2*i+1] 给出;

    2. v4[i]v5[i] 至对应地址。


19. vs1r.v v0, (rs1)

  • 语义:Whole-store 变体,将 vl 个元素一次性写到内存连续区域。

  • 执行过程

    1. v0[0…vl−1] 收集 vl 字节;

    2. 一次性写到 rs1 开始的连续地址。


20. vs4r.v v0, (rs1)

  • 语义:与 vs1r.v 对应,但一次对齐 4×vl 字节跨 4 寄存器写出。

  • 执行过程

    1. 聚合 v0…v3vl 元素,形成 4×vl 字节块;

    2. 一次性写到 rs1


  • rva原子与 LR/SC 指令如下:

1. lr.d t0, (rs1)

  • 语义:Load-Reserved 双字(64-bit)——在地址 rs1 上设置保留(reservation),并把该地址处的 64 位值载入寄存器 t0

  • 执行过程

    1. 在内部为 rs1 对应的内存行/缓存行打保留标记;

    2. 从地址 rs1 读取 64 位数据,写入 t0

    3. 保留状态用于后续的 sc.d 判断。

2. sc.d t0, t1, (rs1)

  • 语义:Store-Conditional 双字(64-bit)——仅当先前对同一地址有未被中断的保留,才把 t1 写回内存,并在 t0 中写入 0 表示成功;否则不写内存,在 t0 中写入非零表示失败。

  • 执行过程

    1. 检查针对地址 rs1 的保留标记是否依旧有效(无其他写入打断);

    2. 若有效,执行写入:M[rs1] ← t1,并 t0 ← 0

    3. 若无效,不写内存,仅 t0 ← 1(或其它非零失败码);

    4. 清除保留状态。

3. amoswap.d t1, t0, (rs1)

  • 语义:原子交换双字——将 rs1 处的旧值写入 t1,再把 t0 写回 rs1,整个操作不可被中断。

  • 执行过程

    1. 读出内存 tmp ← M[rs1]

    2. 写回寄存器:t1 ← tmp

    3. 原子写内存:M[rs1] ← t0

4. amoadd.d t1, t0, (rs1)

  • 语义:原子加双字——将 t0 与内存 rs1 处的旧值相加,结果写回内存;旧值写入 t1

  • 执行过程

    1. 读出:old ← M[rs1]

    2. 写寄存器:t1 ← old

    3. 计算与写回:M[rs1] ← old + t0

5. amoand.d t1, t0, (rs1)

  • 语义:原子与双字——把 t0 与内存旧值按位与,结果写回内存,旧值写入 t1

  • 执行过程

    1. old ← M[rs1]

    2. t1 ← old

    3. M[rs1] ← old & t0

6. amomax.d t1, t0, (rs1)

  • 语义:原子求带符号最大双字——比较 old(内存原值)和 t0,将二者中的较大者写回内存,旧值写入 t1

  • 执行过程

    1. old ← M[rs1]

    2. t1 ← old

    3. 比较(带符号):若 old ≥ t0M[rs1] ← old,否则 M[rs1] ← t0

7. amominu.d t1, t0, (rs1)

  • 语义:原子求无符号最小双字——以无符号方式比较 oldt0,将最小者写回内存,旧值写入 t1

  • 执行过程

    1. old ← M[rs1]

    2. t1 ← old

    3. 比较(无符号):若 old ≤ t0M[rs1] ← old,否则 M[rs1] ← t0

This post is licensed under CC BY 4.0 by the author.