From 297562766c14166e62acb5b2d043f5de688824c7 Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Mon, 10 Nov 2025 22:07:15 +0100 Subject: [PATCH 1/3] Adds addiw instruction, all tests passing --- bench/tests/op/addiw.s | 14 +++++++ src/main/scala/projet/Alu.scala | 52 ++++++++++++++++++------- src/main/scala/projet/ControlUnit.scala | 50 ++++++++++++++++++++++++ src/main/scala/projet/Rv64i.scala | 1 + src/main/scala/projet/SignExtend.scala | 11 ++++++ 5 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 bench/tests/op/addiw.s create mode 100644 src/main/scala/projet/SignExtend.scala diff --git a/bench/tests/op/addiw.s b/bench/tests/op/addiw.s new file mode 100644 index 0000000..5943844 --- /dev/null +++ b/bench/tests/op/addiw.s @@ -0,0 +1,14 @@ +# expected: 0000000000000000, FFFFFFFFFFFFFFFF, 0000000000000010 + .text +_start: + # This test CANNOT use li as it uses addiw under the hood + + # 0 addition + addiw x31, x0, 0 + + # all bits addition + sign extension to 32 bits then 64 bits + addiw x31, x0, (-1) + + # simple addition + addi x1, x0, 8 + addiw x31, x1, 8 diff --git a/src/main/scala/projet/Alu.scala b/src/main/scala/projet/Alu.scala index 2639ac4..9f8b633 100644 --- a/src/main/scala/projet/Alu.scala +++ b/src/main/scala/projet/Alu.scala @@ -5,9 +5,9 @@ import chisel3.util.switch import chisel3.util.is object AluOpCode extends ChiselEnum { - val Add, AddW, Sub, And, Or, Xor, ShiftLeft, ShiftRight, - ShiftArithmeticRight, LessThanSigned, LessThanUnsigned, - GreaterEqualSigned, GreaterEqualUnsigned, Equal = Value + val Add, Sub, And, Or, Xor, ShiftLeft, ShiftRight, ShiftArithmeticRight, + LessThanSigned, LessThanUnsigned, GreaterEqualSigned, + GreaterEqualUnsigned, Equal = Value } class Alu extends Module { @@ -19,65 +19,87 @@ class Alu extends Module { val out = Output(UInt(64.W)) val opcode = Input(AluOpCode()) val comp_result = Output(Bool()) + + // ~~ Rv64i ~~ + // If word_mode is enabled, operations are performed + // on 32-bit-truncated inputs and then sign extended to 64bits + val word_mode = Input(Bool()) }) io.out := 0.U; io.comp_result := false.B; + val out = WireInit(0.U(64.W)) + val op_a = WireInit(0.U(64.W)) + val op_b = WireInit(0.U(64.W)) + + // Defaults + op_a := io.a + op_b := io.b + io.out := out + + // Truncate operands, sext result + when(io.word_mode) { + op_a := io.a(31, 0) + op_b := io.b(31, 0) + + io.out := SignExtend.Sext(out(31, 0), 64) + } + switch(io.opcode) { is(Add) { - io.out := io.a + io.b; + out := io.a + io.b; } is(Sub) { - io.out := io.a - io.b; + out := io.a - io.b; } is(And) { - io.out := io.a & io.b; + out := io.a & io.b; } is(Or) { - io.out := io.a | io.b; + out := io.a | io.b; } is(Xor) { - io.out := io.a ^ io.b; + out := io.a ^ io.b; } is(ShiftLeft) { - io.out := io.a << io.b(5, 0); + out := io.a << io.b(5, 0); } is(ShiftRight) { - io.out := io.a >> io.b(5, 0); + out := io.a >> io.b(5, 0); } is(ShiftArithmeticRight) { // >> means arithmetic shift for SInts - io.out := (io.a.asSInt >> io.b(5, 0)).asUInt; + out := (io.a.asSInt >> io.b(5, 0)).asUInt; } is(LessThanUnsigned) { val less_than = io.a < io.b; - io.out := less_than; + out := less_than; io.comp_result := less_than; } is(LessThanSigned) { val less_than = io.a.asSInt < io.b.asSInt - io.out := less_than + out := less_than io.comp_result := less_than; } is(GreaterEqualUnsigned) { val geq_than = io.a >= io.b; - io.out := geq_than + out := geq_than io.comp_result := geq_than; } is(GreaterEqualSigned) { val geq_than = io.a.asSInt >= io.b.asSInt - io.out := geq_than + out := geq_than io.comp_result := geq_than; } diff --git a/src/main/scala/projet/ControlUnit.scala b/src/main/scala/projet/ControlUnit.scala index 9fc7560..291c624 100644 --- a/src/main/scala/projet/ControlUnit.scala +++ b/src/main/scala/projet/ControlUnit.scala @@ -8,8 +8,11 @@ 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()) @@ -95,6 +98,9 @@ object OpCode extends ChiselEnum { // Type I val OpLoad = "b0000011".U + + // ~~ Rv64i ~~ + val OpImmW = "b0011011".U } class ControlUnit() extends Module { @@ -102,6 +108,7 @@ class ControlUnit() extends Module { 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 @@ -196,6 +203,12 @@ class ControlUnit() extends Module { 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 @@ -224,6 +237,7 @@ class ControlUnit() extends Module { 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) { @@ -505,4 +519,40 @@ class ControlUnit() extends Module { } } } + + // ~~~ 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 + } + } + } } diff --git a/src/main/scala/projet/Rv64i.scala b/src/main/scala/projet/Rv64i.scala index b6b8c6b..3237248 100644 --- a/src/main/scala/projet/Rv64i.scala +++ b/src/main/scala/projet/Rv64i.scala @@ -30,6 +30,7 @@ class Rv64i(sim: Boolean = true) extends Module { 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 // PC delayed to execute stage for auipc op val execute_pc = Delay.Delay(reg_pc, 1, 0.U); diff --git a/src/main/scala/projet/SignExtend.scala b/src/main/scala/projet/SignExtend.scala new file mode 100644 index 0000000..9f7e09f --- /dev/null +++ b/src/main/scala/projet/SignExtend.scala @@ -0,0 +1,11 @@ +package projet + +import chisel3.UInt +import chisel3.util.Fill + +// Utility function to sign extend number to higher amount of bits +object SignExtend { + def Sext(x: UInt, n: Int): UInt = { + Fill(n - x.getWidth, x(x.getWidth - 1)) ## x + } +} From 4c967431edb0e061d5dfdf6b2d2f2c4e049736ef Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Tue, 11 Nov 2025 11:05:05 +0100 Subject: [PATCH 2/3] Adds a test for load immediate (li) pseudo instruction This makes sure the other tests can build upon a sane base --- bench/tests/op/li.s | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 bench/tests/op/li.s diff --git a/bench/tests/op/li.s b/bench/tests/op/li.s new file mode 100644 index 0000000..a29f314 --- /dev/null +++ b/bench/tests/op/li.s @@ -0,0 +1,9 @@ +# exepected: 1234567890ABCDEF, 00000000FFFFFFFF + +# li is a PSEUDO-INSTRUCTION. +# This test makes sure we can reliably load 64bit words +# into the processor as it is essential for all other tests + .text +_start: + li x31, 0x1234567890ABCDEF + li x31, 0x00000000FFFFFFFF From f9cbcaf20f1f574098c0e8fe00604747b5dab8d4 Mon Sep 17 00:00:00 2001 From: supersurviveur Date: Tue, 11 Nov 2025 19:12:12 +0100 Subject: [PATCH 3/3] Add BSwitch macro and update gtkwave config --- build.sc | 12 +++++ common/config.gtkw | 2 +- macros/src/utils/BSwitchMacro.scala | 77 +++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 macros/src/utils/BSwitchMacro.scala diff --git a/build.sc b/build.sc index 3093bbb..ccfc53b 100644 --- a/build.sc +++ b/build.sc @@ -13,6 +13,17 @@ import mill.bsp._ // to properly generate and handle test directories and output files. // See: https://github.com/com-lihaoyi/mill/issues/3840 +object macros extends ScalaModule { + def scalaVersion = "2.13.16" + def scalacOptions = Seq[String]() + override def ivyDeps = Agg( + ivy"org.chipsalliance::chisel:7.2.0" + ) + override def scalacPluginIvyDeps = Agg( + ivy"org.chipsalliance:::chisel-plugin:7.2.0" + ) +} + object `TPchisel` extends SbtModule { m => override def millSourcePath = super.millSourcePath / os.up override def scalaVersion = "2.13.16" @@ -27,6 +38,7 @@ object `TPchisel` extends SbtModule { m => "-Ywarn-dead-code", "-Ywarn-unused" ) + override def moduleDeps = Seq(macros) override def ivyDeps = Agg( ivy"org.chipsalliance::chisel:7.2.0" ) diff --git a/common/config.gtkw b/common/config.gtkw index 4d03d84..578db16 100644 --- a/common/config.gtkw +++ b/common/config.gtkw @@ -17,7 +17,7 @@ TOP.svsimTestbench.dut.core.wbVal[31:0] TOP.svsimTestbench.dut.core.clock TOP.svsimTestbench.dut.core.reset @22 -TOP.svsimTestbench.dut.io_x31[31:0] +TOP.svsimTestbench.dut.io_x31[63:0] @28 TOP.svsimTestbench.dut.io_valid_x31 TOP.svsimTestbench.dut.core.rdValid diff --git a/macros/src/utils/BSwitchMacro.scala b/macros/src/utils/BSwitchMacro.scala new file mode 100644 index 0000000..d300903 --- /dev/null +++ b/macros/src/utils/BSwitchMacro.scala @@ -0,0 +1,77 @@ +package macros.utils + +import scala.language.experimental.macros +import scala.reflect.macros.blackbox._ +import chisel3._ +import chisel3.experimental.SourceInfo +import chisel3.util.BitPat + +object bis { + + /** Executes `block` if the bswitch condition matchs `v`. + */ + def apply(v: BitPat)(block: => Any): Unit = { + locally((v, block)) + require( + false, + "The 'bis' keyword may not be used outside of a bswitch." + ) + } +} + +class BSwitchContext( + cond: UInt, + whenContext: Option[WhenContext], + lits: Set[BitPat] +) { + def bis( + pat: BitPat + )(block: => Any)(implicit + sourceInfo: SourceInfo + ): BSwitchContext = { + require( + !lits.exists(w => { + w.overlap(pat) + }), + "all bis conditions must be mutually exclusive!" + ) + + // def instead of val so that logic ends up in legal place + def p = (cond === pat) + whenContext match { + case Some(w) => + new BSwitchContext( + cond, + Some(w.elsewhen(p)(block)), + lits + pat + ) + case None => + new BSwitchContext( + cond, + Some(when(p)(block)), + lits + pat + ) + } + } +} + +object bswitch { + def apply[T <: Element](cond: T)(x: => Any): Unit = macro impl + def impl(c: Context)(cond: c.Tree)(x: c.Tree): c.Tree = { + import c.universe._ + val q"..$body" = x + val res = body.foldLeft( + q"""new macros.utils.BSwitchContext($cond, None, Set.empty)""" + ) { case (acc, tree) => + tree match { + case q"macros.utils.bis.apply( ..$params )( ..$body )" => + q"$acc.bis( ..$params )( ..$body )" + case _ => + throw new Exception( + s"Cannot include blocks that do not begin with bis() in bswitch." + ) + } + } + q"""{ $res }""" + } +}