[TOC]
项目复健
0.1 阅读README, 理解之前的开发思路
当时开启这个项目, 就是为了将我的代码从一周目 OSOC 的开发地狱中解救出来.
非常幸运的是, 我开了一个好头.
整个项目, 起码一进来的 README 还是很舒服的.
这个 README 详细规定了应该安装什么依赖, 如何在 Nvim 下展开开发控制.
更好的是, 我还规范了协作开发流程.
当时, 这个项目还承担了要做 NSCSCC 比赛Chisel方案的代码库, 自然也考虑到了协作开发流程.
可以之后重新写一下这个README, 将这个开发流程拓展到更广泛的场景中. [TODO]
不过, 目前我完全没有打算这么做, 只是避免在重接管项目的一开始就进入文档地狱, 耗尽能量.
更好的是, 我现在在 ./src/README.md 中有开发规范. 十分感谢当时的自己.
0.2 项目结构
项目荒废的时候, 是结构重构到一半的时候.
重构项目, 就是要摆脱 OSOC 一周目的屎.
尝试构建更有道理和逻辑的代码结构, 提高对项目的掌控力.
目前的代码框架
1 | ./src/main/ |
评价:
当时野心很大, 想要做一个”架构分离”的设计. 但是我觉得这个是很错误的选择.
老话, 所有都支持, 就相当于所有都不能做好.
更加重要的是, Loongarch和MIPS架构都有自己特殊的, 难以做架构分离的指令.
例如:
- Loongarch有Cache相关的控制代码, 这个意味着Cache的重新设计.
- MIPS 架构有分支延迟槽, 这个意味这BPU和IFU, 以及整个架构需要很大的更改.
ISA本来就是一个抽象层, 而强行想要在此之上再抽象, 实在是一个蠢到不行的想法.
欠考虑了.
所以需要框架调整:
- 不再追求 Loongarch, MIPS 架构和 RISCV 架构的抽象, 而仅仅追求 RISCV64IM, RISCV32IM, RISCV32E 的抽象(本来手册就是这么追求的)
- 在目录层面上, 不再追求过多的目录划分.
进一步的架构设计:
1 | core 负责所有与 核心相关的代码. |
总体开发目标:
- 建立一个支持选型拓展为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/isaunits -> core/unitsmodule -> 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 | core |
感受
跟着AI一起搞出来的新架构. 很难说是否合理.
但是总体感觉, 没有原来那么不可控了, 然后错误方向的代码也删了很多.
至少目前, 没有IDE报错了.
跟着AI一起维护和建立架构, 发现一些很重要的感受.
我认为, 体会这些感受, 会提升你是用AI的体验和效率.
Take away!
- 在自己犹豫不决的情况下, 频繁使用AI会导致自己无法沉淀出想法.
- 想不出来更好的东西的时候, 问问AI, 它会给你一些更好的视角.
- 当跟AI对话过多的时候, 一定要写Blog/文档, 跟自己对话.
跟自己对话, 才能推动自己思考. - 使用AI过多, 会磨灭自己思考带来的快乐与反馈, 仿佛自己抽离了整个项目.
时间一长(对我而言就是半天时间), 人就会丧失动力. - 使用AI, 往往是”跳跃一大步”, 但是只能”反复从同一个起点跳跃.”
还有一个感受, 就是当你决定使用一个外接键盘, 让屏幕距离自己较远时, 一定确保你的屏幕足够大. 否则会造成视觉上的痛苦!
如果灯光过亮或者过暗, 使用电脑都很痛苦.
下一个阶段的思考
我下一个需要搞定的就是, 配置相关的内容.
引入几个我跟AI探讨出来的好概念吧:
- 不要使用String进行配置
- 配置即代码, 生成即选择
同时, 我也认为我需要明确一个目录(包)的主要职责: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.
最近有几个特别重要的体会:
- C/C++注册回调函数是个很好的策略.
我通过注册回调函数, 在Sim环境assert/段错误的时候,
可以调用函数, 刷新VCD缓存区, 关闭文件.
这个操作解决了这样的问题:
当程序跑飞自杀后, VCD文件会在最关键的最后几个波形上丢失,
而每个周期都刷新, 则会造成20-40%的性能损失.
回调函数可以被大幅度的用到其他地方.
2. 当我们使用AI, 发现它无法在两次调整中实现功能,
就证明它在这方面的语料不足.
如果有任何跟具体的API相关的, 功能无法实现的BUG, 阅读手册可以帮我节省时间.
经验可知, 这类问题平均要消耗3h在AI上, 20m在手册上, 结果是手册解决了问题.
3. Chisel分为两种参数化, 一种是Trait混入, 另一种是Implict val.
Rockt, XiangShan都是用的第二种.
4. 新的”架构”往往在实践中产生.