diff --git a/flake.nix b/flake.nix index b2e8142..c824872 100644 --- a/flake.nix +++ b/flake.nix @@ -111,12 +111,17 @@ nixpkgs-circt162.legacyPackages.${system}.circt yosys cli11 + flex + bison + verilator ]; buildInputs = [ - verilator nvboard openssl + libllvm + libxml2 + readline ] ++ self.checks.${system}.pre-commit-check.enabledPackages; cmakeFlags = [ diff --git a/npc/CMakeLists.txt b/npc/CMakeLists.txt index 78dbfe5..bcd356b 100644 --- a/npc/CMakeLists.txt +++ b/npc/CMakeLists.txt @@ -34,6 +34,10 @@ if(BUILD_SIM_NVBOARD_TARGET) find_package(SDL2_image REQUIRED) endif() find_package(CLI11 CONFIG REQUIRED) +# TODO: Not required +find_package(LLVM CONFIG REQUIRED) + +option(ENABLE_SDB "Enable simple debugger" ON) find_library(NVBOARD_LIBRARY NAMES nvboard) find_path(NVBOARD_INCLUDE_DIR NAMES nvboard.h) @@ -59,6 +63,7 @@ endif() # -- Build Verilator executable and add to test include_directories(include) +add_subdirectory(utils) add_subdirectory(csrc) diff --git a/npc/cmake/FindReadline.cmake b/npc/cmake/FindReadline.cmake new file mode 100644 index 0000000..dc2dd41 --- /dev/null +++ b/npc/cmake/FindReadline.cmake @@ -0,0 +1,49 @@ +# Code copied from sethhall@github +# +# - Try to find readline include dirs and libraries +# +# Usage of this module as follows: +# +# find_package(Readline) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# Readline_ROOT_DIR Set this variable to the root installation of +# readline if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# READLINE_FOUND System has readline, include and lib dirs found +# Readline_INCLUDE_DIR The readline include directories. +# Readline_LIBRARY The readline library. + +find_path(Readline_ROOT_DIR + NAMES include/readline/readline.h +) + +find_path(Readline_INCLUDE_DIR + NAMES readline/readline.h + HINTS ${Readline_ROOT_DIR}/include +) + +find_library(Readline_LIBRARY + NAMES readline + HINTS ${Readline_ROOT_DIR}/lib +) + +if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + set(READLINE_FOUND TRUE) +else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + find_library(Readline_LIBRARY NAMES readline) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) + mark_as_advanced(Readline_INCLUDE_DIR Readline_LIBRARY) +endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + +mark_as_advanced( + Readline_ROOT_DIR + Readline_INCLUDE_DIR + Readline_LIBRARY +) \ No newline at end of file diff --git a/npc/core/src/main/resources/RamDpi.v b/npc/core/src/main/resources/RamDpi.v index d6e3c98..c9f8b7c 100644 --- a/npc/core/src/main/resources/RamDpi.v +++ b/npc/core/src/main/resources/RamDpi.v @@ -8,7 +8,9 @@ module RamDpi ( input [31:0] writeData, input [3:0] writeMask, input reg [31:0] readAddr, - output reg [31:0] readData + output reg [31:0] readData, + input reg [31:0] pc, + output reg [31:0] inst ); always @(*) begin if (valid) begin // 有读写请求时 @@ -20,5 +22,6 @@ module RamDpi ( else begin readData = 0; end + inst = pmem_read(pc); end endmodule \ No newline at end of file diff --git a/npc/core/src/main/scala/components/ALU.scala b/npc/core/src/main/scala/components/ALU.scala index 9a0b0f9..0c22b15 100644 --- a/npc/core/src/main/scala/components/ALU.scala +++ b/npc/core/src/main/scala/components/ALU.scala @@ -6,41 +6,50 @@ import shapeless.{HNil, ::} class ALUControlInterface extends Bundle { object OpSelect extends ChiselEnum { - val aOpAdd, aOpSub, aOpNot, aOpAnd, aOpOr, aOpXor, aOpSlt, aOpEq, aOpNop = Value + val aOpAdd, aOpSub, aOpNot, aOpAnd, aOpOr, aOpXor, aOpSlt, aOpSll, aOpSrl, aOpSra = Value } - object SrcSelect extends ChiselEnum { - val aSrcRs2, aSrcImm = Value + object SrcASelect extends ChiselEnum { + val aSrcARs1, aSrcAPc, aSrcAZero = Value + } + object SrcBSelect extends ChiselEnum { + val aSrcBRs2, aSrcBImmI, aSrcBImmJ, aSrcBImmB, aSrcBImmS = Value } val op = Input(OpSelect()) - val src = Input(SrcSelect()) + val srcASelect = Input(SrcASelect()) + val srcBSelect = Input(SrcBSelect()) + val signExt = Input(Bool()) - type CtrlTypes = OpSelect.Type :: SrcSelect.Type :: HNil - def ctrlBindPorts: CtrlTypes = { - op :: src :: HNil + def ctrlBindPorts = { + op :: srcASelect :: srcBSelect :: signExt :: HNil } } class ALU[T <: UInt](tpe: T) extends Module { val control = IO(new ALUControlInterface) val in = IO(new Bundle { - val a = Input(Vec(control.SrcSelect.all.length, tpe)) - val b = Input(tpe) + val a = Input(Vec(control.SrcASelect.all.length, tpe)) + val b = Input(Vec(control.SrcBSelect.all.length, tpe)) }) val out = IO(new Bundle { + val eq = Output(Bool()) val result = Output(tpe) }) - val a = in.a(control.src.asUInt) + val a = in.a(control.srcASelect.asUInt) + val b = in.b(control.srcBSelect.asUInt) // val adder_b = (Fill(tpe.getWidth, io.op(0)) ^ io.b) + io.op(0) // take (-b) if sub - val add = a + in.b - val sub = a - in.b - val and = a & in.b + val add = a + b + val sub = a - b + val and = a & b val not = ~a - val or = a | in.b - val xor = a ^ in.b - val slt = a < in.b - val eq = a === in.b + val or = a | b + val xor = a ^ b + val slt = a < b + val sll = a << b(log2Ceil(tpe.getWidth), 0) + val srl = a >> b(log2Ceil(tpe.getWidth), 0) + val sra = a.asSInt >> b(log2Ceil(tpe.getWidth), 0) + out.eq := a === b import control.OpSelect._ @@ -52,7 +61,9 @@ class ALU[T <: UInt](tpe: T) extends Module { aOpOr -> or, aOpXor -> xor, aOpSlt -> slt, - aOpEq -> eq + aOpSll -> sll, + aOpSrl -> srl, + aOpSra -> sra.asUInt )) } diff --git a/npc/core/src/main/scala/components/Mem.scala b/npc/core/src/main/scala/components/Mem.scala index 6634edb..7562730 100644 --- a/npc/core/src/main/scala/components/Mem.scala +++ b/npc/core/src/main/scala/components/Mem.scala @@ -6,19 +6,32 @@ import chisel3.util.HasBlackBoxResource import chisel3.util.log2Ceil import chisel3.experimental.noPrefix import scala.collection.SeqMap -import javax.swing.plaf.synth.SynthRadioButtonMenuItemUI +import flow.components +import shapeless.{HNil, ::} -class RamInterface[T <: Data](tpe: T, addrWidth: Int) extends Bundle { +class RamControlInterface(addrWidth: Int) extends Bundle { val valid = Input(Bool()) + val writeMask = Input(UInt((addrWidth / 8).W)) val writeEnable = Input(Bool()) + def ctrlBindPorts = { + valid :: writeMask :: writeEnable :: HNil + } + +} + +/* FIXME: Extends here might not be the best solution. + * We need a way to merge two bundles together + */ +class RamInterface[T <: Data](tpe: T, addrWidth: Int) extends RamControlInterface(addrWidth) { val writeAddr = Input(UInt(addrWidth.W)) val writeData = Input(tpe) - val writeMask = Input(UInt((addrWidth / 8).W)) val readAddr = Input(UInt(addrWidth.W)) val readData = Output(tpe) + val pc = Input(UInt(addrWidth.W)) + val inst = Output(tpe) } class RamDpi extends BlackBox with HasBlackBoxResource { - val io = IO(new RamInterface(UInt(32.W), 32)) + val io = IO((new RamInterface(UInt(32.W), 32))) addResource("/RamDpi.v") } diff --git a/npc/core/src/main/scala/components/ProgramCounter.scala b/npc/core/src/main/scala/components/ProgramCounter.scala index bfb3e1b..01ac115 100644 --- a/npc/core/src/main/scala/components/ProgramCounter.scala +++ b/npc/core/src/main/scala/components/ProgramCounter.scala @@ -6,33 +6,42 @@ import shapeless.{HNil, ::} class PcControlInterface extends Bundle { object SrcSelect extends ChiselEnum { - val pStaticNpc, pBranchResult = Value + val pStaticNpc, pExeOut = Value } + val useImmB = Input(Bool()) val srcSelect = Input(SrcSelect()) - type CtrlTypes = SrcSelect.Type :: HNil - def ctrlBindPorts: CtrlTypes = { - srcSelect :: HNil + def ctrlBindPorts = { + useImmB :: srcSelect :: HNil } } -class ProgramCounter[T <: Data](tpe: T) extends Module { +class ProgramCounter[T <: UInt](tpe: T) extends Module { val control = IO(new PcControlInterface) val in = IO(new Bundle { - val pcSrcs = Input(Vec(control.SrcSelect.all.length, tpe)) + val immB = Input(tpe) + val exeOut = Input(tpe) }) val out = IO(Output(tpe)) - private val pc = RegInit(0x80000000L.U) + private val pc_reg = RegInit(0x80000000L.U) - pc := in.pcSrcs(control.srcSelect.asUInt) - out := pc +// pc := in.pcSrcs(control.srcSelect.asUInt) + import control.SrcSelect._ + when( control.useImmB === true.B ) { + pc_reg := pc_reg + in.immB + }. elsewhen( control.srcSelect === pStaticNpc) { + pc_reg := pc_reg + 4.U + }. elsewhen( control.srcSelect === pExeOut) { + pc_reg := in.exeOut + } + out := pc_reg } object ProgramCounter { - def apply[T <: Data](tpe: T): ProgramCounter[T] = { + def apply[T <: UInt](tpe: T): ProgramCounter[T] = { val pc = Module(new ProgramCounter(tpe)) pc } diff --git a/npc/core/src/main/scala/components/RegisterFile.scala b/npc/core/src/main/scala/components/RegisterFile.scala index 9b50375..b5d1b63 100644 --- a/npc/core/src/main/scala/components/RegisterFile.scala +++ b/npc/core/src/main/scala/components/RegisterFile.scala @@ -5,18 +5,17 @@ import chisel3.util.log2Ceil import chisel3.util.UIntToOH import chisel3.util.MuxLookup import chisel3.experimental.Trace._ -import shapeless.{ HNil, :: } +import shapeless.{ HList, HNil, :: } class RegControl extends Bundle { object WriteSelect extends ChiselEnum { - val rAluOut, rMemOut = Value + val rAluOut, rMemOut, rNpc = Value } val writeEnable = Input(Bool()) val writeSelect = Input(WriteSelect()) - type CtrlTypes = Bool :: WriteSelect.Type :: HNil - def ctrlBindPorts: CtrlTypes = { + def ctrlBindPorts = { writeEnable :: writeSelect :: HNil } traceName(writeEnable) @@ -40,7 +39,8 @@ class RegisterFile[T <: Data](tpe: T, regCount: Int, numReadPorts: Int) extends val writeAddrOH = UIntToOH(in.writeAddr) for ((reg, i) <- regFile.zipWithIndex.tail) { - reg := Mux(writeAddrOH(i.U(log2Ceil(regCount).W)) && control.writeEnable, in.writeData(control.writeSelect.asUInt), reg) + reg := Mux( + writeAddrOH(i.U(log2Ceil(regCount).W)) && control.writeEnable, in.writeData(control.writeSelect.asUInt), reg) } regFile(0) := 0.U diff --git a/npc/core/src/main/scala/top/FlowMain.scala b/npc/core/src/main/scala/top/FlowMain.scala index 73df9ed..899783c 100644 --- a/npc/core/src/main/scala/top/FlowMain.scala +++ b/npc/core/src/main/scala/top/FlowMain.scala @@ -2,12 +2,8 @@ package flow import scala.reflect.runtime.universe._ import chisel3._ -import chisel3.util.{MuxLookup, Fill, Decoupled, Counter, Queue, Reverse} -import chisel3.util.{SRAM} import chisel3.util.experimental.decode.{decoder, TruthTable} -import chisel3.util.log2Ceil -import chisel3.util.BitPat -import chisel3.util.Enum +import chisel3.util._ import chisel3.experimental.Trace._ import shapeless.{HNil, ::} import shapeless.HList @@ -15,68 +11,322 @@ import shapeless.ops.coproduct.Prepend import chisel3.util.{ BinaryMemoryFile, HexMemoryFile } import chisel3.experimental.Trace +import scala.collection.IndexedSeqView +import shapeless.Poly1 +import flow.components.RamControlInterface object RV32Inst { private val bp = BitPat - val addi = this.bp("b???????_?????_?????_000_?????_00100_11") - val inv = this.bp("b???????_?????_?????_???_?????_?????_??") -} -class PcControl(width: Int) extends Bundle { - object SrcSelect extends ChiselEnum { - val pPC, pExeResult = Value - } - val srcSelect = Output(SrcSelect()) + val lui = this.bp("b???????_?????_?????_???_?????_01101_11") + val auipc = this.bp("b???????_?????_?????_???_?????_00101_11") + + val jal = this.bp("b???????_?????_?????_???_?????_11011_11") + val jalr = this.bp("b???????_?????_?????_???_?????_11001_11") + val beq = this.bp("b???????_?????_?????_000_?????_11000_11") + val bne = this.bp("b???????_?????_?????_001_?????_11000_11") + val blt = this.bp("b???????_?????_?????_100_?????_11000_11") + val bge = this.bp("b???????_?????_?????_101_?????_11000_11") + val bltu = this.bp("b???????_?????_?????_110_?????_11000_11") + val bgeu = this.bp("b???????_?????_?????_111_?????_11000_11") + + val lb = this.bp("b???????_?????_?????_000_?????_00000_11") + val lh = this.bp("b???????_?????_?????_001_?????_00000_11") + val lw = this.bp("b???????_?????_?????_010_?????_00000_11") + val lbu = this.bp("b???????_?????_?????_100_?????_00000_11") + val lhu = this.bp("b???????_?????_?????_101_?????_00000_11") + val sb = this.bp("b???????_?????_?????_000_?????_01000_11") + val sh = this.bp("b???????_?????_?????_001_?????_01000_11") + val sw = this.bp("b???????_?????_?????_010_?????_01000_11") + + val addi = this.bp("b???????_?????_?????_000_?????_00100_11") + val slti = this.bp("b???????_?????_?????_010_?????_00100_11") + val sltiu = this.bp("b???????_?????_?????_011_?????_00100_11") + val xori = this.bp("b???????_?????_?????_100_?????_00100_11") + val ori = this.bp("b???????_?????_?????_110_?????_00100_11") + val andi = this.bp("b???????_?????_?????_111_?????_00100_11") + val slli = this.bp("b0000000_?????_?????_001_?????_00100_11") + val srli = this.bp("b0000000_?????_?????_101_?????_00100_11") + val srai = this.bp("b0100000_?????_?????_101_?????_00100_11") + val add = this.bp("b0000000_?????_?????_000_?????_01100_11") + val sub = this.bp("b0100000_?????_?????_000_?????_01100_11") + val sll = this.bp("b0000000_?????_?????_001_?????_01100_11") + val slt = this.bp("b0000000_?????_?????_010_?????_01100_11") + val sltu = this.bp("b0000000_?????_?????_011_?????_01100_11") + val xor = this.bp("b0000000_?????_?????_100_?????_01100_11") + val srl = this.bp("b0000000_?????_?????_101_?????_01100_11") + val sra = this.bp("b0100000_?????_?????_101_?????_01100_11") + val or = this.bp("b0000000_?????_?????_110_?????_01100_11") + val and = this.bp("b0000000_?????_?????_111_?????_01100_11") + + val ebreak = this.bp("b0000000_00001_00000_000_00000_11100_11") + + + val mul = this.bp("b0000001_?????_?????_000_?????_01100_11") + val mulh = this.bp("b0000001_?????_?????_001_?????_01100_11") + val mulhsu = this.bp("b0000001_?????_?????_010_?????_01100_11") + val mulhu = this.bp("b0000001_?????_?????_011_?????_01100_11") + val div = this.bp("b0000001_?????_?????_100_?????_01100_11") + val divu = this.bp("b0000001_?????_?????_101_?????_01100_11") + val rem = this.bp("b0000001_?????_?????_110_?????_01100_11") + val remu = this.bp("b0000001_?????_?????_111_?????_01100_11") + + val inv = this.bp("b???????_?????_?????_???_?????_?????_??") } import flow.components.{RegControl, PcControlInterface, ALUControlInterface} -class Control(width: Int) extends Module { +class Control(width: Int) extends RawModule { + // Helpers + class WrapList[T](vl: T) { type Type = T; val v = vl} + object wrap extends Poly1 { + implicit def default[A] = at[A](Right(_).withLeft[Int]) + } + def lit(x: Element) = { x.litValue.toInt } + def toBits(t: dst.Type): BitPat = { + val list = t.toList + list.map(e => e match { + case Right(x) => BitPat(lit(x).U(x.getWidth.W)) + case Left(x) => BitPat.dontCare(x) + }).reduceLeft(_ ## _) + } + val r = Right + def l[T <: Any](x: T) = x match { + case x: ChiselEnum => Left(log2Ceil(x.all.length)) + case x: Data => Left(x.getWidth) + case _ => throw new IllegalArgumentException + } + val inst = IO(Input(UInt(width.W))) val reg = IO(Flipped(new RegControl)) val pc = IO(Flipped(new PcControlInterface)) val alu = IO(Flipped(new ALUControlInterface)) + val ram = IO(Flipped(new RamControlInterface(32))) - // TODO: Add .ctrlTypes together instead of writing them by hand. - type T = - Bool :: reg.WriteSelect.Type :: pc.SrcSelect.Type :: alu.OpSelect.Type :: alu.SrcSelect.Type :: HNil - val dst: T = reg.ctrlBindPorts ++ pc.ctrlBindPorts ++ alu.ctrlBindPorts + val dst = new WrapList(( + reg.ctrlBindPorts ++ + pc.ctrlBindPorts ++ + alu.ctrlBindPorts ++ + ram.ctrlBindPorts).map(wrap)) - val dstList = dst.toList - val reversePrefixSum = dstList.scanLeft(0)(_ + _.getWidth).reverse - val slices = reversePrefixSum.zip(reversePrefixSum.tail) + val dstList = dst.v.toList + val controlWidth = dstList.map(_.toOption.get.getWidth).reduce(_ + _) + val reversePrefixSum = dstList.scanLeft(0)(_ + _.toOption.get.getWidth) + val sliceIndex = reversePrefixSum.map(controlWidth - _) + val slices = sliceIndex.map(_ - 1).zip(sliceIndex.tail) import reg.WriteSelect._ + import reg._ import pc.SrcSelect._ + import pc._ import alu.OpSelect._ - import alu.SrcSelect._ + import alu.SrcASelect._ + import alu.SrcBSelect._ + import pc._ import RV32Inst._ - val ControlMapping: Array[(BitPat, T)] = Array( - // Regs :: PC :: Exe - // writeEnable :: writeSelect :: srcSelect :: - (addi, true.B :: rAluOut :: pStaticNpc :: aOpAdd :: aSrcImm :: HNil), - ) - val default = BitPat.dontCare(dstList.map(_.getWidth).reduce(_ + _)) + val ControlMapping: Array[(BitPat, dst.Type)] = Array( + // Regs | writeEnable :: writeSelect :: HNil + // PC | useImmB :: srcSelect :: HNil + // Exe | op :: srcASelect :: srcBSelect :: signExt :: HNil + // Mem | valid :: writeMask :: writeEnable :: HNil - def toBits(t: T): BitPat = { - val list: List[Data] = t.toList - list.map(x => BitPat(x.litValue.toInt.U(x.getWidth.W))).reduceLeft(_ ## _) - } + // ---- Control Transfer Instructions ---- + (jal , (r(true.B) :: r(rNpc) :: + r(false.B) :: r(pExeOut) :: + r(aOpAdd) :: r(aSrcAPc) :: r(aSrcBImmJ) :: r(false.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (jalr , (r(true.B) :: r(rNpc) :: + r(false.B) :: r(pExeOut) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmJ) :: r(false.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (beq , (r(false.B) :: l(WriteSelect) :: + r(true.B) :: r(pExeOut) :: + r(aOpSlt) :: r(aSrcAPc) :: r(aSrcBImmB) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (bne , (r(false.B) :: l(WriteSelect) :: + r(true.B) :: r(pExeOut) :: + r(aOpSlt) :: r(aSrcAPc) :: r(aSrcBImmB) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (blt , (r(false.B) :: l(WriteSelect) :: + r(true.B) :: r(pExeOut) :: + r(aOpSlt) :: r(aSrcAPc) :: r(aSrcBImmB) :: r(true.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (bge , (r(false.B) :: l(WriteSelect) :: + r(true.B) :: r(pExeOut) :: + r(aOpSlt) :: r(aSrcAPc) :: r(aSrcBImmB) :: r(true.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (bltu , (r(false.B) :: l(WriteSelect):: + r(true.B) :: r(pExeOut) :: + r(aOpSlt) :: r(aSrcAPc) :: r(aSrcBImmB) :: r(false.B) :: + r(false.B) :: l(UInt(4.W)) :: r(false.B) :: HNil)), + + (bgeu , (r(false.B) :: l(WriteSelect):: + r(true.B) :: r(pExeOut) :: + r(aOpSlt) :: r(aSrcAPc) :: r(aSrcBImmB) :: r(false.B) :: + r(false.B) :: l(UInt(4.W)) :: r(false.B) :: HNil)), + + // ---- Memory Access Instructions ---- + + (lb , (r(true.B) :: r(rMemOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(true.B) :: l(UInt(4.W)) :: r(false.B) :: HNil)), + + (lh , (r(true.B) :: r(rMemOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(true.B) :: l(UInt(4.W)) :: r(false.B) :: HNil)), + + (lw , (r(true.B) :: r(rMemOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(true.B) :: l(UInt(4.W)) :: r(false.B) :: HNil)), + + (sb , (r(false.B) :: l(WriteSelect):: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(true.B) :: r(1.U(4.W)) :: r(false.B) :: HNil)), + + (sh , (r(false.B) :: l(WriteSelect):: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(true.B) :: r(3.U(4.W)) :: r(false.B) :: HNil)), + + (sw , (r(false.B) :: l(WriteSelect):: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(true.B) :: r(15.U(4.W)) :: r(false.B) :: HNil)), + + // ---- Integer Computational Instructions --- + + (addi , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (slti , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSlt) :: r(aSrcARs1) :: r(aSrcBImmI) :: r(true.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (sltiu , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSlt) :: r(aSrcARs1) :: r(aSrcBImmI) :: r(false.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (xori , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpOr) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (ori , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpXor) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (andi , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAnd) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (slli , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSll) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (srli , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSrl) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (srai , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSra) :: r(aSrcARs1) :: r(aSrcBImmI) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (add , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAdd) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (sub , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSub) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (sll , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSll) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (slt , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSlt) :: r(aSrcARs1) :: r(aSrcBRs2) :: r(true.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (sltu , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSlt) :: r(aSrcARs1) :: r(aSrcBRs2) :: r(false.B) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (xor , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpXor) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (srl , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSrl) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (sra , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpSra) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (or , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpOr) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + + (and , (r(true.B) :: r(rAluOut) :: + r(false.B) :: r(pStaticNpc) :: + r(aOpAnd) :: r(aSrcARs1) :: r(aSrcBRs2) :: l(Bool()) :: + r(false.B) :: l(UInt(4.W)):: r(false.B) :: HNil)), + ) + + val default = BitPat(0.U(controlWidth.W)) + +// println(s"ControlMapping = ${ControlMapping.map(it => (it._1 -> toBits(it._2))).foreach(x => println(x._2))}\n") val out = decoder( inst, TruthTable(ControlMapping.map(it => (it._1 -> toBits(it._2))), default)) - val srcList = slices.map(s => out(s._1 - 1, s._2)) + val srcList = slices.map(s => out(s._1, s._2)) + assert(out != default) + println(s"out = $out, default = $default\n") + println(s"dstList = ${dstList}\n") + println(s"srcList = ${srcList}\n") srcList - .zip(dstList.reverse) + .zip(dstList) .foreach({ case (src, dst) => - dst := src.asTypeOf(dst) + dst.toOption.get := src.asTypeOf(dst.toOption.get) }) + + pc.useImmB := DontCare } import flow.components.{RegisterFile, ProgramCounter, ALU, RamDpi} import chisel3.util.experimental.loadMemoryFromFileInline class Flow extends Module { + def lit(x: Data) = { x.litValue.toInt } + val dataType = UInt(32.W) val ram = Module(new RamDpi) val control = Module(new Control(32)) @@ -84,42 +334,61 @@ class Flow extends Module { val pc = Module(new ProgramCounter(dataType)) val alu = Module(new ALU(dataType)) - ram.io.readAddr := pc.out - val inst = ram.io.readData + // TODO: Switch to Decoupled and Arbiter later + ram.io.pc := pc.out + val inst = ram.io.inst dontTouch(reg.control.writeEnable) import control.pc.SrcSelect._ - pc.in.pcSrcs(pStaticNpc.litValue.toInt) := pc.out + 4.U - pc.in.pcSrcs(pBranchResult.litValue.toInt) := alu.out.result + val npc = Wire(dataType) + npc := pc.out + 4.U + pc.in.exeOut := alu.out.result + pc.in.immB := Cat(Fill(20, inst(31)), inst(7), inst(30, 25), inst(11, 8), inst(0)) control.inst := inst reg.control <> control.reg + // FIXME: Probably optimizable with bulk connection pc.control <> control.pc alu.control <> control.alu + val branchUseSlt = Wire(Bool()) + val branchInvertResult = Wire(Bool()) + branchUseSlt := inst(14) + branchInvertResult := inst(12) + val _branchResult = Mux(branchUseSlt, alu.out.result(0), alu.out.eq) + val branchResult = Mux(branchInvertResult, _branchResult, !_branchResult) + pc.control.useImmB := control.pc.useImmB && _branchResult import control.reg.WriteSelect._ - reg.in.writeData(rAluOut.litValue.toInt) := alu.out.result - + reg.in.writeData(lit(rAluOut)) := alu.out.result // TODO: Read address in load command goes here - reg.in.writeData(rMemOut.litValue.toInt) := DontCare + reg.in.writeData(lit(rMemOut)) := ram.io.readData + reg.in.writeData(lit(rNpc)) := npc reg.in.writeAddr := inst(11, 7) reg.in.rs(0) := inst(19, 15) // rs1 reg.in.rs(1) := inst(24, 20) // rs2 // TODO: Memory write goes here - ram.io.writeAddr := DontCare - ram.io.writeData := DontCare - ram.io.writeMask := DontCare - ram.io.writeEnable := false.B + ram.io.writeAddr := alu.out.result + ram.io.writeData := reg.out.src(1) + ram.io.writeMask := control.ram.writeMask + ram.io.writeEnable := control.ram.writeEnable ram.io.valid := true.B + ram.io.readAddr := alu.out.result - import control.alu.SrcSelect._ - alu.in.a(aSrcRs2.litValue.toInt) := reg.out.src(1) - alu.in.a(aSrcImm.litValue.toInt) := inst(31, 20) - alu.in.b := reg.out.src(0) + import control.alu.SrcASelect._ + import control.alu.SrcBSelect._ + alu.in.a(lit(aSrcARs1)) := reg.out.src(0) + alu.in.a(lit(aSrcAPc)) := pc.out + alu.in.a(lit(aSrcAZero)) := 0.U + + alu.in.b(lit(aSrcBRs2)) := reg.out.src(1) + alu.in.b(lit(aSrcBImmI)) := inst(31, 20).pad(aSrcBImmI.getWidth) + alu.in.b(lit(aSrcBImmJ)) := Cat(Fill(12, inst(31)), inst(19, 12), inst(20), inst(30, 25), inst(24, 21), 0.U(1.W)) + alu.in.b(lit(aSrcBImmB)) := Cat(Fill(20, inst(31)), inst(7), inst(30, 25), inst(11, 8), inst(0)) + alu.in.b(lit(aSrcBImmS)) := Cat(inst(31), inst(19, 12), inst(20), inst(30, 25), inst(24, 21), 0.U(1.W)).pad(aSrcBImmS.getWidth) Trace.traceName(pc.out); dontTouch(control.out) diff --git a/npc/core/src/main/scala/top/Main.scala b/npc/core/src/main/scala/top/Main.scala index 3c02ff3..6de6893 100644 --- a/npc/core/src/main/scala/top/Main.scala +++ b/npc/core/src/main/scala/top/Main.scala @@ -34,7 +34,7 @@ object VerilogMain extends App { } val annos = (new ChiselStage).execute( - Array("--target-dir", opt.targetDir.toString, "--target", "systemverilog", "--split-verilog"), + Array("--target-dir", opt.targetDir.toString, "--target", "systemverilog", "--split-verilog", "--full-stacktrace"), Seq( ) ++ (if(config.traceConfig.enable) Seq(ChiselGeneratorAnnotation(() => new Flow)) else Seq()) @@ -56,9 +56,9 @@ object VerilogMain extends App { .foreach( ct => println(s"""TOP.${ct.circuit}.${ct.path.map { case (Instance(i), _) => i }.mkString(".")}.${ct.tokens.collectFirst { case Ref(r) => r - }.get}""") + }.get}""") ) - + val verilatorConfigWriter = new PrintWriter(new File(opt.targetDir, opt.verilatorConfigFileOut.toString())) verilatorConfigWriter.write("`verilator_config\n") try { diff --git a/npc/csrc/Flow/CMakeLists.txt b/npc/csrc/Flow/CMakeLists.txt index 606fd73..ad8d2e9 100644 --- a/npc/csrc/Flow/CMakeLists.txt +++ b/npc/csrc/Flow/CMakeLists.txt @@ -1,5 +1,6 @@ include(ChiselBuild) add_executable(V${TOPMODULE} config.cpp main.cpp) +target_link_libraries(V${TOPMODULE} PRIVATE disasm sdb) verilate(V${TOPMODULE} TRACE COVERAGE THREADS TOP_MODULE ${TOPMODULE} @@ -8,6 +9,7 @@ verilate(V${TOPMODULE} TRACE COVERAGE THREADS INCLUDE_DIRS ${CHISEL_OUTPUT_DIR} VERILATOR_ARGS "--vpi" # Enable VPI + "-Wno-UNOPTFLAT" ) add_test( diff --git a/npc/csrc/Flow/main.cpp b/npc/csrc/Flow/main.cpp index 50eeeeb..b1c37c9 100644 --- a/npc/csrc/Flow/main.cpp +++ b/npc/csrc/Flow/main.cpp @@ -1,10 +1,15 @@ -#include "components.hpp" +#include "VFlow___024root.h" #include "config.hpp" +#include "disasm.hpp" #include "vl_wrapper.hpp" #include "vpi_user.h" +#include "vpi_wrapper.hpp" #include #include +#include #include +#include +#include using VlModule = VlModuleInterfaceCommon; using Registers = _RegistersVPI; @@ -31,9 +36,10 @@ void pmem_write(int waddr, int wdata, char wmask) { } } +Disassembler d{"riscv32-pc-linux-gnu"}; + VlModule *top; Registers *regs; -using CPUState = CPUStateBase; vpiHandle pc = nullptr; void difftest_memcpy(paddr_t, void *, size_t, bool){}; @@ -58,6 +64,8 @@ void difftest_exec(uint64_t n) { } } } +// std::cout << d.disassemble(top->rootp->Flow__DOT__pc__DOT__pc_reg, (uint8_t *)&top->rootp->Flow__DOT___ram_inst, 4) << std::endl; + void difftest_init(int port) { // top = std::make_unique(config.do_trace, config.wavefile); top = new VlModule{config.do_trace, config.wavefile}; @@ -65,20 +73,34 @@ void difftest_init(int port) { top->reset_eval(10); } +DifftestInterface dut_interface = DifftestInterface{ + &difftest_memcpy, &difftest_regcpy, &difftest_exec, &difftest_init}; + +SDB::SDB sdb_dut; +extern "C" { +word_t reg_str2val(const char *name, bool *success) { + return sdb_dut.reg_str2val(name, success); +} +} + int main(int argc, char **argv, char **env) { config.cli_parse(argc, argv); /* -- Difftest -- */ std::filesystem::path ref{config.lib_ref}; - DifftestInterface dut_interface = DifftestInterface{ - &difftest_memcpy, &difftest_regcpy, &difftest_exec, &difftest_init}; DifftestInterface ref_interface = DifftestInterface{ref}; Difftest> diff{dut_interface, ref_interface, pmem_get(), 128}; int t = 8; + sdb_dut.main_loop(); while (t--) { - diff.step(1); + if (!diff.step(1)) { + uint32_t pc = regs->get_pc(); + uint32_t inst = pmem_read(pc); + std::cout << diff << d.disassemble(pc, (uint8_t *)&inst, 4) << std::endl; + return EXIT_FAILURE; + } } return 0; diff --git a/npc/csrc/Flow/vpi_wrapper.hpp b/npc/csrc/Flow/vpi_wrapper.hpp new file mode 100644 index 0000000..a1dd089 --- /dev/null +++ b/npc/csrc/Flow/vpi_wrapper.hpp @@ -0,0 +1,36 @@ +#ifndef _NPC_VPI_WRAPPER_H_ +#define _NPC_VPI_WRAPPER_H_ +#include +#include + +template +class _RegistersVPI : public _RegistersBase { + std::array reg_handles; + vpiHandle pc_handle; + T vpi_get(vpiHandle vh) { + s_vpi_value v; + v.format = vpiIntVal; + vpi_get_value(vh, &v); + return v.value.integer; + } + T fetch_pc(void) { return vpi_get(pc_handle); } + T fetch_reg(std::size_t id) { return vpi_get(reg_handles[id]); } + +public: + _RegistersVPI(const std::string regs_prefix, + const std::string pcname) { + for (int i = 0; i < nr; i++) { + std::string regname = regs_prefix + std::to_string(i); + vpiHandle vh = vpi_handle_by_name((PLI_BYTE8 *)regname.c_str(), nullptr); + if (vh == nullptr) { + std::cerr << "vpiHandle " << regname.c_str() << " not found" + << std::endl; + exit(EXIT_FAILURE); + } + reg_handles[i] = vh; + } + pc_handle = vpi_handle_by_name((PLI_BYTE8 *)pcname.c_str(), nullptr); + } +}; + +#endif diff --git a/npc/csrc/Flow/components.hpp b/npc/include/components.hpp similarity index 66% rename from npc/csrc/Flow/components.hpp rename to npc/include/components.hpp index a84309a..fced1af 100644 --- a/npc/csrc/Flow/components.hpp +++ b/npc/include/components.hpp @@ -1,13 +1,23 @@ #ifndef _NPC_COMPONENTS_H_ #define _NPC_COMPONENTS_H_ -#include "vpi_user.h" #include #include +#include #include #include #include -#include +#include +#include +#include + +const std::map riscv32_regs_by_name{ + {"$0", 0}, {"ra", 1}, {"sp", 2}, {"gp", 3}, {"tp", 4}, {"t0", 5}, + {"t1", 6}, {"t2", 7}, {"s0", 8}, {"s1", 9}, {"a0", 10}, {"a1", 11}, + {"a2", 12}, {"a3", 13}, {"a4", 14}, {"a5", 15}, {"a6", 16}, {"a7", 17}, + {"s2", 18}, {"s3", 19}, {"s4", 20}, {"s5", 21}, {"s6", 22}, {"s7", 23}, + {"s8", 24}, {"s9", 25}, {"s10", 26}, {"s11", 27}, {"t3", 28}, {"t4", 29}, + {"t5", 30}, {"t6", 31}}; template class _RegistersBase { std::array regs; @@ -25,36 +35,6 @@ public: } }; -template -class _RegistersVPI : public _RegistersBase { - std::array reg_handles; - vpiHandle pc_handle; - T vpi_get(vpiHandle vh) { - s_vpi_value v; - v.format = vpiIntVal; - vpi_get_value(vh, &v); - return v.value.integer; - } - T fetch_pc(void) { return vpi_get(pc_handle); } - T fetch_reg(std::size_t id) { return vpi_get(reg_handles[id]); } - -public: - _RegistersVPI(const std::string regs_prefix, - const std::string pcname) { - for (int i = 0; i < nr; i++) { - std::string regname = regs_prefix + std::to_string(i); - vpiHandle vh = vpi_handle_by_name((PLI_BYTE8 *)regname.c_str(), nullptr); - if (vh == nullptr) { - std::cerr << "vpiHandle " << regname.c_str() << " not found" - << std::endl; - exit(EXIT_FAILURE); - } - reg_handles[i] = vh; - } - pc_handle = vpi_handle_by_name((PLI_BYTE8 *)pcname.c_str(), nullptr); - } -}; - template class Memory { std::size_t addr_to_index(std::size_t addr) { if (addr < 0x80000000) { @@ -74,7 +54,8 @@ template class Memory { public: std::array mem; Memory(std::filesystem::path filepath, bool is_binary = true) { - assert(std::filesystem::exists(filepath)); + if (!std::filesystem::exists(filepath)) + throw std::runtime_error("Memory file not found"); if (is_binary) { std::ifstream file(filepath, std::ios::binary); char *pmem = reinterpret_cast(mem.data()); diff --git a/npc/include/config.hpp b/npc/include/config.hpp new file mode 100644 index 0000000..9f5873c --- /dev/null +++ b/npc/include/config.hpp @@ -0,0 +1,20 @@ +#ifndef _NPC_CONFIG_H_ +#define _NPC_CONFIG_H_ +#include +#include +#include +#include + +struct Config { + std::filesystem::path memory_file; + uint64_t max_sim_time = 1000; + bool memory_file_binary = {true}; + bool do_trace{false}; + std::filesystem::path wavefile; + std::filesystem::path lib_ref; + void cli_parse(int argc, char **argv); +}; + +extern Config config; + +#endif \ No newline at end of file diff --git a/npc/include/difftest.hpp b/npc/include/difftest.hpp index dd690b9..7349004 100644 --- a/npc/include/difftest.hpp +++ b/npc/include/difftest.hpp @@ -1,12 +1,14 @@ #ifndef _DIFFTEST_DIFFTEST_H_ #define _DIFFTEST_DIFFTEST_H_ #include +#include #include #include #include #include #include #include +#include #include using paddr_t = uint32_t; @@ -69,14 +71,11 @@ public: dut.regcpy(dut_state.get(), DIFFTEST_FROM_REF); } - void step(uint64_t n) { + bool step(uint64_t n) { ref.exec(n); dut.exec(n); fetch_state(); - if (*ref_state != *dut_state) { - std::cout << *this; - exit(EXIT_FAILURE); - } + return *ref_state == *dut_state; } friend std::ostream &operator<<(std::ostream &os, const Difftest &d) { @@ -90,6 +89,8 @@ public: template struct CPUStateBase { R reg[nr_reg] = {0}; paddr_t pc = 0x80000000; + static const std::map inline regs_by_name = + riscv32_regs_by_name; CPUStateBase() { for (int i = 0; i < nr_reg; i++) reg[i] = 0; @@ -106,6 +107,9 @@ template struct CPUStateBase { bool operator!=(const CPUStateBase &other) const { return !(*this == other); // Reuse the == operator for != implementation } + + /* This does not update the register!!! */ + R at(std::string name) { return reg[regs_by_name.at(name)]; } }; template diff --git a/npc/include/types.h b/npc/include/types.h new file mode 100644 index 0000000..f6a8d87 --- /dev/null +++ b/npc/include/types.h @@ -0,0 +1,23 @@ +#ifndef _NPC_TYPES_H__ +#define _NPC_TYPES_H__ +#include + +typedef uint32_t word_t; +typedef int32_t sword_t; +static const word_t WORD_T_MAX = UINT32_MAX; +static const sword_t SWORD_T_MAX = INT32_MAX; +static const sword_t SWORD_T_MIN = INT32_MIN; +#define WORD_BYTES 4 + +#define FMT_WORD "0x%08x" +typedef uint32_t vaddr_t; +typedef uint32_t paddr_t; +#define FMT_ADDR "0x%08x" +typedef uint16_t ioaddr_t; + +#ifdef __cplusplus +#include +using CPUState = CPUStateBase; +#endif + +#endif \ No newline at end of file diff --git a/npc/include/vl_wrapper.hpp b/npc/include/vl_wrapper.hpp new file mode 100644 index 0000000..4d42b55 --- /dev/null +++ b/npc/include/vl_wrapper.hpp @@ -0,0 +1,65 @@ +#ifndef _NPC_TRACER_H_ +#define _NPC_TRACER_H_ +#include +#include + +template class Tracer { + std::shared_ptr top; + std::unique_ptr m_trace; + uint64_t cycle = 0; + +public: + Tracer(T *top, std::filesystem::path wavefile) { + top = top; + Verilated::traceEverOn(true); + m_trace = std::make_unique(); + top->trace(m_trace.get(), 5); + m_trace->open(wavefile.c_str()); + } + ~Tracer() { m_trace->close(); } + + /** + * Dump signals to waveform file. Must be called once after every top->eval() + * call. + */ + void update() { m_trace->dump(cycle++); } +}; + +template class VlModuleInterfaceCommon : public T { + uint64_t sim_time = 0; + uint64_t posedge_cnt = 0; + std::unique_ptr> tracer; + +public: + VlModuleInterfaceCommon(bool do_trace, + std::filesystem::path wavefile = "waveform.vcd") { + if (do_trace) + tracer = std::make_unique>(this, wavefile); + } + void eval() { + if (this->is_posedge()) { + posedge_cnt++; + } + T::clock = !T::clock; + sim_time++; + T::eval(); + if (tracer) + tracer->update(); + } + void eval(int n) { + for (int i = 0; i < n; i++) { + this->eval(); + } + } + void reset_eval(int n) { + this->reset = 1; + this->eval(n); + this->reset = 0; + } + bool is_posedge() { + // Will be posedge when eval is called + return T::clock == 0; + } +}; + +#endif diff --git a/npc/utils/CMakeLists.txt b/npc/utils/CMakeLists.txt new file mode 100644 index 0000000..017313c --- /dev/null +++ b/npc/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(disasm) +if (ENABLE_SDB) + add_subdirectory(sdb) +endif() diff --git a/npc/utils/disasm/CMakeLists.txt b/npc/utils/disasm/CMakeLists.txt new file mode 100644 index 0000000..e019bf7 --- /dev/null +++ b/npc/utils/disasm/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(disasm disasm.cpp) +target_include_directories(disasm PUBLIC include) +llvm_map_components_to_libnames(LLVM_LIBS ${LLVM_TARGETS_TO_BUILD}) +target_link_libraries(disasm PUBLIC ${LLVM_LIBS}) diff --git a/npc/utils/disasm/disasm.cpp b/npc/utils/disasm/disasm.cpp new file mode 100644 index 0000000..0e817ba --- /dev/null +++ b/npc/utils/disasm/disasm.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#if LLVM_VERSION_MAJOR >= 14 +#include +#if LLVM_VERSION_MAJOR >= 15 +#include +#endif +#else +#include +#endif +#include +#include +#include + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#if LLVM_VERSION_MAJOR < 11 +#error Please use LLVM with major version >= 11 +#endif + +Disassembler::Disassembler(std::string triple) : triple(triple) { + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllDisassemblers(); + + std::string errstr; + + llvm::MCInstrInfo *gMII = nullptr; + llvm::MCRegisterInfo *gMRI = nullptr; + auto target = llvm::TargetRegistry::lookupTarget(triple, errstr); + if (!target) { + llvm::errs() << "Can't find target for " << triple << ": " << errstr + << "\n"; + assert(0); + } + + llvm::MCTargetOptions MCOptions; + gSTI = target->createMCSubtargetInfo(triple, "", ""); + std::string isa = target->getName(); + if (isa == "riscv32" || isa == "riscv64") { + gSTI->ApplyFeatureFlag("+m"); + gSTI->ApplyFeatureFlag("+a"); + gSTI->ApplyFeatureFlag("+c"); + gSTI->ApplyFeatureFlag("+f"); + gSTI->ApplyFeatureFlag("+d"); + } + gMII = target->createMCInstrInfo(); + gMRI = target->createMCRegInfo(triple); + auto AsmInfo = target->createMCAsmInfo(*gMRI, triple, MCOptions); +#if LLVM_VERSION_MAJOR >= 13 + auto llvmTripleTwine = llvm::Twine(triple); + auto llvmtriple = llvm::Triple(llvmTripleTwine); + auto Ctx = new llvm::MCContext(llvmtriple, AsmInfo, gMRI, nullptr); +#else + auto Ctx = new llvm::MCContext(AsmInfo, gMRI, nullptr); +#endif + gDisassembler = target->createMCDisassembler(*gSTI, *Ctx); + gIP = target->createMCInstPrinter(llvm::Triple(triple), + AsmInfo->getAssemblerDialect(), *AsmInfo, + *gMII, *gMRI); + gIP->setPrintImmHex(true); + gIP->setPrintBranchImmAsAddress(true); + if (isa == "riscv32" || isa == "riscv64") + gIP->applyTargetSpecificCLOption("no-aliases"); +} + +std::string Disassembler::disassemble(uint64_t pc, uint8_t *code, int nbyte) { + llvm::MCInst inst; + llvm::ArrayRef arr(code, nbyte); + uint64_t dummy_size = 0; + gDisassembler->getInstruction(inst, dummy_size, arr, pc, llvm::nulls()); + + std::stringstream ss; + ss << "0x" << std::hex << pc << ": "; + std::string s = ss.str(); + llvm::raw_string_ostream os{s}; + gIP->printInst(&inst, pc, "", *gSTI, os); + + return s; +} diff --git a/npc/utils/disasm/include/disasm.hpp b/npc/utils/disasm/include/disasm.hpp new file mode 100644 index 0000000..a14ebb3 --- /dev/null +++ b/npc/utils/disasm/include/disasm.hpp @@ -0,0 +1,17 @@ +#ifndef _NPC_UTILS_DISASM_ +#define _NPC_UTILS_DISASM_ +#include "llvm/MC/MCDisassembler/MCDisassembler.h" +#include "llvm/MC/MCInstPrinter.h" + +class Disassembler { + llvm::MCDisassembler *gDisassembler = nullptr; + llvm::MCSubtargetInfo *gSTI = nullptr; + llvm::MCInstPrinter *gIP = nullptr; + std::string triple; + +public: + Disassembler(std::string); + std::string disassemble(uint64_t pc, uint8_t *code, int nbyte); +}; + +#endif \ No newline at end of file diff --git a/npc/utils/sdb/CMakeLists.txt b/npc/utils/sdb/CMakeLists.txt new file mode 100644 index 0000000..6b1cb2c --- /dev/null +++ b/npc/utils/sdb/CMakeLists.txt @@ -0,0 +1,14 @@ +find_package(Readline REQUIRED) +find_package(FLEX REQUIRED) +find_package(BISON REQUIRED) +set(PARSER_DIR "${CMAKE_CURRENT_BINARY_DIR}") +set(LEXER_OUT "${PARSER_DIR}/lexer.c") +set(PARSER_OUT "${PARSER_DIR}/parser.c") +flex_target(LEXER addrexp.l "${LEXER_OUT}" DEFINES_FILE "${PARSER_DIR}/addrexp_lex.h") +bison_target(PARSER addrexp.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/addrexp.h") +add_flex_bison_dependency(LEXER PARSER) + +add_library(sdb sdb.cpp console.cpp "${LEXER_OUT}" "${PARSER_OUT}") +target_link_libraries(sdb PRIVATE ${Readline_LIBRARY}) +target_include_directories(sdb PRIVATE ${Readline_INCLUDE_DIR}) +target_include_directories(sdb PUBLIC include) diff --git a/npc/utils/sdb/addrexp.l b/npc/utils/sdb/addrexp.l new file mode 100644 index 0000000..d81f0e1 --- /dev/null +++ b/npc/utils/sdb/addrexp.l @@ -0,0 +1,26 @@ +%{ + #include + #include + #include "addrexp.h" + static bool success = false; + void yyerror(word_t *result, const char *err); + word_t reg_str2val(const char *name, bool*); +%} +%option noyywrap + +%% + +0[xX][0-9a-fA-F]+ { yylval = strtoul(yytext, NULL, 16); return HEX_NUMBER; } +[0-9]+ { yylval = strtoul(yytext, NULL, 10); return NUMBER; } +$[asgprt$][0-9pa][0-9]? { + yylval = reg_str2val(yytext + 1, &success); + if(!success) { + yyerror(NULL, "Failed to convert reg to value"); + return YYerror; + } + return REGISTER; +} +[+\-*/<=()] { return *yytext; } +[ \t] { } +. { printf("Unexpected character: %s\n", yytext); return YYerror; } +%% diff --git a/npc/utils/sdb/addrexp.y b/npc/utils/sdb/addrexp.y new file mode 100644 index 0000000..ca1a5c9 --- /dev/null +++ b/npc/utils/sdb/addrexp.y @@ -0,0 +1,58 @@ +%code requires { + #include + #include + #include + extern int yylex(void); +} +%{ + #include + #include + #include + void yyerror(word_t *result, const char *err) { + fprintf(stderr, "%s", err); + } + int pmem_read(int raddr); +%} + +%token NUMBER HEX_NUMBER +%token REGISTER +%locations +%start input +%define api.value.type { word_t } +%parse-param { uint32_t *result } +%left '-' '+' +%left '*' '/' + +%% +input + : expression { *result = $1; } + ; + +expression + : number { $$ = $1; } + | expression '>' '=' expression { $$ = ($1 >= $4); } + | expression '<' '=' expression { $$ = ($1 <= $4); } + | expression '=' '=' expression { $$ = ($1 == $4); } + | expression '!' '=' expression { $$ = ($1 == $4); } + | expression '>' expression { $$ = ($1 > $3); } + | expression '<' expression { $$ = ($1 < $3); } + | expression '+' expression { $$ = $1 + $3; } + | expression '-' expression { $$ = $1 - $3; } + | expression '*' expression { $$ = $1 * $3; } + | expression '/' expression { + if($3 == 0) { + fprintf(stderr, "Error: divide by zero at" FMT_WORD " / " FMT_WORD "\n", $1, $3); + YYABORT; + }; + $$ = $1 / $3; + } + | '-' number { $$ = -$2; } + | '*' expression { $$ = pmem_read($2); } + | '(' expression ')' { $$ = $2; } + +number + : REGISTER + | NUMBER + | HEX_NUMBER + +%% diff --git a/npc/utils/sdb/console.cpp b/npc/utils/sdb/console.cpp new file mode 100644 index 0000000..485e192 --- /dev/null +++ b/npc/utils/sdb/console.cpp @@ -0,0 +1,215 @@ +// cpp-readline library +// +// @author zmij +// @date Nov 30, 2015 + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace CppReadline { +namespace { + +Console *currentConsole = nullptr; +HISTORY_STATE *emptyHistory = history_get_history_state(); + +} /* namespace */ + +struct Console::Impl { + using RegisteredCommands = + std::unordered_map; + + ::std::string greeting_; + // These are hardcoded commands. They do not do anything and are catched manually in the executeCommand function. + RegisteredCommands commands_; + HISTORY_STATE *history_ = nullptr; + + Impl(::std::string const &greeting) : greeting_(greeting), commands_() {} + ~Impl() { free(history_); } + + Impl(Impl const &) = delete; + Impl(Impl &&) = delete; + Impl &operator=(Impl const &) = delete; + Impl &operator=(Impl &&) = delete; +}; + +// Here we set default commands, they do nothing since we quit with them +// Quitting behaviour is hardcoded in readLine() +Console::Console(std::string const &greeting) : pimpl_{new Impl{greeting}} { + // Init readline basics + rl_attempted_completion_function = &Console::getCommandCompletions; + + // These are default hardcoded commands. + // Help command lists available commands. + pimpl_->commands_["help"] = [this](const Arguments &) { + auto commands = getRegisteredCommands(); + std::cout << "Available commands are:\n"; + for (auto &command : commands) + std::cout << "\t" << command << "\n"; + return ReturnCode::Ok; + }; + // Run command executes all commands in an external file. + pimpl_->commands_["run"] = [this](const Arguments &input) { + if (input.size() < 2) { + std::cout << "Usage: " << input[0] << " script_filename\n"; + return 1; + } + return executeFile(input[1]); + }; + // Quit and Exit simply terminate the console. + pimpl_->commands_["quit"] = [this](const Arguments &) { + return ReturnCode::Quit; + }; + + pimpl_->commands_["exit"] = [this](const Arguments &) { + return ReturnCode::Quit; + }; +} + +Console::~Console() = default; + +void Console::registerCommand(const std::string &s, CommandFunction f) { + pimpl_->commands_[s] = f; +} + +std::vector Console::getRegisteredCommands() const { + std::vector allCommands; + for (auto &pair : pimpl_->commands_) + allCommands.push_back(pair.first); + + return allCommands; +} + +void Console::saveState() { + free(pimpl_->history_); + pimpl_->history_ = history_get_history_state(); +} + +void Console::reserveConsole() { + if (currentConsole == this) + return; + + // Save state of other Console + if (currentConsole) + currentConsole->saveState(); + + // Else we swap state + if (!pimpl_->history_) + history_set_history_state(emptyHistory); + else + history_set_history_state(pimpl_->history_); + + // Tell others we are using the console + currentConsole = this; +} + +void Console::setGreeting(const std::string &greeting) { + pimpl_->greeting_ = greeting; +} + +std::string Console::getGreeting() const { return pimpl_->greeting_; } + +int Console::executeCommand(const std::string &command) { + // Convert input to vector + std::vector inputs; + { + std::istringstream iss(command); + std::copy(std::istream_iterator(iss), + std::istream_iterator(), std::back_inserter(inputs)); + } + + if (inputs.size() == 0) + return ReturnCode::Ok; + + Impl::RegisteredCommands::iterator it; + if ((it = pimpl_->commands_.find(inputs[0])) != end(pimpl_->commands_)) { + return static_cast((it->second)(inputs)); + } + + std::cout << "Command '" << inputs[0] << "' not found.\n"; + return ReturnCode::Error; +} + +int Console::executeFile(const std::string &filename) { + std::ifstream input(filename); + if (!input) { + std::cout << "Could not find the specified file to execute.\n"; + return ReturnCode::Error; + } + std::string command; + int counter = 0, result; + + while (std::getline(input, command)) { + if (command[0] == '#') + continue; // Ignore comments + // Report what the Console is executing. + std::cout << "[" << counter << "] " << command << '\n'; + if ((result = executeCommand(command))) + return result; + ++counter; + std::cout << '\n'; + } + + // If we arrived successfully at the end, all is ok + return ReturnCode::Ok; +} + +int Console::readLine() { + reserveConsole(); + + char *buffer = readline(pimpl_->greeting_.c_str()); + if (!buffer) { + std::cout + << '\n'; // EOF doesn't put last endline so we put that so that it looks uniform. + return ReturnCode::Quit; + } + + // TODO: Maybe add commands to history only if succeeded? + if (buffer[0] != '\0') + add_history(buffer); + + std::string line(buffer); + free(buffer); + + return executeCommand(line); +} + +char **Console::getCommandCompletions(const char *text, int start, int) { + char **completionList = nullptr; + + if (start == 0) + completionList = rl_completion_matches(text, &Console::commandIterator); + + return completionList; +} + +char *Console::commandIterator(const char *text, int state) { + static Impl::RegisteredCommands::iterator it; + if (!currentConsole) + return nullptr; + auto &commands = currentConsole->pimpl_->commands_; + + if (state == 0) + it = begin(commands); + + while (it != end(commands)) { + auto &command = it->first; + ++it; + if (command.find(text) != std::string::npos) { + return strdup(command.c_str()); + } + } + return nullptr; +} +} // namespace CppReadline diff --git a/npc/utils/sdb/include/console.hpp b/npc/utils/sdb/include/console.hpp new file mode 100644 index 0000000..538ce2f --- /dev/null +++ b/npc/utils/sdb/include/console.hpp @@ -0,0 +1,145 @@ +// cpp-readline library +// +// @author zmij +// @date Nov 30, 2015 + +#ifndef CONSOLE_CONSOLE_HEADER_FILE +#define CONSOLE_CONSOLE_HEADER_FILE + +#include +#include +#include +#include + +namespace CppReadline { +class Console { +public: + /** + * @brief This is the function type that is used to interface with the Console class. + * + * These are the functions that are going to get called by Console + * when the user types in a message. The vector will hold the + * command elements, and the function needs to return its result. + * The result can either be Quit (-1), OK (0), or an arbitrary + * error (>=1). + */ + using Arguments = std::vector; + using CommandFunction = std::function; + + enum ReturnCode { + Quit = -1, + Ok = 0, + Error = 1 // Or greater! + }; + + /** + * @brief Basic constructor. + * + * The Console comes with two predefined commands: "quit" and + * "exit", which both terminate the console, "help" which prints a + * list of all registered commands, and "run" which executes script + * files. + * + * These commands can be overridden or unregistered - but remember + * to leave at least one to quit ;). + * + * @param greeting This represents the prompt of the Console. + */ + explicit Console(std::string const &greeting); + + /** + * @brief Basic destructor. + * + * Frees the history which is been produced by GNU readline. + */ + ~Console(); + + /** + * @brief This function registers a new command within the Console. + * + * If the command already existed, it overwrites the previous entry. + * + * @param s The name of the command as inserted by the user. + * @param f The function that will be called once the user writes the command. + */ + void registerCommand(const std::string &s, CommandFunction f); + + /** + * @brief This function returns a list with the currently available commands. + * + * @return A vector containing all registered commands names. + */ + std::vector getRegisteredCommands() const; + + /** + * @brief Sets the prompt for this Console. + * + * @param greeting The new greeting. + */ + void setGreeting(const std::string &greeting); + + /** + * @brief Gets the current prompt of this Console. + * + * @return The currently set greeting. + */ + std::string getGreeting() const; + + /** + * @brief This function executes an arbitrary string as if it was inserted via stdin. + * + * @param command The command that needs to be executed. + * + * @return The result of the operation. + */ + int executeCommand(const std::string &command); + + /** + * @brief This function calls an external script and executes all commands inside. + * + * This function stops execution as soon as any single command returns something + * different from 0, be it a quit code or an error code. + * + * @param filename The pathname of the script. + * + * @return What the last command executed returned. + */ + int executeFile(const std::string &filename); + + /** + * @brief This function executes a single command from the user via stdin. + * + * @return The result of the operation. + */ + int readLine(); + +private: + Console(const Console &) = delete; + Console(Console &&) = delete; + Console &operator=(Console const &) = delete; + Console &operator=(Console &&) = delete; + + struct Impl; + using PImpl = ::std::unique_ptr; + PImpl pimpl_; + + /** + * @brief This function saves the current state so that some other Console can make use of the GNU readline facilities. + */ + void saveState(); + /** + * @brief This function reserves the use of the GNU readline facilities to the calling Console instance. + */ + void reserveConsole(); + + // GNU newline interface to our commands. + using commandCompleterFunction = char **(const char *text, int start, + int end); + using commandIteratorFunction = char *(const char *text, int state); + + static commandCompleterFunction getCommandCompletions; + static commandIteratorFunction commandIterator; +}; +} // namespace CppReadline + +#endif diff --git a/npc/utils/sdb/include/sdb.hpp b/npc/utils/sdb/include/sdb.hpp new file mode 100644 index 0000000..18aae8a --- /dev/null +++ b/npc/utils/sdb/include/sdb.hpp @@ -0,0 +1,149 @@ +#ifndef _SDB_SDB_HEADER_FILE_ +#define _SDB_SDB_HEADER_FILE_ + +#include +#include +#include +#include +#include +#include + +namespace cr = CppReadline; +using ret = cr::Console::ReturnCode; + +namespace SDB { + +enum SDBStatus { + SDB_SUCCESS, + SDB_WRONG_ARGUMENT, +}; + +struct Handler { + const std::vector names; + cr::Console::CommandFunction f; +}; + +template class _SDBHandlers { + using CPUState = CPUStateBase; + +private: + std::vector all_handlers; + +public: + static CPUState cpu; + +private: + static _SDBHandlers *instance; + static int cmd_continue(const cr::Console::Arguments &input); + static int cmd_step(const std::vector &input); + static int cmd_info_registers(const std::vector &input); + static int cmd_print(const std::vector &input); + _SDBHandlers(std::vector all_handlers) + : all_handlers(all_handlers){}; + +public: + _SDBHandlers(const _SDBHandlers &) = delete; + _SDBHandlers operator=(const _SDBHandlers &) = delete; + + static _SDBHandlers *getInstance() { + if (instance == nullptr) { + std::vector all_handlers{ + Handler{{"c", "continue"}, &_SDBHandlers::cmd_continue}, + Handler{{"si", "step-instruction"}, &_SDBHandlers::cmd_step}, + }; + instance = new _SDBHandlers(all_handlers); + } + return instance; + } + + void registerHandlers(cr::Console *c); +}; + +template +_SDBHandlers *_SDBHandlers::instance = nullptr; + +template +CPUState _SDBHandlers::cpu = CPUState(); + +template +int _SDBHandlers::cmd_continue(const cr::Console::Arguments &input) { + if (input.size() > 1) + return SDB_WRONG_ARGUMENT; + funcs.exec(-1); + return SDB_SUCCESS; +} + +template +int _SDBHandlers::cmd_step(const std::vector &input) { + if (input.size() > 2) { + return SDB_WRONG_ARGUMENT; + } + uint64_t step_count = input.size() == 2 ? std::stoull(input[1]) : 1; + funcs.exec(step_count); + return SDB_SUCCESS; +} + +template +int _SDBHandlers::cmd_info_registers( + const std::vector &input) { + if (input.size() > 1) + return SDB_WRONG_ARGUMENT; + std::cout << _SDBHandlers::getInstance()->cpu << std::endl; + return SDB_SUCCESS; +} + +template +int _SDBHandlers::cmd_print(const std::vector &input) { + exit(1); +} + +template +void _SDBHandlers::registerHandlers(cr::Console *c) { + for (auto &h : this->all_handlers) { + for (auto &name : h.names) { + c->registerCommand(name, h.f); + } + } +} + +template class SDB { +private: + std::unique_ptr c; + using SDBHandlers = _SDBHandlers; + +public: + SDB(std::string const &greeting = "\033[1;34m(npc)\033[0m ") { + c = std::make_unique(greeting); + SDBHandlers::getInstance()->registerHandlers(c.get()); + }; + + word_t reg_str2val(const char *name, bool *success) { + try { + *success = true; + return SDBHandlers::getInstance()->cpu.at(name); + } catch (std::runtime_error) { + *success = false; + return 0; + } + } + + int main_loop() { + int retCode; + do { + retCode = c->readLine(); + // We can also change the prompt based on last return value: + if (retCode == ret::Ok) + c->setGreeting("\033[1;34m(npc)\033[0m "); + else + c->setGreeting("\033[1;31m(npc)\033[0m "); + + if (retCode == SDB_WRONG_ARGUMENT) { + std::cout << "Wrong argument give to command\n"; + } + } while (retCode != ret::Quit); + return 0; + } +}; +} // namespace SDB + +#endif \ No newline at end of file diff --git a/npc/utils/sdb/sdb.cpp b/npc/utils/sdb/sdb.cpp new file mode 100644 index 0000000..9bfa6b5 --- /dev/null +++ b/npc/utils/sdb/sdb.cpp @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include + +namespace cr = CppReadline; +using ret = cr::Console::ReturnCode; + +namespace SDB {}