Lab3 pipelined CPU renewed

- added tutorial
- fix ID reg addr invalid in certain types of instructions
- renamed some variables for better understanding
This commit is contained in:
PurplePower
2025-08-14 16:55:53 +08:00
parent 110132ff95
commit b9865cd612
33 changed files with 494 additions and 98 deletions

View File

@@ -23,17 +23,19 @@ class Top extends Module {
val io = IO(new CPUBundle)
val cpu = Module(new CPU(implementation = ImplementationType.ThreeStage))
io.device_select := 0.U
cpu.io.debug_read_address := io.debug_read_address
io.debug_read_data := cpu.io.debug_read_data
// intercept UART signals
io.device_select := cpu.io.device_select
// CPU instruction input is controlled by external codes
io.memory_bundle <> cpu.io.memory_bundle
io.instruction_address := cpu.io.instruction_address
cpu.io.instruction := io.instruction
cpu.io.instruction_valid := io.instruction_valid
cpu.io.interrupt_flag := io.interrupt_flag
cpu.io.instruction_valid := io.instruction_valid
}
object VerilogGenerator extends App {

View File

@@ -82,6 +82,11 @@ class Top(binaryFilename: String = "say_goodbye.asmbin") extends Module {
cpu.io.memory_bundle <> mem.io.bundle
}
}
when (uart.io.bundle.write_enable) {
val the_char = cpu.io.memory_bundle.write_data(7, 0)
printf(cf"${the_char.asUInt}%c")
}
}
// LED, blinks every second

View File

@@ -81,6 +81,11 @@ class Top(binaryFilename: String = "say_goodbye.asmbin") extends Module {
cpu.io.memory_bundle <> mem.io.bundle
}
}
when (uart.io.bundle.write_enable) {
val the_char = cpu.io.memory_bundle.write_data(7, 0)
printf(cf"${the_char.asUInt}%c")
}
}
// LED, blinks every second

View File

@@ -61,8 +61,8 @@ class CPU extends Module {
inst_fetch.io.rom_instruction := io.instruction
inst_fetch.io.instruction_valid := io.instruction_valid
if2id.io.stall := ctrl.io.if_stall
if2id.io.flush := ctrl.io.if_flush
if2id.io.stall := ctrl.io.if2id_stall
if2id.io.flush := ctrl.io.if2id_flush
if2id.io.instruction := inst_fetch.io.id_instruction
if2id.io.instruction_address := inst_fetch.io.instruction_address
if2id.io.interrupt_flag := io.interrupt_flag
@@ -78,7 +78,7 @@ class CPU extends Module {
id.io.interrupt_assert := clint.io.id_interrupt_assert
id.io.interrupt_handler_address := clint.io.id_interrupt_handler_address
id2ex.io.flush := ctrl.io.id_flush
id2ex.io.flush := ctrl.io.id2ex_flush
id2ex.io.instruction := if2id.io.output_instruction
id2ex.io.instruction_address := if2id.io.output_instruction_address
id2ex.io.reg1_data := regs.io.read_data1

View File

@@ -28,16 +28,16 @@ class Control extends Module {
val memory_read_enable_mem = Input(Bool())
val rd_mem = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val if_flush = Output(Bool())
val id_flush = Output(Bool())
val if2id_flush = Output(Bool())
val id2ex_flush = Output(Bool())
val pc_stall = Output(Bool())
val if_stall = Output(Bool())
val if2id_stall = Output(Bool())
})
// Lab3(Final)
io.if_flush := false.B
io.id_flush := false.B
io.if2id_flush := false.B
io.id2ex_flush := false.B
io.pc_stall := false.B
io.if_stall := false.B
io.if2id_stall := false.B
// Lab3(Final) End
}

View File

@@ -170,8 +170,10 @@ class InstructionDecode extends Module {
val rs1 = io.instruction(19, 15)
val rs2 = io.instruction(24, 20)
io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1)
// Lab3(Final) ID rs
io.regs_reg1_read_address := rs1
io.regs_reg2_read_address := rs2
// Lab3(Final) ID rs End
io.ex_immediate := MuxLookup(
opcode,
Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)),
@@ -208,10 +210,10 @@ class InstructionDecode extends Module {
Instructions.jalr -> RegWriteSource.NextInstructionAddress
)
)
io.ex_reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) ||
(opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) ||
(opcode === Instructions.jal) || (opcode === Instructions.jalr) || (opcode === Instructions.csr)
io.ex_reg_write_address := io.instruction(11, 7)
// Lab3(Final) ID rd
io.ex_reg_write_enable := false.B
io.ex_reg_write_address := rd
// Lab3(Final) ID rd End
io.ex_csr_address := io.instruction(31, 20)
io.ex_csr_write_enable := (opcode === Instructions.csr) && (
funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrwi ||

View File

@@ -58,15 +58,15 @@ class CPU extends Module {
inst_fetch.io.rom_instruction := io.instruction
inst_fetch.io.instruction_valid := io.instruction_valid
if2id.io.stall := ctrl.io.if_stall
if2id.io.flush := ctrl.io.if_flush
if2id.io.stall := ctrl.io.if2id_stall
if2id.io.flush := ctrl.io.if2id_flush
if2id.io.instruction := inst_fetch.io.id_instruction
if2id.io.instruction_address := inst_fetch.io.instruction_address
if2id.io.interrupt_flag := io.interrupt_flag
id.io.instruction := if2id.io.output_instruction
id2ex.io.flush := ctrl.io.id_flush
id2ex.io.flush := ctrl.io.id2ex_flush
id2ex.io.instruction := if2id.io.output_instruction
id2ex.io.instruction_address := if2id.io.output_instruction_address
id2ex.io.reg1_data := regs.io.read_data1

View File

@@ -25,16 +25,16 @@ class Control extends Module {
val memory_read_enable_ex = Input(Bool())
val rd_ex = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val if_flush = Output(Bool())
val id_flush = Output(Bool())
val if2id_flush = Output(Bool())
val id2ex_flush = Output(Bool())
val pc_stall = Output(Bool())
val if_stall = Output(Bool())
val if2id_stall = Output(Bool())
})
// Lab3(Forward)
io.if_flush := false.B
io.id_flush := false.B
io.if2id_flush := false.B
io.id2ex_flush := false.B
io.pc_stall := false.B
io.if_stall := false.B
io.if2id_stall := false.B
// Lab3(Forward) End
}

View File

@@ -156,8 +156,10 @@ class InstructionDecode extends Module {
val rs1 = io.instruction(19, 15)
val rs2 = io.instruction(24, 20)
io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1)
// Lab3(Forwarding) ID rs
io.regs_reg1_read_address := rs1
io.regs_reg2_read_address := rs2
// Lab3(Forwarding) ID rs End
io.ex_immediate := MuxLookup(
opcode,
Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)),
@@ -194,10 +196,10 @@ class InstructionDecode extends Module {
Instructions.jalr -> RegWriteSource.NextInstructionAddress
)
)
io.ex_reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) ||
(opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) ||
(opcode === Instructions.jal) || (opcode === Instructions.jalr) || (opcode === Instructions.csr)
io.ex_reg_write_address := io.instruction(11, 7)
// Lab3(Forwarding) ID rd
io.ex_reg_write_enable := false.B
io.ex_reg_write_address := rd
// Lab3(Forwarding) ID rd End
io.ex_csr_address := io.instruction(31, 20)
io.ex_csr_write_enable := (opcode === Instructions.csr) && (
funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrwi ||

View File

@@ -59,15 +59,15 @@ class CPU extends Module {
inst_fetch.io.rom_instruction := io.instruction
inst_fetch.io.instruction_valid := io.instruction_valid
if2id.io.stall := ctrl.io.if_stall
if2id.io.flush := ctrl.io.if_flush
if2id.io.stall := ctrl.io.if2id_stall
if2id.io.flush := ctrl.io.if2id_flush
if2id.io.instruction := inst_fetch.io.id_instruction
if2id.io.instruction_address := inst_fetch.io.instruction_address
if2id.io.interrupt_flag := io.interrupt_flag
id.io.instruction := if2id.io.output_instruction
id2ex.io.flush := ctrl.io.id_flush
id2ex.io.flush := ctrl.io.id2ex_flush
id2ex.io.instruction := if2id.io.output_instruction
id2ex.io.instruction_address := if2id.io.output_instruction_address
id2ex.io.reg1_data := regs.io.read_data1

View File

@@ -27,17 +27,17 @@ class Control extends Module {
val rd_mem = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val reg_write_enable_mem = Input(Bool())
val if_flush = Output(Bool())
val id_flush = Output(Bool())
val if2id_flush = Output(Bool())
val id2ex_flush = Output(Bool())
val pc_stall = Output(Bool())
val if_stall = Output(Bool())
val if2id_stall = Output(Bool())
})
// Lab3(Stall)
io.if_flush := false.B
io.id_flush := false.B
io.if2id_flush := false.B
io.id2ex_flush := false.B
io.pc_stall := false.B
io.if_stall := false.B
io.if2id_stall := false.B
// Lab3(Stall) End
}

View File

@@ -156,8 +156,10 @@ class InstructionDecode extends Module {
val rs1 = io.instruction(19, 15)
val rs2 = io.instruction(24, 20)
io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1)
// Lab3(Stall) ID rs
io.regs_reg1_read_address := rs1
io.regs_reg2_read_address := rs2
// Lab3(Stall) ID rs End
io.ex_immediate := MuxLookup(
opcode,
Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)),
@@ -194,10 +196,10 @@ class InstructionDecode extends Module {
Instructions.jalr -> RegWriteSource.NextInstructionAddress
)
)
io.ex_reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) ||
(opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) ||
(opcode === Instructions.jal) || (opcode === Instructions.jalr) || (opcode === Instructions.csr)
io.ex_reg_write_address := io.instruction(11, 7)
// Lab3(Stall) ID rd
io.ex_reg_write_enable := false.B
io.ex_reg_write_address := rd
// Lab3(Stall) ID rd End
io.ex_csr_address := io.instruction(31, 20)
io.ex_csr_write_enable := (opcode === Instructions.csr) && (
funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrwi ||

View File

@@ -71,3 +71,4 @@ class FiveStageCPUForwardTest extends AnyFlatSpec with ChiselScalatestTester {
}
}
}

View File

@@ -17,6 +17,8 @@ package riscv
import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import scala.util.Random
import riscv.core.fivestage_stall._
class FiveStageCPUStallTest extends AnyFlatSpec with ChiselScalatestTester {
@@ -71,3 +73,86 @@ class FiveStageCPUStallTest extends AnyFlatSpec with ChiselScalatestTester {
}
}
}
class DecoderStallTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "ID of Five-stage Pipelined CPU with Stalling"
def concatBits(values: (Int, Int)*): Int = {
values.foldLeft(0) { case (result, (value, bits)) =>
val mask = (1 << bits) - 1 // Create mask for the specified bit width
val maskedValue = value & mask // Ensure value fits in specified bits
(result << bits) | maskedValue
}
}
it should "generate correct reg addr" in {
test(new InstructionDecode).withAnnotations(TestAnnotations.annos) { c =>
for (i <- 0 to 100) {
val rs1 = Random.nextInt(32)
val rs2 = Random.nextInt(32)
val rd = Random.nextInt(32)
// for R-type instructions, rs2, rs1 and rd should be valid
// val instR = 0.U(7.W) ## rs2 ## rs1 ## 1.U(3.W) ## rd ## InstructionTypes.RM
val instR = concatBits(
(0, 7), (rs2, 5), (rs1, 5), (1, 3), (rd, 5), (InstructionTypes.RM.litValue.toInt, 7)
)
c.io.instruction.poke(instR)
c.io.regs_reg1_read_address.expect(rs1)
c.io.regs_reg2_read_address.expect(rs2)
c.io.ex_reg_write_address.expect(rd)
c.io.ex_reg_write_enable.expect(true.B)
c.clock.step()
// for I-type instructions, rs1 and rd should be valid
val instI = concatBits((0, 12), (rs1, 5), (1, 3), (rd, 5), (InstructionTypes.I.litValue.toInt, 7))
c.io.instruction.poke(instI)
c.io.regs_reg1_read_address.expect(rs1)
c.io.regs_reg2_read_address.expect(0.U)
c.io.ex_reg_write_address.expect(rd)
c.io.ex_reg_write_enable.expect(true.B)
c.clock.step()
// for S-type instructions, rs2 and rs1 should be valid
val instS = concatBits((0, 7), (rs2, 5), (rs1, 5), (1, 3), (2, 5), (InstructionTypes.S.litValue.toInt, 7))
c.io.instruction.poke(instS)
c.io.regs_reg1_read_address.expect(rs1)
c.io.regs_reg2_read_address.expect(rs2)
c.io.ex_reg_write_address.expect(0.U)
c.io.ex_reg_write_enable.expect(false.B)
c.clock.step()
// for B-type instructions, rs2 and rs1 should be valid
val instB = concatBits((0, 7), (rs2, 5), (rs1, 5), (1, 3), (2, 5), (InstructionTypes.B.litValue.toInt, 7))
c.io.instruction.poke(instB)
c.io.regs_reg1_read_address.expect(rs1)
c.io.regs_reg2_read_address.expect(rs2)
c.io.ex_reg_write_address.expect(0.U)
c.io.ex_reg_write_enable.expect(false.B)
c.clock.step()
// for U-type instructions, rd should be valid
val instU = concatBits((0, 20), (rd, 5), (Instructions.lui.litValue.toInt, 7))
c.io.instruction.poke(instU)
c.io.regs_reg1_read_address.expect(0.U)
c.io.regs_reg2_read_address.expect(0.U)
c.io.ex_reg_write_address.expect(rd)
c.io.ex_reg_write_enable.expect(true.B)
c.clock.step()
// for J-type instructions, rd should be valid
val instJ = concatBits((0, 20), (rd, 5), (Instructions.jal.litValue.toInt, 7))
c.io.instruction.poke(instJ)
c.io.regs_reg1_read_address.expect(0.U)
c.io.regs_reg2_read_address.expect(0.U)
c.io.ex_reg_write_address.expect(rd)
c.io.ex_reg_write_enable.expect(true.B)
c.clock.step()
}
}
}
}

View File

@@ -20,7 +20,7 @@ class Memory {
uint32_t read(size_t address) {
address = address / 4;
if (address >= memory.size()) {
// printf("invalid read address 0x%08x\n", address * 4);
printf("invalid read address 0x%08x\n", address * 4);
return 0;
}
return memory[address];
@@ -29,8 +29,8 @@ class Memory {
uint32_t readInst(size_t address) {
address = address / 4;
if (address >= memory.size()) {
// printf("invalid read Inst address 0x%08x\n", address * 4);
return 0;
printf("invalid read Inst address 0x%08x\n", address * 4);
return 0;
}
return memory[address];
@@ -44,7 +44,7 @@ class Memory {
if (write_strobe[2]) write_mask |= 0x00FF0000;
if (write_strobe[3]) write_mask |= 0xFF000000;
if (address >= memory.size()) {
// printf("invalid write address 0x%08x\n", address * 4);
printf("invalid write address 0x%08x\n", address * 4);
return;
}
memory[address] = (memory[address] & ~write_mask) | (value & write_mask);
@@ -133,12 +133,12 @@ class Simulator {
if (auto it = std::find(args.begin(), args.end(), "-memory");
it != args.end()) {
memory_words = std::stoul(*(it + 1));
memory_words = std::stoull(*(it + 1));
}
if (auto it = std::find(args.begin(), args.end(), "-time");
it != args.end()) {
max_sim_time = std::stoul(*(it + 1));
max_sim_time = std::stoull(*(it + 1));
}
if (auto it = std::find(args.begin(), args.end(), "-vcd");
@@ -179,9 +179,10 @@ class Simulator {
uint32_t data_memory_read_word = 0;
uint32_t inst_memory_read_word = 0;
uint32_t timer_interrupt = 0;
uint32_t counter = 0;
uint32_t clocktime = 1;
uint32_t counter = 0;
uint32_t clocktime = 1;
bool memory_write_strobe[4] = {false};
int uart_write_time_counter = 0, uart_write_time_limit = 4; // every limit, an UART write completes; this is tricky part
while (main_time < max_sim_time && !Verilated::gotFinish()) {
++main_time;
++counter;
@@ -197,17 +198,23 @@ class Simulator {
if (main_time > 2) {
top->reset = 0;
}
// top->io_mem_slave_read_data = memory_read_word;
// top->io_mem_slave_read_data = memory_read_word;
top->io_memory_bundle_read_data = data_memory_read_word;
top->io_instruction = inst_memory_read_word;
top->clock = !top->clock;
top->eval();
top->io_interrupt_flag = 0;
data_memory_read_word = memory->read(top->io_memory_bundle_address);
if (top->io_deviceSelect == 2 && top->io_memory_bundle_write_enable) {
if (uart_write_time_counter == 0) std::cout << (char)top->io_memory_bundle_write_data << std::flush; // Output to UART
uart_write_time_counter = (uart_write_time_counter + 1) % uart_write_time_limit;
}
else {
uart_write_time_counter = 0;
}
inst_memory_read_word = memory->readInst(top->io_instruction_address);
data_memory_read_word = memory->read(top->io_memory_bundle_address);
inst_memory_read_word = memory->readInst(top->io_instruction_address);
if (top->io_memory_bundle_write_enable) {
memory_write_strobe[0] = top->io_memory_bundle_write_strobe_0;
@@ -223,6 +230,11 @@ class Simulator {
break;
}
}
// print simulation progress in percentage every 1%
if (main_time % (max_sim_time / 100) == 0) {
std::cout << "Simulation progress: " << (main_time * 100 / max_sim_time) << "%" << std::endl;
}
}
if (dump_signature) {