模拟用 Chisel 编写的 CPU 设计
Simulating a CPU design written in Chisel
我在Chisel3中写了一个单循环的CPU实现了大部分的RV32I指令(CSR,Fence,ECALL/BREAK,LB/SB除外,以后可能会包括在内) ).这些指令目前硬编码在指令内存中,但我将更改它,以便它改为从文件中读取指令。我 运行 遇到了如何实际模拟我的设计的麻烦。这是我将 "glued" 所有组件放在一起的代码。
class Core extends Module {
val io = IO(new Bundle {
val dc = Input(Bool())
})
io := DontCare
val pc = RegInit(0.U)
val pcSelect = Module(new PcSelect())
val pcPlusFour = Module(new Adder())
val alu = Module(new ALU())
val aluControl = Module(new AluControl())
val control = Module(new Control())
val immGen = Module(new ImmGen())
val branchLogic = Module(new BranchLogic())
val branchUnit = Module(new Adder())
val jumpReg = Module(new JumpReg())
val regFile = Module(new RegFile())
val jumpAdder = Module(new Adder())
val dataMem = Module(new DataMemory())
val instrMem = Module(new InstructionMemory())
// Mux from data memory
val dataMux = Mux(control.io.memToReg, dataMem.io.readDataOutput, alu.io.result)
// Mux to register file
val regFileMux = Mux(control.io.writeSrc, pcPlusFour.io.result, dataMux)
// PC + 4
pcPlusFour.io.in1 := pc
pcPlusFour.io.in2 := 4.U
// Instruction memory
instrMem.io.address := pc
val instruction = instrMem.io.instruction
val opcode = instruction(6, 0)
// Control
control.io.opcode := opcode
// Register file
regFile.io.readReg1 := instruction(19, 15) // rs1
regFile.io.readReg2 := instruction(24, 20) // rs2
regFile.io.writeReg := instruction(11, 7) // rd
regFile.io.regWrite := control.io.regWrite
regFile.io.writeData := regFileMux
// ALU
val aluMux1 = Mux(control.io.aluSrc1, immGen.io.extendedU, regFile.io.readData1)
alu.io.in1 := aluMux1
val src = control.io.aluSrc2
val aluMux2 = Mux(src === 1.U, immGen.io.extendedI, Mux(src === 2.U, immGen.io.extendedS, Mux(src === 3.U, pc, regFile.io.readData2)))
alu.io.in2 := aluMux2
alu.io.aluOp := aluControl.io.output
// ALU control
aluControl.io.aluOp := control.io.aluOp
aluControl.io.funct7 := instruction(31, 25)
aluControl.io.funct3 := instruction(14, 12)
// Data Memory
dataMem.io.readAddress := alu.io.result
dataMem.io.writeData := regFile.io.readData2
dataMem.io.memWrite := control.io.memWrite
dataMem.io.memRead := control.io.memRead
// Immediate generator
immGen.io.instr := instruction
// Branch logic
branchLogic.io.reg1 := regFile.io.readData1
branchLogic.io.reg2 := regFile.io.readData2
branchLogic.io.branch := control.io.branch
branchLogic.io.funct3 := instruction(14, 12)
// Jump reg
jumpReg.io.reg1 := regFile.io.readData1
jumpReg.io.imm := immGen.io.extendedI
// Jump
jumpAdder.io.in1 := pc
jumpAdder.io.in2 := immGen.io.extendedJ
// Branch
branchUnit.io.in1 := pc
branchUnit.io.in2 := immGen.io.extendedB
// PC-select
pcSelect.io.pcPlus4 := pcPlusFour.io.result
pcSelect.io.branch := branchUnit.io.result
pcSelect.io.jump := jumpAdder.io.result
pcSelect.io.jalr := jumpReg.io.output
pcSelect.io.branchSignal := branchLogic.io.result
pcSelect.io.jumpSignal := control.io.jump
pcSelect.io.jalrSignal := control.io.jumpReg
pc := pcSelect.io.output
}
所以我的问题是:
- 如何模拟此设计以查看它是否正确执行所有指令?
- 我想 运行 基准 "dhrystone" 在它上面衡量性能。我该怎么做(这可能吗?)?如果需要,我不确定如何处理系统调用。
提前致谢!
好问题:有很多方法可以解决这个问题。
一种常见的方法是从 Chisel 中获取生成的 Verilog 并编写您自己的测试工具来实例化您的设计。此测试框架可以用 C++、Verilog、SystemVerilog 或您喜欢的其他 test-harness/glue 语言编写。
Sodor (https://github.com/ucb-bar/riscv-sodor) and Rocket-Chip (https://github.com/freechipsproject/rocket-chip) 使用这种方法,最外层的测试工具代码是用 C++ 编写的,但能够与 Verilog 模拟器(如 Verilator 和 VCS)接口。 C++ 测试逻辑允许用户通过命令行传入测试二进制文件,然后通过某种 "magic" 将二进制文件加载到测试内存中。这个魔法可能是一个外部调试接口,一个 Tether 串行接口,或者它可能是一个提供的外部 RAM 模型,可以由测试工具加载(你自己写的简单的东西或者像 dramsim2 这样复杂的东西)。
这些东西相当复杂,所以我建议从简单开始;一种选择是在 Chisel 中创建一个黑盒内存,该内存由使用 readmemh 初始化自身的简单内存支持。这里的好处是您不需要将代码重新编译为 运行 新的二进制文件,您只需换出要加载到测试内存中的文件。
Chisel 还提供了自己的独立测试器,因此也许您可以完全在 Scala 中进行测试,但我还没有看到像内核这样复杂的东西这样做,它非常依赖外部刺激和需要与外界沟通。
凿子设计 Risc-V cpu
https://fatalfeel.blogspot.com/2013/12/chisel-design-ic-for-risc-v.html
使用 rocket-chip be cpu 核心,你可以在那里找到 ~/XiangShan/rocket-chip
simulator 运行 c++ 编译器,你可以使用 Vivado 运行 simulator
也可以在intellij中调试IDE
我在Chisel3中写了一个单循环的CPU实现了大部分的RV32I指令(CSR,Fence,ECALL/BREAK,LB/SB除外,以后可能会包括在内) ).这些指令目前硬编码在指令内存中,但我将更改它,以便它改为从文件中读取指令。我 运行 遇到了如何实际模拟我的设计的麻烦。这是我将 "glued" 所有组件放在一起的代码。
class Core extends Module {
val io = IO(new Bundle {
val dc = Input(Bool())
})
io := DontCare
val pc = RegInit(0.U)
val pcSelect = Module(new PcSelect())
val pcPlusFour = Module(new Adder())
val alu = Module(new ALU())
val aluControl = Module(new AluControl())
val control = Module(new Control())
val immGen = Module(new ImmGen())
val branchLogic = Module(new BranchLogic())
val branchUnit = Module(new Adder())
val jumpReg = Module(new JumpReg())
val regFile = Module(new RegFile())
val jumpAdder = Module(new Adder())
val dataMem = Module(new DataMemory())
val instrMem = Module(new InstructionMemory())
// Mux from data memory
val dataMux = Mux(control.io.memToReg, dataMem.io.readDataOutput, alu.io.result)
// Mux to register file
val regFileMux = Mux(control.io.writeSrc, pcPlusFour.io.result, dataMux)
// PC + 4
pcPlusFour.io.in1 := pc
pcPlusFour.io.in2 := 4.U
// Instruction memory
instrMem.io.address := pc
val instruction = instrMem.io.instruction
val opcode = instruction(6, 0)
// Control
control.io.opcode := opcode
// Register file
regFile.io.readReg1 := instruction(19, 15) // rs1
regFile.io.readReg2 := instruction(24, 20) // rs2
regFile.io.writeReg := instruction(11, 7) // rd
regFile.io.regWrite := control.io.regWrite
regFile.io.writeData := regFileMux
// ALU
val aluMux1 = Mux(control.io.aluSrc1, immGen.io.extendedU, regFile.io.readData1)
alu.io.in1 := aluMux1
val src = control.io.aluSrc2
val aluMux2 = Mux(src === 1.U, immGen.io.extendedI, Mux(src === 2.U, immGen.io.extendedS, Mux(src === 3.U, pc, regFile.io.readData2)))
alu.io.in2 := aluMux2
alu.io.aluOp := aluControl.io.output
// ALU control
aluControl.io.aluOp := control.io.aluOp
aluControl.io.funct7 := instruction(31, 25)
aluControl.io.funct3 := instruction(14, 12)
// Data Memory
dataMem.io.readAddress := alu.io.result
dataMem.io.writeData := regFile.io.readData2
dataMem.io.memWrite := control.io.memWrite
dataMem.io.memRead := control.io.memRead
// Immediate generator
immGen.io.instr := instruction
// Branch logic
branchLogic.io.reg1 := regFile.io.readData1
branchLogic.io.reg2 := regFile.io.readData2
branchLogic.io.branch := control.io.branch
branchLogic.io.funct3 := instruction(14, 12)
// Jump reg
jumpReg.io.reg1 := regFile.io.readData1
jumpReg.io.imm := immGen.io.extendedI
// Jump
jumpAdder.io.in1 := pc
jumpAdder.io.in2 := immGen.io.extendedJ
// Branch
branchUnit.io.in1 := pc
branchUnit.io.in2 := immGen.io.extendedB
// PC-select
pcSelect.io.pcPlus4 := pcPlusFour.io.result
pcSelect.io.branch := branchUnit.io.result
pcSelect.io.jump := jumpAdder.io.result
pcSelect.io.jalr := jumpReg.io.output
pcSelect.io.branchSignal := branchLogic.io.result
pcSelect.io.jumpSignal := control.io.jump
pcSelect.io.jalrSignal := control.io.jumpReg
pc := pcSelect.io.output
}
所以我的问题是:
- 如何模拟此设计以查看它是否正确执行所有指令?
- 我想 运行 基准 "dhrystone" 在它上面衡量性能。我该怎么做(这可能吗?)?如果需要,我不确定如何处理系统调用。
提前致谢!
好问题:有很多方法可以解决这个问题。
一种常见的方法是从 Chisel 中获取生成的 Verilog 并编写您自己的测试工具来实例化您的设计。此测试框架可以用 C++、Verilog、SystemVerilog 或您喜欢的其他 test-harness/glue 语言编写。
Sodor (https://github.com/ucb-bar/riscv-sodor) and Rocket-Chip (https://github.com/freechipsproject/rocket-chip) 使用这种方法,最外层的测试工具代码是用 C++ 编写的,但能够与 Verilog 模拟器(如 Verilator 和 VCS)接口。 C++ 测试逻辑允许用户通过命令行传入测试二进制文件,然后通过某种 "magic" 将二进制文件加载到测试内存中。这个魔法可能是一个外部调试接口,一个 Tether 串行接口,或者它可能是一个提供的外部 RAM 模型,可以由测试工具加载(你自己写的简单的东西或者像 dramsim2 这样复杂的东西)。
这些东西相当复杂,所以我建议从简单开始;一种选择是在 Chisel 中创建一个黑盒内存,该内存由使用 readmemh 初始化自身的简单内存支持。这里的好处是您不需要将代码重新编译为 运行 新的二进制文件,您只需换出要加载到测试内存中的文件。
Chisel 还提供了自己的独立测试器,因此也许您可以完全在 Scala 中进行测试,但我还没有看到像内核这样复杂的东西这样做,它非常依赖外部刺激和需要与外界沟通。
凿子设计 Risc-V cpu
https://fatalfeel.blogspot.com/2013/12/chisel-design-ic-for-risc-v.html
使用 rocket-chip be cpu 核心,你可以在那里找到 ~/XiangShan/rocket-chip
simulator 运行 c++ 编译器,你可以使用 Vivado 运行 simulator
也可以在intellij中调试IDE