Create & connect base pipeline objects (no forwarding/hazard detection etc.)
MODIFY
RISC-V-sim.pro
RISC-V-sim.pro
@@ -39,7 +39,8 @@ SOURCES += src/main.cpp \
src/graphics/backgrounditems.cpp \
src/instructionmodel.cpp \
src/pipeline.cpp \
- src/mainmemory.cpp
+ src/mainmemory.cpp \
+ src/pipelineobjects.cpp
HEADERS += \
src/parser.h \

MODIFY
src/pipeline.cpp
src/pipeline.cpp
Pipeline::Pipeline() {
registerRegs();
- // Build pipeline
+ // Connect pipeline
// ------ IF ---------
// Connect alu to PC and a constant "4" signal, and set operator to "Add"
c_4 = Signal<32>(4);
- alu_pc4.setInputs(std::vector<Signal<32>*>({r_PC.getOutput(), &c_4}));
+ m_add_pc4.setInputs(r_PC_IF.getOutput(), &c_4);
+ r_instr_IFID.setInput(&instr_IF);
+
+ // ------ ID ---------
+ m_reg.setInputs(r_instr_IFID.getOutput(), r_writeReg_MEMWB.getOutput(), mux_memToReg.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);
+
+ // ------ EX ---------
+ m_alu.setInputs(r_rd1_IDEX.getOutput(), r_rd2_IDEX.getOutput());
+ m_alu.setControl(&alu_ctrl);
+ r_alures_EXMEM.setInput(m_alu.getOutput());
+ r_writeData_EXMEM.setInput(r_rd2_IDEX.getOutput());
+ r_writeReg_EXMEM.setInput(r_writeReg_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());
+
+ // ------ WB ---------
+ mux_memToReg.setInput(0, r_alures_MEMWB.getOutput());
+ mux_memToReg.setInput(1, r_readData_MEMWB.getOutput());
}
void Pipeline::registerRegs() {
// Registers all registers in m_regs
- m_regs.push_back(&r_IFID);
+ m_regs.push_back(&r_instr_IFID);
}
void Pipeline::immGen() {
// 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_IFID & 0b1100000) >> 5) {
+ switch (((uint32_t)r_instr_IFID & 0b1100000) >> 5) {
case 0b00: {
// Load instruction, sign extend bits 31:20
- imm_ID = Signal<32>(signextend<int32_t, 12>(((uint32_t)r_IFID >> 20)));
+ imm_ID = Signal<32>(signextend<int32_t, 12>(((uint32_t)r_instr_IFID >> 20)));
break;
}
case 0b01: {
// Store instruction
- uint32_t v(r_IFID);
+ uint32_t v(r_instr_IFID);
imm_ID = Signal<32>(signextend<int32_t, 12>(((v & 0xfe000000)) >> 20) | ((v & 0xf80) >> 7));
break;
}
default: {
// Conditional branch instructions
- uint32_t v(r_IFID);
+ uint32_t v(r_instr_IFID);
imm_ID = Signal<32>(signextend<int32_t, 13>(((v & 0x80000000)) >> 20) | ((v & 0x7e) >> 20) |
((v & 0xf00) >> 7) | ((v & 0x80) << 6));
break;
@@ -47,10 +71,25 @@ void Pipeline::step() {
// sequential logic afterwards
// 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
+ mux_memToReg.update();
+
+ // MEM
+
+ // EX
+ m_alu.update();
+
+ // ID
+ immGen();
+ m_reg.update();
- // IF
- alu_pc4.update();
- instr_IF = Signal<32>(m_memory.read((uint32_t)r_PC));
+ // IF - Opted not to create an "Instruction memory" object
+ m_add_pc4.update();
+ instr_IF = Signal<32>(m_memory.read((uint32_t)r_PC_IF)); // Read instruction at current PC
+ writeReg = Signal<5>(((uint32_t)r_instr_IFID >> 7) & 0b11111);
// Clock all registers
clock();

MODIFY
src/pipeline.h
src/pipeline.h
class Pipeline {
public:
Pipeline();
-
-private:
// Utility functions
void restart();
void reset();
+ void step();
+ static Pipeline* getPipeline() {
+ static Pipeline pipeline;
+ return &pipeline;
+ }
+
+private:
// Simulator functions
void registerRegs();
void immGen();
+ void aluCtrl();
void clock();
- void step();
- // Objects
+ // Memory
+ Registers m_reg;
MainMemory m_memory;
- ALU<32> alu_pc4;
+ // Combinatorial items & signals
+ ALU<32> m_add_pc4; // 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;
+
+ Mux<2, 32> mux_memToReg;
+ Signal<1> ctrl_memToReg;
+
+ Signal<32> imm_ID = Signal<32>("Immediate");
+ Signal<32> instr_IF = Signal<32>("Instruction");
+ Signal<32> readData_MEM;
- // Signals - Stage name is appended to name
- Signal<32> c_4;
- Signal<32> imm_ID;
- Signal<32> instr_IF;
// Registers
std::vector<RegBase*> m_regs;
- Reg<32> r_IFID;
- Reg<32> r_PC;
+ Reg<32> r_PC_IF, r_instr_IFID, r_rd1_IDEX, r_rd2_IDEX, r_imm_IDEX, r_alures_EXMEM, r_writeData_EXMEM,
+ r_readData_MEMWB, r_alures_MEMWB;
+ // Write register value is passed from instruction through all stages
+ Signal<5> writeReg;
+ Reg<5> r_writeReg_IDEX, r_writeReg_EXMEM, r_writeReg_MEMWB;
};
#endif // PIPELINE_H

ADD
src/pipelineobjects.cpp
+#include "pipelineobjects.h"
+
+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]);
+
+ // Write
+ if (*m_regWrite)
+ m_reg[(uint32_t)*m_writeRegister] = (uint32_t)*m_writeData;
+}
+
+void Registers::setInputs(Signal<32>* instr, Signal<5>* writeReg, Signal<32>* writeData) {
+ m_regWrite = nullptr;
+ m_writeData = writeData;
+ m_writeRegister = writeReg;
+ m_instr = instr;
+}

MODIFY
src/pipelineobjects.h
src/pipelineobjects.h
#include "binutils.h"
#include "defines.h"
+#include "mainmemory.h"
#include <cstdint>
#include <stdexcept>
/* PIPELINE OBJECTS
* all pipeline objects are built around the Signal class - a boolean vector of immutable size.
* The Signal class ensures size compatability of I/O signals, much like regular HDL languages
+ * Signals can be cast to signed/unsigned integers or boolean values, to easily use them in ie. computational expression
+ * or logic expressions
*
* All combinatorial components have an update() function, which corresponds to propagating the
* input signal values through the combinatorial circuit
@@ -32,8 +35,8 @@ public:
Signal(std::string name = ""){ASSERT_SIZE CREATE_VEC SETNAME};
Signal(std::vector<bool> v, std::string name = "") {
ASSERT_SIZE
- SETNAME
// Builds a signal from a std::vector
+ SETNAME
if (v.size() != n) {
throw std::invalid_argument("Input vector size does not match Signal size");
}
@@ -65,15 +68,12 @@ private:
std::string m_name;
};
-// Register class
-// I/O of registers are handled via. the -> operator
-// reg1 -> reg2 connects m_current of reg1 to m_next of reg2
-
class RegBase {
public:
virtual void clock() = 0;
};
+// The Reg class is used for sequential signal assignment. Has a single input/output of a given signal size.
template <int n>
class Reg : public RegBase {
public:
@@ -88,10 +88,9 @@ public:
explicit operator int() const { return (int)m_current; }
explicit operator uint32_t() const { return (uint32_t)m_current; }
explicit operator bool() const { return (bool)m_current; }
-
-private:
void setInput(const Signal<n>* in) { m_next = in; }
+private:
Signal<n> m_current;
const Signal<n>* m_next;
};
@@ -106,21 +105,22 @@ public:
void update() {
if (!initialized())
throw std::runtime_error("Mux not initialized");
- m_output = *m_inputs[*m_control];
+ m_output = *m_inputs[(uint32_t)*m_control];
}
bool setInput(int input, const Signal<n>* sig) {
- if (input < 1 || input >= inputs)
+ if (input < 0 || input >= inputs)
return false;
m_inputs[input] = sig;
return true;
}
void setInputs(std::vector<const Signal<n>*> sigs) {
- if (sigs.size() != inputs)
- return false;
+ static_assert(sigs.size() == inputs, "Input vector must be equal size as number of multiplexer inputs");
m_inputs = sigs;
}
void setControl(std::vector<const Signal<bitcount(inputs)>*> sig) { m_control = sig; }
+ Signal<n>* getOutput() { return &m_output; }
+
private:
std::vector<const Signal<n>*> m_inputs;
const Signal<bitcount(inputs)>* m_control; // control size = roof(log2(inputs))
@@ -136,23 +136,23 @@ private:
}
};
+static const int CTRL_SIZE = 5;
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
- enum OPCODE { ADD, SUB, MUL, DIV, AND, OR, XOR, SL, SRA, SRL, _Count };
- const static int CTRL_SIZE = bitcount(_Count);
+ enum OPCODE { ADD, SUB, MUL, DIV, AND, OR, XOR, SL, SRA, SRL };
void update();
- void setInputs(std::vector<Signal<n>*> sigs) {
- if (sigs.size() != 2)
- throw std::invalid_argument("Input vector size must be 2 (2 ALU operands)");
- m_op1 = sigs[0];
- m_op2 = sigs[1];
+ void setInputs(Signal<n>* s1, Signal<n>* s2) {
+ m_op1 = s1;
+ m_op2 = s2;
}
+ Signal<n>* getOutput() { return &m_output; }
+
void setControl(const Signal<CTRL_SIZE>* sig) { m_control = sig; }
private:
@@ -205,11 +205,33 @@ void ALU<n>::update() {
case SRL:
m_output = (int)*m_op1 + (uint32_t)*m_op2;
break;
- case _Count:
default:
throw std::runtime_error("Invalid ALU opcode");
break;
}
}
+class Registers {
+public:
+ Registers() {}
+
+ void update();
+
+ void setInputs(Signal<32>* instr, Signal<5>* writeReg, Signal<32>* writeData);
+ 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_writeRegister;
+ const Signal<32>* m_writeData;
+ Signal<32> m_readData1;
+ Signal<32> m_readData2;
+
+ std::vector<uint32_t> m_reg = std::vector<uint32_t>(32, 0); // Internal registers
+
+ std::string m_name = std::string("Registers");
+};
+
#endif // PIPELINEOBJECTS_H

Create & connect base pipeline objects (no forwarding/hazard detection etc.)