Fix various bugs in Pipeline
Control signals in IDEX had not been assigned to the IDEX register bank and as such did not receive reset signals upon flushing.
Furthermore, added support for gui to discern between flushing and stalling.
MODIFY
src/defines.h
src/defines.h
@@ -23,8 +23,8 @@ public:
typedef struct {
uint32_t pc;
bool initialized; // If false, no text will be written above a pipeline stage when pipeline is reset
- bool invalid; // If true, a stage has been flushed (the PC for the stage is set to 0) - "nop" will be written
- // above the pipeline stage
+ uint32_t invalidReason; // 1: a stage has been flushed because of branch taken
+ // 2: A stage is stalled because of hazards
} PC;
StagePCS() {}
void reset() {

MODIFY
src/instructionmodel.cpp
src/instructionmodel.cpp
@@ -30,8 +30,12 @@ namespace {
#define VALIDATE(stage) \
if (!m_pcsptr.stage.initialized) { \
emit textChanged(Stage::stage, ""); \
- } else if (m_pcsptr.stage.invalid) { \
- emit textChanged(Stage::stage, "nop (branch taken)"); \
+ } else if (m_pcsptr.stage.invalidReason == 1) { \
+ emit textChanged(Stage::stage, "nop (flush)"); \
+ } else if (m_pcsptr.stage.invalidReason == 2) { \
+ emit textChanged(Stage::stage, "nop (stall)"); \
+ } else if (m_pcsptr.stage.invalidReason == 3) { \
+ emit textChanged(Stage::stage, ""); /*EOF*/ \
}
}
@@ -65,32 +69,32 @@ QVariant InstructionModel::data(const QModelIndex& index, int role) const {
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) {
+ if (byteIndex == m_pcsptr.EX.pc && m_pcsptr.EX.initialized && m_pcsptr.EX.invalidReason == 0) {
emit textChanged(Stage::EX, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::ID, "");
}
retStrings << "EX";
}
- if (byteIndex == m_pcsptr.ID.pc && m_pcsptr.ID.initialized && !m_pcsptr.ID.invalid) {
+ if (byteIndex == m_pcsptr.ID.pc && m_pcsptr.ID.initialized && m_pcsptr.ID.invalidReason == 0) {
emit textChanged(Stage::ID, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::IF, "");
}
retStrings << "ID";
}
- if (byteIndex == m_pcsptr.IF.pc && m_pcsptr.IF.initialized && !m_pcsptr.IF.invalid) {
+ if (byteIndex == m_pcsptr.IF.pc && m_pcsptr.IF.initialized && m_pcsptr.IF.invalidReason == 0) {
emit textChanged(Stage::IF, m_parserPtr->genStringRepr(memRead(row * 4)));
retStrings << "IF";
}
- if (byteIndex == m_pcsptr.MEM.pc && m_pcsptr.MEM.initialized && !m_pcsptr.MEM.invalid) {
+ if (byteIndex == m_pcsptr.MEM.pc && m_pcsptr.MEM.initialized && m_pcsptr.MEM.invalidReason == 0) {
emit textChanged(Stage::MEM, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::EX, "");
}
retStrings << "MEM";
}
- if (byteIndex == m_pcsptr.WB.pc && m_pcsptr.WB.initialized && !m_pcsptr.WB.invalid) {
+ if (byteIndex == m_pcsptr.WB.pc && m_pcsptr.WB.initialized && m_pcsptr.WB.invalidReason == 0) {
emit textChanged(Stage::WB, m_parserPtr->genStringRepr(memRead(row * 4)));
if (byteIndex == maxInstr) {
emit textChanged(Stage::MEM, "");
@@ -98,7 +102,7 @@ QVariant InstructionModel::data(const QModelIndex& index, int role) const {
retStrings << "WB";
}
if (retStrings.isEmpty()) {
- // Clear invalid PC values for each stage (used when resetting the simlation)
+ // Clear invalid PC values for each stage (used when resetting the program)
VALIDATE(IF);
VALIDATE(ID);
VALIDATE(EX);

MODIFY
src/pipeline.cpp
src/pipeline.cpp
@@ -45,19 +45,6 @@ Pipeline::Pipeline() {
r_invalidPC_IDEX.setInput(r_invalidPC_IFID.getOutput());
r_PC4_IDEX.setInput(r_PC4_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)
- .addToBank(&r_PC4_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, mux_alures_PC4_MEM.getOutput());
@@ -82,6 +69,29 @@ Pipeline::Pipeline() {
r_jal_IDEX.setInput(&s_jal);
r_jalr_IDEX.setInput(&s_jalr);
+ // Gather all registers in a bank - making it easier to assign equal signals to all.
+ // We do not include r_invalidPC_IDEX since this value is needed for the GUI to correctly identify flushed/stalled
+ // stages
+ 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_PC4_IDEX)
+ .addToBank(&r_regWrite_IDEX)
+ .addToBank(&r_ALUOP_IDEX)
+ .addToBank(&r_ALUSrc1_IDEX)
+ .addToBank(&r_ALUSrc2_IDEX)
+ .addToBank(&r_MemWrite_IDEX)
+ .addToBank(&r_MemRead_IDEX)
+ .addToBank(&r_memToReg_IDEX)
+ .addToBank(&r_jal_IDEX)
+ .addToBank(&r_jal_IDEX);
+ bank_IDEX.setReset(&s_IDEX_reset);
+ bank_IDEX.setRegisterControls();
+
// ------ EX ---------
alu_mainALU.setInputs(mux_ALUSrc1.getOutput(), mux_ALUSrc2.getOutput());
alu_mainALU.setControl(r_ALUOP_IDEX.getOutput());
@@ -647,13 +657,22 @@ void Pipeline::propagateCombinational() {
// 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_branchTaken && (bool)s_IFID_write;
+ s_invalidPC = (bool)s_branchTaken && (bool)s_IFID_write || s_jal;
+ s_invalidPC = (uint32_t)r_PC_IFID > m_textSize ? HazardReason::eof : s_invalidPC;
+ if (s_IDEX_reset) {
+ // ID has a dependancy and requires a stall of EX. Set IDEX s_invalidPC register accordingly
+ r_invalidPC_IDEX.overrideNext(HazardReason::STALL);
+ }
// handle ECALL I/O. If a0 = 10 for an ECALL, this sets m_finishing and increments finishing counter
// if m_finishing is set, we disable PC writing
handleEcall();
s_PCWrite = m_finishing ? 0 : s_PCWrite;
s_IFID_reset = m_finishing ? 1 : s_IFID_reset;
+ // if finishing, ECALL will stay in ID stage, and we will force overrideNext for idex stage
+ if (m_finishing) {
+ r_invalidPC_IDEX.overrideNext(HazardReason::eof);
+ }
}
void Pipeline::handleEcall() {
@@ -730,7 +749,7 @@ int Pipeline::step() {
}
#define PCVAL(pc, reg) \
- StagePCS::PC { (uint32_t) pc, true, (bool)reg }
+ StagePCS::PC { (uint32_t) pc, (uint32_t)pc < m_textSize, (uint32_t)reg }
void Pipeline::setStagePCS() {
// To validate a PC value (whether there is actually an instruction in the stage, or if the pipeline has
@@ -742,7 +761,7 @@ void Pipeline::setStagePCS() {
m_pcs.MEM = m_pcs.EX.initialized ? PCVAL(r_PC_EXMEM, r_invalidPC_EXMEM) : m_pcs.MEM;
m_pcs.EX = m_pcs.ID.initialized ? PCVAL(r_PC_IDEX, r_invalidPC_IDEX) : m_pcs.EX;
m_pcs.ID = m_pcs.IF.initialized ? PCVAL(r_PC_IFID, r_invalidPC_IFID) : m_pcs.ID;
- m_pcs.IF = PCVAL(r_PC_IF, false);
+ m_pcs.IF = PCVAL(r_PC_IF, 0);
}
int Pipeline::run() {

MODIFY
src/pipeline.h
src/pipeline.h
@@ -77,7 +77,8 @@ private:
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<1> s_invalidPC;
+
+ Signal<2> s_invalidPC;
Mux<2, 32> mux_alures_PC4_MEM;
@@ -87,7 +88,7 @@ private:
r_alures_MEMWB;
Reg<32> r_PC_IF, r_PC_IFID, r_PC_IDEX, r_PC_EXMEM, r_PC_MEMWB;
Reg<32> r_PC4_IFID, r_PC4_IDEX, r_PC4_EXMEM, r_PC4_MEMWB;
- Reg<1> r_invalidPC_IFID, r_invalidPC_IDEX, r_invalidPC_EXMEM, r_invalidPC_MEMWB;
+ Reg<2> r_invalidPC_IFID, r_invalidPC_IDEX, r_invalidPC_EXMEM, r_invalidPC_MEMWB;
Reg<1> r_jal_IDEX, r_jal_EXMEM, r_jalr_IDEX, r_jalr_EXMEM;
Signal<5> writeReg;
@@ -137,4 +138,8 @@ namespace MemToReg {
enum values { MEMREAD = 0, ALURES = 1, PC4 = 2 };
}
+namespace HazardReason {
+enum values { NONE = 0, BRANCHTAKEN = 1, STALL = 2, eof = 3 };
+}
+
#endif // PIPELINE_H

MODIFY
src/pipelineobjects.h
src/pipelineobjects.h
@@ -135,6 +135,13 @@ public:
void connect(Reg<n>& r) { setInput(&r.m_current); }
void connect(const Signal<n>* s) { setInput(s); }
Signal<n>* getOutput() { return &m_current; }
+ void overrideNext(uint32_t val) {
+ // Only used when transforming invalidPC reasons for a stage (for proper GUI visualization)
+ // sets the next state value of m_current to the overriden value, regardles of m_next
+ // WARNING! This disregards all control signals (clock enable, reset) etc.
+ m_overrideNext = true;
+ m_nextSaved = val;
+ }
explicit operator int() const { return (int)m_current; }
explicit operator uint32_t() const { return (uint32_t)m_current; }
@@ -154,11 +161,18 @@ protected:
} else {
// Do nothing - clock enable is deasserted
}
+ if (m_overrideNext) {
+ // disregard control signals and override current value
+ m_current = m_nextSaved;
+ m_overrideNext = false;
+ }
}
void save() override {
assert(m_next != nullptr);
+ if (!m_overrideNext) {
m_nextSaved = *m_next;
}
+ }
void reset() override {
m_current = Signal<n>(0);
m_nextSaved = Signal<n>(0);
@@ -168,6 +182,7 @@ private:
Signal<n> m_current;
Signal<n> m_nextSaved;
const Signal<n>* m_next;
+ bool m_overrideNext = false;
};
template <int inputs, int n>

Fix various bugs in Pipeline

Control signals in IDEX had not been assigned to the IDEX register bank and as such did not receive reset signals upon flushing.
Furthermore, added support for gui to discern between flushing and stalling.