#simulation #hdl #riscv #chisel
#Симуляция #hdl #riscv #chisel
Вопрос:
Я написал одноцикловый процессор в Chisel3, который реализует большинство инструкций RV32I (за исключением CSR, Fence, ECALL / BREAK, LB / SB, которые могут быть включены позже). Инструкции в настоящее время жестко запрограммированы в памяти команд, но я изменю это, чтобы вместо этого он считывал инструкции из файла. У меня возникли проблемы с тем, как на самом деле имитировать мой дизайн. Вот код, в котором я «склеил» все компоненты вместе.
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» для измерения производительности. Как я могу это сделать (возможно ли это?)? Я не уверен, как обрабатывать системные вызовы, если это необходимо.
Заранее спасибо!
Ответ №1:
Отличный вопрос: есть несколько способов подойти к этому.
Общий подход заключается в том, чтобы взять сгенерированный Verilog из Chisel и написать свой собственный тестовый жгут для создания экземпляра вашего дизайна. Этот тестовый жгут может быть написан на C , Verilog, SystemVerilog или на другом вашем любимом языке тестирования / клея.
Этот подход используется Sodor (https://github.com/ucb-bar/riscv-sodor ) и Ракетный Чип (https://github.com/freechipsproject/rocket-chip ), с самым внешним кодом для тестирования, написанным на C , но способным взаимодействовать с симуляторами Verilog, такими как Verilator и VCS. Логика тестирования C позволяет пользователю передавать тестовый двоичный файл через командную строку, а затем загружать двоичный файл — с помощью какой-то «магии» — в тестовую память. Это волшебство может быть внешним интерфейсом отладки, последовательным интерфейсом Tether, или это может быть предоставленная внешняя модель ОЗУ, которая может быть загружена с помощью тестового жгута (либо что-то простое, что вы пишете сами, либо что-то столь же сложное, как dramsim2).
Это довольно сложно, поэтому я рекомендую начать с простого; одним из вариантов является создание памяти черного ящика в Chisel, поддерживаемой простой памятью, которая использует readmemh для инициализации себя. Приятная особенность здесь в том, что вам не нужно перекомпилировать свой код для запуска новых двоичных файлов, вы просто меняете файл, который хотите загрузить в тестовую память.
Chisel также предоставляет свои собственные автономные тестеры, поэтому, возможно, вы могли бы полностью использовать свои тесты в Scala, но я не видел, чтобы это делалось для чего-то столь сложного, как ядро, которое очень зависит от внешних раздражителей и необходимости общаться с внешним миром.
Ответ №2:
Процессор Risc-V Chisel design
https://fatalfeel.blogspot.com/2013/12/chisel-design-ic-for-risc-v.html
используя ядро процессора rocket-chip be, вы можете найти там ~/ XiangShan/ rocket-chip
симулятор запускает компилятор c , и вы можете использовать Vivado run simulator
также можно выполнять отладку в intellij IDE