diff --git a/.asm-lsp.toml b/.asm-lsp.toml index 81418b3..2033678 100644 --- a/.asm-lsp.toml +++ b/.asm-lsp.toml @@ -4,5 +4,10 @@ assembler = "gas" instruction_set = "riscv" [default_config.opts] +compiler = "riscv64-unknown-elf-gcc" +compiler_args = [ + "-march=rv64imfd", + "-mabi=lp64d" +] diagnostics = true default_diagnostics = true diff --git a/src/main/scala/projet/Constants.scala b/src/main/scala/projet/Constants.scala index def0bec..d5dcec2 100644 --- a/src/main/scala/projet/Constants.scala +++ b/src/main/scala/projet/Constants.scala @@ -1,5 +1,9 @@ package projet +import chisel3._; object Constants { def DIVISION_CYCLES_COUNT = 64 + def RISCV_NOP = "b00000000000000000000000000010011".U; + def RV64I_XLEN = 64; + def RV64I_RESET_VECTOR = "x10000".U(RV64I_XLEN.W); } diff --git a/src/main/scala/projet/Rv64i.scala b/src/main/scala/projet/Rv64i.scala index 67eae89..abc9b46 100644 --- a/src/main/scala/projet/Rv64i.scala +++ b/src/main/scala/projet/Rv64i.scala @@ -10,93 +10,68 @@ class Rv64i(sim: Boolean = true) extends Module { val valid_x31 = if (sim) Some(Output(Bool())) else None }) - // Define components - val alu = Module(new Alu()); - val immediate_decoder = Module(new ImmediateDecoder()); + // ### Pipeline stages + // Fetch : The current instruction is currently being loaded from the memory + + // Decode : The current instruction is being read by the control unit, the register file, the immediate decoder + // Execute : The current instruction immediate, or register operands are currently being operated (eg. in the alu) + // (Currently Decode and execute are the same stage) + + // Memory Read : The memory in being read/operands stored + + // ### Processing unit components + + // Generates the control signals for the following components and defines data_flow in the processor val control_unit = Module(new ControlUnit()); + + // Arithmetic logic unit : computes operations between operands + val alu = Module(new Alu()); + + // Decodes the immediates from the instruction based on the instruction type + val immediate_decoder = Module(new ImmediateDecoder()); + + // Wraps the memory behind a interface (manages full, word, half, byte load/store operations) val dmem = Module(new DMem()); + + // Contains the 32 GP registers, two output ports, one input port val reg_file = Module(new RegFile(sim)); - // PC - val reg_pc = RegInit("x10000".U(64.W)); - // PC delayed to execute stage for auipc op - val execute_pc = Delay.Delay(reg_pc, 1, "x10000".U(64.W)); - // True if the instruction in the execute stage is a jump - val is_jump = Delay.Delay(control_unit.io.is_jump, 1, false.B); + // Register containing the address of the instruction in the Fetch stage + val reg_pc = RegInit(Constants.RV64I_RESET_VECTOR); - dmem.io.dbus <> io.dbus - dmem.io.en := control_unit.io.memory_en - dmem.io.we := control_unit.io.memory_we - dmem.io.size := control_unit.io.memory_size - dmem.io.sign_extend := control_unit.io.memory_sign_extend - control_unit.io.alu_comp_result := alu.io.comp_result - alu.io.word_mode := control_unit.io.alu_word_mode - io.ibus.wdata := 0.U; - io.ibus.be := VecInit( - false.B, - false.B, - false.B, - false.B, - false.B, - false.B, - false.B, - false.B - ) - io.x31 := reg_file.io.x31 - if (sim) { - io.valid_x31.get := reg_file.io.valid_x31.get - } + // Register containing the address of the instruction in teh Decode stage TODO: Rename (why "execute") + val execute_pc = Delay.Delay(reg_pc, 1, Constants.RV64I_RESET_VECTOR); + // Wire : contains the result (i.e output of the Execution Stage) of the instruction + val instruction_result = WireInit(0.U(Constants.RV64I_XLEN.W)); - if (sim) { - val log = SimLog.file("core_pc.log"); - when(!is_jump) { - log.printf(cf"0x${execute_pc}%x\n") - } - } + // Wire : contains the data to be written back by the instruction in Memory Read stage (e.g. memory data) + val writeback_line = WireInit("xdeadbeef".U(Constants.RV64I_XLEN.W)); - // Insert no-op if jump - val decoded_instruction = - Mux( - is_jump, - "b00000000000000000000000000010011".U, - io.ibus.rdata >> (execute_pc(2) * 32.U) - ) + // Wether the executing instruction is stalling the processor (e.g. waiting for a result : division multiplication) + val is_stalled = RegInit(false.B); - // Manage the stalled state of the processor - val stalled_instruction = RegInit(0.U) + // Holds the instruction in the decode stage if it stalls + val stalling_insruction = RegInit(0.U(32.W)); + + // Control signal that tells the core to resume execution val should_stop_stall = control_unit.io.should_stop_stall || alu.io.should_stop_stall - val is_stalled = RegInit(false.B) - val is_immediatly_stalled = - (!should_stop_stall && (is_stalled || control_unit.io.should_stall =/= 0.U)) - when(!is_stalled && control_unit.io.should_stall =/= 0.U) { - // Stall the pipeline - is_stalled := true.B - stalled_instruction := decoded_instruction - } - when(is_stalled && should_stop_stall) { - is_stalled := false.B - } - // Currently executed instruction - val instruction = Mux(is_stalled, stalled_instruction, decoded_instruction) + // Connect memory wrapper to bus interconnect + dmem.io.dbus <> io.dbus - // Pipelining registers for mem stage - val reg_rd_index = RegInit(0.U); - val reg_execute_out = RegInit(0.U); - val reg_mux_executeout_dout = RegInit(true.B); - val reg_regile_we = RegInit(false.B); + // ############################# + // ######## FETCH STAGE ######## + // ############################# - immediate_decoder.io.instruction := instruction - immediate_decoder.io.op_type := control_unit.io.optype - alu.io.opcode := control_unit.io.alu_opcode; - - // Reg file write-enable - reg_regile_we := control_unit.io.reg_file_we; - reg_file.io.we := reg_regile_we && !is_stalled; - - // Increment PC each clock + // PC ADDER + // Sums either : + // - PC (normal program progression) + // - Decode PC (jump, branch) + // with + // - 4 constant (normal program progresion) + // - Immediate (jump, branch) val pc_adder_out = Mux( control_unit.io.mux_regpc_executepc, reg_pc, @@ -107,80 +82,223 @@ class Rv64i(sim: Boolean = true) extends Module { immediate_decoder.io.immediate ); - // Fetch instruction + // Program counter + // Either : + // - Update PC according to adder (normal progression, jump, branch) + // - Update PC according to result of instruction in Execute Stage (JALR) + reg_pc := Mux( + control_unit.io.mux_pcadder_jalrwriteback, + pc_adder_out, + instruction_result + ); + + // Handle stall state + // We should : + // - Stop counting as soon as we require wating + // - Keep counting when we are stalled + // - Start counting as soon as we requiring starting again + when(!should_stop_stall && (is_stalled || control_unit.io.should_stall)) { + reg_pc := reg_pc; + } + + // Fetch instruction from memory io.ibus.en := true.B; io.ibus.addr := reg_pc; - control_unit.io.instruction := instruction; + // Default data for Ibus + io.ibus.wdata := 0.U; + io.ibus.be := VecInit( + false.B, + false.B, + false.B, + false.B, + false.B, + false.B, + false.B, + false.B + ); + + // After this stage: the instruction will be read from memory and become available on the decode stage on next cycle + + // ###################################### + // ######## DECODE/EXECUTE STAGE ######## + // ###################################### + // Instruction is available on ibus + + // Instruction being decoded + // Either : + // - Stalling instructino if the cpu is waiting + // - Data from ibus if normal operation + // - If previous instruction being executed is a jump => Insert bubble to flush pipeline + val decoded_instruction = + Mux( + is_stalled, + stalling_insruction, + Mux( + Delay.Delay(control_unit.io.is_jump, 1, false.B), + Constants.RISCV_NOP, + io.ibus.rdata >> (execute_pc(2) * 32.U) + ) + ); + decoded_instruction.suggestName("instruction"); + + // Decide control signals for instruction + // (Control unit does not hold any state, output signals are about "decoded_instruction") + control_unit.io.instruction := decoded_instruction; + + // Decode immediate in the instruction based on the optype + immediate_decoder.io.op_type := control_unit.io.optype; + immediate_decoder.io.instruction := decoded_instruction; + + // Decode register indices // Decode rs1 index - val rs1_index = instruction(19, 15) + val rs1_index = decoded_instruction(19, 15) reg_file.io.rs1_addr := rs1_index - // Decode rs2 index - val rs2_index = instruction(24, 20) - reg_file.io.rs2_addr := rs2_index - // Decode rd index - val rd_index = instruction(11, 7) - reg_rd_index := rd_index; - reg_file.io.rd_addr := reg_rd_index; - val writeback_line = Mux( - reg_mux_executeout_dout, - reg_execute_out, - dmem.io.data_out - ); + // Decode rs2 index + val rs2_index = decoded_instruction(24, 20) + reg_file.io.rs2_addr := rs2_index + + // Decode rd index + val rd_index = decoded_instruction(11, 7) + + // Manage the stalled state of the processor + + // Stalling behaviour + when(!is_stalled && control_unit.io.should_stall) { + // Set core to stalled state when instructions requires so and store it + is_stalled := true.B; + stalling_insruction := decoded_instruction; + } + + when(is_stalled && should_stop_stall) { + is_stalled := false.B + } + + // Stalling note : + // When instruction requires stalling it needs to be held in the decode state as its decoded information will + // be needed in later stages when in resumes execution + + // Data gotten from register file. + // Either : + // - Data from register file + // - If PREVIOUS instruction wrote to the register, get the data currently being written in the register file val rs1_data = Mux( - rs1_index === reg_rd_index && rs1_index =/= 0.U + rs1_index === Delay.Delay(rd_index, 1, 0.U) && rs1_index =/= 0.U && Delay.Delay(control_unit.io.reg_file_we, 1, false.B), writeback_line, reg_file.io.rs1_data ) val rs2_data = Mux( - rs2_index === reg_rd_index && rs2_index =/= 0.U + rs2_index === Delay.Delay(rd_index, 1, 0.U) && rs2_index =/= 0.U && Delay.Delay(control_unit.io.reg_file_we, 1, false.B), writeback_line, reg_file.io.rs2_data ) - // EXECUTE - - val imm = immediate_decoder.io.immediate + // Feed ALU + // First operand + // Either : + // - Data from registers + // - PC of next instruction (instruction being decoded) for JALR alu.io.a := Mux( control_unit.io.mux_rega_pc, rs1_data, execute_pc ); - alu.io.b := Mux(control_unit.io.mux_regb_imm, rs2_data, imm); - // Select what to send on writeback line - val execute_out = Mux(control_unit.io.mux_alu_imm, alu.io.out, imm); - reg_execute_out := execute_out; - // Jalr line is directly the execute output - val jalr_line = execute_out + // Second operand + // Either : + // - Data from registers + // - Immediate from instruction + alu.io.b := Mux( + control_unit.io.mux_regb_imm, + rs2_data, + immediate_decoder.io.immediate + ); - reg_mux_executeout_dout := control_unit.io.mux_executeout_dout; + // Alu Op code, result, W mode + alu.io.opcode := control_unit.io.alu_opcode; + control_unit.io.alu_comp_result := alu.io.comp_result + alu.io.word_mode := control_unit.io.alu_word_mode + // Result of execute stage + // Either : + // - ALU Computation result + // - Immediate from instruction + instruction_result := Mux( + control_unit.io.mux_alu_imm, + alu.io.out, + immediate_decoder.io.immediate + ); + + // Memory setup + dmem.io.en := control_unit.io.memory_en; + dmem.io.we := control_unit.io.memory_we; + dmem.io.size := control_unit.io.memory_size; + dmem.io.sign_extend := control_unit.io.memory_sign_extend; + dmem.io.data_in := rs2_data; + dmem.io.addr := instruction_result; + + // Debugging/ testing + io.x31 := reg_file.io.x31 + if (sim) { + io.valid_x31.get := reg_file.io.valid_x31.get + + val log = SimLog.file("core_pc.log"); + when(!control_unit.io.is_jump) { + log.printf(cf"0x${execute_pc}%x\n") + } + } + + // ############################################# + // ######## MEMORY READ/WRITEBACK STAGE ######## + // ############################################# + // One stage after Decode/Execute => Control signals should be delayed by 1 + + // Pipelining registers for mem stage + val reg_mux_executeout_dout = RegInit(true.B); + val reg_regile_we = RegInit(false.B); + + // Writeback line + // Either : + // - Result from the exection of the instruction (previous stage) + // - Data gotten from memory + writeback_line := Mux( + Delay.Delay(control_unit.io.mux_executeout_dout, 1, true.B), + + // Data from previous stage + Delay.Delay( + instruction_result, + 1, + 0.U(Constants.RV64I_XLEN.W) + ), + dmem.io.data_out + ); + + // Register writeback + reg_file.io.rd_addr := Delay.Delay(rd_index, 1, 0.U); + + // Register write + // Either : + // - Writeback line (instruction result/read) + // - This PC following this instruction's PC (JAL, JALR) reg_file.io.rd_data := Mux( Delay.Delay(control_unit.io.mux_writeback_pc, 1, true.B), writeback_line, - Delay.Delay(reg_pc, 1, 0.U) + execute_pc ); - // Writeback pipelining registers - reg_pc := Mux( - control_unit.io.mux_pcadder_jalrwriteback, - pc_adder_out, - jalr_line - ); - - dmem.io.data_in := rs2_data - dmem.io.addr := execute_out - - when(is_immediatly_stalled) { - reg_pc := reg_pc - } + // Reg file write-enable + // Do not write back if the instruction is stalling + reg_file.io.we := Delay.Delay( + control_unit.io.reg_file_we, + 1, + false.B + ) && !is_stalled; }