Litex-VexRiscv 编译SoC

Litex-VexRiscv 编译SoC

Litex 框架通过以下流程调用 VexRiscv 源码并编译成 SoC:

Text
1
make.py → VexRiscvSMP (core.py) → VexRiscvSmpLitexCluster.scala → Verilog生成 → FPGA比特流

流程分析

入口

make.py 是整个构建过程的入口点,负责:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 导入必要模块
from litex.soc.cores.cpu.vexriscv_smp import VexRiscvSMP
from soc_linux import SoCLinux

# 2. 解析命令行参数
parser = argparse.ArgumentParser(...)
VexRiscvSMP.args_fill(parser) # 填充VexRiscv相关参数
args = parser.parse_args()

# 3. 创建SoC实例
soc = SoCLinux(board.soc_cls, **soc_kwargs)

# 4. 使用Builder构建
builder = Builder(soc,
output_dir = os.path.join("build", board_name),
bios_console = "lite",
csr_json = os.path.join(build_dir, "csr.json"),
csr_csv = os.path.join(build_dir, "csr.csv")
)

# 5. 执行构建
builder.build(run=args.build, build_name=board_name)

Litex 封装

core.py VexRiscvSMP 类是 Litex 对 VexRiscv 的封装,核心功能:

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
class VexRiscvSMP(CPU):
# 配置参数
cpu_count = 1
dcache_size = 4096
icache_size = 4096
dtlb_size = 4 # TLB大小配置
itlb_size = 4
...

# 关键方法:生成Verilog网表
@staticmethod
def generate_netlist():
...
# 构建sbt命令
cmd = 'cd {path} && sbt "runMain vexriscv.demo.smp.VexRiscvLitexSmpClusterCmdGen {args}"'

# 调用SpinalHDL生成Verilog
subprocess.check_call(cmd, shell=True)

...

# 添加Verilog源文件到构建流程
def add_sources(self, platform):
...
# 添加生成的集群Verilog文件
cluster_filename = os.path.join(vdir, self.cluster_name + ".v")
platform.add_source(cluster_filename, "verilog")
...

...

VexRiscv 核心实现

VexRiscvSmpLitexCluster.scala 是用 SpinalHDL 编写的 VexRiscv SMP 集群实现:

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
47
48
49
50
51
52
class VexRiscvLitexSmpCluster(p : VexRiscvLitexSmpClusterParameter) 
extends VexRiscvSmpClusterWithPeripherals(p.cluster) {

// 互连结构配置
val iArbiter = BmbBridgeGenerator()
val iBridge = !p.wishboneMemory generate BmbToLiteDramGenerator(p.liteDramMapping)
val dBridge = !p.wishboneMemory generate BmbToLiteDramGenerator(p.liteDramMapping)

// 总线连接
for(core <- cores) interconnect.addConnection(core.cpu.iBus -> List(iArbiter.bmb))
...

// FPU配置(如果启用)
val fpu = p.cluster.fpu generate {
for(group <- fpuGroups) yield new Area{
val extraStage = group.size > 2 // 如果FPU核心数大于2,启用额外阶段
// FPU核心实例化
val logic = Handle{
new FpuCore(
portCount = group.size,
p = FpuParameter(
withDouble = true,
asyncRegFile = false,
schedulerM2sPipe = extraStage
)
)
}

...
}
}

...
}

// 命令行生成器
object VexRiscvLitexSmpClusterCmdGen extends App {
...
// 解析命令行参数
assert(new scopt.OptionParser[Unit]("VexRiscvLitexSmpClusterCmdGen") {
opt[String]("cpu-count") action { (v, c) => cpuCount = v.toInt }
opt[String]("dtlb-size") action { (v, c) => dTlbSize = v.toInt }
opt[String]("itlb-size") action { (v, c) => iTlbSize = v.toInt }
// 其他参数...
}.parse(args, Unit).nonEmpty)

...

// 生成Verilog
val genConfig = SpinalConfig(targetDirectory = netlistDirectory, inlineRom = true)
genConfig.generateVerilog(dutGen.setDefinitionName(netlistName))
}

添加自定义指令

SimdAddPlugin示例

以下是一个简单插件的示例,它添加了一个简单的SIMD_ADD指令。

SimdAddPlugin 实现了一个 SIMD 加法指令,将 32 位寄存器分为 4 个 8 位通道,分别进行加法运算:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import spinal.core._
import vexriscv.plugin.Plugin
import vexriscv.{Stageable, DecoderService, VexRiscv}

//This plugin example will add a new instruction named SIMD_ADD which does the following:
//
//RD : Regfile Destination, RS : Regfile Source
//RD( 7 downto 0) = RS1( 7 downto 0) + RS2( 7 downto 0)
//RD(16 downto 8) = RS1(16 downto 8) + RS2(16 downto 8)
//RD(23 downto 16) = RS1(23 downto 16) + RS2(23 downto 16)
//RD(31 downto 24) = RS1(31 downto 24) + RS2(31 downto 24)
//
//Instruction encoding :
//0000011----------000-----0110011
//funct7 rs2 rs1 funct3 rd opcode
// |RS2||RS1| |RD |
//
//Note : RS1, RS2, RD positions follow the RISC-V spec and are common for all instruction of the ISA

class SimdAddPlugin extends Plugin[VexRiscv]{
//Define the concept of IS_SIMD_ADD signals, which specify if the current instruction is destined for this plugin
object IS_SIMD_ADD extends Stageable(Bool)

//Callback to setup the plugin and ask for different services
override def setup(pipeline: VexRiscv): Unit = {
import pipeline.config._

//Retrieve the DecoderService instance
val decoderService = pipeline.service(classOf[DecoderService])

//Specify the IS_SIMD_ADD default value when instructions are decoded
decoderService.addDefault(IS_SIMD_ADD, False)

//Specify the instruction decoding which should be applied when the instruction matches the 'key' parttern
decoderService.add(
//Bit pattern of the new SIMD_ADD instruction
key = M"0000011----------000-----0110011",

//Decoding specification when the 'key' pattern is recognized in the instruction
List(
IS_SIMD_ADD -> True,
REGFILE_WRITE_VALID -> True, //Enable the register file write
BYPASSABLE_EXECUTE_STAGE -> True, //Notify the hazard management unit that the instruction result is already accessible in the EXECUTE stage (Bypass ready)
BYPASSABLE_MEMORY_STAGE -> True, //Same as above but for the memory stage
RS1_USE -> True, //Notify the hazard management unit that this instruction uses the RS1 value
RS2_USE -> True //Same as above but for RS2.
)
)
}

override def build(pipeline: VexRiscv): Unit = {
import pipeline._
import pipeline.config._

//Add a new scope on the execute stage (used to give a name to signals)
execute plug new Area {
//Define some signals used internally by the plugin
val rs1 = execute.input(RS1).asUInt
//32 bits UInt value of the regfile[RS1]
val rs2 = execute.input(RS2).asUInt
val rd = UInt(32 bits)

//Do some computations
rd(7 downto 0) := rs1(7 downto 0) + rs2(7 downto 0)
rd(16 downto 8) := rs1(16 downto 8) + rs2(16 downto 8)
rd(23 downto 16) := rs1(23 downto 16) + rs2(23 downto 16)
rd(31 downto 24) := rs1(31 downto 24) + rs2(31 downto 24)

//When the instruction is a SIMD_ADD, write the result into the register file data path.
when(execute.input(IS_SIMD_ADD)) {
execute.output(REGFILE_WRITE_DATA) := rd.asBits
}
}
}
}

如果你想把这个插件添加到某个CPU上,只需把它添加到其参数化插件列表。

示例代码分析

1.导入和类定义

1
2
3
4
5
import spinal.core._
import vexriscv.plugin.Plugin
import vexriscv.{Stageable, DecoderService, VexRiscv}

class SimdAddPlugin extends Plugin[VexRiscv]{

这部分代码导入了必要的包,并定义了 SimdAddPlugin 类,继承自 Plugin [VexRiscv],表明这是一个 VexRiscv 插件。

2.阶段信号定义

1
object IS_SIMD_ADD extends Stageable(Bool)

这行代码定义了一个 Stageable 对象 IS_SIMD_ADD,类型为 Bool,用于在流水线中传递控制信号,表示当前指令是否是 SIMD_ADD 指令。

3.setup 方法

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
override def setup(pipeline: VexRiscv): Unit = {
import pipeline.config._

//Retrieve the DecoderService instance
val decoderService = pipeline.service(classOf[DecoderService])

//Specify the IS_SIMD_ADD default value when instructions are decoded
decoderService.addDefault(IS_SIMD_ADD, False)

//Specify the instruction decoding which should be applied when the instruction matches the 'key' parttern
decoderService.add(
//Bit pattern of the new SIMD_ADD instruction
key = M"0000011----------000-----0110011",

//Decoding specification when the 'key' pattern is recognized in the instruction
List(
IS_SIMD_ADD -> True,
REGFILE_WRITE_VALID -> True, //Enable the register file write
BYPASSABLE_EXECUTE_STAGE -> True, //Notify the hazard management unit that the instruction result is already accessible in the EXECUTE stage (Bypass ready)
BYPASSABLE_MEMORY_STAGE -> True, //Same as above but for the memory stage
RS1_USE -> True, //Notify the hazard management unit that this instruction uses the RS1 value
RS2_USE -> True //Same as above but for RS2.
)
)
}

setup 方法用于配置插件和获取服务:

  1. 获取 DecoderService 实例,用于配置指令解码规则
  2. 设置 IS_SIMD_ADD 的默认值为 False
  3. 定义指令编码模式和解码规则:
    • key = M”0000011—————000——-0110011”:定义了 SIMD_ADD 指令的编码模式
    • 当指令匹配该模式时,设置以下控制信号:
      • IS_SIMD_ADD -> True:表示这是 SIMD_ADD 指令
      • REGFILE_WRITE_VALID -> True:启用寄存器文件写入
      • BYPASSABLE_EXECUTE_STAGE -> True:通知冒险管理单元指令结果在执行阶段可用于旁路
      • BYPASSABLE_MEMORY_STAGE -> True:通知冒险管理单元指令结果在内存阶段可用于旁路
      • RS1_USE -> True:通知冒险管理单元该指令使用 RS1 寄存器
      • RS2_USE -> True:通知冒险管理单元该指令使用 RS2 寄存器

4.build 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
override def build(pipeline: VexRiscv): Unit = {
import pipeline._
import pipeline.config._

//Add a new scope on the execute stage (used to give a name to signals)
execute plug new Area {
//Define some signals used internally by the plugin
val rs1 = execute.input(RS1).asUInt
//32 bits UInt value of the regfile[RS1]
val rs2 = execute.input(RS2).asUInt
val rd = UInt(32 bits)

//Do some computations
rd(7 downto 0) := rs1(7 downto 0) + rs2(7 downto 0)
rd(16 downto 8) := rs1(16 downto 8) + rs2(16 downto 8)
rd(23 downto 16) := rs1(23 downto 16) + rs2(23 downto 16)
rd(31 downto 24) := rs1(31 downto 24) + rs2(31 downto 24)

//When the instruction is a SIMD_ADD, write the result into the register file data path.
when(execute.input(IS_SIMD_ADD)) {
execute.output(REGFILE_WRITE_DATA) := rd.asBits
}
}
}

build 方法用于实现指令的功能逻辑:

  1. 在执行阶段(execute)添加一个新的 Area,用于组织相关信号
  2. 定义内部信号:
    • rs1:从流水线输入获取 RS1 寄存器的值,转换为 UInt 类型
    • rs2:从流水线输入获取 RS2 寄存器的值,转换为 UInt 类型
    • rd:定义一个 32 位 UInt 类型的信号,用于存储结果
  3. 实现 SIMD 加法逻辑:
    • rd (7 downto 0) := rs1 (7 downto 0) + rs2 (7 downto 0):第 0 个 8 位通道的加法
    • rd (16 downto 8) := rs1 (16 downto 8) + rs2 (16 downto 8):第 1 个 8 位通道的加法
    • rd (23 downto 16) := rs1 (23 downto 16) + rs2 (23 downto 16):第 2 个 8 位通道的加法
    • rd (31 downto 24) := rs1 (31 downto 24) + rs2 (31 downto 24):第 3 个 8 位通道的加法
  4. 结果写回逻辑:
    • 当 IS_SIMD_ADD 信号为 True 时,将 rd 的值转换为 Bits 类型,并写入 REGFILE_WRITE_DATA 信号,以便在写回阶段写入寄存器文件

添加自定义CSR

CSR 地址空间分配

RISC-V ISA 预留了 12 位编码空间(csr [11:0],即 0x000-0xFFF),最多可支持 4096 个 CSR。CSR 地址的高 4 位(csr [11:8])用于根据特权级别编码 CSR 的读写访问权限:

  • 最高两位(csr [11:10])表示寄存器是读 / 写(00,01, 或 10)还是只读(11)
  • 接下来两位(csr [9:8])编码可以访问 CSR 的最低特权级别

CSR 插件实现

VexRiscv 通过两个主要插件实现 CSR 功能:

  1. CsrAccessPlugin:
    • 实现了 CSR 的读写指令
    • 为其他插件提供了 API 来指定 CSR 寄存器和 CSR 指令之间的映射
    • 处理 CSR 访问的特权级别检查
  2. CsrRamPlugin:
    • 提供了一个 API,允许静态分配空间
    • 创建读写端口,以 FPGA 高效的方式存储 CSR 内容
    • 被各种插件用于存储 CSR 内容

VexRiscv 插件系统提供了一个 CsrInterface 服务,允许其他插件轻松地添加自定义 CSR。这个服务提供了以下主要方法:

  • rw(address: Int, data: Data):将一个可读写的 CSR 映射到指定地址
  • r(address: Int, data: Data):将一个只读的 CSR 映射到指定地址
  • onWrite(address: Int)(callback: => Unit):当向指定地址写入时执行回调
  • onRead(address: Int)(callback: => Unit):当从指定地址读取时执行回调

通过这些方法,插件可以轻松地将自定义寄存器映射到 CSR 地址空间,并定义读写行为。

CustomCsrDemoPlugin示例

CustomCsrDemoPlugin.scala 包含两个插件类:

  1. CustomCsrDemoPlugin:添加了指令计数器和时钟周期计数器

  2. CustomCsrDemoGpioPlugin:创建了一个直接映射到 CSR 的 GPIO 外设

这两个类都继承自Plugin[VexRiscv],表明它们是 VexRiscv 插件。

CustomCsrDemoPlugin类:

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
class CustomCsrDemoPlugin extends Plugin[VexRiscv]{
override def build(pipeline: VexRiscv): Unit = {
import pipeline._
import pipeline.config._

pipeline plug new Area{
val instructionCounter = Reg(UInt(32 bits))
val cycleCounter = Reg(UInt(32 bits))

cycleCounter := cycleCounter + 1
when(writeBack.arbitration.isFiring) {
instructionCounter := instructionCounter + 1
}

val csrService = pipeline.service(classOf[CsrInterface])

csrService.rw(0xB04, instructionCounter)
csrService.r(0xB05, cycleCounter)

csrService.onWrite(0xB06){
instructionCounter := 0
}

csrService.onRead(0xB07){
instructionCounter := 0x40000000
}
}
}
}

这个插件实现了以下功能:

  1. 创建计数器寄存器:
    • instructionCounter:记录已执行的指令数
    • cycleCounter:记录时钟周期数
  2. 计数器更新逻辑:
    • cycleCounter每个时钟周期加 1
    • instructionCounter在写回阶段(writeBack)触发时加 1
  3. CSR 映射:
    • csrService.rw(0xB04, instructionCounter):将 instructionCounter 映射到 0xB04 地址,可读写
    • csrService.r(0xB05, cycleCounter):将 cycleCounter 映射到 0xB05 地址,只读
  4. 特殊 CSR 行为:
    • csrService.onWrite(0xB06):当向 0xB06 地址写入时,重置 instructionCounter
    • csrService.onRead(0xB07):当从 0xB07 地址读取时,返回 0x40000000

CustomCsrDemoGpioPlugin类:

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
class CustomCsrDemoGpioPlugin extends Plugin[VexRiscv]{
var gpio : TriStateArray = null

override def setup(pipeline: VexRiscv): Unit = {
gpio = master(TriStateArray(32 bits)).setName("gpio")
}

override def build(pipeline: VexRiscv): Unit = {
import pipeline._
import pipeline.config._

pipeline plug new Area{
val writeReg, writeEnableReg = Reg(Bits(32 bits))

val csrService = pipeline.service(classOf[CsrInterface])

csrService.rw(0xB08, writeReg)
csrService.rw(0xB09, writeEnableReg)
csrService.r(0xB0A, gpio.read)

gpio.writeEnable := writeEnableReg
gpio.write := writeReg
}
}
}

这个插件实现了以下功能:

  1. GPIO 接口定义:
    • 在setup方法中定义了一个 32 位的三态数组 GPIO 接口
    • 这个接口被设置为 master 类型,表示它是一个输出接口
  2. GPIO 控制寄存器:
    • writeReg:存储要写入 GPIO 的值
    • writeEnableReg:存储 GPIO 的写使能信号
  3. CSR 映射:
    • csrService.rw(0xB08, writeReg):将 writeReg 映射到 0xB08 地址,可读写
    • csrService.rw(0xB09, writeEnableReg):将 writeEnableReg 映射到 0xB09 地址,可读写
    • csrService.r(0xB0A, gpio.read):将 gpio.read 映射到 0xB0A 地址,只读
  4. GPIO 控制逻辑:
    • gpio.writeEnable := writeEnableReg:将 writeEnableReg 的值赋给 gpio.writeEnable
    • gpio.write := writeReg:将 writeReg 的值赋给 gpio.write

自定义CSR步骤

  1. 创建插件类:
    • 继承自Plugin[VexRiscv]
    • 实现setup和 / 或build方法
  2. 定义自定义寄存器:
    • 在插件中定义需要映射到 CSR 的寄存器
    • 定义寄存器的更新逻辑
  3. 获取 CsrInterface 服务:
    • 使用pipeline.service(classOf[CsrInterface])获取 CsrInterface 服务
  4. 映射 CSR 地址:
    • 使用csrService.rw、csrService.r、csrService.onWrite或csrService.onRead方法映射 CSR 地址
  5. 配置 CPU 时添加插件:
    • 在 CPU 配置中添加自定义 CSR 插件

自定义示例代码

1.CSR 插件示例:

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
class MyCustomCsrPlugin extends Plugin[VexRiscv]{
override def build(pipeline: VexRiscv): Unit = {
import pipeline._
import pipeline.config._

pipeline plug new Area{
// 定义自定义寄存器
val myCounter = Reg(UInt(32 bits)) init(0)
val myControl = Reg(Bits(4 bits)) init(0)

// 定义寄存器更新逻辑
myCounter := myCounter + 1

// 获取CsrInterface服务
val csrService = pipeline.service(classOf[CsrInterface])

// 映射CSR地址(使用自定义CSR范围)
csrService.rw(0x7C0, myCounter) // 读写访问
csrService.rw(0x7C1, myControl) // 读写访问

// 定义特殊行为
csrService.onWrite(0x7C2){ // 当写入0x7C2时
myCounter := 0 // 重置myCounter
}

csrService.onRead(0x7C3){ // 当读取0x7C3时
// 执行一些操作
}
}
}
}

2.在 CPU 配置中添加插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建CPU配置
val cpu = new VexRiscv(
// 添加自定义CSR插件
plugins = List(
new IBusSimplePlugin(resetVector = 0x80000000l),
new DecoderSimplePlugin,
new RegFilePlugin(regFileReadyKind = plugin.SYNC, zeroBoot = false),
new IntAluPlugin,
new SrcPlugin(separatedAddSub = false, executeInsertion = true),
new FullBarrelShifterPlugin,
new HazardSimplePlugin(bypassExecute = true, bypassMemory = true, bypassWriteBack = true, bypassWriteBackBuffer = true),
new BranchPlugin(earlyBranch = false, catchAddressMisaligned = true),
new CsrPlugin(CsrPluginConfig.small), // 需要添加CsrPlugin
new MyCustomCsrPlugin // 添加自定义CSR插件
)
)


Litex-VexRiscv 编译SoC
http://ruak.github.io/2026/02/03/Litex-VexRiscv-编译SoC/
作者
HUANGDAN
发布于
2026年2月3日
许可协议