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
  
        
    
Uh oh!
There was an error while loading. Please reload this page.
-
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&DRscans. These JTAGIR&DRscans are handled inside the JTAG TAP module(JtagTapController). JTAG data registerdmiis used as a proxy that can read from or write to theDMIaddress space of DM. Once thedmijtag data register is selected (by scanning intoIRthedmidata chain index0x11, the value will take effect inUpdate-IR), we then can scan into thedmijtag data register the corresponding value through a series ofShift-DRs. Once the needed value is shifted in, the DTM starts the operation specified inop(field of thedmijtag data register) during jtag stateUpdate-DR: sending translated dmi access request(op== 1 for read fromaddressandop== 2 for writedatatoaddress) through theDMIbus.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
haselandhartselfields in thedmcontroldmi register);The DM receives specific
dmiregister access request from DMI bus; For example, when there is a halt request from debugger to the DM(assertinghaltreqindmcontrol), 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(HALTEDin RC, the simultaneous write toHALTEDby 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 atHALTEDnow is in halting status. The DM can then conduct specific debugging operations on that hart. This is what the dmicommandregister come to play.The
commandcan be used to access internal states of the hart, like GPRs, etc. Once acommandis wrote by debugger(control signalgoAbstractin 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:transferofcommandbeing0means don't conduct the operation specified bywritefield,1means otherwise conduct the operation specified bywrite. So, if0is set, anopwill be put inABSTRACT(0), while if1is set and thewritefield is asserted, the instruction atABSTRACT(0)will beLW(writebeingtruemeans write to register specified byregno, therefore it'a memory load), otherwise it's aSW(Copy data from the specifiedregnoregister intoarg0portion ofdata.), the dmi registerdatais 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 thearg0portion ofdatainto the specified GPR. The content atABSTRACT(1)is decided by thepostexecfield ofcommand, if it's asserted, that means execute the program in the Program Buffer exactly once after performing thetransfer, if any. So, anopwill be inserted atABSTRACT(1)whenpostexecis asserted, the execution flow will slip into the program buffer right next to theABSTRACT(1). Ifpostexecis0, then this means no automatic execution of program buffer, therefore aebreakshould 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 thedataandprogbufdmi registers to correctly configure the intended debugging operation. The specific count ofdataandprogbufregisters can be configured via theprogbufsizeanddatacountfields ofabstractcs.Once
data, andprogbufregisters are correctly configured, write tocommandwill 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 previouscommandis at play:COMMANDWrEnLegal := (ctrlStateReg === CtrlState(Waiting))) , the state will transfer toCheckGenerate, during this state thecommand(that isCOMMANDWrData) is registered intoCOMMANDRegand further legality is checked based onCOMMANDReg(commandRegBadHaltResumeandcommandRegIsUnsupported). An Err will make the state transfer back toWaiting, and set corresponding bits inabstractcs:If legality check passes and the
commandis checked not for accessing custom registers(accessRegIsCustom) atCheckGenerate, then the state will go toExec, a control signalgoAbstractis set at the same time:The assertion of
goAbstractwill 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
goAbstractwill 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
flagsis 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,
s0will be stored inCSR_DSCRATCHso that it can be used by the ROM code, like hold value ofMHARTID, and before jump toWHERETO, thes0needs to be restored to the original value:What stores at
WHERETOis ajal x0 abstractinstruction: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 theGOorder 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
commandcauses the state transfers toExec, and the state will be inExecuntil anebreakis 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 anebreakis 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 specificebreakrelated fields indcsrand current hart priv level. However, if a hart has already been in debug mode(indicating byreg_debug), anebreakwill 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 todebugEntrywhen anebreakis met in debug mode(indicated by thereg_debug), becausetrapToDebugis 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 anothercommandcan be accepted by debug module, therefore the state should be transferred back toWaitingfromExec, 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 anebreakis met in program buffer or inABSTRACT:goReg === false.Bindicates 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 inABSTRACTand the program buffer.hartHaltedWrEnis 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 toHALTEDits hartid acknowledging to debug module), normally anebreakin program buffer orABSTRACTwill fulfil thisgoReg === false.B && hartHaltedWrEn. ConsequentlyhartHaltedWrEnis 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_breakjust means an exception happens in debug mode. If an exception happens in debug mode,reg_debugwill stop that exception from handling as normal exception, instead will alwaystrapToDebugand jump to address ofdebugException.Summing up, the debug module can use
commandscheme 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_debugwill be asserted, and the execution flow will jumps to debug rom (io.evec := tvec):ebreakin program buffer will transfer the execution flow back to the Debug ROM again, and thereg_debugis still asserted, means that theebreakdoes 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
ebreakis 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,progbufandcommand. After finish all intended operations, the debugger can ask the hart to resume execution via writing todmcontrol.resumereq,TLDebugModuleOuterhandlesdmcontrolrelated 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 aresumeReqfor a specific hart is ongoing) for selected harts(the selected harts are represented by bits inhamaskWrSel). Asserted bits inresumeReqRegswill combinationally set the corresponding bits inflagsfor 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_resumesection will write itshartidtoRESUMING, debug module will be aware of this write(viahartResumingWrEnandhartResumingId)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,s0register is saved so that it can be used as debugging purpose, since we are leaving the debug mode now,s0shall 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 otherxrets ,dretis an instruction that only has meaning while in Debug Mode. Its recommended encoding is 0x7b200073. When dret is executed,pcis restored fromdpcand normal execution resumes at the privilege level set byprvindcsr. Also,dretwill deassert thereg_debug, marking the real end of debug mode:Except for the general debugging process depicted above(entering debug mode via
haltreq, writingcommandto 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,resumeanddmresetare 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
TLDebugModuleOuterAsyncandTLDebugModuleInnerAsyncwork under different clock domain). The outer will send thehamask(represent the harts selected byhasel) andhartselto inner, andselectedHartRegin the inner indicates the hart selected byhartsel, andhamaskFullin the inner is a vector of all selected harts includinghartsel, whether or notsupportHartArrayis true. The related code is straightforward as follows:The
hamaskis 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
setresethaltreqandclrresethaltreqfields indmcontrol, refer to the debug spec for detailed depiction. In short words, asserted bit insetresethaltreqindicates that selected harts will enterhaltingmode after snaping out of reset. Thehalt-on-resetconfiguration can only be cleared by assertingclrresethaltreq(Spec says if bothsetresethaltreqandclrresethaltreqare 1, thenclrresethaltreqis executed). Thedmcontrollogic is handled at theTLDebugModuleOuter, there is ahrmaskRegin Outer that indicates whether a specific hart shouldhatl on reset, the logic is pretty staightforward:The
hrmaskwill be sent to the inner part for further handling via theinnerCtrlbundle along with other signals that's needed by the inner part:The inner part will use
hrmaskto decide whether a reset will cause the specific hart halting: if specific bit inhrmaskis set, and that hart is undergoing a reset(indicated by thehartIsInResetSync) , corresponding bit inhrDebugIntRegwill 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
hrDebugIntRegwill keep asserting until the halt request (initiated by reset) has been acked by hart:(hrDebugIntReg & ~(haltedBitRegs.asBools));hrDebugIntis used to interrupt the hart, make it trap to debug mode:io.hgDebugInt := hgDebugInt | hrDebugIntIn terms of general
resetrelated logic, refer to 3.2 of debug spec for detailed info; Specifically, the debug module have 2 methods to reset harts:ndmresetresets 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
hartresetindmcontrl, this will assert the corresponding bit inio.hartResetReqofTLDebugModuleOuterfor selected harts, this signal will propagate through the debug module into the selected harts.Note code above that
hartResetReqis for reset request initiated by debug module(the outer part specifically,dmcontrlis handled in the outer part), whilehartIsInResetcomes from the platform into the debug module indicating that a hart's reset signal has been asserted:Loc above indicates that individual bits inside
hartIsInResetthat corresponds to harts in the system are all drived by the system level reset signalrinstead of individual hartreset. Also note thathartIsInResetactually 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.hartIsInResetwill cause corresponding bit inhaveResetBitRegs(inner) being asserted.haveResetBitRegsis used by debug module to notify the reset staus of controlled harts:The debugger can write to
ackhaveresetofdmcontrol, indicating the reset of selected harts has been acked by debugger, therefore the corresponding bits inhaveResetBitRegsshould be cleared:One thing that confuses me is that
hartResetReqdoes 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 inhaltedBitRegswhich 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, thecauseindcsrwill 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
groupand other fields indmcs2and firing detection of a specific group.The group configuration of a hart is specified by writing to
groupfield 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 thehgwriteto indicate that this is a group configuration, instead of merely a group obtain(read) from debugger(The value written togroupfield is ignored unlesshgwriteis also written 1). Fieldhgselectaffects whether this is for configuring the harts(hgselectbeing 0) or external trigger(hgselectbeing 1); If the configuration is for external trigger(hgselectbeing 1), only group of external trigger indicated indmexttriggerwill be configured, while group configuration for harts(hgselectbeing 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, whenhgwriteis deasserted), the group obtained by readgroupfield will be group of thehartsel(DMCS2RdData.haltgroup := hgParticipateHart(selectedHartReg)) whenhgselectbeing 0 and group of external trigger(DMCS2RdData.haltgroup := hgParticipateTrig(hgExtTrigger))dmexttriggerwhenhgselectbeing 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 signalhgFiredindicates 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'shgFiredindicator, marking end of one round halt group firing.When bit in
hgFiredfor a specific group is set, we need to send halt request to all the harts in that group, even including the hart that causedhgFiredto 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
hgDebugIntis 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 nodeintnodein outer, and therefore send interrupt request for each hart. Note thatintnodein 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
1tonHaltGroupsofhgHartFiring,hgTrigFiringandhgFired.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.UandABSTRACTCSReset.progbufsize := cfg.nProgramBufferWords.U),busyfield indicates whether a command is executing (abstractCommandBusy := (ctrlStateReg =/= CtrlState(Waiting))), thecmderris 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
autoexecprogbuforautoexecdatais set, access to the corresponding word in program buffer or data section will cause the DM to act as if the current value incommandwas written there again . Therefore, theABSTRACTand possible instruction in program buffer will execute again using the new value in data section or program buffer. The code sequence is as follows:autoexecindicates a word in program buffer or data section that corresponds the set bits inautoexecprogbuforautoexecdatais 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
commanditself in RC can now only access registers(quick access and access memory command is not supported). However, RCcommandnot 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 viadebugCustomXbarOptinHasPeripheryDebug. The bundle behind this diplomatic binding is as follows:When an abstract
commandintends to conduct a custom register read(indicated byaccessRegIsCustom--just checks whether theregnois in the range ofcustomP.addrs; Also note thataddrandvalidas output request from debug module, anddataandreadyas response from the platform), the state machine will transfer toCustomfromCheckGenerate, in stateCustomwiregoCustomwill 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
Waitingwhen 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
TLDebugModuleInnerAsyncandTLDebugModuleOuterAsync,TLDebugModuleInnerAsyncandTLDebugModuleOuterAsyncworks at different clock domains.TLDebugModuleOuterAsyncworks at thedmiClockdomain, that is theTCKfed into the DM fromSimJTAG.TLDebugModuleOuterAsyncexists so that some debugging operation can be conducted even when the hart is not ready, This allowsDMCONTROL.haltreq,hartsel,hasel,hawindowsel,hawindow,dmactive, andndresetto 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
dmiregister 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
dmi2tlOptutility insideTLDebugModuleOuterAyncconverting dmi request to TL-UL to take advantage ofTLRegisterNodefunctionality(TLRegisterNodeis intensively used in RC, refer to chipyard documentation 2.9.4 Register Router for further detail),dmi2tlOptgoes to a crossbar(dmiXbar) diplomatically, (node of)dmiXbarwill flow downwards to 2 other nodes: 1, it will go to thedmiNodeinside 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_HAWINDOWare encapsulated into thisTLRegisterNode; 2,dmiXbarinTLDebugModuleOuterAyncwill 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.nodein 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 | ~dmactiveAckwheredmactiveAckmeans thedmactivehas 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
TLRegisterNodetlNodefor this in the inner part. ThetlNodeis 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