4. Rocket Chip生成器:Chisel语言基础,用Rocket Chip生成SoC,配置L1/L2 Cache与总线

好,咱们进入正题。这一章我打算聊聊Rocket Chip生成器。说实话,我第一次接触Rocket Chip时,心里是有点发怵的——一个能自动生成完整SoC的工具,听起来就像黑魔法。但用熟了之后你会发现,它其实就是一套用Scala写的“硬件模板库”,核心是Chisel语言。

4.1 Chisel语言:硬件构建的“脚手架”

Chisel,全称是Constructing Hardware in a Scala Embedded Language。说白了,它是在Scala里嵌入的一套硬件描述语言。你写的是Scala代码,但生成的是Verilog网表。

为什么用Chisel而不是直接写Verilog? 我个人习惯用Chisel做三件事:参数化、代码复用、自动生成。举个例子,你想生成一个32位或64位的RISC-V核,用Verilog你得写两套代码,用Chisel改个参数就行。

核心概念: Chisel把硬件模块看成“对象”,连线看成“赋值”,寄存器看成“状态”。你写的是面向对象的硬件描述。

来看一个最简单的Chisel模块——一个带使能的计数器:

import chisel3._

class CounterWithEnable extends Module {
  val io = IO(new Bundle {
    val en   = Input(Bool())
    val out  = Output(UInt(8.W))
  })

  val count = RegInit(0.U(8.W))

  when(io.en) {
    count := count + 1.U
  }

  io.out := count
}

这段代码里,RegInit定义了一个初始值为0的8位寄存器。when语句相当于Verilog里的always @(posedge clk)。嗯,这里要注意:Chisel默认所有寄存器都是时钟上升沿触发,复位是同步复位。

我在项目中遇到过一个坑:Chisel生成的Verilog里,复位信号默认是高有效。如果你的FPGA板子是低有效复位,记得在生成时加参数--resetType asyncLow

4.2 Rocket Chip生成器:一键生成SoC

Rocket Chip是UC Berkeley开发的开源SoC生成器。它基于Chisel,能生成完整的RISC-V处理器系统。你给它一个配置文件,它给你吐出一个完整的SoC——包括CPU核、缓存、总线、外设。

下图是Rocket Chip生成SoC的核心架构:

Rocket Chip SoC 核心架构 Rocket Core RISC-V 64位 顺序执行 L1 I-Cache 16KB~64KB L1 D-Cache 16KB~64KB TileLink 总线(主互连) L2 Cache Bank 128KB~2MB 外设(UART/SPI/GPIO) 通过MMIO映射 DDR控制器 主存接口 处理器核 L1缓存 TileLink总线 L2缓存 外设 DDR

这张图展示了Rocket Chip生成SoC的典型结构。Rocket Core通过L1缓存连接到TileLink总线,总线再挂载L2缓存、外设和DDR控制器。你想想看,这其实就是个标准的现代处理器架构,只不过所有模块都是自动生成的。

4.3 配置L1/L2 Cache:参数化设计

Rocket Chip的缓存配置是通过Scala的case class实现的。你不需要改任何硬件代码,改几个参数就行。

L1 Cache配置示例:

// 在Config文件中配置L1缓存
class MyL1Config extends Config((site, here, up) => {
  case CacheBlockBytes => 64        // 缓存块大小:64字节
  case L1ICacheSets   => 64        // 指令缓存组数
  case L1ICacheWays   => 4         // 指令缓存路数(4路组相联)
  case L1DCacheSets   => 64        // 数据缓存组数
  case L1DCacheWays   => 4         // 数据缓存路数
  case L1DCacheMSHRs  => 8         // 未命中状态寄存器数
})

L2 Cache配置示例:

// L2缓存配置
class MyL2Config extends Config((site, here, up) => {
  case L2Sets         => 1024      // L2缓存组数
  case L2Ways         => 8         // L2缓存路数(8路组相联)
  case L2BankCount    => 4         // L2缓存bank数
  case L2CapacityKB   => 512       // L2总容量:512KB
})

我的经验: L1缓存大小建议设为16KB~64KB,L2缓存建议128KB~2MB。FPGA上L2别设太大,否则布线会很难受。我曾经在一个项目中把L2设成4MB,结果综合了整整两天还没跑完...

4.4 总线配置:TileLink与AXI的桥接

Rocket Chip内部使用TileLink总线协议。但很多FPGA外设IP用的是AXI协议。怎么办?Rocket Chip提供了TileLink-to-AXI桥接器。

总线配置示例:

// 添加AXI外设桥接
class MySoCConfig extends Config(new WithNBreakpoints(1) ++
  new WithL1ICacheSets(64) ++
  new WithL1DCacheSets(64) ++
  new WithL2Cache(512, 1024, 8) ++
  new WithAXI4MemPort ++          // 添加AXI内存端口
  new WithAXI4MMIOPort ++         // 添加AXI MMIO端口
  new BaseConfig)                 // 基础配置

这段配置做了三件事:

  • WithAXI4MemPort:将TileLink总线桥接到AXI4,连接DDR控制器
  • WithAXI4MMIOPort:桥接外设总线,连接UART、SPI等
  • WithL2Cache:配置L2缓存参数

避坑指南: 我曾经在配置总线时,忘记设置MMIO地址映射范围。结果外设地址和内存地址冲突,CPU一访问外设就死机。记得在配置中显式指定外设的基地址:WithMMIOBase(0x40000000L)

4.5 生成SoC的完整流程

配置写好了,怎么生成SoC?流程很简单:

  1. 编写Config文件:定义缓存大小、总线类型、外设数量
  2. 运行生成命令make verilog CONFIG=MySoCConfig
  3. 检查生成结果:在build/目录下找到Top.vTop.anno.json
  4. 集成到FPGA工程:将生成的Verilog文件添加到Vivado或Quartus工程

生成命令示例:

# 进入Rocket Chip目录
cd rocket-chip

# 生成Verilog
make verilog CONFIG=MySoCConfig

# 生成的文件在:
# build/MySoCConfig/Top.v
# build/MySoCConfig/Top.anno.json

生成的Top.v文件里,你会看到Rocket Core、L1/L2缓存、TileLink总线、AXI桥接器全部例化好了。你只需要在FPGA工程里添加时钟、复位和IO引脚约束就行。

4.6 实战建议:从简单开始

如果你是第一次用Rocket Chip,我建议你:

  • 先用默认配置:Rocket Chip自带的DefaultConfig能生成一个完整的SoC,先跑通再说
  • 逐步修改参数:先改L1缓存大小,再改L2,最后改总线
  • 仿真验证:用make emulator CONFIG=MyConfig生成C++仿真模型,跑几个测试程序

嗯,这一章的内容就这些。Rocket Chip生成器说白了就是个“硬件编译器”,你给它配置,它给你SoC。关键是要理解Chisel的参数化思想——所有硬件模块都是可配置的,你改的是Scala代码,生成的是Verilog网表。

一句话总结: Chisel是笔,Rocket Chip是模板,配置是蓝图。三者结合,你就能在FPGA上快速搭建一个RISC-V SoC。


公众号:蓝海资料掘金营,微信deep3321