Code Anatomy on Debug Module #3024
DecodeTheEncoded
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Debugging is a core concept in terms of soc design. RISC-V debug spec has supports for two forms of debugging. External debugging and native debugging.
External debugging is that the internal state of a hardware platform could be obtained or modified by an external debugger. You can read the Chapter 2 System Overview of RISC-V debug spec to obtain a holistic understanding of RISC-V debugging system. Specifically, a high level debugging intent is translated to a sequence of JTAG
IR
&DR
scans. These JTAGIR
&DR
scans are handled inside the JTAG TAP module(JtagTapController
). JTAG data registerdmi
is used as a proxy that can read from or write to theDMI
address space of DM. Once thedmi
jtag data register is selected (by scanning intoIR
thedmi
data chain index0x11
, the value will take effect inUpdate-IR
), we then can scan into thedmi
jtag data register the corresponding value through a series ofShift-DR
s. Once the needed value is shifted in, the DTM starts the operation specified inop
(field of thedmi
jtag data register) during jtag stateUpdate-DR
: sending translated dmi access request(op
== 1 for read fromaddress
andop
== 2 for writedata
toaddress
) through theDMI
bus.The DTM is connected with the DM via DMI bus, DM(debug module) is where magic of external debugging happens. One DM manages a bunch of harts in terms of external debugging(normally, there is only one DM in charging of all the harts in a hardware platform; And specific debugging operation can be applied to harts that are selected by hart array mask scheme
hasel
andhartsel
fields in thedmcontrol
dmi register);The DM receives specific
dmi
register access request from DMI bus; For example, when there is a halt request from debugger to the DM(assertinghaltreq
indmcontrol
), DM will fire an interrupt to all the selected harts. Consequently the hart's execution flow will then jump to debug rom serviced by the debug module(trapToDebug
), the hart then acknowledges its halt status to Debug Module by writing its hartid to a specific address(HALTED
in RC, the simultaneous write toHALTED
by multiple harts is serialized by the tilelink protocol), and waits for subsequent orders from debug module in a loop, DM is aware of value changes atHALTED
(hartHaltedWrEn
), and therefore knows the hart indicated by hartid stored atHALTED
now is in halting status. The DM can then conduct specific debugging operations on that hart. This is what the dmicommand
register come to play.The
command
can be used to access internal states of the hart, like GPRs, etc. Once acommand
is wrote by debugger(control signalgoAbstract
in RC impl is asserted), DM will construct some specific instructions at the address ofABSTRACT(0)
(abstractGeneratedMem(0)
) andABSTRACT(1)
(abstractGeneratedMem(1)
) based on the detailed configuration of acommand
, for example:transfer
ofcommand
being0
means don't conduct the operation specified bywrite
field,1
means otherwise conduct the operation specified bywrite
. So, if0
is set, anop
will be put inABSTRACT(0)
, while if1
is set and thewrite
field is asserted, the instruction atABSTRACT(0)
will beLW
(write
beingtrue
means write to register specified byregno
, therefore it'a memory load), otherwise it's aSW
(Copy data from the specifiedregno
register intoarg0
portion ofdata
.), the dmi registerdata
is the MR(message register), it's used for exchanging values between the hart being debugged and the debug module, for example, the LW(if any) instruction inABSTRACT(0)
means copy data from thearg0
portion ofdata
into the specified GPR. The content atABSTRACT(1)
is decided by thepostexec
field ofcommand
, if it's asserted, that means execute the program in the Program Buffer exactly once after performing thetransfer
, if any. So, anop
will be inserted atABSTRACT(1)
whenpostexec
is asserted, the execution flow will slip into the program buffer right next to theABSTRACT(1)
. Ifpostexec
is0
, then this means no automatic execution of program buffer, therefore aebreak
should be inserted here, marking end of abstract command execution. It's worth noting that program buffer is a memory region right next toABSTRACT(1)
, the debugger could put some instructions there to perform specific debugging operations. The program buffer scheme can be used to access CSRs and addresses buses besides GPRs. Before writing tocommand
, the debugger should write appropriate data and instructions to thedata
andprogbuf
dmi registers to correctly configure the intended debugging operation. The specific count ofdata
andprogbuf
registers can be configured via theprogbufsize
anddatacount
fields ofabstractcs
.Once
data
, andprogbuf
registers are correctly configured, write tocommand
will mark the beginning of a specific debugging operation, the RC impl has a state machine that handles write ofcommand
: when a supportedcommand
(val commandWrIsUnsupported = COMMANDWrEn && !commandWrIsAccessRegister
)write comes at right time (without a previouscommand
is at play:COMMANDWrEnLegal := (ctrlStateReg === CtrlState(Waiting))
) , the state will transfer toCheckGenerate
, during this state thecommand
(that isCOMMANDWrData
) is registered intoCOMMANDReg
and further legality is checked based onCOMMANDReg
(commandRegBadHaltResume
andcommandRegIsUnsupported
). An Err will make the state transfer back toWaiting
, and set corresponding bits inabstractcs
:If legality check passes and the
command
is checked not for accessing custom registers(accessRegIsCustom
) atCheckGenerate
, then the state will go toExec
, a control signalgoAbstract
is set at the same time:The assertion of
goAbstract
will drive 2 process:Abstract(0)
andAbstract(1)
For now, just concern the case that debug module is at
0x00
, see this PR.The assertion of
goAbstract
will assert thegoReg
(val goReg = Reg(Bool())
), this register is used to set the per-hart flag to indicate to hart that debug module has prepared the data and program needed to perform debugging operation, therefore it's time for the hart to go:Note that
flags
is a vec of wire that is memory mapped to the address space of all harts in system:Now let's check out logic of hart side, after the hart halts, the hart is executing in a loop, and waiting for the Debug Module for further operation, and once it sees the change in the
FLAGS[hartid].go
, the pc will jump to addressWHERETO
:It's worth noting that right after the hart enters debug ROM code,
s0
will be stored inCSR_DSCRATCH
so that it can be used by the ROM code, like hold value ofMHARTID
, and before jump toWHERETO
, thes0
needs to be restored to the original value:What stores at
WHERETO
is ajal x0 abstract
instruction:Also Note that before the hart pc jumps to
WHERETO
, and begins to conduct real debugging operations there, hart executes thesw zero, GOING(zero)
, this acknowledges that hart has already accepted theGO
order from debug module, and once the debug module sees this write(indicated byhartGoingWrEn
) by hart, it wll deassert thegoReg
, therefore combinationally deassert theFLAGS[selected].go
:Note that write to
command
causes the state transfers toExec
, and the state will be inExec
until anebreak
is met in program buffer orABSTRACT
; According to the spec, The EBREAK instruction is used by debuggers to cause control to be transferred back to a debugging environment. It generates a breakpoint exception and performs no other operation. That is to say, when anebreak
is met, it will generate a breakpoint exception, and traps to M-mode(or delegate to lower priv level according to the configuration ofmedeleg
). Or, the hart may enter debug mode depending on the specificebreak
related fields indcsr
and current hart priv level. However, if a hart has already been in debug mode(indicating byreg_debug
), anebreak
will always trap to the beginning of the debug ROM. See code section below:The code above is inside the CSR.scala, for handling the debug related interrupt and exception. We can see that the execution flow(
io.evec
) will jump todebugEntry
when anebreak
is met in debug mode(indicated by thereg_debug
), becausetrapToDebug
is always asserted indebug mode
:val trapToDebug = Bool(usingDebug) && (reg_singleStepped || causeIsDebugInt || causeIsDebugTrigger || causeIsDebugBreak || reg_debug)
, note thereg_debug
. This also indicates that if we met some unexpected scenario in debug mode, the debug mode will always be re-trapped, instead of trapping into other priv levels.After the program buffer executes
ebreak
, that means anothercommand
can be accepted by debug module, therefore the state should be transferred back toWaiting
fromExec
, this is guaranteed by the following code:Note code above that in the state of
Exec
, whengoReg === false.B && hartHaltedWrEn && (hartSelFuncs.hartIdToHartSel(hartHaltedId) === selectedHartReg)
, this normally means anebreak
is met in program buffer or inABSTRACT
:goReg === false.B
indicates that the hart has acknowledged the "GO" order from DM, then hart will jump toWEHRETO
, beginning executing the debugging operation represented by the instructions generated inABSTRACT
and the program buffer.hartHaltedWrEn
is asserted by writes toHALTED
, this happens when the hart is re-entered into the Debug Rom(remember that right after entering the debug rom, hart will write toHALTED
its hartid acknowledging to debug module), normally anebreak
in program buffer orABSTRACT
will fulfil thisgoReg === false.B && hartHaltedWrEn
. ConsequentlyhartHaltedWrEn
is overloaded to mean 'got an ebreak' in this scenario;Also note that when an exception happens during the execution of program buffer, then the execution flow will jump to a specific location inside the debug module:
Note the code above that the
reg_debug & !insn_break
just means an exception happens in debug mode. If an exception happens in debug mode,reg_debug
will stop that exception from handling as normal exception, instead will alwaystrapToDebug
and jump to address ofdebugException
.Summing up, the debug module can use
command
scheme depicted above to execute instructions generated by debug module to conduct specific high level debugging intents. I previously have confusion on what marks begin and end of debug mode, now it's clear: thereg_debug
, when the DM first fires a debug interrupt due to request from debugger, thereg_debug
will be asserted, and the execution flow will jumps to debug rom (io.evec := tvec
):ebreak
in program buffer will transfer the execution flow back to the Debug ROM again, and thereg_debug
is still asserted, means that theebreak
does not end debug mode. Instead, the debug mode is ended via explicit request from debugger by asserting the resume request indmcontrol
:resumereq
(sending resume request to all selected harts);Once an
ebreak
is met when executing acommand
, the hart pc will jump to ROM code, and then waits in loop for further order from the debug module, the debugger can then execute another command by writing todata
,progbuf
andcommand
. After finish all intended operations, the debugger can ask the hart to resume execution via writing todmcontrol.resumereq
,TLDebugModuleOuter
handlesdmcontrol
related logic and feed the resume request signal intoTLDebugModuleInner
(val resumereq = io.innerCtrl.fire() && io.innerCtrl.bits.resumereq
), this will set corresponding bits inresumeReqRegs
(indicating that aresumeReq
for a specific hart is ongoing) for selected harts(the selected harts are represented by bits inhamaskWrSel
). Asserted bits inresumeReqRegs
will combinationally set the corresponding bits inflags
for selected harts. Hart now is executing in the ROM loop, and will be aware this flags value change, and therefore will jump to_resume
, code inside the_resume
section will write itshartid
toRESUMING
, debug module will be aware of this write(viahartResumingWrEn
andhartResumingId
)and will clear the related bits inhaltedBitRegs
(meaning the select hart will snap out of halt mode), also the dm will clear the corresponding bits inresumeReqRegs
(meaning the resume request has been acked by hart.) The code is as follows:After
sw s0, RESUMING(zero)
, the hart will continue executing; Note that when entering the debug mode,s0
register is saved so that it can be used as debugging purpose, since we are leaving the debug mode now,s0
shall be restored:csrr s0, CSR_DSCRATCH
(the value is saved in thedscratch
).After doing all above, the hart will execute one last instruction in debug mode:
dret
, like otherxret
s ,dret
is an instruction that only has meaning while in Debug Mode. Its recommended encoding is 0x7b200073. When dret is executed,pc
is restored fromdpc
and normal execution resumes at the privilege level set byprv
indcsr
. Also,dret
will deassert thereg_debug
, marking the real end of debug mode:Except for the general debugging process depicted above(entering debug mode via
haltreq
, writingcommand
to conduct specific debugging operation, then exiting the debug mode viaresumereq
), there are extra details need to clarify in terms of documentation completeness, I will clarify them in detail as follows:A large portion of debugging operations are applied to selected harts, refer to setcion3.3 of the riscv debug spec for detailed info on how to select a single or multiple harts. Note that even though the debugging operations like
halt
,resume
anddmreset
are applied to all selected harts, execution of Abstract Commands ignores this mechanism and only applies to the hart selected byhartsel
.The logic for selecting harts are handled in the Outer, so that specific harts can be selected even though the hart is not running(the
TLDebugModuleOuterAsync
andTLDebugModuleInnerAsync
work under different clock domain). The outer will send thehamask
(represent the harts selected byhasel
) andhartsel
to inner, andselectedHartReg
in the inner indicates the hart selected byhartsel
, andhamaskFull
in the inner is a vector of all selected harts includinghartsel
, whether or notsupportHartArray
is true. The related code is straightforward as follows:The
hamask
is constructed in outer by writing tohasel
(enable the hart array mask functionality),hawindowsel
(select a specific window to set up), andhawindow
(set up a sepcific window, write 1 to each bit means that hart is selected)The functionality of halt on reset is configured by
setresethaltreq
andclrresethaltreq
fields indmcontrol
, refer to the debug spec for detailed depiction. In short words, asserted bit insetresethaltreq
indicates that selected harts will enterhalting
mode after snaping out of reset. Thehalt-on-reset
configuration can only be cleared by assertingclrresethaltreq
(Spec says if bothsetresethaltreq
andclrresethaltreq
are 1, thenclrresethaltreq
is executed). Thedmcontrol
logic is handled at theTLDebugModuleOuter
, there is ahrmaskReg
in Outer that indicates whether a specific hart shouldhatl on reset
, the logic is pretty staightforward:The
hrmask
will be sent to the inner part for further handling via theinnerCtrl
bundle along with other signals that's needed by the inner part:The inner part will use
hrmask
to decide whether a reset will cause the specific hart halting: if specific bit inhrmask
is set, and that hart is undergoing a reset(indicated by thehartIsInResetSync
) , corresponding bit inhrDebugIntReg
will be set, indicating that the hart should halt immdeidately on the next deassertion of its reset. I still have trouble figuring out the sync and async reset stuff:Note that individual signal in
hrDebugIntReg
will keep asserting until the halt request (initiated by reset) has been acked by hart:(hrDebugIntReg & ~(haltedBitRegs.asBools))
;hrDebugInt
is used to interrupt the hart, make it trap to debug mode:io.hgDebugInt := hgDebugInt | hrDebugInt
In terms of general
reset
related logic, refer to 3.2 of debug spec for detailed info; Specifically, the debug module have 2 methods to reset harts:ndmreset
resets all the harts in the hardware platform, as well as all other parts of the hardware platform except for the Debug Modules, Debug Transport Modules, and Debug Module Interface:Also, the debug module can reset selected harts specfically by asserting the
hartreset
indmcontrl
, this will assert the corresponding bit inio.hartResetReq
ofTLDebugModuleOuter
for selected harts, this signal will propagate through the debug module into the selected harts.Note code above that
hartResetReq
is for reset request initiated by debug module(the outer part specifically,dmcontrl
is handled in the outer part), whilehartIsInReset
comes from the platform into the debug module indicating that a hart's reset signal has been asserted:Loc above indicates that individual bits inside
hartIsInReset
that corresponds to harts in the system are all drived by the system level reset signalr
instead of individual hartreset
. Also note thathartIsInReset
actually goes into the inner part:hartIsInResetSync(component) := AsyncResetSynchronizerShiftReg(io.hartIsInReset(component), 3, Some(s"debug_hartReset_$component"))
, I thought there is no need to add cross domain facility here because the inner part of the DTM and the platform may work under same clock domain, I need more knowledge of domain crossing stuff to make this clear.hartIsInReset
will cause corresponding bit inhaveResetBitRegs
(inner) being asserted.haveResetBitRegs
is used by debug module to notify the reset staus of controlled harts:The debugger can write to
ackhavereset
ofdmcontrol
, indicating the reset of selected harts has been acked by debugger, therefore the corresponding bits inhaveResetBitRegs
should be cleared:One thing that confuses me is that
hartResetReq
does not actually drive the reset signal of selected harts. Maybe I need to confirm this;The Halt Group utility is useful when you want to halt a group of related harts simultaneously. In RISCV debug spec, resume group is also defined, only the halt group is supported however in RC impl. Refer to the section 3.6 for further introduction on halt&resume group.
In terms of halt group, each hart has an group number(stored inside
hgParticipateHart
), when any hart inside the same halt group halts(indicate by bits inhaltedBitRegs
which means the halting request for that hart has been acked by the hart itself), or any external triggers(the group number of each trigger is stored inhgParticipateTrig
) which belongs to that group is firing(val trigInReq = ResetSynchronizerShiftReg(in=extTriggerInReq, sync=3, name=Some("dm_extTriggerInReqSync"))
), the hart will halt and trap to debug mode, thecause
indcsr
will be marked as 6(6 means halt group--However, the RC impl only reports 3 in this scenario);According to the spec, external triggers are abstract concepts that can signal the DM and/or receive signals from the DM. External triggers could be used to implement near simultaneous halting/resuming of all cores in a hardware platform, when not all cores are RISC-V cores. The corresponding signal in terms of external triggers is as follows, they are directly sent to(from) inner from(to) platforms instead of outer:
The logic needed to handle halt groups can be divided into 2 parts: configuration of
group
and other fields indmcs2
and firing detection of a specific group.The group configuration of a hart is specified by writing to
group
field group number(starting from 0, and increase continuously, max value is impl dependent) the hart(or the external trigger) should be placed into, and assert thehgwrite
to indicate that this is a group configuration, instead of merely a group obtain(read) from debugger(The value written togroup
field is ignored unlesshgwrite
is also written 1). Fieldhgselect
affects whether this is for configuring the harts(hgselect
being 0) or external trigger(hgselect
being 1); If the configuration is for external trigger(hgselect
being 1), only group of external trigger indicated indmexttrigger
will be configured, while group configuration for harts(hgselect
being 0) will affect the group of all selected harts(not justhartsel
). It's worth noting that when the debugger just wants to obtain group instead of setting it(that is, whenhgwrite
is deasserted), the group obtained by readgroup
field will be group of thehartsel
(DMCS2RdData.haltgroup := hgParticipateHart(selectedHartReg)
) whenhgselect
being 0 and group of external trigger(DMCS2RdData.haltgroup := hgParticipateTrig(hgExtTrigger)
)dmexttrigger
whenhgselect
being 1; The corresponding code is as follows:In terms of group firing detetcion, we need to detect both the halting of harts (
hgHartFiring
) and the firing of external triggers(hgTrigFiring
) in one specific group, specific bit in signalhgFired
indicates that a specific halt group is firing, meaning that a hart in that group has(the way we say a hart is halted is that the halting request from debug module has been acked by the hart) halted(hgHartFiring(hg) := hartHaltedWrEn & ~haltedBitRegs(hartHaltedId) & (hgParticipateHart(hartSelFuncs.hartIdToHartSel(hartHaltedId)) === hg.U)
) or an external trigger in the same group is firing(hgTrigFiring(hg) := (trigInReq & ~RegNext(trigInReq) & hgParticipateTrig.map(_ === hg.U)).reduce(_ | _)
):One thing to note about code above is that when all harts halted (
hgHartsAllHalted(hg) := (haltedBitRegs.asBools | hgParticipateHart.map(_ =/= hg.U)).reduce(_ & _)
) and all external triggers's firing has been acked by the platform(hgTrigsAllAcked(hg) := (trigOutAck | hgParticipateTrig.map(_ =/= hg.U)).reduce(_ & _)
), we need to clear that specific halt group'shgFired
indicator, marking end of one round halt group firing.When bit in
hgFired
for a specific group is set, we need to send halt request to all the harts in that group, even including the hart that causedhgFired
to be set, sending debug interrupt again to a hart when it's in debug mode(reg_debug
) will be masked:We also need to notify all the external triggers in that fired(
hgFired
) group the firing action:As a summary, note the circumstances that may cause a hart to halt: 1,when a hart's halt group fired; 2, a hart just snaps out of reset, and halt-on-reset fuctionality takes effect on that hart; 3,an explicit halt request by debugger; 1 and 2 are handled in inner, and
hgDebugInt
is sent to outer(I have trouble understanding why there is no crossing domain logic needed for this signal???),io.hgDebugInt := hgDebugInt | hrDebugInt
, this signal along with 3(explicit hart request) will drive bundle of diplomatic nodeintnode
in outer, and therefore send interrupt request for each hart. Note thatintnode
in outerAsync is the corss-domain version of that node in outerOne last thing to note in terms of halt group is that the group 0 is specical, according to the spec: In both halt and resume groups, group 0 is special. Harts in group 0 halt/resume as if groups aren’t implemented at all.When the DM is reset, all harts must be placed in the lowest-numbered halt and resume groups that they can be in. (This will usually be group 0.) In RC impl, all harts and external triggers are in group 0 right after snap out of reset. Consequently, we only set range
1
tonHaltGroups
ofhgHartFiring
,hgTrigFiring
andhgFired
.cmderr
)This DMI register contains info about size of program buffer and data section in word unit of the debug module(
ABSTRACTCSReset.datacount := cfg.nAbstractDataWords.U
andABSTRACTCSReset.progbufsize := cfg.nProgramBufferWords.U
),busy
field indicates whether a command is executing (abstractCommandBusy := (ctrlStateReg =/= CtrlState(Waiting))
), thecmderr
is used for recording the executing status of an abstract command, it will get set if an abstract command fails, it's worth noting that this field is W1C(writing 1 to clear). Refer to the 3.15.6 of debug spec for detailed depiction. The related code is as follows:This register is a useful proxy for executing brust access, refer to 3.15.8 for further detial. In short words, when one bit in
autoexecprogbuf
orautoexecdata
is set, access to the corresponding word in program buffer or data section will cause the DM to act as if the current value incommand
was written there again . Therefore, theABSTRACT
and possible instruction in program buffer will execute again using the new value in data section or program buffer. The code sequence is as follows:autoexec
indicates a word in program buffer or data section that corresponds the set bits inautoexecprogbuf
orautoexecdata
is accessed through dmi bus(it's worth noting that the program buffer and data section also are memory-mapped in the hart's address space, but here only access from dmi bus is considered.dmiAbstractDataAccessVec := (dmiAbstractDataWrEnMaybe zip dmiAbstractDataRdEn).map{ case (r,w) => r | w}
anddmiProgramBufferAccessVec := (dmiProgramBufferWrEnMaybe zip dmiProgramBufferRdEn).map{ case (r,w) => r | w}
); This will assertval regAccessRegisterCommand = autoexec && commandRegIsAccessRegister && (ABSTRACTCSReg.cmderr === 0.U)
if no previous err are set, and therefore starts another round of command execution(transfer the state machine toExec
);The
command
itself in RC can now only access registers(quick access and access memory command is not supported). However, RCcommand
not only support general purpose register access, it can also support custom register access. Diplomatic sink nodeval customNode = new DebugCustomSink()
in TLDebugModuleInner is used for this purpose. Some implementation dependent node can connect to this viadebugCustomXbarOpt
inHasPeripheryDebug
. The bundle behind this diplomatic binding is as follows:When an abstract
command
intends to conduct a custom register read(indicated byaccessRegIsCustom
--just checks whether theregno
is in the range ofcustomP.addrs
; Also note thataddr
andvalid
as output request from debug module, anddata
andready
as response from the platform), the state machine will transfer toCustom
fromCheckGenerate
, in stateCustom
wiregoCustom
will be asserted, and this will send a custom regiser access request to the the platform:When the response is ready with valid data, the dmi data register will be updated:
Also, the state machine should transfer back to
Waiting
when the custom bundle fires during the state ofCustom
, marking end of a custom register access:For now, consider the debugger connects to DM via JTAG DTM;
Inside the Debug Module, there are two sub-components
TLDebugModuleInnerAsync
andTLDebugModuleOuterAsync
,TLDebugModuleInnerAsync
andTLDebugModuleOuterAsync
works at different clock domains.TLDebugModuleOuterAsync
works at thedmiClock
domain, that is theTCK
fed into the DM fromSimJTAG
.TLDebugModuleOuterAsync
exists so that some debugging operation can be conducted even when the hart is not ready, This allowsDMCONTROL.haltreq
,hartsel
,hasel
,hawindowsel
,hawindow
,dmactive
, andndreset
to be modified even while the Core is in reset or not being clocked. Therefore this Outer part is reasonable to function under the outer clock: that isdmiClock
;The inner part is for handling other dmi register except the ones in outer part, these
dmi
register is only effective once the harts being debugged snap out of reset and are normally clocked; Moreover, some signal inside the inner part is also memory mapped into the hart's memory space so that the hart could be aware of the debugger request(like FLAGS[hartid].go); Therefore the inner part works at the clock domain oftl_clock
, coming from the bus where debug module is connected as a slave, and hart is the master. (There are actually 2 clock domains for the inner part, one is debug_clock, the other is tl_clock, I have trouble figuring out the connection between the two, see this question I posted on RC disscussions which is not being answered yet, but orignal comment somewhere in RC says that these 2 clock domains are synced)The original DMI request comes out of DTM, then feeds into the outer part, there is a
dmi2tlOpt
utility insideTLDebugModuleOuterAync
converting dmi request to TL-UL to take advantage ofTLRegisterNode
functionality(TLRegisterNode
is intensively used in RC, refer to chipyard documentation 2.9.4 Register Router for further detail),dmi2tlOpt
goes to a crossbar(dmiXbar
) diplomatically, (node of)dmiXbar
will flow downwards to 2 other nodes: 1, it will go to thedmiNode
inside synced version of outer(it's worth noting that both the inner and outer has an async wrapper that handles the clock domain crossing stuff, for example, shift-register an async signal and then feed that signal into the synced version), dmi registers likeDMI_DMCONTROL
,DMI_HARTINFO
,DMI_HAWINDOWSEL
,DMI_HAWINDOW
are encapsulated into thisTLRegisterNode
; 2,dmiXbar
inTLDebugModuleOuterAync
will cross clock domain, and go to the inner part(val dmiInnerNode = TLAsyncCrossingSource() := dmiBypass.node := dmiXbar.node
), all other dmi registers are encapulated insideval dmiNode = dmiXing.node
in the synced inner. Other non-diplomatic signals that go from outer(innerCtrl
,dmactive
, etc. ) to inner or vice versa(hgDebugInt
) needs to be wrapped with clock domain utilities. As mentioned above that the rationale for seperating outer from inner is because some functionality of the debug module needs to take effect even though the hart is not normally clocked, consequently the request to these inner dmi registers by debugger will be bypassed under this condition: (dmiBypass.module.io.bypass := ~io.ctrl.dmactive | ~dmactiveAck
wheredmactiveAck
means thedmactive
has been acked by harts, indicating the system has been normally clocked). The code base corresponding to these logic is staightfoward, see individual trait or classes for details.Also note that as mentioned above the inner part of debug module can be accessed by hart, therefore there is a
TLRegisterNode
tlNode
for this in the inner part. ThetlNode
is slave of tilelink bus where the hart masters :debug.node := tlbus.coupleTo("debug"){ TLFragmenter(tlbus) := _ }
. See structure chart about some key connections among different modulesBeta Was this translation helpful? Give feedback.
All reactions