Simulation Triggers
It is often useful to globally coordinate debug and instrumentation features using specific target-events that may be distributed across the target design.
For instance, you may wish to enable collection of synthesized prints and sampling of
AutoCounters simulataenously when a specific instruction is committed on any
core, or alternatively if the memory system sees a write to a
particular memory address. Golden Gate’s trigger system enables this by aggregating annotated TriggerSources
distributed throughout the design using a centralized credit-based system
which then drives a single-bit level-sensitive enable to all TriggerSinks
distributed throughout the design.
This enable signal is asserted while the design remains in the region-of-interest
(ROI). Sources signal the start of the ROI by granting a
credit and signal the end of the ROI by asserting a debit. Since there can be
multiple sources, each of which might grant credits, the trigger is only
disabled when the system has been debited as exactly as many times as it has
been credited (it has a balance of 0).
Quick-Start Guide
Level-Sensitive Trigger Source
Assert the trigger while some boolean enable
is true.
import midas.targetutils.TriggerSource
TriggerSource.levelSensitiveEnable(enable)
- Caveats:
The trigger visible at the sink is delayed. See Trigger Timing.
Assumes this is the only source; the trigger is only cleared if no additional credit has been granted.
Distributed, Edge-Sensitive Trigger Source
Assert trigger enable when some boolean start undergoes a positive transition, and clear the trigger when a second signal stop undergoes a positive transition.
// Some arbitarily logic to drive the credit source and sink. Replace with your own!
val start = lfsr(1)
val stop = ShiftRegister(lfsr(0), 5)
// Now annotate the signals.
import midas.targetutils.TriggerSource
TriggerSource.credit(start)
TriggerSource.debit(stop)
// Note one could alternatively write: TriggerSource(start, stop)
- Caveats:
The trigger visible at the sink is delayed. See Trigger Timing.
Assumes these are the only sources; the trigger is only cleared if no additional credit has been granted.
Chisel API
Trigger sources and sinks are Boolean signals, synchronous to a particular
clock domain, that have been annotated as such. The midas.targetutils
package provides chisel-facing utilities for annotating these signals in your
design. We describe these utilities below, the source for which can be found in
sim/midas/targetutils/src/main/scala/midas/annotations.scala.
Trigger Sources
In order to permit distributing trigger sources across the whole design, you
must annotate distinct boolean signals as credits and debits using methods
provided by the TriggerSource
object. We provide an example below (the distributed example from the quick-start guide).
// Some arbitarily logic to drive the credit source and sink. Replace with your own!
val start = lfsr(1)
val stop = ShiftRegister(lfsr(0), 5)
// Now annotate the signals.
import midas.targetutils.TriggerSource
TriggerSource.credit(start)
TriggerSource.debit(stop)
// Note one could alternatively write: TriggerSource(start, stop)
Using the methods above, credits and debits issued while
the design is under reset are not counted (the reset used is implicit
reset of the Chisel Module in which you invoked the method).
If the module provides no implicit reset or if you wish to credit or debit the trigger system while the
local module’s implicit reset is asserted, use TriggerSource.{creditEvenUnderReset, debitEvenUnderReset}
instead.
Trigger Sinks
Like sources, trigger sinks are boolean signals that have been annotated
alongside their associated clock. These signals will be driven by a Boolean
value created by the trigger system. If trigger sources exist in your design, the
generated trigger will override all assignments made in the chisel to the same
signal, otherwise, it will take on a default value provided by the user. We
provide an example of annotating a sink using the the TriggerSink
object below.
// Note: this can be any reference you wish to have driven by the trigger.
val sinkBool = WireDefault(true.B)
import midas.targetutils.TriggerSink
// Drives true.B if no TriggerSource credits exist in the design.
// Note: noSourceDefault defaults to true.B if unset, and can be omitted for brevity
TriggerSink(sinkBool, noSourceDefault = true.B)
Alternatively, if you wish to use a trigger sink as a predicate for a Chisel
when
block, you may use TriggerSink.whenEnabled
instead
/** A simpler means for predicating stateful updates, printfs, and assertions.
* Sugar for:
* val sinkEnable = Wire(Bool())
* TriggerSink(sinkEnable, false.B)
* when (sinkEnable) { <...> }
*/
TriggerSink.whenEnabled(noSourceDefault = false.B) {
SynthesizePrintf(printf(s"${printfPrefix}CYCLE: %d\n", cycle))
}
Trigger Timing
Golden Gate implements the trigger system by generating a target circuit that synchronizes all credit and debits into the base clock domain using a single register stage, before doing a global accounting. If the total number of credits exceeds debits the trigger is asserted. This trigger is then synchronized in each sink domain using a single register stage before driving the annotated sink. The circuit that implements this functionality is depicted below:
Given the present implementation, an enabled trigger becomes visible in a sink domain no sooner than one base-clock edge and one local-clock edge have elapsed, in that order, after the credit was asserted. This is depicted in the waveform below.
Note that trigger sources and sinks that reside in the base clock domain still have the additional synchronization registers even though they are uneeded. Thus, a credit issued by a source in the base clock domain will be visible to a sink also in the base clock domain exactly 2 cycles after it was issued.
Bridges that use the default HostPort
interface add an additional cycle of
latency in the bridge’s local domain since their token channels model a single
register stage to improve simulation FMR. Thus, without using a different
HostPort
implementation, trigger sources generated by a Bridge and trigger
sinks that feed into a Bridge will each see one additional bridge-local cycle
of latency. In constrast, synthesized printfs and assertions, and AutoCounters
all use wire channels (since they are unidirectional interfaces, the extra
register stage is not required to improve FMR) and will see no additional sink
latency.
Limitations & Pitfalls
The system is limited to no more than one trigger signal. Presently, there is no means to generate unique triggers for distinct sets of sinks.
Take care to never issue more debits than credits, as this may falsely enable the trigger under the current implementation.