1
0
rocket-chip/src/main/scala/devices/debug/DebugTransport.scala

264 lines
8.6 KiB
Scala
Raw Normal View History

// See LICENSE.SiFive for license details.
package freechips.rocketchip.devices.debug
import Chisel._
import freechips.rocketchip.config._
import freechips.rocketchip.jtag._
import freechips.rocketchip.util._
This commit adds Logic & test support for JTAG implementation of Debug Transport Module. - The DebugTransportModuleJtag is written in Verilog. It probably could be written in Chisel except for some negative edge clocking requirement. - For real implementations, the AsyncDebugBusTo/From is insufficient. This commit includes cases where they are used, but because they are not reset asynchronously, a Verilog 'AsyncMailbox' is used when p(AsyncDebug) is false. - This commit differs significantly from the earlier attempt. Now, the DTM and synchronizer is instantiated within Top, as it is a real piece of hardware (vs. test infrastructure). -TestHarness takes a parameter vs. creating an entirely new TestHarness class. It does not make sense to instantiate TestHarness when p(IncludeJtagDTM) is false, and it would not make sense to insantiate some other TestHarness if p(IncludeJtagDTM) is true. To build Verilog which includes the JtagDTM within Top: make CONFIG=WithJtagDTM_... To test using gdb->OpenOCD->jtag_vpi->Verilog: First, install openocd (included in this commit) ./bootstrap ./configure --prefix=$OPENOCD --enable-jtag-vpi make make install Then to run a simulation: On a 32-bit core: $(ROCKETCHIP)/riscv-tools/riscv-tests/debug/gdbserver.py \ --run ./simv-TestHarness-WithJtagDTM_... \ --cmd="$OPENOCD/bin/openocd --s $OPENOCD/share/openocd/scripts/" \ --freedom-e300-sim \ SimpleRegisterTest.test_s0 On a 64-bit core: $(ROCKETCHIP)/riscv-tools/riscv-tests/debug/gdbserver.py \ --run ./simv-TestHarness-WithJtagDTM_... \ --cmd="$OPENOCD/bin/openocd --s $OPENOCD/share/openocd/scripts/" \ --freedom-u500-sim \ SimpleRegisterTest.test_s0
2016-08-19 23:01:33 +02:00
case class JtagDTMConfig (
idcodeVersion : Int, // chosen by manuf.
idcodePartNum : Int, // Chosen by manuf.
idcodeManufId : Int, // Assigned by JEDEC
// Note: the actual manufId is passed in through a wire.
// Do not forget to wire up io.jtag_mfr_id through your top-level to set the
// mfr_id for this core.
// If you wish to use this field in the config, you can obtain it along
// the lines of p(JtagDTMKey).idcodeManufId.U(11.W).
debugIdleCycles : Int)
case object JtagDTMKey extends Field[JtagDTMConfig](new JtagDTMKeyDefault())
class JtagDTMKeyDefault extends JtagDTMConfig(
idcodeVersion = 0,
idcodePartNum = 0,
idcodeManufId = 0,
debugIdleCycles = 5) // Reasonable guess for synchronization.
object dtmJTAGAddrs {
def IDCODE = 0x1
def DTM_INFO = 0x10
def DMI_ACCESS = 0x11
}
class DMIAccessUpdate(addrBits: Int) extends Bundle {
val addr = UInt(width = addrBits)
val data = UInt(width = DMIConsts.dmiDataSize)
val op = UInt(width = DMIConsts.dmiOpSize)
override def cloneType = new DMIAccessUpdate(addrBits).asInstanceOf[this.type]
}
class DMIAccessCapture(addrBits: Int) extends Bundle {
val addr = UInt(width = addrBits)
val data = UInt(width = DMIConsts.dmiDataSize)
val resp = UInt(width = DMIConsts.dmiRespSize)
override def cloneType = new DMIAccessCapture(addrBits).asInstanceOf[this.type]
}
class DTMInfo extends Bundle {
val reserved1 = UInt(15.W)
val dmireset = Bool()
val reserved0 = UInt(1.W)
val dmiIdleCycles = UInt(3.W)
val dmiStatus = UInt(2.W)
val debugAddrBits = UInt(6.W)
val debugVersion = UInt(4.W)
}
/** A wrapper around JTAG providing a reset signal and manufacturer id. */
class SystemJTAGIO extends Bundle {
val jtag = new JTAGIO(hasTRSTn = false).flip
val reset = Bool(INPUT)
val mfr_id = UInt(INPUT, 11)
}
class DebugTransportModuleJTAG(debugAddrBits: Int, c: JtagDTMConfig)
(implicit val p: Parameters) extends Module {
val io = new Bundle {
val dmi = new DMIIO()(p)
val jtag = Flipped(new JTAGIO(hasTRSTn = false)) // TODO: re-use SystemJTAGIO here?
val jtag_reset = Bool(INPUT)
val jtag_mfr_id = UInt(INPUT, 11)
val fsmReset = Bool(OUTPUT)
}
//--------------------------------------------------------
// Reg and Wire Declarations
val dtmInfo = Wire(new DTMInfo)
val busyReg = RegInit(Bool(false))
val stickyBusyReg = RegInit(Bool(false))
val stickyNonzeroRespReg = RegInit(Bool(false))
val skipOpReg = Reg(init = Bool(false)) // Skip op because we're busy
val downgradeOpReg = Reg(init = Bool(false)) // downgrade op because prev. failed.
val busy = Wire(Bool())
val nonzeroResp = Wire(Bool())
val busyResp = Wire(new DMIAccessCapture(debugAddrBits))
val nonbusyResp = Wire(new DMIAccessCapture(debugAddrBits))
val dmiResp = Wire(new DMIAccessCapture(debugAddrBits))
val nopResp = Wire(new DMIAccessCapture(debugAddrBits))
val dmiReqReg = Reg(new DMIReq(debugAddrBits))
val dmiReqValidReg = Reg(init = Bool(false));
val dmiStatus = Wire(UInt(width = 2))
//--------------------------------------------------------
// DTM Info Chain Declaration
dmiStatus := Cat(stickyNonzeroRespReg, stickyNonzeroRespReg | stickyBusyReg)
dtmInfo.debugVersion := 1.U // This implements version 1 of the spec.
dtmInfo.debugAddrBits := UInt(debugAddrBits)
dtmInfo.dmiStatus := dmiStatus
dtmInfo.dmiIdleCycles := UInt(c.debugIdleCycles)
dtmInfo.reserved0 := 0.U
dtmInfo.dmireset := false.B // This is write-only
dtmInfo.reserved1 := 0.U
val dtmInfoChain = Module (CaptureUpdateChain(gen = new DTMInfo()))
dtmInfoChain.io.capture.bits := dtmInfo
//--------------------------------------------------------
// Debug Access Chain Declaration
val dmiAccessChain = Module(CaptureUpdateChain(genCapture = new DMIAccessCapture(debugAddrBits),
genUpdate = new DMIAccessUpdate(debugAddrBits)))
//--------------------------------------------------------
// Debug Access Support
// Busy Register. We become busy when we first try to send a request.
// We stop being busy when we accept a response.
when (io.dmi.req.valid) {
busyReg := Bool(true)
}
when (io.dmi.resp.fire()) {
busyReg := Bool(false)
}
// We are busy during a given CAPTURE
// if we haven't received a valid response yet or if we
// were busy last time without a reset.
// busyReg will still be set when we check it,
// so the logic for checking busy looks ahead.
busy := (busyReg & !io.dmi.resp.valid) | stickyBusyReg;
// Downgrade/Skip. We make the decision to downgrade or skip
// during every CAPTURE_DR, and use the result in UPDATE_DR.
// The sticky versions are reset by write to dmiReset in DTM_INFO.
when (dmiAccessChain.io.update.valid) {
skipOpReg := Bool(false)
downgradeOpReg := Bool(false)
}
when (dmiAccessChain.io.capture.capture) {
skipOpReg := busy
downgradeOpReg := (!busy & nonzeroResp)
stickyBusyReg := busy
stickyNonzeroRespReg := nonzeroResp
}
when (dtmInfoChain.io.update.valid) {
when (dtmInfoChain.io.update.bits.dmireset) {
stickyNonzeroRespReg := Bool(false)
stickyBusyReg := Bool(false)
}
}
// Especially for the first request, we must consider dtmResp.valid,
// so that we don't consider junk in the FIFO to be an error response.
// The current specification says that any non-zero response is an error.
// But there is actually no case in the current design where you SHOULD get an error,
// as we haven't implemented Bus Masters or Serial Ports, which are the only cases errors
// can occur.
nonzeroResp := stickyNonzeroRespReg | (io.dmi.resp.valid & (io.dmi.resp.bits.resp != UInt(0)))
assert(!nonzeroResp, "There is no reason to get a non zero response in the current system.");
assert(!stickyNonzeroRespReg, "There is no reason to have a sticky non zero response in the current system.");
busyResp.addr := UInt(0)
busyResp.resp := Fill(DMIConsts.dmiRespSize, 1.U) // Generalizing busy to 'all-F'
busyResp.data := UInt(0)
dmiResp.addr := dmiReqReg.addr
dmiResp.resp := io.dmi.resp.bits.resp
dmiResp.data := io.dmi.resp.bits.data
nopResp.addr := UInt(0)
nopResp.resp := UInt(0)
nopResp.data := UInt(0)
//--------------------------------------------------------
// Debug Access Chain Implementation
dmiAccessChain.io.capture.bits := Mux(busy, busyResp, Mux(io.dmi.resp.valid, dmiResp, nopResp))
when (dmiAccessChain.io.update.valid) {
skipOpReg := Bool(false)
downgradeOpReg := Bool(false)
}
when (dmiAccessChain.io.capture.capture) {
skipOpReg := busy
downgradeOpReg := (!busy & nonzeroResp)
stickyBusyReg := busy
stickyNonzeroRespReg := nonzeroResp
}
//--------------------------------------------------------
// Drive Ready Valid Interface
2017-04-19 22:56:47 +02:00
val dmiReqValidCheck = Wire(init = Bool(false))
assert(!(dmiReqValidCheck && io.dmi.req.fire()), "Conflicting updates for dmiReqValidReg, should not happen.");
when (dmiAccessChain.io.update.valid) {
when (skipOpReg) {
// Do Nothing
2017-04-19 23:07:21 +02:00
}.elsewhen (downgradeOpReg || (dmiAccessChain.io.update.bits.op === DMIConsts.dmi_OP_NONE)) {
2017-04-19 22:56:47 +02:00
//Do Nothing
dmiReqReg.addr := UInt(0)
dmiReqReg.data := UInt(0)
dmiReqReg.op := UInt(0)
}.otherwise {
dmiReqReg := dmiAccessChain.io.update.bits
dmiReqValidReg := Bool(true)
2017-04-19 22:56:47 +02:00
dmiReqValidCheck := Bool(true)
}
}
2017-04-19 22:56:47 +02:00
when (io.dmi.req.fire()) {
dmiReqValidReg := Bool(false)
}
io.dmi.resp.ready := dmiAccessChain.io.capture.capture
io.dmi.req.valid := dmiReqValidReg
// This is a name-based, not type-based assignment. Do these still work?
io.dmi.req.bits := dmiReqReg
//--------------------------------------------------------
// Actual JTAG TAP
val idcode = Wire(init = new JTAGIdcodeBundle().fromBits(0.U))
idcode.always1 := 1.U
idcode.version := c.idcodeVersion.U
idcode.partNumber := c.idcodePartNum.U
idcode.mfrId := io.jtag_mfr_id
val tapIO = JtagTapGenerator(irLength = 5,
instructions = Map(
dtmJTAGAddrs.DMI_ACCESS -> dmiAccessChain,
dtmJTAGAddrs.DTM_INFO -> dtmInfoChain),
icode = Some(dtmJTAGAddrs.IDCODE)
)
tapIO.idcode.get := idcode
tapIO.jtag <> io.jtag
tapIO.control.jtag_reset := io.jtag_reset
//--------------------------------------------------------
// Reset Generation (this is fed back to us by the instantiating module,
// and is used to reset the debug registers).
io.fsmReset := tapIO.output.reset
}