diff options
Diffstat (limited to 'VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala')
-rw-r--r-- | VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala new file mode 100644 index 0000000..01c2acd --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala @@ -0,0 +1,364 @@ +package vexriscv.plugin + +import spinal.lib.com.jtag.{Jtag, JtagTapInstructionCtrl} +import spinal.lib.system.debugger.{JtagBridge, JtagBridgeNoTap, SystemDebugger, SystemDebuggerConfig, SystemDebuggerMemBus} +import vexriscv.plugin.IntAluPlugin.{ALU_CTRL, AluCtrlEnum} +import vexriscv._ +import vexriscv.ip._ +import spinal.core._ +import spinal.lib._ +import spinal.lib.blackbox.xilinx.s7.BSCANE2 +import spinal.lib.bus.amba3.apb.{Apb3, Apb3Config} +import spinal.lib.bus.avalon.{AvalonMM, AvalonMMConfig} +import spinal.lib.bus.bmb.{Bmb, BmbAccessCapabilities, BmbAccessParameter, BmbParameter} +import spinal.lib.bus.simple.PipelinedMemoryBus + +import scala.collection.mutable.ArrayBuffer + + +case class DebugExtensionCmd() extends Bundle{ + val wr = Bool + val address = UInt(8 bit) + val data = Bits(32 bit) +} +case class DebugExtensionRsp() extends Bundle{ + val data = Bits(32 bit) +} + +object DebugExtensionBus{ + def getBmbAccessParameter(source : BmbAccessCapabilities) = source.copy( + addressWidth = 8, + dataWidth = 32, + lengthWidthMax = 2, + alignment = BmbParameter.BurstAlignement.LENGTH + ) +} + +case class DebugExtensionBus() extends Bundle with IMasterSlave{ + val cmd = Stream(DebugExtensionCmd()) + val rsp = DebugExtensionRsp() //one cycle latency + + override def asMaster(): Unit = { + master(cmd) + in(rsp) + } + + def fromApb3(): Apb3 ={ + val apb = Apb3(Apb3Config( + addressWidth = 8, + dataWidth = 32, + useSlaveError = false + )) + + cmd.valid := apb.PSEL(0) && apb.PENABLE + cmd.wr := apb.PWRITE + cmd.address := apb.PADDR + cmd.data := apb.PWDATA + + apb.PREADY := cmd.ready + apb.PRDATA := rsp.data + + apb + } + + def fromAvalon(): AvalonMM ={ + val bus = AvalonMM(AvalonMMConfig.fixed(addressWidth = 8,dataWidth = 32, readLatency = 1)) + + cmd.valid := bus.read || bus.write + cmd.wr := bus.write + cmd.address := bus.address + cmd.data := bus.writeData + + bus.waitRequestn := cmd.ready + bus.readData := rsp.data + + bus + } + + def fromPipelinedMemoryBus(): PipelinedMemoryBus ={ + val bus = PipelinedMemoryBus(32, 32) + + cmd.arbitrationFrom(bus.cmd) + cmd.wr := bus.cmd.write + cmd.address := bus.cmd.address.resized + cmd.data := bus.cmd.data + + bus.rsp.valid := RegNext(cmd.fire) init(False) + bus.rsp.data := rsp.data + + bus + } + + def fromBmb(): Bmb ={ + val bus = Bmb(BmbParameter( + addressWidth = 8, + dataWidth = 32, + lengthWidth = 2, + sourceWidth = 0, + contextWidth = 0 + )) + + cmd.arbitrationFrom(bus.cmd) + cmd.wr := bus.cmd.isWrite + cmd.address := bus.cmd.address + cmd.data := bus.cmd.data + + bus.rsp.valid := RegNext(cmd.fire) init(False) + bus.rsp.data := rsp.data + bus.rsp.last := True + bus.rsp.setSuccess() + + bus + } + + def from(c : SystemDebuggerConfig) : SystemDebuggerMemBus = { + val mem = SystemDebuggerMemBus(c) + cmd.valid := mem.cmd.valid + cmd.wr := mem.cmd.wr + cmd.data := mem.cmd.data + cmd.address := mem.cmd.address.resized + mem.cmd.ready := cmd.ready + mem.rsp.valid := RegNext(cmd.fire).init(False) + mem.rsp.payload := rsp.data + mem + } + + def fromJtag(): Jtag ={ + val jtagConfig = SystemDebuggerConfig( + memAddressWidth = 32, + memDataWidth = 32, + remoteCmdWidth = 1 + ) + val jtagBridge = new JtagBridge(jtagConfig) + val debugger = new SystemDebugger(jtagConfig) + debugger.io.remote <> jtagBridge.io.remote + debugger.io.mem <> this.from(jtagConfig) + + jtagBridge.io.jtag + } + + def fromJtagInstructionCtrl(jtagClockDomain : ClockDomain, jtagHeaderIgnoreWidth : Int): JtagTapInstructionCtrl ={ + val jtagConfig = SystemDebuggerConfig( + memAddressWidth = 32, + memDataWidth = 32, + remoteCmdWidth = 1 + ) + val jtagBridge = new JtagBridgeNoTap(jtagConfig, jtagClockDomain, jtagHeaderIgnoreWidth) + val debugger = new SystemDebugger(jtagConfig) + debugger.io.remote <> jtagBridge.io.remote + debugger.io.mem <> this.from(jtagConfig) + + jtagBridge.io.ctrl + } + + def fromBscane2(usedId : Int, jtagHeaderIgnoreWidth : Int): Unit ={ + val jtagConfig = SystemDebuggerConfig() + + val bscane2 = BSCANE2(usedId) + val jtagClockDomain = ClockDomain(bscane2.TCK) + + val jtagBridge = new JtagBridgeNoTap(jtagConfig, jtagClockDomain, jtagHeaderIgnoreWidth) + jtagBridge.io.ctrl << bscane2.toJtagTapInstructionCtrl() + + val debugger = new SystemDebugger(jtagConfig) + debugger.io.remote <> jtagBridge.io.remote + debugger.io.mem <> this.from(debugger.io.mem.c) + } +} + +case class DebugExtensionIo() extends Bundle with IMasterSlave{ + val bus = DebugExtensionBus() + val resetOut = Bool + + override def asMaster(): Unit = { + master(bus) + in(resetOut) + } +} + +class DebugPlugin(var debugClockDomain : ClockDomain, hardwareBreakpointCount : Int = 0, BreakpointReadback : Boolean = false) extends Plugin[VexRiscv] { + + var io : DebugExtensionIo = null + val injectionAsks = ArrayBuffer[(Stage, Bool)]() + var injectionPort : Stream[Bits] = null + + + object IS_EBREAK extends Stageable(Bool) + object DO_EBREAK extends Stageable(Bool) + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + io = slave(DebugExtensionIo()).setName("debug") + + val decoderService = pipeline.service(classOf[DecoderService]) + + decoderService.addDefault(IS_EBREAK, False) + decoderService.add(EBREAK,List(IS_EBREAK -> True)) + + injectionPort = pipeline.service(classOf[IBusFetcher]).getInjectionPort() + + if(pipeline.serviceExist(classOf[ReportService])){ + val report = pipeline.service(classOf[ReportService]) + report.add("debug" -> { + val e = new DebugReport() + e.hardwareBreakpointCount = hardwareBreakpointCount + e + }) + } + } + + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val logic = debugClockDomain {pipeline plug new Area{ + val iBusFetcher = service(classOf[IBusFetcher]) + val firstCycle = RegNext(False) setWhen (io.bus.cmd.ready) + val secondCycle = RegNext(firstCycle) + val resetIt = RegInit(False) + val haltIt = RegInit(False) + val stepIt = RegInit(False) + + val isPipBusy = RegNext(stages.map(_.arbitration.isValid).orR || iBusFetcher.incoming()) + val godmode = RegInit(False) setWhen(haltIt && !isPipBusy) + val haltedByBreak = RegInit(False) + val debugUsed = RegInit(False) setWhen(io.bus.cmd.valid) addAttribute(Verilator.public) + val disableEbreak = RegInit(False) + + val allowEBreak = debugUsed && !disableEbreak + + val hardwareBreakpoints = Vec(Reg(new Bundle{ + val valid = Bool() + val pc = UInt(31 bits) + }), hardwareBreakpointCount) + hardwareBreakpoints.foreach(_.valid init(False)) + + val busReadDataReg = Reg(Bits(32 bit)) + when(stages.last.arbitration.isValid) { + busReadDataReg := stages.last.output(REGFILE_WRITE_DATA) + } + io.bus.cmd.ready := True + io.bus.rsp.data := busReadDataReg + when(!RegNext(io.bus.cmd.address(2))){ + io.bus.rsp.data(0) := resetIt + io.bus.rsp.data(1) := haltIt + io.bus.rsp.data(2) := isPipBusy + io.bus.rsp.data(3) := haltedByBreak + io.bus.rsp.data(4) := stepIt + } + if (BreakpointReadback) { + switch(RegNext(io.bus.cmd.address(7 downto 2))) { + for(i <- 0 until hardwareBreakpointCount){ + is(0x10 + i){ + io.bus.rsp.data(31 downto 1) := hardwareBreakpoints(i).pc.asBits + io.bus.rsp.data(0) := hardwareBreakpoints(i).valid + } + } + } + } + + + injectionPort.valid := False + injectionPort.payload := io.bus.cmd.data + + when(io.bus.cmd.valid) { + switch(io.bus.cmd.address(7 downto 2)) { + is(0x0) { + when(io.bus.cmd.wr) { + stepIt := io.bus.cmd.data(4) + resetIt setWhen (io.bus.cmd.data(16)) clearWhen (io.bus.cmd.data(24)) + haltIt setWhen (io.bus.cmd.data(17)) clearWhen (io.bus.cmd.data(25)) + haltedByBreak clearWhen (io.bus.cmd.data(25)) + godmode clearWhen(io.bus.cmd.data(25)) + disableEbreak setWhen (io.bus.cmd.data(18)) clearWhen (io.bus.cmd.data(26)) + } + } + is(0x1) { + when(io.bus.cmd.wr) { + injectionPort.valid := True + io.bus.cmd.ready := injectionPort.ready + } + } + for(i <- 0 until hardwareBreakpointCount){ + is(0x10 + i){ + when(io.bus.cmd.wr){ + hardwareBreakpoints(i).assignFromBits(io.bus.cmd.data) + } + } + } + } + } + + decode.insert(DO_EBREAK) := !haltIt && (decode.input(IS_EBREAK) || hardwareBreakpoints.map(hb => hb.valid && hb.pc === (decode.input(PC) >> 1)).foldLeft(False)(_ || _)) && allowEBreak + when(execute.arbitration.isValid && execute.input(DO_EBREAK)){ + execute.arbitration.haltByOther := True + busReadDataReg := execute.input(PC).asBits + when(stagesFromExecute.tail.map(_.arbitration.isValid).orR === False){ + iBusFetcher.haltIt() + execute.arbitration.flushIt := True + execute.arbitration.flushNext := True + haltIt := True + haltedByBreak := True + } + } + + when(haltIt) { + iBusFetcher.haltIt() + } + + when(stepIt && iBusFetcher.incoming()) { + iBusFetcher.haltIt() + when(decode.arbitration.isValid) { + haltIt := True + } + } + + //Avoid having two C instruction executed in a single step + if(pipeline.config.withRvc){ + val cleanStep = RegNext(stepIt && decode.arbitration.isFiring) init(False) + execute.arbitration.flushNext setWhen(cleanStep) + when(cleanStep){ + execute.arbitration.flushNext := True + iBusFetcher.forceNoDecode() + } + } + + io.resetOut := RegNext(resetIt) + + if(serviceExist(classOf[InterruptionInhibitor])) { + when(haltIt || stepIt) { + service(classOf[InterruptionInhibitor]).inhibateInterrupts() + } + } + + when(godmode) { + pipeline.plugins.foreach{ + case p : ExceptionInhibitor => p.inhibateException() + case _ => + } + pipeline.plugins.foreach{ + case p : PrivilegeService => p.forceMachine() + case _ => + } + pipeline.plugins.foreach{ + case p : PredictionInterface => p.inDebugNoFetch() + case _ => + } + if(pipeline.things.contains(DEBUG_BYPASS_CACHE)) pipeline(DEBUG_BYPASS_CACHE) := True + } + when(allowEBreak) { + pipeline.plugins.foreach { + case p: ExceptionInhibitor => p.inhibateEbreakException() + case _ => + } + } + + val wakeService = serviceElse(classOf[IWake], null) + if(wakeService != null) when(haltIt){ + wakeService.askWake() + } + }} + } +} |