Implement hazard detection & forwarding support
ADD
examples/binary/loadbranch.bin
examples/binary/loadbranch.bin
Binary files /dev/null and b/examples/binary/loadbranch.bin differ

MODIFY
src/instructionmodel.cpp
src/instructionmodel.cpp
@@ -43,9 +43,12 @@ QVariant InstructionModel::data(const QModelIndex& index, int role) const {
switch (index.column()) {
case 0:
return row * 4;
- case 1: { // check if instruction is in any pipeline stage, and whether the given PC for the pipeline stage is
- // valid. For the pipelinewidget, determine if an instruction in a given stage is equal to the text
- // size, if so, clear previous instruction texts in the pipeline widget
+ case 1: {
+ // check if instruction is in any pipeline stage, and whether the given PC for the pipeline stage is
+ // valid.
+ // Furthermore, because of branching, an instruction can be in multiple stages at once - therefore, we build
+ // up a return string by checking all stage program counters
+ QStringList retStrings;
uint32_t byteIndex = row * 4;
uint32_t maxInstr = m_textSize - 4;
if (byteIndex == m_pcsptr.EX.pc && m_pcsptr.EX.initialized && !m_pcsptr.EX.invalid) {
@@ -53,29 +56,34 @@ QVariant InstructionModel::data(const QModelIndex& index, int role) const {
if (byteIndex == maxInstr) {
emit textChanged(Stage::ID, "");
}
- return QString("EX");
- } else if (byteIndex == m_pcsptr.ID.pc && m_pcsptr.ID.initialized && !m_pcsptr.ID.invalid) {
+ retStrings << "EX";
+ }
+ if (byteIndex == m_pcsptr.ID.pc && m_pcsptr.ID.initialized && !m_pcsptr.ID.invalid) {
emit textChanged(Stage::ID, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::IF, "");
}
- return QString("ID");
- } else if (byteIndex == m_pcsptr.IF.pc && m_pcsptr.IF.initialized && !m_pcsptr.IF.invalid) {
+ retStrings << "ID";
+ }
+ if (byteIndex == m_pcsptr.IF.pc && m_pcsptr.IF.initialized && !m_pcsptr.IF.invalid) {
emit textChanged(Stage::IF, m_parserPtr->genStringRepr(memRead(row * 4)));
- return QString("IF");
- } else if (byteIndex == m_pcsptr.MEM.pc && m_pcsptr.MEM.initialized && !m_pcsptr.MEM.invalid) {
+ retStrings << "IF";
+ }
+ if (byteIndex == m_pcsptr.MEM.pc && m_pcsptr.MEM.initialized && !m_pcsptr.MEM.invalid) {
emit textChanged(Stage::MEM, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::EX, "");
}
- return QString("MEM");
- } else if (byteIndex == m_pcsptr.WB.pc && m_pcsptr.WB.initialized && !m_pcsptr.WB.invalid) {
+ retStrings << "MEM";
+ }
+ if (byteIndex == m_pcsptr.WB.pc && m_pcsptr.WB.initialized && !m_pcsptr.WB.invalid) {
emit textChanged(Stage::WB, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::MEM, "");
}
- return QString("WB");
- } else {
+ retStrings << "WB";
+ }
+ if (retStrings.isEmpty()) {
// Clear invalid PC values for each stage (used when resetting the simlation)
VALIDATE(IF);
VALIDATE(ID);
@@ -83,6 +91,9 @@ QVariant InstructionModel::data(const QModelIndex& index, int role) const {
VALIDATE(MEM);
VALIDATE(WB);
return QVariant();
+ } else {
+ // Join strings by "/"
+ return retStrings.join("/");
}
}
case 2:

MODIFY
src/parser.cpp
src/parser.cpp
@@ -265,15 +265,15 @@ QString Parser::generateLoadString(uint32_t instr) const {
// dereferencing. This will handle whether to sign or zero extend.
switch (fields[2]) {
case 0b000: // LB - load sign extended byte
- return QString("lb x%1 %2(x%3)").arg(fields[3]).arg(fields[0]).arg(fields[1]);
+ return QString("lb x%1 %2(x%3)").arg(fields[3]).arg(signextend<int, 12>(fields[0])).arg(fields[1]);
case 0b001: // LH load sign extended halfword
- return QString("lh x%1 %2(x%3)").arg(fields[3]).arg(fields[0]).arg(fields[1]);
+ return QString("lh x%1 %2(x%3)").arg(fields[3]).arg(signextend<int, 12>(fields[0])).arg(fields[1]);
case 0b010: // LW load word
- return QString("lw x%1 %2(x%3)").arg(fields[3]).arg(fields[0]).arg(fields[1]);
+ return QString("lw x%1 %2(x%3)").arg(fields[3]).arg(signextend<int, 12>(fields[0])).arg(fields[1]);
case 0b100: // LBU load zero extended byte
- return QString("lbu x%1 %2(x%3)").arg(fields[3]).arg(fields[0]).arg(fields[1]);
+ return QString("lbu x%1 %2(x%3)").arg(fields[3]).arg(signextend<int, 12>(fields[0])).arg(fields[1]);
case 0b101: // LHU load zero extended halfword
- return QString("lhu x%1 %2(x%3)").arg(fields[3]).arg(fields[0]).arg(fields[1]);
+ return QString("lhu x%1 %2(x%3)").arg(fields[3]).arg(signextend<int, 12>(fields[0])).arg(fields[1]);
default:
return QString();
}

MODIFY
src/pipeline.cpp
src/pipeline.cpp
@@ -12,20 +12,55 @@ Pipeline::Pipeline() {
alu_pc4.setInputs(r_PC_IF.getOutput(), &c_4);
alu_pc4.setControl(&s_alu_const_add);
r_PC_IF.setInput(mux_PCSrc.getOutput());
+ r_PC_IF.setEnable(&s_PCWrite);
r_instr_IFID.setInput(&s_instr_IF);
r_instr_IFID.setReset(&s_PCSrc);
+ r_instr_IFID.setEnable(&s_IFID_write);
mux_PCSrc.setControl(&s_PCSrc);
mux_PCSrc.setInput(0, alu_pc4.getOutput());
mux_PCSrc.setInput(1, alu_pc_target.getOutput());
+ r_PC_IFID.setInput(r_PC_IF.getOutput());
+ r_PC_IFID.setReset(&s_PCSrc);
+ r_PC_IFID.setEnable(&s_IFID_write);
+ r_invalidPC_IFID.setInput(&s_invalidPC);
+
// ------ ID ---------
- m_reg.setInputs(r_instr_IFID.getOutput(), r_writeReg_MEMWB.getOutput(), mux_memToReg.getOutput(),
+ m_reg.setInputs(&s_readRegister1, &s_readRegister2, r_writeReg_MEMWB.getOutput(), mux_memToReg.getOutput(),
r_regWrite_MEMWB.getOutput());
r_imm_IDEX.setInput(&s_imm_ID);
r_rd1_IDEX.setInput(m_reg.getOutput(1));
r_rd2_IDEX.setInput(m_reg.getOutput(2));
r_writeReg_IDEX.setInput(&writeReg);
+ r_readRegister1_IDEX.setInput(&s_readRegister1);
+ r_readRegister2_IDEX.setInput(&s_readRegister2);
+
+ r_PC_IDEX.setInput(r_PC_IFID.getOutput());
+ r_invalidPC_IDEX.setInput(r_invalidPC_IFID.getOutput());
+
+ // Gather all registers in a bank - making it easier assign equal signals to all
+ bank_IDEX.addToBank(&r_imm_IDEX)
+ .addToBank(&r_rd1_IDEX)
+ .addToBank(&r_rd2_IDEX)
+ .addToBank(&r_writeReg_IDEX)
+ .addToBank(&r_readRegister1_IDEX)
+ .addToBank(&r_readRegister2_IDEX)
+ .addToBank(&r_PC_IDEX)
+ .addToBank(&r_invalidPC_IDEX);
+ bank_IDEX.setReset(&s_IDEX_reset);
+ bank_IDEX.setRegisterControls();
+
+ mux_forwardA_ID.setControl(&s_forwardA_ID);
+ mux_forwardA_ID.setInput(Forwarding::NONE, m_reg.getOutput(1));
+ mux_forwardA_ID.setInput(Forwarding::EXMEM, r_alures_EXMEM.getOutput());
+ mux_forwardA_ID.setInput(Forwarding::MEMWB, mux_memToReg.getOutput());
+
+ mux_forwardB_ID.setControl(&s_forwardB_ID);
+ mux_forwardB_ID.setInput(Forwarding::NONE, m_reg.getOutput(2));
+ mux_forwardB_ID.setInput(Forwarding::EXMEM, r_alures_EXMEM.getOutput());
+ mux_forwardB_ID.setInput(Forwarding::MEMWB, mux_memToReg.getOutput());
+
alu_pc_target.setControl(&s_alu_const_add);
alu_pc_target.setInputs(r_PC_IFID.getOutput(), &s_imm_ID);
@@ -38,15 +73,29 @@ Pipeline::Pipeline() {
r_MemtoReg_IDEX.setInput(&s_MemToReg);
// ------ EX ---------
- alu_mainALU.setInputs(r_rd1_IDEX.getOutput(), mux_ALUSrc.getOutput());
- alu_mainALU.setControl(&alu_ctrl);
- mux_ALUSrc.setInput(0, r_rd2_IDEX.getOutput());
+ alu_mainALU.setInputs(mux_forwardA_EX.getOutput(), mux_ALUSrc.getOutput());
+ alu_mainALU.setControl(r_ALUOP_IDEX.getOutput());
+
+ mux_ALUSrc.setInput(0, mux_forwardB_EX.getOutput());
mux_ALUSrc.setInput(1, r_imm_IDEX.getOutput());
mux_ALUSrc.setControl(r_ALUSrc_IDEX.getOutput());
r_alures_EXMEM.setInput(alu_mainALU.getOutput());
- r_writeData_EXMEM.setInput(r_rd2_IDEX.getOutput());
+ r_writeData_EXMEM.setInput(mux_forwardB_EX.getOutput());
r_writeReg_EXMEM.setInput(r_writeReg_IDEX.getOutput());
+ r_PC_EXMEM.setInput(r_PC_IDEX.getOutput());
+ r_invalidPC_EXMEM.setInput(r_invalidPC_IDEX.getOutput());
+
+ mux_forwardA_EX.setControl(&s_forwardA_EX);
+ mux_forwardA_EX.setInput(Forwarding::NONE, r_rd1_IDEX.getOutput());
+ mux_forwardA_EX.setInput(Forwarding::EXMEM, r_alures_EXMEM.getOutput());
+ mux_forwardA_EX.setInput(Forwarding::MEMWB, mux_memToReg.getOutput());
+
+ mux_forwardB_EX.setControl(&s_forwardB_EX);
+ mux_forwardB_EX.setInput(Forwarding::NONE, r_rd2_IDEX.getOutput());
+ mux_forwardB_EX.setInput(Forwarding::EXMEM, r_alures_EXMEM.getOutput());
+ mux_forwardB_EX.setInput(Forwarding::MEMWB, mux_memToReg.getOutput());
+
// Control signals
r_regWrite_EXMEM.setInput(r_regWrite_IDEX.getOutput());
r_MemWrite_EXMEM.setInput(r_MemWrite_IDEX.getOutput());
@@ -61,26 +110,13 @@ Pipeline::Pipeline() {
// Control signals
r_regWrite_MEMWB.setInput(r_regWrite_EXMEM.getOutput());
r_MemtoReg_MEMWB.setInput(r_MemtoReg_EXMEM.getOutput());
+ r_PC_MEMWB.setInput(r_PC_EXMEM.getOutput());
+ r_invalidPC_MEMWB.setInput(r_invalidPC_EXMEM.getOutput());
// ------ WB ---------
mux_memToReg.setInput(0, r_alures_MEMWB.getOutput());
mux_memToReg.setInput(1, r_readData_MEMWB.getOutput());
mux_memToReg.setControl(r_MemtoReg_MEMWB.getOutput());
-
- // Program counters & PC invalidation signals
- // Propagate invalidPC signal through pipeline, so GUI can display nop's on invalidated stages
- r_PC_IFID.setInput(r_PC_IF.getOutput());
- r_PC_IFID.setReset(&s_PCSrc);
- r_invalidPC_IFID.setInput(&s_PCSrc);
-
- r_PC_IDEX.setInput(r_PC_IFID.getOutput());
- r_invalidPC_IDEX.setInput(r_invalidPC_IFID.getOutput());
-
- r_PC_EXMEM.setInput(r_PC_IDEX.getOutput());
- r_invalidPC_EXMEM.setInput(r_invalidPC_IDEX.getOutput());
-
- r_PC_MEMWB.setInput(r_PC_EXMEM.getOutput());
- r_invalidPC_MEMWB.setInput(r_invalidPC_EXMEM.getOutput());
}
void Pipeline::immGen() {
@@ -199,6 +235,26 @@ void Pipeline::controlGen() {
s_ALUOP = ALUDefs::AND;
break;
}
+ case 0b001: {
+ // SLLI
+ s_ALUOP = ALUDefs::SL;
+ break;
+ }
+ case 0b101: {
+ switch ((uint32_t)r_instr_IFID >> 25) {
+ case 0b0: {
+ // SRLI
+ s_ALUOP = ALUDefs::SRL;
+ break;
+ }
+ case 0b0100000: {
+ // SRAI
+ s_ALUOP = ALUDefs::SRA;
+ break;
+ }
+ }
+ break;
+ }
}
CTRL_I_TYPE
break;
@@ -379,10 +435,98 @@ void Pipeline::controlGen() {
}
}
+namespace {
+// Assert that the regWrite signal is asserted for a stage when checking forwarding, and that the destination registor
+// is not 0. If this check is not done, coincidences between ie. SW immediate fields and rd fields can occur
+#define EXMEM_WILL_WRITE (uint32_t) r_writeReg_EXMEM != 0 && (bool)r_regWrite_EXMEM
+#define MEMWB_WILL_WRITE (uint32_t) r_writeReg_MEMWB != 0 && (bool)r_regWrite_MEMWB
+}
+
+void Pipeline::forwardingControlGen() {
+ // After clocking new values in all registers, examine register sources and assign forwading multiplexer control
+ // signals accordingly
+
+ // ----- ID stage ----- forwarding to register comparison logic
+ // Register 1
+ if ((uint32_t)s_readRegister1 == (uint32_t)r_writeReg_EXMEM && EXMEM_WILL_WRITE) {
+ // Forward alures
+ s_forwardA_ID = Forwarding::EXMEM;
+ } else if ((uint32_t)s_readRegister1 == (uint32_t)r_writeReg_MEMWB && MEMWB_WILL_WRITE) {
+ // Forward memread
+ s_forwardA_ID = Forwarding::MEMWB;
+ } else {
+ s_forwardA_ID = Forwarding::NONE;
+ }
+
+ // Register 2
+ if ((uint32_t)s_readRegister2 == (uint32_t)r_writeReg_EXMEM && EXMEM_WILL_WRITE) {
+ s_forwardB_ID = Forwarding::EXMEM;
+ } else if ((uint32_t)s_readRegister2 == (uint32_t)r_writeReg_MEMWB && MEMWB_WILL_WRITE) {
+ s_forwardB_ID = Forwarding::MEMWB;
+ } else {
+ s_forwardB_ID = Forwarding::NONE;
+ }
+
+ // ----- EX stage ----- forwarding to ALU
+ // Register 1
+ if ((uint32_t)r_readRegister1_IDEX == (uint32_t)r_writeReg_EXMEM && EXMEM_WILL_WRITE) {
+ s_forwardA_EX = Forwarding::EXMEM;
+ } else if ((uint32_t)r_readRegister1_IDEX == (uint32_t)r_writeReg_MEMWB && MEMWB_WILL_WRITE) {
+ s_forwardA_EX = Forwarding::MEMWB;
+ } else {
+ s_forwardA_EX = Forwarding::NONE;
+ }
+
+ // Register 2
+ if ((uint32_t)r_readRegister2_IDEX == (uint32_t)r_writeReg_EXMEM && EXMEM_WILL_WRITE) {
+ s_forwardB_EX = Forwarding::EXMEM;
+ } else if ((uint32_t)r_readRegister2_IDEX == (uint32_t)r_writeReg_MEMWB && MEMWB_WILL_WRITE) {
+ s_forwardB_EX = Forwarding::MEMWB;
+ } else {
+ s_forwardB_EX = Forwarding::NONE;
+ }
+}
+
+void Pipeline::hazardControlGen() {
+ // Some shorter names for readability
+ uint32_t r1 = (uint32_t)s_readRegister1;
+ uint32_t r2 = (uint32_t)s_readRegister2;
+
+ // Branch hazard: Result from EX stage is needed, or value from memory is needed
+ bool branchHazard = (r1 == (uint32_t)r_writeReg_IDEX || // EX hazard
+ r2 == (uint32_t)r_writeReg_IDEX || // EX hazard
+ ((r1 == (uint32_t)r_writeReg_EXMEM) && (r_MemRead_IDEX || r_MemRead_EXMEM)) || // MEM hazard
+ ((r2 == (uint32_t)r_writeReg_EXMEM) && (r_MemRead_IDEX || r_MemRead_EXMEM))) && // MEM hazard
+ ((uint32_t)r_instr_IFID & 0b1111111) == 0b1100011;
+
+ // Load Use hazard: Loaded variable is needed in execute stage
+ bool loadUseHazard = (r1 == (uint32_t)r_writeReg_IDEX || r2 == (uint32_t)r_writeReg_IDEX) && (bool)r_MemRead_IDEX;
+
+ if (branchHazard || loadUseHazard) { // Require branch instruction
+ // Stall until hazard is resolved - keep IFID and PC vaues, and reset IDEX registers
+ s_PCWrite = 0;
+ s_IFID_write = 0;
+ s_IDEX_reset = 1;
+ } else {
+ s_PCWrite = 1;
+ s_IFID_write = 1;
+ s_IDEX_reset = 0;
+ }
+}
+
void Pipeline::propagateCombinational() {
// The order of component updating must be done in the correct sequential order!
// The rightmost stage is updated first, going from left to right in the given stage.
// This is to ensure that feedback signals are valid
+
+ // Extract register sources from current instruction
+ s_readRegister1 = (((uint32_t)r_instr_IFID) >> 15) & 0b11111;
+ s_readRegister2 = (((uint32_t)r_instr_IFID) >> 20) & 0b11111;
+
+ // Do hazard detection and forwarding control generation
+ forwardingControlGen();
+ hazardControlGen();
+
// ----- WB -----
mux_memToReg.update();
@@ -413,7 +557,9 @@ void Pipeline::propagateCombinational() {
}
// ----- EX -----
+ mux_forwardB_EX.update();
mux_ALUSrc.update();
+ mux_forwardA_EX.update();
alu_mainALU.update();
// ----- ID -----
@@ -422,31 +568,33 @@ void Pipeline::propagateCombinational() {
alu_pc_target.update();
m_reg.update();
writeReg = Signal<5>(((uint32_t)r_instr_IFID >> 7) & 0b11111);
+ mux_forwardA_ID.update();
+ mux_forwardB_ID.update();
// Compare read register values and '&' with s_branch control signal
switch ((CompOp)(int)s_CompOp) {
case BEQ: {
- s_PCSrc = s_Branch && ((uint32_t)*m_reg.getOutput(1) == (uint32_t)*m_reg.getOutput(2));
+ s_PCSrc = s_Branch && ((uint32_t)mux_forwardA_ID == (uint32_t)mux_forwardB_ID);
break;
}
case BNE: {
- s_PCSrc = s_Branch && ((uint32_t)*m_reg.getOutput(1) != (uint32_t)*m_reg.getOutput(2));
+ s_PCSrc = s_Branch && ((uint32_t)mux_forwardA_ID != (uint32_t)mux_forwardB_ID);
break;
}
case BLT: {
- s_PCSrc = s_Branch && ((int32_t)*m_reg.getOutput(1) < (int32_t)*m_reg.getOutput(2));
+ s_PCSrc = s_Branch && ((int32_t)mux_forwardA_ID < (int32_t)mux_forwardB_ID);
break;
}
case BLTU: {
- s_PCSrc = s_Branch && ((uint32_t)*m_reg.getOutput(1) < (uint32_t)*m_reg.getOutput(2));
+ s_PCSrc = s_Branch && ((uint32_t)mux_forwardA_ID < (uint32_t)mux_forwardB_ID);
break;
}
case BGE: {
- s_PCSrc = s_Branch && ((int32_t)*m_reg.getOutput(1) >= (int32_t)*m_reg.getOutput(2));
+ s_PCSrc = s_Branch && ((int32_t)mux_forwardA_ID >= (int32_t)mux_forwardB_ID);
break;
}
case BGEU: {
- s_PCSrc = s_Branch && ((uint32_t)*m_reg.getOutput(1) >= (uint32_t)*m_reg.getOutput(2));
+ s_PCSrc = s_Branch && ((uint32_t)mux_forwardA_ID >= (uint32_t)mux_forwardB_ID);
break;
}
default: { s_PCSrc = 0; }
@@ -459,6 +607,10 @@ void Pipeline::propagateCombinational() {
// Load nops if PC is greater than text size
s_instr_IF = Signal<32>(
(uint32_t)r_PC_IF > m_textSize ? 0 : m_memory.read((uint32_t)r_PC_IF)); // Read instruction at current PC
+
+ // For GUI - set invalidPC (branch taken indicator) if PCSrc both PCSrc and s_IFID_write is asserted - in this
+ // case, a new program counter value is starting to propagate, indicating an invalid ID branch
+ s_invalidPC = (bool)s_PCSrc && (bool)s_IFID_write;
}
int Pipeline::step() {
@@ -494,7 +646,7 @@ int Pipeline::step() {
setStagePCS();
// Execution is finished if nops are in all stages except WB
- if (m_pcs.WB.pc == m_textSize) {
+ if (m_pcs.WB.pc > m_textSize) {
m_finished = true;
return 1;
} else {

MODIFY
src/pipeline.h
src/pipeline.h
@@ -33,6 +33,8 @@ private:
void propagateCombinational();
void immGen();
void controlGen();
+ void forwardingControlGen();
+ void hazardControlGen();
void aluCtrl();
void clock();
bool m_ready = false;
@@ -56,7 +58,6 @@ private:
ALU<32> alu_pc_target = ALU<32>("ALU PC target"); // ALU for PC target computation
ALU<32> alu_mainALU = ALU<32>("Main ALU"); // Arithmetic ALU
- Signal<ALUDefs::CTRL_SIZE> alu_ctrl;
Mux<2, 32> mux_ALUSrc;
Mux<2, 32> mux_memToReg;
Signal<1> ctrl_memToReg;
@@ -65,7 +66,15 @@ private:
Signal<32> s_instr_IF = Signal<32>("Instruction");
Signal<32> readData_MEM;
+ Signal<5> s_readRegister1, s_readRegister2;
+
+ Mux<3, 32> mux_forwardA_EX, mux_forwardB_EX, mux_forwardA_ID,
+ mux_forwardB_ID; // Forwarding mux'es for execute stage and ID stage (branch comparison operation)
+ Signal<2> s_forwardA_EX, s_forwardB_EX, s_forwardA_ID, s_forwardB_ID;
+ Signal<1> s_invalidPC;
+
// Registers
+ Reg<5> r_readRegister1_IDEX, r_readRegister2_IDEX;
std::vector<RegBase*> m_regs;
Reg<32> r_instr_IFID, r_rd1_IDEX, r_rd2_IDEX, r_imm_IDEX, r_alures_EXMEM, r_writeData_EXMEM, r_readData_MEMWB,
r_alures_MEMWB;
@@ -73,6 +82,7 @@ private:
Reg<1> r_invalidPC_IFID, r_invalidPC_IDEX, r_invalidPC_EXMEM, r_invalidPC_MEMWB;
Signal<5> writeReg;
Reg<5> r_writeReg_IDEX, r_writeReg_EXMEM, r_writeReg_MEMWB; // Write register (# of register to write to)
+ RegBank bank_IDEX;
// Control propagation registers
Reg<1> r_regWrite_IDEX, r_regWrite_EXMEM, r_regWrite_MEMWB; // Register write signal
@@ -91,11 +101,17 @@ private:
Signal<ALUDefs::CTRL_SIZE> s_ALUOP;
Signal<3> s_CompOp;
Signal<1> s_validPC; // used for GUI to determine whether a PC = 0 is a nop (register flush) or valid
+ Signal<1> s_PCWrite; // Used when a branch instruction requires a pipeline stall
+ Signal<1> s_IFID_write; // Used when a branch instruction requires a pipeline stall
+ Signal<1> s_IDEX_reset; // Used when a branch instruction requires a pipeline stall
// Control signal enums
enum CompOp { BEQ = 1, BNE = 2, BLT = 3, BLTU = 4, BGE = 5, BGEU = 6 };
enum MemRead { LB = 1, LH = 2, LW = 3, LBU = 4, LHU = 5 };
enum MemWrite { SB = 1, SH = 2, SW = 3 };
};
+namespace Forwarding {
+enum MuxForward { NONE = 0, EXMEM = 1, MEMWB = 2 };
+}
#endif // PIPELINE_H

MODIFY
src/pipelineobjects.cpp
src/pipelineobjects.cpp
@@ -5,23 +5,24 @@ std::vector<RegBase*> RegBase::registers;
void Registers::update() {
// Read
- int readRegister1 = (((uint32_t)*m_instr) >> 15) & 0b11111;
- int readRegister2 = (((uint32_t)*m_instr) >> 20) & 0b11111;
- m_readData1 = Signal<32>(m_reg[readRegister1]);
- m_readData2 = Signal<32>(m_reg[readRegister2]);
+ m_readData1 = Signal<32>(m_reg[(uint32_t)*m_readRegister1]);
+ m_readData2 = Signal<32>(m_reg[(uint32_t)*m_readRegister2]);
}
void Registers::clock() {
// if regWrite is high, write data to register
if (*m_regWrite)
m_reg[(uint32_t)*m_writeRegister] = (uint32_t)*m_writeData;
+ update();
}
-void Registers::setInputs(Signal<32>* instr, Signal<5>* writeReg, Signal<32>* writeData, Signal<1>* regWrite) {
+void Registers::setInputs(Signal<5>* readRegister1, Signal<5>* readRegister2, Signal<5>* writeReg,
+ Signal<32>* writeData, Signal<1>* regWrite) {
m_regWrite = regWrite;
m_writeData = writeData;
m_writeRegister = writeReg;
- m_instr = instr;
+ m_readRegister1 = readRegister1;
+ m_readRegister2 = readRegister2;
}
void Registers::init() {

MODIFY
src/pipelineobjects.h
src/pipelineobjects.h
@@ -72,8 +72,11 @@ private:
// The Reg class is used for sequential signal assignment. Has a single input/output of a given signal size.
// Upon construction, new registers are added to "registers", which is used when clocking the pipeline
-// input reset is a synchronous reset
+// Input "Reset" is a synchronous reset
+// input "Enable" is a synchronous clock enable signal. If not set (nullptr), it is tied to High (always enabled)
class RegBase {
+ friend class RegBank;
+
public:
static std::vector<RegBase*> registers;
static void clockAll() {
@@ -86,11 +89,38 @@ public:
static void resetAll() {
std::for_each(registers.begin(), registers.end(), [](auto& reg) { reg->reset(); });
}
+ void setReset(const Signal<1>* in) { m_reset = in; }
+ void setEnable(const Signal<1>* in) { m_enable = in; }
protected:
virtual void clock() = 0;
virtual void save() = 0;
virtual void reset() = 0;
+
+ const Signal<1>* m_reset;
+ const Signal<1>* m_enable;
+};
+
+class RegBank {
+public:
+ // Collection of RegBase pointers - for grouping registers and setting equal control signals to all
+ RegBank& addToBank(RegBase* regPtr) {
+ registers.push_back(regPtr);
+ return *this;
+ }
+ void setReset(const Signal<1>* sig) { m_reset = sig; }
+ void setEnable(const Signal<1>* sig) { m_enable = sig; }
+ void setRegisterControls() {
+ if (m_reset != nullptr)
+ std::for_each(registers.begin(), registers.end(), [this](auto& reg) { reg->setReset(m_reset); });
+ if (m_enable != nullptr)
+ std::for_each(registers.begin(), registers.end(), [this](auto& reg) { reg->setEnable(m_enable); });
+ }
+
+private:
+ std::vector<RegBase*> registers;
+ const Signal<1>* m_reset;
+ const Signal<1>* m_enable;
};
template <int n>
@@ -110,10 +140,10 @@ public:
explicit operator uint32_t() const { return (uint32_t)m_current; }
explicit operator bool() const { return (bool)m_current; }
void setInput(const Signal<n>* in) { m_next = in; }
- void setReset(const Signal<1>* in) { m_reset = in; }
protected:
void clock() override {
+ if (m_enable == nullptr || (bool)*m_enable == true) {
if (m_reset == nullptr)
m_current = m_nextSaved;
else if ((bool)*m_reset == true) {
@@ -121,6 +151,9 @@ protected:
} else {
m_current = m_nextSaved;
}
+ } else {
+ // Do nothing - clock enable is deasserted
+ }
}
void save() override {
assert(m_next != nullptr);
@@ -134,7 +167,6 @@ protected:
private:
Signal<n> m_current;
Signal<n> m_nextSaved;
- const Signal<1>* m_reset;
const Signal<n>* m_next;
};
@@ -154,6 +186,8 @@ public:
void setControl(const Signal<bitcount(inputs)>* sig) { m_control = sig; }
Signal<n>* getOutput() { return &m_output; }
+ explicit operator uint32_t() const { return (uint32_t)m_output; }
+ explicit operator int() const { return (int)m_output; }
protected:
std::vector<const Signal<n>*> m_inputs = std::vector<const Signal<n>*>(inputs);
@@ -179,7 +213,7 @@ public:
{
if (!this->initialized())
throw std::runtime_error("Mux not initialized");
- this->m_output = *this->m_inputs[(uint32_t) * this->m_control];
+ this->m_output = (uint32_t) * this->m_inputs[(uint32_t) * this->m_control];
}
}
};
@@ -216,7 +250,22 @@ private:
namespace ALUDefs {
static const int CTRL_SIZE = 5;
-enum OPCODE { ADD, SUB, MUL, DIV, AND, OR, XOR, SL, SRA, SRL, LUI, LT /*Less than*/, LTU /*Less than Unsigned*/, EQ };
+enum OPCODE {
+ ADD = 0,
+ SUB = 1,
+ MUL = 2,
+ DIV = 3,
+ AND = 4,
+ OR = 5,
+ XOR = 6,
+ SL = 7,
+ SRA = 8,
+ SRL = 9,
+ LUI = 10,
+ LT = 11,
+ LTU = 12,
+ EQ = 13
+};
} // namespace ALUDefs
template <int n>
@@ -281,10 +330,10 @@ void ALU<n>::update() {
m_output = (uint32_t)*m_op1 << (uint32_t)*m_op2;
break;
case ALUDefs::SRA:
- m_output = (uint32_t)*m_op1 >> (uint32_t)*m_op2;
+ m_output = (int32_t)*m_op1 >> (uint32_t)*m_op2;
break;
case ALUDefs::SRL:
- m_output = (int)*m_op1 + (uint32_t)*m_op2;
+ m_output = (uint32_t)*m_op1 >> (uint32_t)*m_op2;
break;
case ALUDefs::LUI:
m_output = (uint32_t)*m_op2;
@@ -314,13 +363,15 @@ public:
}
void init();
- void setInputs(Signal<32>* instr, Signal<5>* writeReg, Signal<32>* writeData, Signal<1>* regWrite);
+ void setInputs(Signal<5>* readRegister1, Signal<5>* readRegister2, Signal<5>* writeReg, Signal<32>* writeData,
+ Signal<1>* regWrite);
Signal<32>* getOutput(int n = 1) { return n == 2 ? &m_readData2 : &m_readData1; }
private:
// ReadRegister 1/2 is deduced from the input instruction signal
const Signal<1>* m_regWrite;
- const Signal<32>* m_instr;
+ const Signal<5>* m_readRegister1;
+ const Signal<5>* m_readRegister2;
const Signal<5>* m_writeRegister;
const Signal<32>* m_writeData;
Signal<32> m_readData1;

MODIFY
src/processortab.cpp
src/processortab.cpp
@@ -12,18 +12,19 @@ ProcessorTab::ProcessorTab(QWidget* parent) : QWidget(parent), m_ui(new Ui::Proc
// Setup buttons
connect(m_ui->start, &QPushButton::toggled, [=](bool state) {
if (state) {
- m_ui->start->setText("Pause simulation");
+ m_ui->start->setText("Pause simulation (F5)");
+ m_ui->start->setShortcut(
+ Qt::Key_F5); // The shortcut is for some reason cleared when editing the button text
m_timer.start();
} else {
- m_ui->start->setText("Start simulation");
+ m_ui->start->setText("Start simulation (F5)");
+ m_ui->start->setShortcut(Qt::Key_F5);
m_timer.stop();
};
m_ui->step->setEnabled(!state);
});
// Setup execution speed slider
- m_ui->execSpeed->setSliderPosition(2);
-
connect(m_ui->execSpeed, &QSlider::valueChanged, [=](int pos) {
// Reverse the slider, going from high to low
const static int delay = m_ui->execSpeed->maximum() + m_ui->execSpeed->minimum();
@@ -70,7 +71,6 @@ void ProcessorTab::initInstructionView() {
m_ui->instructionView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_ui->instructionView->setModel(m_instrModel);
m_ui->instructionView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
- m_ui->instructionView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
m_ui->instructionView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
connect(this, &ProcessorTab::update, m_ui->instructionView, QOverload<>::of(&QWidget::update));
connect(this, &ProcessorTab::update, m_instrModel, &InstructionModel::update);

MODIFY
src/processortab.ui
src/processortab.ui
<string>Start simulation, stepping through the program</string>
</property>
<property name="text">
- <string>Start simulation</string>
+ <string>Start simulation (F5)</string>
+ </property>
+ <property name="shortcut">
+ <string>F5</string>
</property>
<property name="checkable">
<bool>true</bool>
<item>
<widget class="QPushButton" name="step">
<property name="text">
- <string>Step</string>
+ <string>Step (F6)</string>
+ </property>
+ <property name="shortcut">
+ <string>F6</string>
</property>
</widget>
</item>
<string/>
</property>
<property name="text">
- <string>Reset</string>
+ <string>Reset (F7)</string>
+ </property>
+ <property name="shortcut">
+ <string>F7</string>
</property>
</widget>
</item>
</sizepolicy>
</property>
<property name="minimum">
- <number>100</number>
+ <number>50</number>
</property>
<property name="maximum">
- <number>700</number>
+ <number>500</number>
+ </property>
+ <property name="value">
+ <number>250</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</sizepolicy>
</property>
<property name="toolTip">
- <string>Fit pipeline to view</string>
+ <string>Fit pipeline to view (F10)</string>
</property>
<property name="text">
<string>...</string>
<height>24</height>
</size>
</property>
+ <property name="shortcut">
+ <string>F10</string>
+ </property>
</widget>
</item>
<item>

Implement hazard detection & forwarding support