Simulator correctly runs simple programs
this does not include Hazard detection, control flow changes and save & load instructions.
ADD
examples/binary/addposnoforwarding.bin
examples/binary/addposnoforwarding.bin
Binary files /dev/null and b/examples/binary/addposnoforwarding.bin differ

MODIFY
src/pipeline.cpp
src/pipeline.cpp
#include "pipeline.h"
+#include "parser.h"
+
Pipeline::Pipeline() {
// Connect pipeline
// ------ IF ---------
// Connect alu to PC and a constant "4" signal, and set operator to "Add"
c_4 = Signal<32>(4);
- m_add_pc4.setInputs(r_PC_IF.getOutput(), &c_4);
- m_add_pc4.setControl(&alu_const_add);
- r_PC_IF.setInput(m_add_pc4.getOutput());
+ alu_pc4.setInputs(r_PC_IF.getOutput(), &c_4);
+ alu_pc4.setControl(&s_alu_const_add);
+ r_PC_IF.setInput(alu_pc4.getOutput());
r_instr_IFID.setInput(&instr_IF);
// ------ ID ---------
- m_reg.setInputs(r_instr_IFID.getOutput(), r_writeReg_MEMWB.getOutput(), mux_memToReg.getOutput(), &m_regWrite);
+ m_reg.setInputs(r_instr_IFID.getOutput(), r_writeReg_MEMWB.getOutput(), mux_memToReg.getOutput(),
+ r_regWrite_MEMWB.getOutput());
r_imm_IDEX.setInput(&imm_ID);
r_rd1_IDEX.setInput(m_reg.getOutput(1));
r_rd2_IDEX.setInput(m_reg.getOutput(2));
r_writeReg_IDEX.setInput(&writeReg);
+ // Control signals
+ r_regWrite_IDEX.setInput(&s_RegWrite);
+ r_ALUOP_IDEX.setInput(&s_ALUOP);
+ r_ALUSrc_IDEX.setInput(&s_ALUSrc);
+ r_MemWrite_IDEX.setInput(&s_MemWrite);
+ r_MemRead_IDEX.setInput(&s_MemRead);
+ r_MemtoReg_IDEX.setInput(&s_MemtoReg);
+
// ------ EX ---------
- m_alu.setInputs(r_rd1_IDEX.getOutput(), r_rd2_IDEX.getOutput());
- m_alu.setControl(&alu_ctrl);
- r_alures_EXMEM.setInput(m_alu.getOutput());
+ alu_mainALU.setInputs(r_rd1_IDEX.getOutput(), mux_ALUSrc.getOutput());
+ alu_mainALU.setControl(&alu_ctrl);
+ mux_ALUSrc.setInput(0, r_rd2_IDEX.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_writeReg_EXMEM.setInput(r_writeReg_IDEX.getOutput());
+ // Control signals
+ r_regWrite_EXMEM.setInput(r_regWrite_IDEX.getOutput());
+ r_MemWrite_EXMEM.setInput(r_MemWrite_IDEX.getOutput());
+ r_MemRead_EXMEM.setInput(r_MemRead_IDEX.getOutput());
+ r_MemtoReg_EXMEM.setInput(r_MemtoReg_IDEX.getOutput());
+
// ------ MEM --------
r_readData_MEMWB.setInput(&readData_MEM);
r_alures_MEMWB.setInput(r_alures_EXMEM.getOutput());
r_writeReg_MEMWB.setInput(r_writeReg_EXMEM.getOutput());
+ // Control signals
+ r_regWrite_MEMWB.setInput(r_regWrite_EXMEM.getOutput());
+ r_MemtoReg_MEMWB.setInput(r_MemtoReg_EXMEM.getOutput());
+
// ------ WB ---------
mux_memToReg.setInput(0, r_alures_MEMWB.getOutput());
mux_memToReg.setInput(1, r_readData_MEMWB.getOutput());
- mux_memToReg.setControl(&ctrl_memToReg);
+ mux_memToReg.setControl(r_MemtoReg_MEMWB.getOutput());
// Program counters
r_PC_IFID.setInput(r_PC_IF.getOutput());
@@ -43,6 +67,10 @@ Pipeline::Pipeline() {
}
void Pipeline::immGen() {
+ if ((uint32_t)r_instr_IFID & 0b1111111 == 0b0110111) {
+ // LUI
+ imm_ID = Signal<32>((uint32_t)r_instr_IFID & 0xfffff000);
+ } else {
// Generates an immediate value on the basis of an instruction opcode
// Opcode bits 5 and 6 can define the required fields for generating the immediate
switch (((uint32_t)r_instr_IFID & 0b1100000) >> 5) {
@@ -66,34 +94,206 @@ void Pipeline::immGen() {
}
}
}
+}
-int Pipeline::step() {
- // Main processing loop for the pipeline
- // propagates the signals throgh the combinational logic and clocks the
- // sequential logic afterwards
+namespace {
+#define CTRL_I_TYPE \
+ s_ALUSrc = 1; \
+ s_MemtoReg = 0; \
+ s_RegWrite = 1; \
+ s_MemRead = 0; \
+ s_MemWrite = 0;
+
+#define CTRL_R_TYPE \
+ s_ALUSrc = 0; \
+ s_MemtoReg = 0; \
+ s_RegWrite = 1; \
+ s_MemRead = 0; \
+ s_MemWrite = 0;
+}
+void Pipeline::controlGen() {
+ // Generates control signals for the pipeline based on the input instruction
+ switch ((uint32_t)r_instr_IFID & 0b1111111) {
+ case 0b0110111: {
+ // LUI
+ s_ALUOP = ALUDefs::LUI;
+ CTRL_I_TYPE
+ break;
+ }
+ case 0b0010011: {
+ // I-Type
+ auto fields = Parser::getParser()->decodeIInstr((uint32_t)r_instr_IFID);
+ switch (fields[2]) {
+ case 0b000: {
+ // ADDI
+ s_ALUOP = ALUDefs::ADD;
+ CTRL_I_TYPE
+ break;
+ }
+ case 0b010: {
+ // SLTI
+ s_ALUOP = ALUDefs::LT;
+ CTRL_I_TYPE
+ break;
+ }
+ case 0b011: {
+ // SLTIU
+ s_ALUOP = ALUDefs::LTU;
+ CTRL_I_TYPE
+ break;
+ }
+ case 0b100: {
+ // XORI
+ s_ALUOP = ALUDefs::XOR;
+ CTRL_I_TYPE
+ break;
+ }
+ case 0b110: {
+ // ORI
+ s_ALUOP = ALUDefs::OR;
+ CTRL_I_TYPE
+ break;
+ }
+ case 0b111: {
+ // ANDI
+ s_ALUOP = ALUDefs::AND;
+ CTRL_I_TYPE
+ break;
+ }
+ }
+ break;
+ }
+ case 0b0110011: {
+ // R-type
+ auto fields = Parser::getParser()->decodeRInstr((uint32_t)r_instr_IFID);
+ switch (fields[3]) {
+ case 0b000: {
+ switch (fields[0]) {
+ case 0b0000000: {
+ // ADD
+ s_ALUOP = ALUDefs::ADD;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0100000: {
+ // SUB
+ s_ALUOP = ALUDefs::SUB;
+ CTRL_R_TYPE
+ break;
+ }
+ }
+ break;
+ }
+ case 0b001: {
+ // SLL
+ s_ALUOP = ALUDefs::SL;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0b010: {
+ // SLT
+ s_ALUOP = ALUDefs::LT;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0b011: {
+ // SLTU
+ s_ALUOP = ALUDefs::LTU;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0b100: {
+ // XOR
+ s_ALUOP = ALUDefs::XOR;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0b101: {
+ switch (fields[0]) {
+ case 0b0000000: {
+ // SRL
+ s_ALUOP = ALUDefs::SRL;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0b0100000: {
+ // SRA
+ s_ALUOP = ALUDefs::SRA;
+ CTRL_R_TYPE
+ break;
+ }
+ }
+ break;
+ }
+ case 0b110: {
+ // OR
+ s_ALUOP = ALUDefs::OR;
+ CTRL_R_TYPE
+ break;
+ }
+ case 0b111: {
+ // AND
+ s_ALUOP = ALUDefs::AND;
+ CTRL_R_TYPE
+ break;
+ }
+ }
+ break;
+ }
+ default: {
+ s_ALUOP = 0;
+ s_ALUSrc = 0;
+ s_MemtoReg = 0;
+ s_RegWrite = 0;
+ s_MemRead = 0;
+ s_MemWrite = 0;
+ break;
+ }
+ }
+}
+
+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
-
- // WB
+ // ----- WB -----
mux_memToReg.update();
- // MEM
+ // ----- MEM -----
+ if (r_MemWrite_EXMEM) {
+ } else if (r_MemRead_EXMEM) {
+ }
- // EX
- m_alu.update();
+ // ----- EX -----
+ mux_ALUSrc.update();
+ alu_mainALU.update();
- // ID
+ // ----- ID -----
+ controlGen();
immGen();
m_reg.update();
writeReg = Signal<5>(((uint32_t)r_instr_IFID >> 7) & 0b11111);
- // IF - Opted not to create an "Instruction memory" object
- m_add_pc4.update();
+ // ----- IF -----
+ alu_pc4.update();
// Load nops if PC is greater than text size
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
+}
+
+int Pipeline::step() {
+ // Main processing loop for the pipeline
+ // propagates the signals throgh the combinational logic and clocks the
+ // sequential logic afterwards
+
+ // Clock all registers
+ m_reg.clock(); // write register values. Must happen before clocking all registers (to apply the correct regWrite
+ // value)
+ RegBase::clockAll();
+
+ // Propagate signals throuh logic
+ propagateCombinational();
// Set stage program counters
setStagePCS();
@@ -101,12 +301,10 @@ int Pipeline::step() {
// Execution is finished if nops are in all stages except WB
if (m_pcs.WB.first == m_textSize) {
return 1;
- }
-
- // Clock all registers
- RegBase::clockAll();
+ } else {
return 0;
}
+}
#define PCVAL(pc) std::pair<uint32_t, bool>((uint32_t)pc, true)
void Pipeline::setStagePCS() {
@@ -147,5 +345,11 @@ void Pipeline::restart() {
m_memory.reset(m_textSize);
m_reg.init();
m_pcs.reset();
+
+ // Reset all registers to 0 and propagate signals through combinational logic
RegBase::resetAll();
+ propagateCombinational();
+
+ // set PC_IF stage to the first instruction
+ m_pcs.IF = PCVAL(r_PC_IF);
}

MODIFY
src/pipeline.h
src/pipeline.h
@@ -28,7 +28,9 @@ public:
private:
// Simulator functions
+ void propagateCombinational();
void immGen();
+ void controlGen();
void aluCtrl();
void clock();
bool m_ready = false;
@@ -40,19 +42,18 @@ private:
// Memory
Registers m_reg;
- Signal<1> m_regWrite = Signal<1>(0);
MainMemory m_memory;
uint32_t m_textSize = 0;
// Combinatorial items & signals
- ALU<32> m_add_pc4; // ALU for incrementing PC by 4
+ ALU<32> alu_pc4 = ALU<32>("ADD PC 4"); // ALU for incrementing PC by 4
Signal<32> c_4 = Signal<32>("4");
- ALU<32> m_add_target; // ALU for PC target computation
-
- ALU<32> m_alu; // Arithmetic ALU
- Signal<CTRL_SIZE> alu_ctrl;
+ 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;
@@ -66,11 +67,23 @@ private:
r_alures_MEMWB;
Reg<32> r_PC_IF, r_PC_IFID, r_PC_IDEX, r_PC_EXMEM, r_PC_MEMWB;
Signal<5> writeReg;
- Reg<5> r_writeReg_IDEX, r_writeReg_EXMEM, r_writeReg_MEMWB;
+ Reg<5> r_writeReg_IDEX, r_writeReg_EXMEM, r_writeReg_MEMWB; // Write register (# of register to write to)
+
+ // Control propagation registers
+ Reg<1> r_regWrite_IDEX, r_regWrite_EXMEM, r_regWrite_MEMWB; // Register write signal
+ Reg<ALUDefs::CTRL_SIZE> r_ALUOP_IDEX;
+ Reg<1> r_ALUSrc_IDEX, r_MemtoReg_IDEX, r_MemtoReg_EXMEM, r_MemtoReg_MEMWB; // Multiplexor control signals
+ Reg<3> r_MemRead_IDEX, r_MemRead_EXMEM; // Write/read from memory signals. Funct3 value for instruction determines
+ // signal value (ie. byte width and signed/unsigned load)
+ Reg<2> r_MemWrite_IDEX, r_MemWrite_EXMEM;
// Control signals
- Signal<CTRL_SIZE> alu_const_add =
- Signal<CTRL_SIZE>((uint32_t)OPCODE::ADD); // A constant "ADD" control signal, to turn an ALU into an adder
+ Signal<ALUDefs::CTRL_SIZE> s_alu_const_add = Signal<ALUDefs::CTRL_SIZE>(
+ (uint32_t)ALUDefs::OPCODE::ADD); // A constant "ADD" control signal, to turn an ALU into an adder
+ Signal<3> s_MemRead;
+ Signal<2> s_MemWrite;
+ Signal<1> s_RegWrite, s_ALUSrc, s_MemtoReg;
+ Signal<ALUDefs::CTRL_SIZE> s_ALUOP;
};
#endif // PIPELINE_H

MODIFY
src/pipelineobjects.cpp
src/pipelineobjects.cpp
@@ -9,8 +9,10 @@ void Registers::update() {
int readRegister2 = (((uint32_t)*m_instr) >> 20) & 0b11111;
m_readData1 = Signal<32>(m_reg[readRegister1]);
m_readData2 = Signal<32>(m_reg[readRegister2]);
+}
- // Write
+void Registers::clock() {
+ // if regWrite is high, write data to register
if (*m_regWrite)
m_reg[(uint32_t)*m_writeRegister] = (uint32_t)*m_writeData;
}

MODIFY
src/pipelineobjects.h
src/pipelineobjects.h
@@ -115,7 +115,10 @@ protected:
assert(m_next != nullptr);
m_nextSaved = *m_next;
}
- void reset() override { m_current = Signal<n>(0); }
+ void reset() override {
+ m_current = Signal<n>(0);
+ m_nextSaved = Signal<n>(0);
+ }
private:
Signal<n> m_current;
@@ -164,15 +167,17 @@ private:
}
};
+namespace ALUDefs {
static const int CTRL_SIZE = 5;
-enum OPCODE { ADD, SUB, MUL, DIV, AND, OR, XOR, SL, SRA, SRL };
+enum OPCODE { ADD, SUB, MUL, DIV, AND, OR, XOR, SL, SRA, SRL, LUI, LT /*Less than*/, LTU /*Less than Unsigned*/, EQ };
+} // namespace ALUDefs
template <int n>
class ALU {
public:
// When calculating ALU control signals, the OPCODE is implicitely converted to an int in the Signal, which is later
// reinterpreted as an OPCODE
-
+ ALU(std::string name = "ALU") { m_name = name; }
void update();
void setInputs(Signal<n>* s1, Signal<n>* s2) {
@@ -182,7 +187,7 @@ public:
Signal<n>* getOutput() { return &m_output; }
- void setControl(const Signal<CTRL_SIZE>* sig) { m_control = sig; }
+ void setControl(const Signal<ALUDefs::CTRL_SIZE>* sig) { m_control = sig; }
private:
bool initialized() {
@@ -192,48 +197,57 @@ private:
b &= m_op2 != nullptr;
return b;
}
-
+ std::string m_name;
Signal<n> m_output;
const Signal<n>* m_op1;
const Signal<n>* m_op2;
- const Signal<CTRL_SIZE>* m_control;
+ const Signal<ALUDefs::CTRL_SIZE>* m_control;
};
template <int n>
void ALU<n>::update() {
if (!initialized())
throw std::runtime_error("Mux not initialized");
- switch ((OPCODE)(int)*m_control) {
- case ADD:
+ switch ((ALUDefs::OPCODE)(int)*m_control) {
+ case ALUDefs::ADD:
m_output = (uint32_t)*m_op1 + (uint32_t)*m_op2;
break;
- case SUB:
+ case ALUDefs::SUB:
m_output = (uint32_t)*m_op1 - (uint32_t)*m_op2;
break;
- case MUL:
+ case ALUDefs::MUL:
m_output = (uint32_t)*m_op1 * (uint32_t)*m_op2;
break;
- case DIV:
+ case ALUDefs::DIV:
m_output = (uint32_t)*m_op1 / (uint32_t)*m_op2;
break;
- case AND:
+ case ALUDefs::AND:
m_output = (uint32_t)*m_op1 & (uint32_t)*m_op2;
break;
- case OR:
+ case ALUDefs::OR:
m_output = (uint32_t)*m_op1 | (uint32_t)*m_op2;
break;
- case XOR:
+ case ALUDefs::XOR:
m_output = (uint32_t)*m_op1 ^ (uint32_t)*m_op2;
break;
- case SL:
+ case ALUDefs::SL:
m_output = (uint32_t)*m_op1 << (uint32_t)*m_op2;
break;
- case SRA:
+ case ALUDefs::SRA:
m_output = (uint32_t)*m_op1 >> (uint32_t)*m_op2;
break;
- case SRL:
+ case ALUDefs::SRL:
m_output = (int)*m_op1 + (uint32_t)*m_op2;
break;
+ case ALUDefs::LUI:
+ m_output = (uint32_t)*m_op2;
+ break;
+ case ALUDefs::LT:
+ m_output = (int)*m_op1 < (int)*m_op2 ? 1 : 0;
+ break;
+ case ALUDefs::LTU:
+ m_output = (uint32_t)*m_op1 < (uint32_t)*m_op2 ? 1 : 0;
+ break;
default:
throw std::runtime_error("Invalid ALU opcode");
break;
@@ -246,6 +260,7 @@ public:
std::vector<uint32_t>* getRegPtr() { return &m_reg; }
void update();
+ void clock();
void clear() {
for (auto& reg : m_reg)
reg = 0;

MODIFY
src/processortab.ui
src/processortab.ui
<number>0</number>
</property>
<property name="maximum">
- <number>4</number>
+ <number>6</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</layout>
</widget>
<customwidgets>
- <customwidget>
- <class>PipelineWidget</class>
- <extends>QGraphicsView</extends>
- <header>pipelinewidget.h</header>
- </customwidget>
<customwidget>
<class>RegisterContainerWidget</class>
<extends>QWidget</extends>
<header>registercontainerwidget.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>PipelineWidget</class>
+ <extends>QGraphicsView</extends>
+ <header>pipelinewidget.h</header>
+ </customwidget>
</customwidgets>
<resources>
<include location="../resources/images.qrc"/>

Simulator correctly runs simple programs

this does not include Hazard detection, control flow changes and save & load instructions.