前言
本文是对一个自行设计的 LoongArch32 (LA32) 指令集 CPU 的架构总结。该 CPU 用于龙芯杯(NSCSCC)竞赛,在 Verilator 仿真环境下主频达到 75MHz,已通过全部 58 个功能测试点。与上一篇文章聚焦于 I-Cache 一致性和 RAW 冒险的调试不同,本文从架构设计的角度,用 STAR 框架系统性地梳理整个 CPU 的设计决策、模块划分和技术选型。
Situation(背景)
项目目标
设计一个完整支持 LoongArch32 精简指令集的流水线 CPU,要求:
- 支持 LA32R 全部指令(算术逻辑、访存、分支、特权、CSR、TLB、原子等)
- 支持虚拟内存(页表映射、TLB 管理)
- 支持 Cache(I-Cache / D-Cache,含 cacop 维护指令)
- 支持异常和中断处理(11 种异常 + 外部中断)
- 通过 Chiplab 仿真环境的 NEMU difftest 验证
- 在 Verilator 仿真下达到 75MHz 主频
LoongArch32 指令集特点
LoongArch32 是龙芯自主设计的 RISC 指令集,主要特点:
- 32 个通用寄存器($r0 恒为 0)
- 定长 32-bit 指令编码
- 延迟槽分支(B/BL 后有一条延迟槽指令)
- 特权等级:PLV0(内核态)/ PLV3(用户态)
- 软件管理 TLB(TLBSRCH / TLBRD / TLBWR / TLBFILL / INVTLB)
- 直接映射窗口(DMW0/DMW1)支持无页表直访
- CSR 寄存器通过 csrrd / csrwr / csrxchg 访问
Task(任务)
将上述需求映射为一个可综合的 Verilog 设计,核心设计任务包括:
- 流水线划分:确定流水线级数和各级职责
- 取指子系统:I-Cache 设计、PC 管理、分支预测
- 译码与执行:指令译码、ALU 设计、乘除法实现
- 访存子系统:D-Cache 设计、Load/Store 对齐处理
- 特权架构:CSR 寄存器、TLB、异常/中断处理
- 总线接口:AXI4 桥接器设计
- 冒险处理:数据转发与流水线停顿
Action(行动)
一、流水线架构
1.1 六级流水线划分
本设计采用 6 级流水线,与经典的 5 级(IF→ID→EX→MEM→WB)相比,将执行阶段拆分为 EXE1 和 EXE2 两级:
1 | ┌──────┐ ┌──────┐ ┌───────┐ ┌───────┐ ┌──────┐ ┌──────┐ |
设计决策:为什么拆分 EXE1/EXE2?
在 5 级流水线中,EXE 阶段需要在一个周期内完成 ALU 计算 + D-Cache 地址翻译 + D-Cache 访问。这条组合逻辑路径较长:
1 | ALU计算 → 物理地址生成(TLB查找) → D-Cache Tag比较 → 数据选择 |
在 75MHz 的目标频率下,这条路径的时序裕量不足。将 ALU 计算放在 EXE1,将访存地址发射和 TLB 查找放在 EXE2,有效缩短了关键路径。代价是增加了一级流水线延迟,加大了数据冒险窗口和分支惩罚。
1.2 流水线控制信号
每一级通过 ready_go 和 allow_in
两个信号控制推进:
1 | // 每级的推进条件 |
这种”握手”式控制使得每一级可以独立地暂停,而不影响后级的正常推进。全局停顿信号
pipline_is_not_stalled
作为取指请求的门控条件,确保在冒险停顿时不会发起新的取指。
二、取指子系统
2.1 PC 管理
PC
寄存器(pc_reg.v)维护当前取指地址和下一地址,处理以下重定向源:
| 重定向源 | 优先级 | 说明 |
|---|---|---|
| Reset | 最高 | 初始化 PC 到 0x1bfffffc |
| wb_ex | 高 | 异常处理,跳转到 CSR.EENTRY |
| wb_is_ertn | 高 | 异常返回,跳转到 CSR.ERA |
| pc_br_taken | 中 | ID 阶段分支判定跳转 |
| btb_hit | 低 | BTB 预测跳转 |
| 顺序执行 | 默认 | nextpc = pc + 4 |
BTB 命中时,PC 在顺序推进的同时预取预测目标地址的指令,实现零延迟分支预测。
2.2 分支预测:BTB
采用 16 项直接映射 BTB(btb.v),结构简单:
1 | // BTB 表项结构 |
设计特点:
- 无预测器:BTB 只记录目标地址,不预测是否跳转(taken/not-taken)。分支是否跳转由 ID 阶段的 ALU 组合逻辑判定,延迟为 1 个周期。
- 直接映射:16 项,索引 4-bit,冲突率较高但面积小。
- 延迟槽处理:LoongArch 的分支指令后有一条延迟槽,BTB 预测的是跳转指令本身的目标,延迟槽指令正常执行。
2.3 I-Cache 设计
I-Cache 采用 2 路组相联设计(icache_two_way.v):
| 参数 | 值 |
|---|---|
| 相联度 | 2 路 |
| 组数 | 256 |
| 缓存行大小 | 128 bit(4 个字) |
| 总容量 | 16 KB |
| 替换策略 | 伪 LRU(1-bit used) |
| 地址分解 | Tag[31:12] | Index[11:4] | Word[3:2] | Byte[1:0] |
I-Cache 采用阻塞式(Blocking)状态机:
1 | S_IDLE ──hit──▶ S_HIT_RESP ──▶ S_IDLE |
- 命中路径:2 个周期(IDLE → HIT_RESP → IDLE)
- 缺失惩罚:10+ 周期(4 拍 Refill + AXI 延迟)
- Cacop 支持:S_CACOP_RESP 状态处理 cacop 指令的 Index Invalidate 和 Hit Invalidate 操作
三、译码与执行
3.1 指令译码
译码器(ID_stage.v)采用两级译码结构:
- 一级译码:使用
decoder_6_64将 opcode[31:26] 译码为 64 个 one-hot 信号 - 二级译码:根据子操作码字段([25:22]、[21:20]、[19:15] 等)进一步区分具体指令
译码器输出以下控制信号:
| 信号类别 | 示例 | 说明 |
|---|---|---|
| 寄存器读 | id_rj, id_rk, id_rd |
源/目标寄存器编号 |
| ALU控制 | id_alu_op[4:0] |
ALU 操作码(20 种操作) |
| 立即数 | id_imm12, id_imm5, id_imm16,
id_imm20, id_imm26 |
各类立即数 |
| 访存控制 | id_dram_we, id_dram_re,
id_rdram_num, id_wdram_num |
Load/Store 控制 |
| 分支控制 | id_br_taken, id_br_target |
分支判定结果 |
| CSR控制 | id_csr_num, id_csr_we,
id_csr_mask_all_one |
CSR 读写 |
| TLB控制 | id_invtlb_valid, id_inst_tlbrd,
id_tlb_we |
TLB 维护指令 |
| 异常检测 | id_ex_adef, id_ex_ale_h,
id_ex_ale_w, id_ex_ine,
id_ex_brk |
取指/执行阶段异常 |
3.2 ALU 设计
ALU(alu.v)支持 20 种操作,全部在 EXE1
阶段组合逻辑完成:
1 | case (alu_op) |
乘法器:使用 Verilog
$signed * $signed,综合时映射到 DSP 硬块,单周期完成。
地址对齐异常检测:ALU 同时检测访存地址的对齐错误:
1 | // 半字访存地址最低位不为0 → ALE异常 |
3.3 除法器设计
除法器(div.v)采用迭代恢复余数法,32
周期完成一次除法:
1 | // 核心思想:将被除数逐位移入余数寄存器,试减除数 |
特点:
- 支持
div.w(有符号除)、div.wu(无符号除)、mod.w(取余)、mod.wu(无符号取余) - 通过取绝对值实现有符号除法,最后根据符号位恢复结果
- 除数为 0 时不产生异常(LoongArch 硬件不检测除零,由软件处理)
- 除法期间流水线停顿,EXE1 阶段等待
complete信号
四、访存子系统
4.1 D-Cache 设计
D-Cache(dcache_two_way.v)与 I-Cache
结构对称,但增加了写回(Write-Back)支持:
| 参数 | 值 |
|---|---|
| 相联度 | 2 路 |
| 组数 | 256 |
| 缓存行大小 | 128 bit |
| 总容量 | 16 KB |
| 写策略 | Write-Back + Write-Allocate |
| 脏位 | 每路每行 1-bit dirty |
写回流程:当 Store 指令命中 D-Cache
且目标行已脏时,需要先将脏行写回内存,再写入新数据。这通过状态机中的
S_WB_REQ / S_WB_WAIT 状态完成。
4.2 Load/Store 对齐处理
LoongArch 要求访存地址自然对齐(半字地址 2 对齐,字地址 4 对齐)。非对齐访问触发 ALE 异常。对齐处理在 MEM 阶段完成:
- Load:根据
id_rdram_num(ld.b/ld.h/ld.w)和地址低 2 位,从 32-bit D-Cache 数据中选择对应字节/半字,并进行零扩展或符号扩展 - Store:根据
id_wdram_num和地址低 2 位,将数据放置到正确的字节通道(通过wstrb字节使能掩码控制)
五、特权架构
5.1 CSR 寄存器
CSR 模块(csr.v)实现了完整的 LA32 CSR 集合:
| CSR | 地址 | 说明 |
|---|---|---|
| CRMD | 0x0 | 当前模式信息(PLV, IE, DA, PG, DATF, DATM) |
| PRMD | 0x1 | 异常前模式信息(PPLV, PIE) |
| ECFG | 0x4 | 异常配置(中断使能掩码) |
| ESTAT | 0x5 | 异常状态(中断挂起位, ECode, ESubCode) |
| ERA | 0x6 | 异常返回地址 |
| BADV | 0x7 | 错误虚地址 |
| EENTRY | 0xC | 异常入口地址 |
| TLBIDX | 0x10 | TLB 索引 |
| TLBEHI | 0x11 | TLB 高位 |
| TLBELO0/1 | 0x12/0x13 | TLB 低位(奇偶页) |
| ASID | 0x18 | 地址空间标识 |
| TLBRENTRY | 0x88 | TLB 重填异常入口 |
| DMW0/1 | 0x180/0x181 | 直接映射窗口 |
| SAVE0-3 | 0x30-0x33 | 通用保存寄存器 |
| TID | 0x40 | 定时器编号 |
| TCFG | 0x41 | 定时器配置 |
CSR 写机制:采用掩码写入方式,支持 csrxchg 指令的原子读-改-写:
1 | // 通用 CSR 写模式:mask + value |
异常时的 CSR 自动更新:
wb_ex触发时:PRMD.PPLV ← CRMD.PLV,PRMD.PIE ← CRMD.IE,CRMD.PLV ← 0,CRMD.IE ← 0,ERA ← WB.PC,ESTAT.ECode ← 异常码wb_ertn触发时:CRMD.PLV ← PRMD.PPLV,CRMD.IE ← PRMD.PIE
5.2 TLB 设计
TLB(tlb.v)实现 16 项全相联查找,支持 4KB 和 4MB
两种页大小:
1 | // 三端口并行查找 |
TLB 维护指令支持:
| 指令 | 功能 |
|---|---|
| TLBSRCH | 查找 TLB 中是否存在指定虚页映射,结果写入 TLBIDX.NE |
| TLBRD | 读取 TLBIDX.Index 指向的表项到 TLBEHI/TLBELO0/1 |
| TLBWR | 将 CSR 中的 TLB 表项数据写入 TLBIDX.Index 指向的表项 |
| TLBFILL | 查找空闲项或随机替换一项,写入 CSR 中的表项数据 |
| INVTLB | 按 op 码无效化 TLB 表项(6 种模式:全清、按ASID、按VPPN等) |
5.3 异常与中断处理
异常处理采用 WB 阶段统一检测
策略(trap_unit.v + wb_stage.v):
1 | 异常检测点 传递路径 最终判定 |
异常优先级(从高到低):
1 | 中断(INT) > TLB重填(TLBR) > 页修改(PME) > TLB无效(PIL/PIS/PIF) |
中断处理(interrupt.v):
1 | // 中断使能:CRMD.IE && (ESTAT.IS & ECFG.LIE) != 0 |
支持 13 个中断源:2 个软件中断(IS[1:0])、8 个硬件中断(IS[9:2])、定时器中断(IS[11])、核间中断(IS[12])。
定时器:64 位自由运行计数器
csr_timer_64,配合 32 位倒计时器 timer_cnt 和
TCFG 配置寄存器,支持单次和周期定时中断。
六、总线接口
6.1 AXI4 桥接器
AXI 桥接器(sram_axi_bridge.v)将 CPU 的 SRAM-like
接口转换为 AXI4 总线协议,取指和数据共享同一组 AXI 通道:
1 | CPU侧 AXI Bridge AXI总线 |
读通道仲裁:取指和数据读请求通过
read_req_is_data
信号仲裁,数据请求优先。请求被接受后锁定(read_hold_*
寄存器),直到响应返回。
读响应缓冲:AXI
读响应通过一级寄存器缓冲(r_buf_rvalid/r_buf_rid/r_buf_rdata),过滤潜在的毛刺,并按
ID 分发到正确的端口:
1 | assign inst_sram_data_ok = r_buf_rvalid && r_buf_rid == 4'b0; // ID=0: 取指 |
写通道:AW 和 W 通道独立握手,支持地址和数据先后到达的灵活时序。写响应(B 通道)用于确认写完成。
七、冒险处理
7.1 数据冒险
采用 ID 阶段集中转发 策略,从 EXE1、EXE2、MEM、WB 四个后级阶段转发:
1 | // 优先级:EXE1 > EXE2 > MEM > WB(越近越新) |
Load-Use 停顿:当 MEM 阶段有 Load 指令且 ID 阶段的消费者依赖其结果时,停顿 IF 和 ID:
1 | assign if_ready_go = /* ... */ : |
7.2 控制冒险
- BTB 预测:取指阶段根据 BTB 预测跳转目标,减少分支惩罚
- ID 阶段判定:分支指令在 ID 阶段通过 ALU 组合逻辑判定是否跳转,延迟 1 周期
- 预测失败恢复:ID 阶段发现实际跳转方向与 BTB 预测不一致时,冲刷 IF 阶段的错误预取指令
- 异常冲刷:异常和 ertn 指令在 WB 阶段触发全流水线冲刷
7.3 结构冒险
- AXI 总线竞争:取指和数据访存共享 AXI 通道,通过 ID 区分(0=取指, 1=数据),数据请求优先
- RegFile 读写竞争:RegFile 采用同步写、异步读,WB 阶段写入的数据在下一周期可见。同一周期内 WB 写入和 ID 读取同一寄存器时,通过 WB→ID 转发解决
八、Difftest 支持
设计中集成了 NEMU difftest 接口,通过 DIFFTEST_EN
宏条件编译:
- 寄存器对比:RegFile 导出全部 32
个寄存器值(
rf_regs_diff) - CSR 对比:CSR 模块导出 26 个 CSR
的完整状态(
csr_all_diff,832-bit 总线) - 在线比对:每条指令在 WB 阶段提交时,将 PC、寄存器、CSR 状态发送给 NEMU 比对
Result(结果)
设计成果
| 指标 | 结果 |
|---|---|
| 指令集 | LoongArch32 (LA32R) 全指令支持 |
| 主频 | 75 MHz(Verilator 仿真) |
| 流水线 | 6 级(IF → ID → EXE1 → EXE2 → MEM → WB) |
| I-Cache | 16 KB,2 路组相联 |
| D-Cache | 16 KB,2 路组相联,Write-Back |
| TLB | 16 项全相联,3 端口并行查找 |
| 分支预测 | 16 项直接映射 BTB |
| 功能测试 | 58 个测试点全部通过(82 个用例) |
| 验证方法 | Verilator + NEMU difftest |
模块规模统计
| 模块 | 文件 | 功能 |
|---|---|---|
| core_top | core_top.v | 顶层模块,流水线互连和冒险控制 |
| PC_Reg | pc_reg.v | PC 寄存器,重定向管理 |
| ID_stage | ID_stage.v | 指令译码器 |
| ALU | alu.v | 算术逻辑单元 |
| Div | div.v | 迭代除法器 |
| regfile | regfile.v | 通用寄存器文件 |
| icache_two_way | icache_two_way.v | 指令 Cache |
| dcache_two_way | dcache_two_way.v | 数据 Cache |
| btb | btb.v | 分支目标缓冲器 |
| tlb | tlb.v | 地址翻译旁路缓冲器 |
| CSRREG | csr.v | CSR 寄存器文件 |
| trap_unit | trap_unit.v | 异常优先级判定 |
| Wb_stage | wb_stage.v | 异常编码 |
| interrupt | interrupt.v | 中断检测 |
| axi_bridge | sram_axi_bridge.v | AXI4 总线桥接器 |
| tools | tools.v | 译码器宏模块 |
| *_readygo / *_allow_in / *_reg | 多个文件 | 流水线级寄存器和控制 |