From 4cd3f6529672a4f7d753aba865f2f3b7d75ff475 Mon Sep 17 00:00:00 2001 From: Dolu1990 Date: Wed, 19 Oct 2022 12:36:45 +0200 Subject: [PATCH] Add official RISC-V debug support (WIP, but can already load / step / run code via openocd telnet) --- README.md | 38 ++ src/main/scala/vexriscv/Riscv.scala | 8 + src/main/scala/vexriscv/TestsWorkspace.scala | 19 +- .../demo/smp/VexRiscvSmpCluster.scala | 8 +- .../scala/vexriscv/plugin/CsrPlugin.scala | 355 ++++++++++++++++-- .../scala/vexriscv/plugin/DebugPlugin.scala | 1 - .../vexriscv/plugin/EmbeddedRiscvJtag.scala | 86 +++++ src/test/cpp/regression/jtag.h | 188 ++++++++++ src/test/cpp/regression/main.cpp | 27 +- src/test/cpp/regression/makefile | 7 + 10 files changed, 691 insertions(+), 46 deletions(-) create mode 100644 src/main/scala/vexriscv/plugin/EmbeddedRiscvJtag.scala create mode 100644 src/test/cpp/regression/jtag.h diff --git a/README.md b/README.md index 760c63c..6208319 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ - [MmuPlugin](#mmuplugin) - [PmpPlugin](#pmpplugin) - [DebugPlugin](#debugplugin) + - [EmbeddedRiscvJtag](#embeddedRiscvJtag) - [YamlPlugin](#yamlplugin) - [FpuPlugin](#fpuplugin) - [AesPlugin](#aesplugin) @@ -773,6 +774,7 @@ This chapter describes the currently implemented plugins. - [StaticMemoryTranslatorPlugin](#staticmemorytranslatorplugin) - [MemoryTranslatorPlugin](#memorytranslatorplugin) - [DebugPlugin](#debugplugin) +- [EmbeddedRiscvJtag](#embeddedRiscvJtag) - [YamlPlugin](#yamlplugin) - [FpuPlugin](#fpuplugin) @@ -1180,6 +1182,42 @@ Write Address 0x04 -> The OpenOCD port is here: +#### EmbeddedRiscvJtag + +VexRiscv also support the official RISC-V debug specification (Thanks Efinix for the funding !). + +To enable it, you need to add the EmbeddedRiscvJtag to the plugin list : + +```scala +new EmbeddedRiscvJtag( + p = DebugTransportModuleParameter( + addressWidth = 7, + version = 1, + idle = 7 + ), + withTunneling = false, + withTap = true +) +``` + +And turn on the withPrivilegedDebug option in the CsrPlugin config. + +Here is an example of openocd tcl script to connect : + +```tcl +# ADD HERE YOUR JTAG ADAPTER SETTINGS + +set _CHIPNAME riscv +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10002FFF + +set _TARGETNAME $_CHIPNAME.cpu + +target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME + +init +halt +``` + #### YamlPlugin This plugin offers a service to other plugins to generate a useful Yaml file describing the CPU configuration. It contains, for instance, the sequence of instructions required diff --git a/src/main/scala/vexriscv/Riscv.scala b/src/main/scala/vexriscv/Riscv.scala index 9e45e7a..f0197bc 100644 --- a/src/main/scala/vexriscv/Riscv.scala +++ b/src/main/scala/vexriscv/Riscv.scala @@ -238,5 +238,13 @@ object Riscv{ val FFLAGS = 0x1 val FRM = 0x2 val FCSR = 0x3 + + val DCSR = 0x7B0 + val DPC = 0x7B1 + val TSELECT = 0x7A0 + val TDATA1 = 0x7A1 + val TDATA2 = 0x7A2 + val TINFO = 0x7a4 + val TCONTROL = 0x7A5 } } diff --git a/src/main/scala/vexriscv/TestsWorkspace.scala b/src/main/scala/vexriscv/TestsWorkspace.scala index c961f05..f102d81 100644 --- a/src/main/scala/vexriscv/TestsWorkspace.scala +++ b/src/main/scala/vexriscv/TestsWorkspace.scala @@ -24,6 +24,7 @@ import spinal.core._ import spinal.lib._ import vexriscv.ip._ import spinal.lib.bus.avalon.AvalonMM +import spinal.lib.cpu.riscv.debug.DebugTransportModuleParameter import spinal.lib.eda.altera.{InterruptReceiverTag, ResetEmitterTag} import vexriscv.demo.smp.VexRiscvSmpClusterGen import vexriscv.ip.fpu.FpuParameter @@ -141,14 +142,28 @@ object TestsWorkspace { withFloat = true, withDouble = true, externalFpu = false, - simHalt = true + simHalt = true, + privilegedDebug = true ) + config.plugins += new EmbeddedRiscvJtag( + p = DebugTransportModuleParameter( + addressWidth = 7, + version = 1, + idle = 7 + ), + withTunneling = false, + withTap = true + ) + +// l.foreach{ +// case p : EmbeddedRiscvJtag => p.debugCd.load(ClockDomain.current.copy(reset = Bool().setName("debug_reset"))) +// case _ => +// } println("Args :") println(config.getRegressionArgs().mkString(" ")) - val toplevel = new VexRiscv(config) // val toplevel = new VexRiscv(configLight) // val toplevel = new VexRiscv(configTest) diff --git a/src/main/scala/vexriscv/demo/smp/VexRiscvSmpCluster.scala b/src/main/scala/vexriscv/demo/smp/VexRiscvSmpCluster.scala index ec2aa50..dfc26b5 100644 --- a/src/main/scala/vexriscv/demo/smp/VexRiscvSmpCluster.scala +++ b/src/main/scala/vexriscv/demo/smp/VexRiscvSmpCluster.scala @@ -194,14 +194,15 @@ object VexRiscvSmpClusterGen { withDataCache : Boolean = true, withInstructionCache : Boolean = true, forceMisa : Boolean = false, - forceMscratch : Boolean = false + forceMscratch : Boolean = false, + privilegedDebug : Boolean = false ) = { assert(iCacheSize/iCacheWays <= 4096, "Instruction cache ways can't be bigger than 4096 bytes") assert(dCacheSize/dCacheWays <= 4096, "Data cache ways can't be bigger than 4096 bytes") assert(!(withDouble && !withFloat)) val csrConfig = if(withSupervisor){ - CsrPluginConfig.openSbi(mhartid = hartId, misa = Riscv.misaToInt(s"ima${if(withFloat) "f" else ""}${if(withDouble) "d" else ""}s")).copy(utimeAccess = CsrAccess.READ_ONLY) + CsrPluginConfig.openSbi(mhartid = hartId, misa = Riscv.misaToInt(s"ima${if(withFloat) "f" else ""}${if(withDouble) "d" else ""}s")).copy(utimeAccess = CsrAccess.READ_ONLY, withPrivilegedDebug = privilegedDebug) } else { CsrPluginConfig( catchIllegalAccess = true, @@ -223,7 +224,8 @@ object VexRiscvSmpClusterGen { ebreakGen = true, wfiGenAsWait = false, wfiGenAsNop = true, - ucycleAccess = CsrAccess.NONE + ucycleAccess = CsrAccess.NONE, + withPrivilegedDebug = privilegedDebug ) } val config = VexRiscvConfig( diff --git a/src/main/scala/vexriscv/plugin/CsrPlugin.scala b/src/main/scala/vexriscv/plugin/CsrPlugin.scala index 7731a41..edff425 100644 --- a/src/main/scala/vexriscv/plugin/CsrPlugin.scala +++ b/src/main/scala/vexriscv/plugin/CsrPlugin.scala @@ -9,6 +9,8 @@ import vexriscv.plugin.IntAluPlugin.{ALU_BITWISE_CTRL, ALU_CTRL, AluBitwiseCtrlE import scala.collection.mutable.ArrayBuffer import scala.collection.mutable import spinal.core.sim._ +import spinal.lib.cpu.riscv.debug._ +import spinal.lib.fsm.{State, StateMachine} /** * Created by spinalvm on 21.03.17. @@ -78,7 +80,9 @@ case class CsrPluginConfig( pipelinedInterrupt : Boolean = true, csrOhDecoder : Boolean = true, deterministicInteruptionEntry : Boolean = false, //Only used for simulatation purposes - wfiOutput : Boolean = false + wfiOutput : Boolean = false, + withPrivilegedDebug : Boolean = false, //For the official RISC-V debug spec implementation + debugTriggers : Int = 2 ){ assert(!ucycleAccess.canWrite) def privilegeGen = userGen || supervisorGen @@ -465,6 +469,10 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep var externalMhartId : UInt = null var utime : UInt = null + var debugBus : DebugHartBus = null + var debugMode : Bool = null + var injectionPort : Stream[Bits] = null + override def askWake(): Unit = thirdPartyWake := True override def isContextSwitching = contextSwitching @@ -622,6 +630,11 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep decoderService.addDefault(IS_SFENCE_VMA, False) decoderService.add(SFENCE_VMA, List(IS_SFENCE_VMA -> True)) } + + + injectionPort = withPrivilegedDebug generate pipeline.service(classOf[IBusFetcher]).getInjectionPort() + debugMode = withPrivilegedDebug generate Bool().setName("debugMode") + debugBus = withPrivilegedDebug generate slave(DebugHartBus()).setName("debugBus") } def inhibateInterrupts() : Unit = allowInterrupts := False @@ -657,11 +670,244 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep val base = UInt(xlen-2 bits) } + val trapEvent = False + val privilegeReg = privilegeGen generate RegInit(U"11") privilege := (if(privilegeGen) privilegeReg else U"11") when(forceMachineWire) { privilege := 3 } + val debug = withPrivilegedDebug generate pipeline plug new Area{ + val iBusFetcher = service(classOf[IBusFetcher]) + def bus = debugBus + + + val running = RegInit(True) + debugMode := !running + + + when(!running) { + iBusFetcher.haltIt() + } + + bus.resume.rsp.valid := False + + bus.running := running + bus.halted := !running + bus.unavailable := RegNext(ClockDomain.current.isResetActive) + val enterHalt = running.getAheadValue().fall(False) + + val doHalt = RegInit(False) setWhen(bus.haltReq && bus.running && !debugMode) clearWhen(enterHalt) + val forceResume = False + val doResume = forceResume || bus.resume.isPending(1) + + // Pipeline execution timeout used to trigger some redo + val timeout = Timeout(3) + when(pipeline.stages.map(_.arbitration.isValid).orR){ + timeout.clear() + } + + val inject = new Area{ + val cmd = bus.dmToHart.translateWith(bus.dmToHart.data).takeWhen(bus.dmToHart.op === DebugDmToHartOp.EXECUTE) + injectionPort << cmd.toStream.stage + + + val pending = RegInit(False) setWhen(cmd.valid) clearWhen(bus.exception || bus.commit || bus.ebreak || bus.redo) + when(cmd.valid){ timeout.clear() } + bus.redo := pending && timeout.state + } + val dataCsrr = new Area{ + bus.hartToDm.valid := isWriting(DebugModule.CSR_DATA) + bus.hartToDm.address := 0 + bus.hartToDm.data := execute.input(SRC1) + } + + val dataCsrw = new Area{ + val value = Reg(Bits(32 bits)) + + val fromDm = new Area{ + when(bus.dmToHart.valid && bus.dmToHart.op === DebugDmToHartOp.DATA){ + value := bus.dmToHart.data + } + } + + val toHart = new Area{ + r(DebugModule.CSR_DATA, value) + } + } + + val dpc = Reg(UInt(32 bits)) + val dcsr = new Area{ + rw(CSR.DPC, dpc) + val prv = RegInit(U"11") + val step = RegInit(False) //TODO + val nmip = False + val mprven = False + val cause = RegInit(U"000") + val stoptime = False + val stopcount = False + val stepie = RegInit(False) //TODO + val ebreaku = userGen generate RegInit(False) + val ebreaks = supervisorGen generate RegInit(False) + val ebreakm = RegInit(False) + val xdebugver = U(4, 4 bits) + + val stepLogic = new StateMachine{ + val IDLE, SINGLE, WAIT, DELAY = new State() + setEntry(IDLE) + + val isCause = RegInit(False) + + IDLE whenIsActive{ + when(step && bus.resume.rsp.valid){ + goto(SINGLE) + } + } + SINGLE whenIsActive{ + when(iBusFetcher.incoming()) { + iBusFetcher.haltIt() + when(decode.arbitration.isValid) { + goto(WAIT) + } + } + } + + WAIT whenIsActive{ + iBusFetcher.haltIt() + when(pipeline.lastStageIsFiring || trapEvent){ //TODO + doHalt := True + isCause := True + goto(DELAY) + } + //re resume the execution in case of timeout (ex cache miss) + when(timeout.state){ + forceResume := True + goto(DELAY) + } + } + DELAY whenIsActive{ + iBusFetcher.haltIt() + goto(IDLE) + } + + always{ + when(enterHalt){ + goto(IDLE) + } + } + build() + } + + + r(CSR.DCSR, 3 -> nmip, 6 -> cause, 28 -> xdebugver, 4 -> mprven, 9 -> stoptime, 10 -> stopcount) + rw(CSR.DCSR, 0 -> prv, 2 -> step, 11 -> stepie, 15 -> ebreakm) + if(supervisorGen) rw(CSR.DCSR, 13 -> ebreaks) + if(userGen) rw(CSR.DCSR, 12 -> ebreaku) + + + when(debugMode) { + pipeline.plugins.foreach{ + case p : PredictionInterface => p.inDebugNoFetch() + case _ => + } + if(pipeline.things.contains(DEBUG_BYPASS_CACHE)) pipeline(DEBUG_BYPASS_CACHE) := True + } + + val wakeService = serviceElse(classOf[IWake], null) + if(wakeService != null) when(debugMode || step){ + wakeService.askWake() + } + } + + //Very limited subset of the trigger spec + val trigger = (debugTriggers > 0) generate new Area { + val tselect = new Area{ + val index = Reg(UInt(log2Up(debugTriggers) bits)) + rw(CSR.TSELECT, index) + + val outOfRange = if(isPow2(debugTriggers)) False else index < debugTriggers + } + + val tinfo = new Area{ + r(CSR.TINFO, 0 -> tselect.outOfRange, 2 -> !tselect.outOfRange) + } + + val decodeBreak = new Area { + val enabled = False + val timeout = Timeout(3).clearWhen(!enabled || stages.tail.map(_.arbitration.isValid).orR) + when(enabled) { + decode.arbitration.haltByOther := True + when(timeout.state) { + dpc := decode.input(PC) + running := False + dcsr.cause := 2 + dcsr.prv := privilege + privilegeReg := 3 + } + } + } + + val slots = for(slotId <- 0 until debugTriggers) yield new Area { + val selected = tselect.index === slotId + def csrw(csrId : Int, thats : (Int, Data)*): Unit ={ + onWrite(csrId){ + when(selected) { + for((offset, data) <- thats){ + data.assignFromBits(writeData()(offset, widthOf(data) bits)) + } + } + } + } + def csrr(csrId : Int, read : Bits, thats : (Int, Data)*): Unit ={ + when(selected) { + for((offset, data) <- thats){ + read(offset, widthOf(data) bits) := data.asBits + } + } + } + def csrrw(csrId : Int, read : Bits, thats : (Int, Data)*) : Unit = { + csrw(csrId, thats :_*) + csrr(csrId, read, thats :_*) + } + + val tdata1 = new Area{ + val read = B(0, 32 bits) + val tpe = Reg(UInt(4 bits)) init(2) + val dmode = Reg(Bool()) init(False) + + val execute = RegInit(False) + val m, s, u = RegInit(False) + val action = RegInit(U"0000") + val privilegeHit = !debugMode && privilege.mux( + 0 -> u, + 1 -> s, + 3 -> m, + default -> False + ) + + csrrw(CSR.TDATA1, read, 2 -> execute , 3 -> u, 4-> s, 6 -> m, 32 - 4 -> tpe, 32 - 5 -> dmode, 12 -> action) + + + //TODO action sizelo timing select sizehi maskmax + } + + val tdata2 = new Area{ + val value = Reg(PC) + csrw(CSR.TDATA2, 0 -> value) + + val execute = new Area{ + val enabled = !debugMode && tdata1.action === 1 && tdata1.execute && tdata1.privilegeHit + val hit = enabled && value === decode.input(PC) + decodeBreak.enabled.setWhen(hit) + } + } + } + + r(CSR.TDATA1, 0 -> slots.map(_.tdata1.read).read(tselect.index)) + } + } + + val machineCsr = pipeline plug new Area{ //Define CSR registers // Status => MXR, SUM, TVM, TW, TSE ? @@ -973,6 +1219,8 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep } code.addTag(Verilator.public) + + if(withPrivilegedDebug) valid setWhen(debug.doHalt) } @@ -1012,6 +1260,11 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep val hadException = RegNext(exception) init(False) addTag(Verilator.public) pipelineLiberator.done.clearWhen(hadException) + if(withPrivilegedDebug) { + debugBus.commit := debugMode && pipeline.stages.last.arbitration.isFiring + debugBus.exception := debugMode && hadException + debugBus.ebreak := False + } val targetPrivilege = CombInit(interrupt.targetPrivilege) if(exceptionPortCtrl != null) when(hadException) { @@ -1019,8 +1272,17 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep } val trapCause = CombInit(interrupt.code.resize(trapCodeWidth)) + val trapCauseEbreakDebug = False if(exceptionPortCtrl != null) when( hadException){ trapCause := exceptionPortCtrl.exceptionContext.code.resized + if(withPrivilegedDebug) { + when(exceptionPortCtrl.exceptionContext.code === 3){ + trapCauseEbreakDebug setWhen(debugMode) + trapCauseEbreakDebug setWhen(privilege === 3 && debug.dcsr.ebreakm) + if (userGen) trapCauseEbreakDebug setWhen (privilege === 0 && debug.dcsr.ebreaku) + if (supervisorGen) trapCauseEbreakDebug setWhen (privilege === 1 && debug.dcsr.ebreaks) + } + } } val xtvec = Xtvec().assignDontCare() @@ -1029,38 +1291,61 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep is(3){ xtvec := machineCsr.mtvec } } + val trapEnterDebug = False + if(withPrivilegedDebug) trapEnterDebug setWhen(debug.doHalt || trapCauseEbreakDebug || !hadException && debug.doHalt) when(hadException || interruptJump){ + trapEvent := True fetcher.haltIt() //Avoid having the fetch confused by the incomming privilege switch jumpInterface.valid := True jumpInterface.payload := (if(!xtvecModeGen) xtvec.base @@ U"00" else (xtvec.mode === 0 || hadException) ? (xtvec.base @@ U"00") | ((xtvec.base + trapCause) @@ U"00") ) lastStage.arbitration.flushNext := True - if(privilegeGen) privilegeReg := targetPrivilege + when(!trapEnterDebug){ + if(privilegeGen) privilegeReg := targetPrivilege + switch(targetPrivilege){ + if(supervisorGen) is(1) { + sstatus.SIE := False + sstatus.SPIE := sstatus.SIE + sstatus.SPP := privilege(0 downto 0) + scause.interrupt := !hadException + scause.exceptionCode := trapCause + sepc := mepcCaptureStage.input(PC) + if (exceptionPortCtrl != null) when(hadException){ + stval := exceptionPortCtrl.exceptionContext.badAddr + } + } - switch(targetPrivilege){ - if(supervisorGen) is(1) { - sstatus.SIE := False - sstatus.SPIE := sstatus.SIE - sstatus.SPP := privilege(0 downto 0) - scause.interrupt := !hadException - scause.exceptionCode := trapCause - sepc := mepcCaptureStage.input(PC) - if (exceptionPortCtrl != null) when(hadException){ - stval := exceptionPortCtrl.exceptionContext.badAddr + is(3){ + mstatus.MIE := False + mstatus.MPIE := mstatus.MIE + mstatus.MPP := privilege + mcause.interrupt := !hadException + mcause.exceptionCode := trapCause + mepc := mepcCaptureStage.input(PC) + if(exceptionPortCtrl != null) when(hadException){ + mtval := exceptionPortCtrl.exceptionContext.badAddr + } } } - - is(3){ - mstatus.MIE := False - mstatus.MPIE := mstatus.MIE - mstatus.MPP := privilege - mcause.interrupt := !hadException - mcause.exceptionCode := trapCause - mepc := mepcCaptureStage.input(PC) - if(exceptionPortCtrl != null) when(hadException){ - mtval := exceptionPortCtrl.exceptionContext.badAddr + } otherwise { + if(withPrivilegedDebug) { + debug.running := False + when(!debugMode) { + debug.dpc := mepcCaptureStage.input(PC) + debug.dcsr.cause := 3 + when(debug.dcsr.step) { + debug.dcsr.cause := 4 + } + when(trapCauseEbreakDebug) { + debug.dcsr.cause := 1 + } + debug.dcsr.prv := privilege + } otherwise { + debugBus.exception := !trapCauseEbreakDebug //TODO mask interrupt while in debug mode + debugBus.ebreak := trapCauseEbreakDebug } + privilegeReg := 3 } } } @@ -1097,9 +1382,21 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep } } - contextSwitching := jumpInterface.valid + // Debug resume + if(withPrivilegedDebug) { + when(debug.doResume) { + jumpInterface.valid := True + jumpInterface.payload := debug.dpc + + privilegeReg := debug.dcsr.prv + debug.running := True + debug.bus.resume.rsp.valid := True + } + } + + //CSR read/write instructions management decode plug new Area{ import decode._ @@ -1200,12 +1497,6 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep memory.output(REGFILE_WRITE_DATA) := memory.input(PIPELINED_CSR_READ) } } -// -// Component.current.rework{ -// when(arbitration.isFiring && input(IS_CSR)) { -// memory.input(REGFILE_WRITE_DATA).getDrivingReg := readData -// } -// } //Translation of the csrMapping into real logic val csrAddress = input(INSTRUCTION)(csrRange) @@ -1307,11 +1598,15 @@ class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with Excep illegalAccess clearWhen(csrMapping.allowCsrSignal) - when(privilege < csrAddress(9 downto 8).asUInt){ + val forceFail = False + forceFail setWhen(privilege < csrAddress(9 downto 8).asUInt) + if(withPrivilegedDebug) forceFail setWhen(!debugMode && csrAddress >> 4 === 0x7B) + when(forceFail){ illegalAccess := True readInstruction := False writeInstruction := False } + illegalAccess clearWhen(!arbitration.isValid || !input(IS_CSR)) } } diff --git a/src/main/scala/vexriscv/plugin/DebugPlugin.scala b/src/main/scala/vexriscv/plugin/DebugPlugin.scala index 01c2acd..4d0cbac 100644 --- a/src/main/scala/vexriscv/plugin/DebugPlugin.scala +++ b/src/main/scala/vexriscv/plugin/DebugPlugin.scala @@ -179,7 +179,6 @@ case class DebugExtensionIo() extends Bundle with IMasterSlave{ 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 diff --git a/src/main/scala/vexriscv/plugin/EmbeddedRiscvJtag.scala b/src/main/scala/vexriscv/plugin/EmbeddedRiscvJtag.scala new file mode 100644 index 0000000..21eb3d9 --- /dev/null +++ b/src/main/scala/vexriscv/plugin/EmbeddedRiscvJtag.scala @@ -0,0 +1,86 @@ +/** + * Thanks Efinix for funding the official RISC-V debug implementation on VexRiscv ! + */ + + +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import spinal.lib.com.jtag._ +import spinal.lib.cpu.riscv.debug._ +import vexriscv._ + + +class EmbeddedRiscvJtag(var p : DebugTransportModuleParameter, + var withTap : Boolean = true, + var withTunneling : Boolean = false + ) extends Plugin[VexRiscv] with VexRiscvRegressionArg{ + + + override def getVexRiscvRegressionArgs() = List("DEBUG_PLUGIN=RISCV") + + var jtag : Jtag = null + var jtagInstruction : JtagTapInstructionCtrl = null + var ndmreset : Bool = null + +// val debugCd = Handle[ClockDomain].setName("debugCd") +// val noTapCd = Handle[ClockDomain].setName("jtagCd") + + override def setup(pipeline: VexRiscv): Unit = { + jtag = withTap generate slave(Jtag()).setName("jtag") + jtagInstruction = !withTap generate slave(JtagTapInstructionCtrl()).setName("jtagInstruction") + ndmreset = Bool().setName("ndmreset") + } + + override def build(pipeline: VexRiscv): Unit = { + val XLEN = 32 + val dm = DebugModule( + DebugModuleParameter( + version = p.version + 1, + harts = 1, + progBufSize = 2, + datacount = XLEN/32, + xlens = List(XLEN) + ) + ) + + ndmreset := dm.io.ndmreset + + val dmiDirect = if(withTap && !withTunneling) new Area { + val logic = DebugTransportModuleJtagTap( + p.copy(addressWidth = 7), + debugCd = ClockDomain.current + ) + dm.io.ctrl <> logic.io.bus + logic.io.jtag <> jtag + } + val dmiTunneled = if(withTap && withTunneling) new Area { + val logic = DebugTransportModuleJtagTapWithTunnel( + p.copy(addressWidth = 7), + debugCd = ClockDomain.current + ) + dm.io.ctrl <> logic.io.bus + logic.io.jtag <> jtag + } + + val privBus = pipeline.service(classOf[CsrPlugin]).debugBus.setAsDirectionLess() + privBus <> dm.io.harts(0) + privBus.dmToHart.removeAssignments() <-< dm.io.harts(0).dmToHart + } +} + +/* +make IBUS=CACHED IBUS_DATA_WIDTH=64 COMPRESSED=no DBUS=CACHED DBUS_LOAD_DATA_WIDTH=64 DBUS_STORE_DATA_WIDTH=64 LRSC=yes AMO=yes DBUS_EXCLUSIVE=yes DBUS_INVALIDATE=yes MUL=yes DIV=yes RVF=yes RVD=yes RISCV_JTAG=yes REDO=1 WITH_RISCV_REF=no DEBUG_PLUGIN_EXTERNAL=yes DEBUG_PLUGIN=no +src/openocd -f ../VexRiscvOoo/src/main/tcl/openocd/naxriscv_sim.tcl -c "sleep 5000" -c "reg pc 0x80000000" -c "exit" -d3 + +mdw 0x1000 16 +mww 0x1000 0x12345678 +mdw 0x1000 16 + +load_image /media/data/open/VexRiscv/src/test/resources/hex/dhrystoneO3.hex 0 ihex +mdw 0x80000000 16 +reg pc 0x80000000 +bp 0x80000114 4 +resume +*/ diff --git a/src/test/cpp/regression/jtag.h b/src/test/cpp/regression/jtag.h new file mode 100644 index 0000000..8dabe93 --- /dev/null +++ b/src/test/cpp/regression/jtag.h @@ -0,0 +1,188 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +/** Returns true on success, or false if there was an error */ +bool SetSocketBlockingEnabled(int fd, bool blocking) +{ + if (fd < 0) return false; + +#ifdef WIN32 + unsigned long mode = blocking ? 0 : 1; + return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? true : false; +#else + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) return false; + flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK); + return (fcntl(fd, F_SETFL, flags) == 0) ? true : false; +#endif +} + +class Jtag : public SimElement{ +public: + CData *tms, *tdi, *tdo, *tck; + enum State {reset}; + uint32_t state; + + int serverSocket, clientHandle; + struct sockaddr_in serverAddr; + struct sockaddr_storage serverStorage; + socklen_t addr_size; + uint64_t tooglePeriod; +// char buffer[1024]; + + uint32_t timer; + Jtag(CData *tms, CData *tdi, CData *tdo, CData* tck,uint64_t period){ + this->tms = tms; + this->tdi = tdi; + this->tdo = tdo; + this->tck = tck; + this->tooglePeriod = period-1; + *tms = 0; + *tdi = 0; + *tdo = 0; + *tck = 0; + state = 0; + timer = 0; + + + //---- Create the socket. The three arguments are: ----// + // 1) Internet domain 2) Stream socket 3) Default protocol (TCP in this case) // + serverSocket = socket(PF_INET, SOCK_STREAM, 0); + assert(serverSocket != -1); + int flag = 1; + setsockopt( serverSocket, /* socket affected */ + IPPROTO_TCP, /* set option at TCP level */ + TCP_NODELAY, /* name of option */ + (char *) &flag, /* the cast is historical + cruft */ + sizeof(int)); /* length of option value */ + + /*int a = 0xFFF; + if (setsockopt(serverSocket, SOL_SOCKET, SO_RCVBUF, &a, sizeof(int)) == -1) { + fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno)); + } + a = 0xFFFFFF; + if (setsockopt(serverSocket, SOL_SOCKET, SO_SNDBUF, &a, sizeof(int)) == -1) { + fprintf(stderr, "Error setting socket opts: %s\n", strerror(errno)); + }*/ + + SetSocketBlockingEnabled(serverSocket,0); + + + //---- Configure settings of the server address struct ----// + // Address family = Internet // + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(7894); + serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); + + //---- Bind the address struct to the socket ----// + bind(serverSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr)); + + //---- Listen on the socket, with 5 max connection requests queued ----// + listen(serverSocket,1); + + //---- Accept call creates a new socket for the incoming connection ----// + addr_size = sizeof serverStorage; + clientHandle = -1; + + } + void connectionReset(){ + printf("CONNECTION RESET\n"); + shutdown(clientHandle,SHUT_RDWR); + clientHandle = -1; + } + + + virtual ~Jtag(){ + if(clientHandle != -1) { + shutdown(clientHandle,SHUT_RDWR); + usleep(100); + } + if(serverSocket != -1) { + close(serverSocket); + usleep(100); + } + } + + uint32_t selfSleep = 0; + uint32_t checkNewConnectionsTimer = 0; + uint8_t rxBuffer[100]; + int32_t rxBufferSize = 0; + int32_t rxBufferRemaining = 0; + +// virtual void onReset(){} +// virtual void postReset(){} +// virtual void preCycle(){} +// virtual void postCycle(){} + virtual void postCycle(){ + if(timer != 0){ + timer -= 1; + return; + } + checkNewConnectionsTimer++; + if(checkNewConnectionsTimer == 5000){ + checkNewConnectionsTimer = 0; + int newclientHandle = accept(serverSocket, (struct sockaddr *) &serverStorage, &addr_size); + if(newclientHandle != -1){ + if(clientHandle != -1){ + connectionReset(); + } + clientHandle = newclientHandle; + printf("CONNECTED\n"); + } + else{ + if(clientHandle == -1) + selfSleep = 1000; + } + } + if(selfSleep) + selfSleep--; + else{ + if(clientHandle != -1){ + uint8_t buffer; + int n; + + if(rxBufferRemaining == 0){ + if(ioctl(clientHandle,FIONREAD,&n) != 0) + connectionReset(); + else if(n >= 1){ + rxBufferSize = read(clientHandle,&rxBuffer,100); + if(rxBufferSize < 0){ + connectionReset(); + }else { + rxBufferRemaining = rxBufferSize; + } + }else { + selfSleep = 30; + } + } + + if(rxBufferRemaining != 0){ + uint8_t buffer = rxBuffer[rxBufferSize - (rxBufferRemaining--)]; + *tms = (buffer & 1) != 0; + *tdi = (buffer & 2) != 0; + *tck = (buffer & 8) != 0; + if(buffer & 4){ + buffer = (*tdo != 0); + //printf("TDO=%d\n",buffer); + if(-1 == send(clientHandle,&buffer,1,0)) + connectionReset(); + }else { + + // printf("\n"); + } + } + } + } + timer = tooglePeriod; + } + +}; \ No newline at end of file diff --git a/src/test/cpp/regression/main.cpp b/src/test/cpp/regression/main.cpp index e5866fa..adf7b5b 100644 --- a/src/test/cpp/regression/main.cpp +++ b/src/test/cpp/regression/main.cpp @@ -3071,6 +3071,8 @@ public: #endif +#include "jtag.h" + void Workspace::fillSimELements(){ #ifdef IBUS_SIMPLE simElements.push_back(new IBusSimple(this)); @@ -3121,6 +3123,9 @@ void Workspace::fillSimELements(){ #ifdef DEBUG_PLUGIN_AVALON simElements.push_back(new DebugPluginAvalon(this)); #endif + #ifdef RISCV_JTAG + simElements.push_back(new Jtag(&top->jtag_tms, &top->jtag_tdi, &top->jtag_tdo, &top->jtag_tck, 4)); + #endif } mutex Workspace::staticMutex; @@ -4126,16 +4131,7 @@ int main(int argc, char **argv, char **env) { - #ifdef RVF - for(const string &name : riscvTestFloat){ - redo(REDO,RiscvTest(name).withRiscvRef()->bootAt(0x80000188u)->writeWord(0x80000184u, 0x00305073)->run();) - } - #endif - #ifdef RVD - for(const string &name : riscvTestDouble){ - redo(REDO,RiscvTest(name).withRiscvRef()->bootAt(0x80000188u)->writeWord(0x80000184u, 0x00305073)->run();) - } - #endif + //return 0; //#ifdef LITEX @@ -4365,6 +4361,17 @@ int main(int argc, char **argv, char **env) { redo(REDO,WorkspaceRegression("amo").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../raw/amo/build/amo.hex")->bootAt(0x00000000u)->run(10e3);); #endif + #ifdef RVF + for(const string &name : riscvTestFloat){ + redo(REDO,RiscvTest(name).withRiscvRef()->bootAt(0x80000188u)->writeWord(0x80000184u, 0x00305073)->run();) + } + #endif + #ifdef RVD + for(const string &name : riscvTestDouble){ + redo(REDO,RiscvTest(name).withRiscvRef()->bootAt(0x80000188u)->writeWord(0x80000184u, 0x00305073)->run();) + } + #endif + #ifdef DHRYSTONE Dhrystone("dhrystoneO3_Stall","dhrystoneO3",true,true).run(1.5e6); #if defined(COMPRESSED) diff --git a/src/test/cpp/regression/makefile b/src/test/cpp/regression/makefile index b8759c9..13bfbb3 100644 --- a/src/test/cpp/regression/makefile +++ b/src/test/cpp/regression/makefile @@ -30,6 +30,7 @@ AMO?=no NO_STALL?=no DEBUG_PLUGIN?=STD DEBUG_PLUGIN_EXTERNAL?=no +RISCV_JTAG?=no RUN_HEX=no WITH_RISCV_REF=yes CUSTOM_SIMD_ADD?=no @@ -302,6 +303,12 @@ ifeq ($(DEBUG_PLUGIN_EXTERNAL),yes) ADDCFLAGS += -CFLAGS -DDEBUG_PLUGIN_EXTERNAL endif + +ifeq ($(RISCV_JTAG),yes) + ADDCFLAGS += -CFLAGS -DRISCV_JTAG +endif + + ifeq ($(REF),yes) ADDCFLAGS += -CFLAGS -DREF endif