[TOC]

项目复健

0.1 阅读README, 理解之前的开发思路

参考Commit

当时开启这个项目, 就是为了将我的代码从一周目 OSOC 的开发地狱中解救出来.
非常幸运的是, 我开了一个好头.

整个项目, 起码一进来的 README 还是很舒服的.
这个 README 详细规定了应该安装什么依赖, 如何在 Nvim 下展开开发控制.
更好的是, 我还规范了协作开发流程.

当时, 这个项目还承担了要做 NSCSCC 比赛Chisel方案的代码库, 自然也考虑到了协作开发流程.
可以之后重新写一下这个README, 将这个开发流程拓展到更广泛的场景中. [TODO]
不过, 目前我完全没有打算这么做, 只是避免在重接管项目的一开始就进入文档地狱, 耗尽能量.

更好的是, 我现在在 ./src/README.md 中有开发规范. 十分感谢当时的自己.

0.2 项目结构

项目荒废的时候, 是结构重构到一半的时候.
重构项目, 就是要摆脱 OSOC 一周目的屎.
尝试构建更有道理和逻辑的代码结构, 提高对项目的掌控力.

目前的代码框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
./src/main/
├── blackbox 新新架构可以保留
│   ├── fu
│   └── ip
├── bus 新新架构可保留
│   ├── apb
│   ├── axi4
│   ├── cacheBus
│   └── debugBus
├── config - 新架构. 这是一个纯 Scala 目录
├── core
│   ├── backend
│   │   ├── fu
│   │   └── unit
│   ├── cache
│   └── frontend
│   ├── fu
│   └── unit
├── defs - 新架构. 新架构要保留, 看README. 新新架构放到settings下.
├── device - 这个目录下是一些偷来的 device 代码. 新新架构保留
│   └── vga
├── isa 新新架构下在core类中.
│   ├── loongarch
│   └── riscv
├── module 旧架构在 core 下, 新架构被独立出来, 新新架构则重新回到core下.
│   ├── cache
│   │   └── chiplab
│   ├── fu
│   │   ├── basis
│   │   ├── loongarch
│   │   └── riscv
│   ├── ooo
│   └── tlb
├── settings 旧的架构. 新架构替代的目标(就不存在了), 也是新新架构中配置类的整体父类
├── top 旧架构, 新架构还是新新架构都有的
│   ├── chiplab
│   ├── sta
│   └── vivado
├── units - 这个目录用于存放流水线架构中每一个流水阶段模块的地址. 但是这个目录下错误的包含了Loongarch, 而不仅仅是RISCV
│   ├── loongarch - Remove me
│   └── riscv - 修改成, 不同架构为主的.
└── utils - 这个是OK的.
├── difftest - 修改, 这个difftest仅仅是RISCV64IM的.
└── fu - 可以用, 需要进一步读

43 directories

评价:
当时野心很大, 想要做一个”架构分离”的设计. 但是我觉得这个是很错误的选择.
老话, 所有都支持, 就相当于所有都不能做好.
更加重要的是, Loongarch和MIPS架构都有自己特殊的, 难以做架构分离的指令.
例如:

  • Loongarch有Cache相关的控制代码, 这个意味着Cache的重新设计.
  • MIPS 架构有分支延迟槽, 这个意味这BPU和IFU, 以及整个架构需要很大的更改.

ISA本来就是一个抽象层, 而强行想要在此之上再抽象, 实在是一个蠢到不行的想法.
欠考虑了.

所以需要框架调整:

  • 不再追求 Loongarch, MIPS 架构和 RISCV 架构的抽象, 而仅仅追求 RISCV64IM, RISCV32IM, RISCV32E 的抽象(本来手册就是这么追求的)
  • 在目录层面上, 不再追求过多的目录划分.

进一步的架构设计:

1
2
3
4
5
6
7
8
9
10
core 负责所有与 核心相关的代码. 
device 负责所有与外设与外设控制的代码
bus 负责所有总线设计的代码. 这个可能是需要考虑的. 因为bus中肯定涵盖了"只用在core中的, cachebus, debugbus之类的与core密切相关的"
settings 全局配置与设置相关. 分为几个部分: Config(可配置的纯Scala目录) defs(全局定义, 可以在代码中调用). 某种程度上, 新新架构对标旧架构的settings, 不过还是把settings中原本的def和config尽心了区分.
utils 工具类
top 根据对外架构进行分类, 用于包装之后进行交互.
blackbox 如题
core.isa 用来支持64IM, 32IM和32E的
core.units 不同微架构的阶段模块
core.module 不同微架构可以一定程度通用的模块

总体开发目标:

  • 建立一个支持选型拓展为M的core
  • 建立一个可以选型拓展为 RV64I, RV32I, RV32E.
  • M为拓展(ISA中应该也是这么规定的)
  • 在过去错误的”全ISA支持”开发想法的基础上, 进行全面的拨乱反正

0.3 后续开发RoadMap&Timeline

既然要“拨乱反正”,那我们就得拿出点“断舍离”的魄力来。 现在的代码库就像一个堆满了未拆封快递和旧报纸的房间,我们需要先清理,再收纳,最后才是装修。

Phase 1: The Great Purge (大清洗) - [T + 1 Day]

目标: 彻底移除所有非 RISC-V 的代码。不要心疼,旧的不去新的不来。

  • Delete LoongArch & MIPS Artifacts:
    • 移除 isa/loongarch & mips
    • 移除 config/LoongArch.scala & config/MIPSConfig.scala
    • 移除 defs/LoongArchDefs.scala & defs/MIPSDefs.scala
    • 移除 module/fu/loongarch & module/fu/mips (看着就头大, 删!)
    • 移除 units/loongarch & units/mips
  • Cleanup Top-Level:
    • 检查 top/chiplab。虽然 ChipLab 是个好平台,但如果它强绑定 LoongArch 接口,暂时先移除或注释掉,等核稳了再加回来。
  • Review Dependencies: 检查 build.mill 或 build.sbt,移除任何为了支持多架构而引入的奇怪依赖(如果有的话)。

Phase 2: The Great Migration (大迁徙) - [T + 2 Days]

目标: 收束目录结构,确立 core 的绝对核心地位。让 src/main/scala 下只保留真正的 Top-Level 概念。

  • Move to Core:
    • isa -> core/isa
    • units -> core/units
    • module -> core/module
  • Fix References: 这一步最痛苦, 移动文件夹后, 所有的 import 都会报错

Tip: 使用 IDE 的 Refactor Move 功能, 或者写个脚本批量替换 package isa -> package core.isa以及对应的 import。

重点检查 Bundles 和 Config 中对这些模块的引用.

Phase 3: Standardization (立规矩) - [T + 3 Days]

目标: 落实 settings vs config 的分治策略,确保参数化配置的逻辑闭环。

  • Config (Pure Scala):
    • 确保 config/RISCVConfig.scala 只包含 case class 和 enum
    • 定义 XLEN (32/64) 和 Extension (I/M/E) 的枚举
  • Settings (Hardware Defs):
    • 重构 settings. 将原本散落在 defs 中的硬件参数计算逻辑移入这里
    • 例如:根据 Config 中的 XLEN=64,在 Settings 中推导出 BussWidth=64 等硬件常量
  • Defs (Constants):
    • 保留 defs 作为真正的“常量池”(OpCodes, CSR 地址等),不应该包含动态计算逻辑

Phase 4: Feature Implementation (搞建设) - [T + 1 Week]

目标: 在干净的架构上,实现真正的 RV64I/RV32I/RV32E 选型支持。

  • Selectable ISA Logic:
    • core.isa 中实现一个 ISADecoder trait, 根据 Config 动态混入不同的解码逻辑
    • RV32E 主要是寄存器数量 (16 vs 32) 的区别, 在 RegFile 模块中加入参数化支持
  • Difftest Adaptation:
    • 修改 utils/difftest. 既然现在的目标是 RISC-V 全家桶, Difftest 也要能通过配置适配 32 位和 64 位模式
    • Task: 剥离 Difftest 中硬编码的 “64” 假设。

0.4 自我提醒 (Note to Self)

不要回头: 既然决定砍掉多架构支持,就不要在代码里留任何 if (isLoongArch) … 的尸体。看着心烦,还容易诱发“以后万一要加回来”的这种不切实际的幻想。

保持简单: core 就是 core, 不要在 core 外面搞什么 function_unit 这种看起来很厉害但实际上只会增加 import 长度的文件夹

文档同步: 改完代码记得回来更新 src/README.md. 现在的 README 写得很好, 别让它变成谎言.

0.5 拨乱反正日记

抉择

第一个做出的抉择是, 我要将 Mul 和 Div 分开.
Mul往往只用2-4个周期, 但是Div是20-60周期. 时间非常长.
这样的话, 我可以避免因为Div阻塞Mul.

第二个抉择是, 目前有两种打包方式:

  • core.module.fu + core.units
  • core.frontend + core.backend

经过抉择吧, 我认为第二种更加合适. 第二种是站在系统架构师视角来看的.
不过, Cache是有特殊地位的.
Cache 的底层存储逻辑(SRAM 封装, Tag 比较逻辑, 替换算法)是通用的, 但 Cache 的控制器行为是分前端/后端的

Cache 部分的最佳实践, 我认为是, 既然代码上面ICache和DCache是一样的, 那就放到core.cache中就好.
这样, 在其他领域需要用到Cache的时候, 我也可以直接使用Core.cache.RANDCache或者, Core.cache.LRUCache来实现.

1. 初步的调整

date: 2026-01-24

经过两天的调整, 我目前完成了Core核心代码的调整.
目前的大概架构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
core
+---frontend
| +---fu
| +---unit
+---backend
| +---fu
| +---unit
| +---rf
| +---ooo
+---mem
| +---cache
| +---mmu
| +---ram
+---isa
| +---csr
| +---instr
+---uarch
| +---fu
| +---branch
| +---interfaces

感受

跟着AI一起搞出来的新架构. 很难说是否合理.
但是总体感觉, 没有原来那么不可控了, 然后错误方向的代码也删了很多.
至少目前, 没有IDE报错了.

跟着AI一起维护和建立架构, 发现一些很重要的感受.
我认为, 体会这些感受, 会提升你是用AI的体验和效率.

Take away!

  1. 在自己犹豫不决的情况下, 频繁使用AI会导致自己无法沉淀出想法.
  2. 想不出来更好的东西的时候, 问问AI, 它会给你一些更好的视角.
  3. 当跟AI对话过多的时候, 一定要写Blog/文档, 跟自己对话.
    跟自己对话, 才能推动自己思考.
  4. 使用AI过多, 会磨灭自己思考带来的快乐与反馈, 仿佛自己抽离了整个项目.
    时间一长(对我而言就是半天时间), 人就会丧失动力.
  5. 使用AI, 往往是”跳跃一大步”, 但是只能”反复从同一个起点跳跃.”

还有一个感受, 就是当你决定使用一个外接键盘, 让屏幕距离自己较远时, 一定确保你的屏幕足够大. 否则会造成视觉上的痛苦!

如果灯光过亮或者过暗, 使用电脑都很痛苦.

下一个阶段的思考

我下一个需要搞定的就是, 配置相关的内容.

引入几个我跟AI探讨出来的好概念吧:

  1. 不要使用String进行配置
  2. 配置即代码, 生成即选择

同时, 我也认为我需要明确一个目录(包)的主要职责:
top

  • Top目录负责承接Boring出的代码.
  • Top负责面向不同的外部环境, 来维护对外接口, 总线转发等功能.

2. 完成了 Elaborate

date: 2026-01-26

喜报! 完成了全部重构. 但是代价是没有Difftest和其他的东西了. 很伤心.

接下来就是重新写Difftest模块. 看看新的版本会如何提高我的效率.

不过有个伤心的. 就是我花了很长时间才完成这些工作.
其实本来可以更早完成这些东西的, 如果我状态更好一些, 昨天就可以完成了.
伤心.

3. Sim环境开发

date: 2026-1-27

10点左右

昨晚.
Elab出了合理的Verilog, 肉眼检查是OK的.
也向其中添加了Difftest, 设计的也比较合理.

BoringUtils.tapAndRead 可谓是一个绝佳的设计, 完美实现了我心中”探针”的设想.
在使用这些工具的时候, 深刻感觉到, Chisel相关的东西, 在任何一个AI工具那里,
都没法发挥最大的价值.

可能是因为比较新(但是Chisel都快10年了),
也可能是因为语料中关于Chisel3时代的东西比较多,
反正, 我让它给我提供示范代码时,
它依然在提供AddSource这样已经被抛弃的代码.

永远不要忘了,
AI的输出不过是通解+特解. 永远不要期待它能处理缺乏训练的内容.

今天, 我的任务就是, 看着NEMU, 整一套类似的环境在NPC中, 然后运行起来.

13:30

必须得承认, 我之前在做一个小项目,
就是基于OSOC的架构, 剪出来一个小的, 基于Verilator的,
Verilog仿真框架.
而这个框架将会具有很好的拓展性, 并且在一定程度上, 修正NEMU中的混乱.

现在, 我认为自己有这个机会, 将这个荒废了两周的项目重启.

4. 基础设施已经构建, 在完善和添加, 以及对RTL的DEBUG

date: 2026-2-2 10:40

先来写一下最近的进度. 反正就是没有特别努力吧. 半摆的搞了很多零碎的.
目前可以正常使用Sim环境, 完成了所有的迁移. 在修正因为环境更改后的BUG.

最近有几个特别重要的体会:

  1. C/C++注册回调函数是个很好的策略.
    我通过注册回调函数, 在Sim环境assert/段错误的时候,
    可以调用函数, 刷新VCD缓存区, 关闭文件.
    这个操作解决了这样的问题:
    当程序跑飞自杀后, VCD文件会在最关键的最后几个波形上丢失,
    而每个周期都刷新, 则会造成20-40%的性能损失.

回调函数可以被大幅度的用到其他地方.
2. 当我们使用AI, 发现它无法在两次调整中实现功能,
就证明它在这方面的语料不足.
如果有任何跟具体的API相关的, 功能无法实现的BUG, 阅读手册可以帮我节省时间.
经验可知, 这类问题平均要消耗3h在AI上, 20m在手册上, 结果是手册解决了问题.
3. Chisel分为两种参数化, 一种是Trait混入, 另一种是Implict val.
Rockt, XiangShan都是用的第二种.
4. 新的”架构”往往在实践中产生.