619 lines
18 KiB
Scala
619 lines
18 KiB
Scala
package projet
|
||
|
||
import chisel3._
|
||
import chisel3.util.switch
|
||
import chisel3.util.is
|
||
|
||
class CUInterface extends Bundle {
|
||
val instruction = Input(UInt(32.W))
|
||
val reg_file_we = Output(Bool())
|
||
val alu_opcode = Output(AluOpCode())
|
||
|
||
// TODO: Maybe bundle alu <> cu
|
||
// Alu comparison result
|
||
val alu_comp_result = Input(Bool())
|
||
val alu_word_mode = Output(Bool())
|
||
|
||
val optype = Output(OpType())
|
||
|
||
// True if the decoded instruction is a jump
|
||
val is_jump = Output(Bool())
|
||
|
||
// DMem connections
|
||
val memory_size = Output(DMemSize())
|
||
val memory_en = Output(Bool())
|
||
val memory_we = Output(Bool())
|
||
val memory_sign_extend = Output(
|
||
Bool()
|
||
) // Wether to signe extend the output of the memory for bytes, half loads
|
||
|
||
// Muxers
|
||
// The naming convention is as follows : mux_xxx_yyy.
|
||
// If the signal is hot, xxx is selected otherwise yyy is selected
|
||
|
||
// Sends alu or immediate to write back
|
||
val mux_alu_imm = Output(Bool())
|
||
|
||
// Selects regA or pc for alu a
|
||
val mux_rega_pc = Output(Bool())
|
||
|
||
// Selects regB or immediate for alu b
|
||
val mux_regb_imm = Output(Bool())
|
||
|
||
// Selects 4 or immediate for pc incrementation
|
||
val mux_incrpc4_imm = Output(Bool())
|
||
|
||
// Selects pc or previous pc for pc incrementation
|
||
val mux_regpc_executepc = Output(Bool())
|
||
|
||
// Selects pc adder or write back for pc write
|
||
val mux_pcadder_writeback = Output(Bool())
|
||
|
||
// Selects pc or write back for register write
|
||
val mux_writeback_pc = Output(Bool())
|
||
|
||
// Selects aluout or memory data out for writeback_line
|
||
val mux_executeout_dout = Output(Bool())
|
||
}
|
||
|
||
object OpType extends ChiselEnum {
|
||
val U, I, IR, J, B, S = Value
|
||
}
|
||
|
||
object OpCode extends ChiselEnum {
|
||
// Type U
|
||
val LUI = "b0110111".U
|
||
val AUIPC = "b0010111".U
|
||
|
||
// Type I + Type IR
|
||
|
||
// Instruction format
|
||
// xxx rd, rs1, imm
|
||
// Perfomrs some operation with rs1 and immediate
|
||
// and writes back to a register
|
||
|
||
// addi, andi, ori, xori, slli, srli, srai,
|
||
// slti, sltiu
|
||
val OpImm = "b0010011".U
|
||
|
||
// Instruction format
|
||
// xxx rd, rs1, rs2
|
||
// Perfomrs some operation with rs1 and rd2
|
||
// and writes back to a register
|
||
|
||
// add, and, or, xor, sll, srl, sra, slt, stlu, mul, mulh, mulhsu, mulhu, div, divu, rem, remu
|
||
val OpR = "b0110011".U;
|
||
|
||
// mulw, divw, remw
|
||
val OpMw = "b0111011".U;
|
||
|
||
// Type J
|
||
val JAL = "b1101111".U
|
||
|
||
// Type I
|
||
val JALR = "b1100111".U
|
||
|
||
// Type B
|
||
val OpBranch = "b1100011".U
|
||
|
||
// Type S
|
||
val OpStore = "b0100011".U
|
||
|
||
// Type I
|
||
val OpLoad = "b0000011".U
|
||
|
||
// ~~ Rv64i ~~
|
||
val OpImmW = "b0011011".U
|
||
}
|
||
|
||
class ControlUnit() extends Module {
|
||
val io = IO(new CUInterface())
|
||
|
||
io.reg_file_we := false.B
|
||
io.alu_opcode := AluOpCode.Add
|
||
io.alu_word_mode := false.B
|
||
io.optype := OpType.U
|
||
io.is_jump := false.B;
|
||
io.memory_size := DMemSize.Word
|
||
io.memory_en := false.B
|
||
io.memory_we := false.B
|
||
io.memory_sign_extend := false.B;
|
||
io.mux_alu_imm := true.B;
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
io.mux_incrpc4_imm := true.B;
|
||
io.mux_regpc_executepc := true.B;
|
||
io.mux_writeback_pc := true.B;
|
||
io.mux_pcadder_writeback := true.B;
|
||
io.mux_executeout_dout := true.B;
|
||
|
||
// Decode instruction
|
||
val opcode = io.instruction(6, 0);
|
||
val funct3 = io.instruction(14, 12);
|
||
val funct7 = io.instruction(31, 25);
|
||
|
||
switch(opcode) {
|
||
|
||
// U instructions
|
||
is(OpCode.LUI) {
|
||
io.reg_file_we := true.B // Write to regfile
|
||
io.mux_alu_imm := false.B // Send immy directly to refile
|
||
io.optype := OpType.U
|
||
}
|
||
is(OpCode.AUIPC) {
|
||
io.alu_opcode := AluOpCode.Add
|
||
io.reg_file_we := true.B // Write to regfile
|
||
io.mux_rega_pc := false.B // Send PC to ALU A
|
||
io.mux_regb_imm := false.B; // Send immu to ALU B
|
||
io.optype := OpType.U
|
||
}
|
||
|
||
// OpImm instructions
|
||
is(OpCode.OpImm) {
|
||
io.reg_file_we := true.B // Write to regfile
|
||
opImm(funct3, funct7, io);
|
||
}
|
||
|
||
// OpR instructions
|
||
is(OpCode.OpR) {
|
||
io.reg_file_we := true.B // Write to regfile
|
||
opR(funct3, funct7, io);
|
||
}
|
||
|
||
// OpMw instructions
|
||
is(OpCode.OpMw) {
|
||
io.reg_file_we := true.B // Write to regfile
|
||
io.alu_word_mode := true.B
|
||
opM(funct3, io);
|
||
}
|
||
|
||
// J instructions
|
||
is(OpCode.JAL) {
|
||
io.alu_opcode := AluOpCode.Add // ALU is unused
|
||
|
||
io.reg_file_we := true.B // Write to regfile
|
||
io.mux_writeback_pc := false.B;
|
||
|
||
// io.mux_rega_pc := false.B // Send PC to ALU A
|
||
// io.mux_regb_imm := false.B; // Send imm to ALU B
|
||
|
||
io.is_jump := true.B
|
||
io.mux_incrpc4_imm := false.B; // Send the immediate in PC
|
||
io.mux_regpc_executepc := false.B; // Add the immediate to the previous PC
|
||
io.mux_pcadder_writeback := true.B;
|
||
io.optype := OpType.J
|
||
}
|
||
|
||
is(OpCode.JALR) {
|
||
io.alu_opcode := AluOpCode.Add
|
||
|
||
io.reg_file_we := true.B // Write to regfile
|
||
io.mux_writeback_pc := false.B // Write next instruction PC to rd
|
||
io.is_jump := true.B // Insert no-op to discard fetched next instruction
|
||
|
||
io.mux_rega_pc := true.B // Send rs1 to ALU A
|
||
io.mux_regb_imm := false.B; // Send imm to ALU B
|
||
|
||
io.mux_alu_imm := true.B; // Send alu out to write back line
|
||
io.mux_pcadder_writeback := false.B; // Set pc to writeback dest
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(OpCode.OpBranch) {
|
||
io.optype := OpType.B;
|
||
opBranch(funct3, io);
|
||
}
|
||
|
||
is(OpCode.OpStore) {
|
||
io.optype := OpType.S
|
||
opStore(funct3, io);
|
||
}
|
||
|
||
is(OpCode.OpLoad) {
|
||
io.optype := OpType.I
|
||
opLoad(funct3, io);
|
||
}
|
||
|
||
is(OpCode.OpImmW) {
|
||
io.reg_file_we := true.B // Write to regfile
|
||
io.optype := OpType.I
|
||
opImmW(funct3, funct7, io);
|
||
}
|
||
}
|
||
|
||
// Implements functions for all instruction of the opImm kind
|
||
def opImm(funct3: UInt, funct7: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpImm instruction
|
||
object Funct3 extends ChiselEnum {
|
||
// Type I
|
||
val ADDI = "b000".U
|
||
val SLTI = "b010".U
|
||
val SLTIU = "b011".U
|
||
val XORI = "b100".U
|
||
val ORI = "b110".U
|
||
val ANDI = "b111".U
|
||
|
||
// Type IR
|
||
val SLLI = "b001".U
|
||
val SRLI_SRAI = "b101".U // Right shifts
|
||
}
|
||
|
||
// Meaning of Funct7 in the context of SHIFTR IR instructions
|
||
object IRFunct7 extends ChiselEnum {
|
||
val SRLI = "b0000000".U
|
||
val SRAI = "b0100000".U
|
||
}
|
||
|
||
io.mux_regb_imm := false.B; // OpImm are operations with imm, so send imm to ALU
|
||
io.mux_alu_imm := true.B;
|
||
io.mux_rega_pc := true.B;
|
||
io.alu_word_mode := false.B;
|
||
switch(funct3) {
|
||
// Type I
|
||
is(Funct3.ADDI) {
|
||
io.alu_opcode := AluOpCode.Add
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(Funct3.ANDI) {
|
||
io.alu_opcode := AluOpCode.And
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(Funct3.ORI) {
|
||
io.alu_opcode := AluOpCode.Or
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(Funct3.XORI) {
|
||
io.alu_opcode := AluOpCode.Xor
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(Funct3.SLLI) {
|
||
io.alu_opcode := AluOpCode.ShiftLeft
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(Funct3.SLTI) {
|
||
io.alu_opcode := AluOpCode.LessThanSigned
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
is(Funct3.SLTIU) {
|
||
io.alu_opcode := AluOpCode.LessThanUnsigned
|
||
io.optype := OpType.I
|
||
}
|
||
|
||
// Type IR
|
||
is(Funct3.SRLI_SRAI) {
|
||
io.optype := OpType.IR
|
||
switch(funct7) {
|
||
is(IRFunct7.SRLI) {
|
||
io.alu_opcode := AluOpCode.ShiftRight
|
||
}
|
||
|
||
is(IRFunct7.SRAI) {
|
||
io.alu_opcode := AluOpCode.ShiftArithmeticRight
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Implements functions for all instruction of the opR kind
|
||
def opR(funct3: UInt, funct7: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpR instruction
|
||
object Funct3 extends ChiselEnum {
|
||
// Type I
|
||
val ADD_SUB = "b000".U
|
||
val AND = "b111".U
|
||
val OR = "b110".U
|
||
val XOR = "b100".U
|
||
val SLL = "b001".U
|
||
val SLT = "b010".U
|
||
val SLTU = "b011".U
|
||
|
||
// Arithmetic op (discriminate by Funct7)
|
||
val SRL_SRA = "b101".U
|
||
}
|
||
|
||
// Meaning of Funct7 in the context of SHIFTR IR instructions
|
||
object IRFunct7 extends ChiselEnum {
|
||
val SRL = "b0000000".U
|
||
val SRA = "b0100000".U
|
||
}
|
||
|
||
object ADDFunct7 extends ChiselEnum {
|
||
val ADD = "b0000000".U
|
||
val SUB = "b0100000".U
|
||
}
|
||
|
||
io.mux_regb_imm := true.B;
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_alu_imm := true.B;
|
||
switch(funct3) {
|
||
// Type I
|
||
is(Funct3.ADD_SUB) {
|
||
switch(funct7) {
|
||
is(ADDFunct7.ADD) {
|
||
io.alu_opcode := AluOpCode.Add
|
||
}
|
||
is(ADDFunct7.SUB) {
|
||
io.alu_opcode := AluOpCode.Sub
|
||
}
|
||
}
|
||
}
|
||
|
||
is(Funct3.AND) {
|
||
io.alu_opcode := AluOpCode.And
|
||
}
|
||
|
||
is(Funct3.OR) {
|
||
io.alu_opcode := AluOpCode.Or
|
||
}
|
||
|
||
is(Funct3.XOR) {
|
||
io.alu_opcode := AluOpCode.Xor
|
||
}
|
||
|
||
is(Funct3.SLL) {
|
||
io.alu_opcode := AluOpCode.ShiftLeft
|
||
}
|
||
|
||
is(Funct3.SLT) {
|
||
io.alu_opcode := AluOpCode.LessThanSigned
|
||
}
|
||
|
||
is(Funct3.SLTU) {
|
||
io.alu_opcode := AluOpCode.LessThanUnsigned
|
||
}
|
||
|
||
// Type IR
|
||
is(Funct3.SRL_SRA) {
|
||
switch(funct7) {
|
||
is(IRFunct7.SRL) {
|
||
io.alu_opcode := AluOpCode.ShiftRight
|
||
}
|
||
|
||
is(IRFunct7.SRA) {
|
||
io.alu_opcode := AluOpCode.ShiftArithmeticRight
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Switch to mul extension
|
||
val RV32M_FUNCT7 = "b0000001".U
|
||
when(funct7 === RV32M_FUNCT7) {
|
||
opM(funct3, io);
|
||
}
|
||
}
|
||
|
||
// Implements functions for all instruction of the branch kind
|
||
def opBranch(funct3: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpBranch instruction
|
||
object Funct3 extends ChiselEnum {
|
||
val BEQ = "b000".U
|
||
val BNE = "b001".U
|
||
val BLT = "b100".U
|
||
val BGE = "b101".U
|
||
val BLTU = "b110".U
|
||
val BGEU = "b111".U
|
||
}
|
||
|
||
val branch = WireInit(false.B);
|
||
switch(funct3) {
|
||
is(Funct3.BEQ) {
|
||
// Send operands from registers to alu
|
||
io.alu_opcode := AluOpCode.Equal
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
branch := io.alu_comp_result
|
||
}
|
||
|
||
is(Funct3.BNE) {
|
||
// Send operands from registers to alu
|
||
io.alu_opcode := AluOpCode.Equal
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
branch := ~io.alu_comp_result
|
||
}
|
||
|
||
// Signed comparison
|
||
is(Funct3.BLT) {
|
||
// Send operands from registers to alu
|
||
io.alu_opcode := AluOpCode.LessThanSigned
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
branch := io.alu_comp_result
|
||
}
|
||
|
||
is(Funct3.BGE) {
|
||
// Send operands from registers to alu
|
||
io.alu_opcode := AluOpCode.LessThanSigned
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
branch := ~io.alu_comp_result
|
||
}
|
||
|
||
// Signed comparison
|
||
is(Funct3.BLTU) {
|
||
// Send operands from registers to alu
|
||
io.alu_opcode := AluOpCode.LessThanUnsigned
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
branch := io.alu_comp_result
|
||
}
|
||
|
||
is(Funct3.BGEU) {
|
||
// Send operands from registers to alu
|
||
io.alu_opcode := AluOpCode.LessThanUnsigned
|
||
io.mux_rega_pc := true.B;
|
||
io.mux_regb_imm := true.B;
|
||
branch := ~io.alu_comp_result
|
||
}
|
||
}
|
||
|
||
// Branch if comparison is successful
|
||
when(branch) {
|
||
io.mux_pcadder_writeback := true.B;
|
||
io.mux_incrpc4_imm := false.B;
|
||
io.mux_regpc_executepc := false.B;
|
||
io.is_jump := true.B;
|
||
}
|
||
}
|
||
// Implements functions for all instruction of the OpStore kind
|
||
def opStore(funct3: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpStore instruction
|
||
object Funct3 extends ChiselEnum {
|
||
val SB = "b000".U
|
||
val SH = "b001".U
|
||
val SW = "b010".U
|
||
}
|
||
|
||
io.alu_opcode := AluOpCode.Add
|
||
io.mux_alu_imm := true.B
|
||
io.mux_rega_pc := true.B
|
||
io.mux_regb_imm := false.B
|
||
io.memory_we := true.B
|
||
io.memory_en := true.B
|
||
switch(funct3) {
|
||
is(Funct3.SB) {
|
||
io.memory_size := DMemSize.Byte
|
||
}
|
||
is(Funct3.SH) {
|
||
io.memory_size := DMemSize.Half
|
||
}
|
||
is(Funct3.SW) {
|
||
io.memory_size := DMemSize.Word
|
||
}
|
||
}
|
||
}
|
||
// Implements functions for all instruction of the OpLoad kind
|
||
def opLoad(funct3: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpLoad instruction
|
||
object Funct3 extends ChiselEnum {
|
||
val LB = "b000".U
|
||
val LH = "b001".U
|
||
val LW = "b010".U
|
||
val LBU = "b100".U
|
||
val LHU = "b101".U
|
||
}
|
||
|
||
io.alu_opcode := AluOpCode.Add
|
||
io.mux_alu_imm := true.B
|
||
io.mux_rega_pc := true.B
|
||
io.mux_regb_imm := false.B
|
||
io.mux_writeback_pc := true.B;
|
||
io.mux_executeout_dout := false.B;
|
||
io.reg_file_we := true.B
|
||
io.memory_en := true.B
|
||
io.memory_we := false.B
|
||
switch(funct3) {
|
||
// Byte load
|
||
is(Funct3.LB) {
|
||
io.memory_size := DMemSize.Byte
|
||
io.memory_sign_extend := true.B;
|
||
}
|
||
is(Funct3.LBU) {
|
||
io.memory_size := DMemSize.Byte
|
||
}
|
||
|
||
// Half load
|
||
is(Funct3.LH) {
|
||
io.memory_size := DMemSize.Half
|
||
io.memory_sign_extend := true.B;
|
||
}
|
||
is(Funct3.LHU) {
|
||
io.memory_size := DMemSize.Half
|
||
}
|
||
|
||
// Word load
|
||
is(Funct3.LW) {
|
||
io.memory_size := DMemSize.Word
|
||
}
|
||
}
|
||
}
|
||
|
||
// ~~~ EXTENSIONS ~~~
|
||
|
||
// ~~ Rv64i ~~
|
||
|
||
// Implements functions for all instruction of the opImm, w kind
|
||
def opImmW(funct3: UInt, funct7: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpImm instruction
|
||
val _ = funct7;
|
||
object Funct3 extends ChiselEnum {
|
||
// Type I
|
||
val ADDIW = "b000".U
|
||
val SLLIW = "b001".U
|
||
|
||
// Unimplemented
|
||
val SRLIW_SRAIW = "b101".U // Right shifts
|
||
}
|
||
|
||
// Meaning of Funct7 in the context of SHIFTR IR instructions
|
||
// object IRFunct7 extends ChiselEnum {
|
||
// val SRLI = "b0000000".U
|
||
// val SRAI = "b0100000".U
|
||
// }
|
||
|
||
io.mux_regb_imm := false.B; // OpImm are operations with imm, so send imm to ALU
|
||
io.mux_alu_imm := true.B;
|
||
io.mux_rega_pc := true.B;
|
||
switch(funct3) {
|
||
// Type I
|
||
is(Funct3.ADDIW) {
|
||
io.alu_word_mode := true.B
|
||
io.alu_opcode := AluOpCode.Add
|
||
io.optype := OpType.I
|
||
}
|
||
}
|
||
}
|
||
|
||
// ~~ Rv32M ~~
|
||
|
||
// Implements functions for instructions of the M extension of type R
|
||
def opM(funct3: UInt, io: CUInterface) = {
|
||
// Meaning of Funct3 in the context of an OpM instruction
|
||
object Funct3 extends ChiselEnum {
|
||
val MUL = "b000".U
|
||
val MULH = "b001".U
|
||
val MULHSU = "b010".U
|
||
val MULHU = "b011".U
|
||
val DIV = "b100".U
|
||
val DIVU = "b101".U
|
||
val REM = "b110".U
|
||
val REMU = "b111".U
|
||
}
|
||
|
||
switch(funct3) {
|
||
is(Funct3.MUL) {
|
||
io.alu_opcode := AluOpCode.Mul
|
||
}
|
||
is(Funct3.MULH) {
|
||
io.alu_opcode := AluOpCode.Mulh
|
||
}
|
||
is(Funct3.MULHSU) {
|
||
io.alu_opcode := AluOpCode.Mulhsu
|
||
}
|
||
is(Funct3.MULHU) {
|
||
io.alu_opcode := AluOpCode.Mulhu
|
||
}
|
||
is(Funct3.DIV) {
|
||
io.alu_opcode := AluOpCode.Div
|
||
}
|
||
is(Funct3.DIVU) {
|
||
io.alu_opcode := AluOpCode.Divu
|
||
}
|
||
is(Funct3.REM) {
|
||
io.alu_opcode := AluOpCode.Rem
|
||
}
|
||
is(Funct3.REMU) {
|
||
io.alu_opcode := AluOpCode.Remu
|
||
}
|
||
}
|
||
}
|
||
}
|