Simulator

Theamaranth.sim module, also known as the simulator, makes it possible to evaluate a design’s functionality in a virtual environment before it is implemented in hardware.

Simulating circuits

The following examples simulate one of the two designs below: synchronous counter running in thesync clock domain, and combinational adder. They assume familiarity with thelanguage guide.

fromamaranth.libimportwiringfromamaranth.lib.wiringimportIn,OutclassCounter(wiring.Component):en:In(1,init=1)count:Out(4)defelaborate(self,platform):m=Module()withm.If(self.en):m.d.sync+=self.count.eq(self.count+1)returnmclassAdder(wiring.Component):a:In(16)b:In(16)o:Out(17)defelaborate(self,platform):m=Module()m.d.comb+=self.o.eq(self.a+self.b)returnm

Running a simulation

Simulating a design always requires the three basic steps: constructing theDUT, constructing aSimulator for it, and running the simulation with theSimulator.run() orSimulator.run_until() method:

fromamaranth.simimportSimulator,Perioddut=Counter()sim=Simulator(dut)sim.run()

However, the code above neither stimulates the DUT’s inputs nor measures the DUT’s outputs; theSimulator.run() method also immediately returns if no stimulus is added to the simulation. To make it useful, several changes are necessary:

The following code simulates a design and capture the values of all the signals used in the design for each moment of simulation time:

The captured data is saved to aVCD fileexample1.vcd, which can be displayed with awaveform viewer such asSurfer orGTKWave:

{'head': {'tock': 0}, 'signal': [{'name': 'clk', 'wave': 'lp..............'}, {'name': 'rst', 'wave': 'l...............'}, {'name': 'en', 'wave': 'h...............'}, {'name': 'count', 'wave': '================', 'data': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15']}], 'config': {'skin': 'default'}}

TheSimulator.reset() method reverts the simulation to its initial state. It can be used to speed up tests by capturing the waveforms only when the simulation is known to encounter an error:

try:sim.run()except:sim.reset()withsim.write_vcd("example1_error.vcd"):sim.run()

Testing synchronous circuits

To verify that the DUT works as intended during a simulation, known values are provided as the inputs, and the outputs are compared with the expected results.

This is done by adding a different type of stimulus to the simulator, atestbench: anasync Python function that runs concurrently with the DUT and can manipulate the signals used in the simulation. A testbench is added using theSimulator.add_testbench() method, and receives aSimulatorContext object through which it can interact with the simulator: inspect the value of signals using thectx.get() method, change the value of signals using thectx.set() method, or wait for an active edge of aclock domain using thectx.tick() method.

The following example simulates a counter and verifies that it can be stopped using itsen input:

dut=Counter()asyncdeftestbench_example2(ctx):awaitctx.tick().repeat(5)# wait until after the 5th edge of the `sync` domain clockassertctx.get(dut.count)==5# verify that the counter has the expected valuectx.set(dut.en,False)# deassert `dut.en`, disabling the counterawaitctx.tick().repeat(5)# wait until after the 10th edge of clockassertctx.get(dut.count)==5# verify that the counter has not been incrementingctx.set(dut.en,True)# assert `dut.en`, enabling the counter againsim=Simulator(dut)sim.add_clock(Period(MHz=1))sim.add_testbench(testbench_example2)# add the testbench; run_until() calls the functionwithsim.write_vcd("example2.vcd"):sim.run_until(Period(MHz=1)*15)

Since this circuit is synchronous, and thectx.tick() method waits until after the circuit has reacted to the clock edge, the change to theen input affects the behavior of the circuit on the next clock cycle after the change:

{'head': {'tock': 0}, 'signal': [{'name': 'clk', 'wave': 'lp..............'}, {'name': 'rst', 'wave': 'l...............'}, {'name': 'en', 'wave': 'h....0....1.....'}, {'name': 'count', 'wave': '======.....=====', 'data': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']}], 'config': {'skin': 'default'}}

Testing combinational circuits

A testbench that tests a combinational circuit advances simulation time using thectx.delay() method instead of thectx.tick() method, since the simulation does not contain a clock in this case. TheSimulator.run() method stops the simulation and returns once all testbenches finish executing.

The following example simulates an adder:

dut=Adder()asyncdeftestbench_example3(ctx):awaitctx.delay(Period(us=1))ctx.set(dut.a,2)ctx.set(dut.b,2)assertctx.get(dut.o)==4awaitctx.delay(Period(us=1))ctx.set(dut.a,1717)ctx.set(dut.b,420)assertctx.get(dut.o)==2137awaitctx.delay(Period(us=2))sim=Simulator(dut)sim.add_testbench(testbench_example3)withsim.write_vcd("example3.vcd"):sim.run()

Since this circuit is entirely combinational, and the Amaranth simulator uses azero-delay model of combinational circuits, the outputs change in the same instant as the inputs do:

{'signal': [{'name': 'a', 'wave': '===.', 'data': [0, 2, 1717]}, {'name': 'b', 'wave': '===.', 'data': [0, 2, 420]}, {'name': 'o', 'wave': '===.', 'data': [0, 4, 2137]}], 'config': {'skin': 'default'}}

Replacing circuits with code

Note

This section describes an advanced technique that is not commonly used. If you are first learning how to use the simulator, you can skip it.

During simulation, it is possible to replace an Amaranth circuit with the equivalent Python code. This can be used to improve simulation performance, or to avoid reimplementing complex Python algorithms in Amaranth if they do not need to be synthesized.

This is done by adding aprocess to the simulator: anasync Python function that runs as an integral part of the simulation, simultaneously with the DUT. A process is added using theSimulator.add_process() method, and receives aSimulatorContext object through which it can interact with the simulator. A process is conceptually similar to a testbench but differs from it in two important ways:

  • Testbenches run in a well-defined order (from first to last in the order they were added, yielding control only atawait points) and cannot observe inconsistent intermediate states of a design, but processes run in an undefined order while the design is converging after a change to its inputs.

  • In a process, it is not possible to inspect the value of a signal using thectx.get() method, which guarantees that inconsistent intermediate states of a design cannot be observed by a process either.

A process communicates with the rest of the design in the same way an elaboratable would: throughSignals.

Replacing synchronous circuits

Processes cannot inspect values of signals using thectx.get() method. Instead, values of signals in a synchronous process are sampled at each active edge of the clock domain (or, for domains with asynchronous reset, at the assertion of the reset signal) using thectx.tick() method.

The following code replaces theCounter elaboratable with the equivalent Python code in a process, and uses a testbench to verify its correct operation:

m=Module()m.domains.sync=cd_sync=ClockDomain()en=Signal(init=1)count=Signal(4)asyncdefprocess_example4(ctx):count_value=0# initialize counter to 0asyncforclk_edge,rst_value,en_valueinctx.tick().sample(en):ifrst_value:# can be asserted with or without clk_edgecount_value=0# re-initialize counterelifclk_edgeanden_value:count_value+=1# advance the counterctx.set(count,count_value)# publish its value to the simulationasyncdeftestbench_example4(ctx):awaitctx.tick().repeat(5)assertctx.get(count)==5ctx.set(en,False)awaitctx.tick().repeat(5)assertctx.get(count)==5ctx.set(en,True)sim=Simulator(m)sim.add_clock(Period(MHz=1))sim.add_process(process_example4)sim.add_testbench(testbench_example4)withsim.write_vcd("example4.vcd",traces=(cd_sync.clk,cd_sync.rst,en,count)):sim.run()

Unless it is instructed otherwise, theSimulator.write_vcd() method only captures values of signals that appear in the circuit provided to the simulator when it is created. Theen andcount signals do not, and are added explicitly using thetraces argument so that they will appear in the VCD file.

Replacing combinational circuits

Values of signals in a combinational process are sampled anytime they change using thectx.changed() method.

The following code replaces theAdder elaboratable with the equivalent Python code in a process, and uses a testbench to verify its correct operation:

m=Module()a=Signal(16)b=Signal(16)o=Signal(17)asyncdefprocess_example5(ctx):asyncfora_value,b_valueinctx.changed(a,b):ctx.set(o,a_value+b_value)asyncdeftestbench_example5(ctx):awaitctx.delay(Period(us=1))ctx.set(a,2)ctx.set(b,2)assertctx.get(o)==4awaitctx.delay(Period(us=1))ctx.set(a,1717)ctx.set(b,420)assertctx.get(o)==2137awaitctx.delay(Period(us=2))sim=Simulator(m)sim.add_process(process_example5)sim.add_testbench(testbench_example5)withsim.write_vcd("example5.vcd",traces=[a,b,o]):sim.run()

Reference

classamaranth.sim.Simulator(toplevel)

Simulator for Amaranth designs.

The simulator accepts atop-level design (anelaboratable),processes that replace circuits with behavioral code,clocks that drive clock domains, andtestbenches that exercise the circuits and verify that they work correctly.

The simulator lifecycle consists of four stages:

  1. The simulator is created:

    sim=Simulator(design)
  2. Processes, clocks, and testbenches are added as necessary:

    sim.add_clock(Period(MHz=1))sim.add_clock(Period(MHz=10),domain="fast")sim.add_process(process_instr_decoder)sim.add_testbench(testbench_cpu_execute)
  3. The simulation is run:

    withsim.write_vcd("waveform.vcd"):sim.run()
  4. (Optional) The simulator is reset:

    sim.reset()

After the simulator is reset, it may be reused to run the simulation again.

Note

Resetting the simulator can also be used to amortize the startup cost of repeatedlysimulating a large design.

Parameters:

toplevel (Elaboratable) – Simulated design.

add_clock(period,*,phase=None,domain='sync',if_exists=False)

Add a clock to the simulation.

Adds a stimulus that toggles the clock signal ofdomain at a 50% duty cycle.

The driven clock signal will toggle every half-period seconds starting atphaseseconds after the beginning of the simulation; if not specified,phase defaults tohalf-period to avoid coinciding the first active edge with the beginning ofthe simulation.

The clock domain to drive is selected by thedomain argument, which may beaClockDomain object or astr. If it is a string,the clock domain with that name is retrieved from thetoplevel elaboratable.

Raises:
  • NameError – Ifdomain is astr, thetoplevel elaboratable does not have a clock domain with that name, andif_exists isFalse.

  • DriverConflict – Ifdomain already has a clock driving it.

  • RuntimeError – If the simulation has been advanced since its creation or last reset.

add_testbench(constructor,*,background=False)

Add a testbench to the simulation.

Adds a testbench that runs concurrently with thetoplevel elaboratable and is able tomanipulate its inputs, outputs, and state.

The behavior of the testbench is defined by itsconstructor function, which isanasync function that takes a single argument, theSimulatorContext:

asyncdeftestbench(ctx):...awaitctx.tick()...sim.add_testbench(testbench)

This method does not accept coroutines. Rather, the providedconstructor coroutinefunction is called immediately when the testbench is added to create a coroutine, as well asby thereset() method.

The testbench can becritical (the default) orbackground (if thebackground=Trueargument is specified). Therun() method will continue advancing the simulation whileany critical testbenches or processes are running, and will exit when only backgroundtestbenches or processes remain. A background testbench can temporarily become criticalusing thecritical() context manager.

At each point in time, all of the non-waiting testbenches are executed in the order inwhich they were added. If two testbenches share state, or must manipulate the design ina coordinated way, they may rely on this execution order for correctness.

Raises:

RuntimeError – If the simulation has been advanced since its creation or last reset.

add_process(process)

Add a process to the simulation.

Adds a process that is evaluated as a part of thetoplevel elaboratable and is able toreplace circuits with Python code.

The behavior of the process is defined by itsconstructor function, which isanasync function that takes a single argument, theSimulatorContext:

asyncdefprocess(ctx):asyncforclk_edge,rst,...inctx.tick().sample(...):...sim.add_process(process)

This method does not accept coroutines. Rather, the providedconstructor coroutinefunction is called immediately when the procss is added to create a coroutine, as well asby thereset() method.

Processes can becritical orbackground, and are always background when added.Therun() method will continue advancing the simulation while any critical testbenchesor processes are running, and will exit when only background testbenches or processesremain. A background process can temporarily become critical usingthecritical() context manager.

At each point in time, all of the non-waiting processes are executed in an arbitrary orderthat may be different between individual simulation runs.

Warning

If two processes share state, they must do so in a way that does not rely ona particular order of execution for correctness.

Preferably, the shared state would be stored inSignals (evenif it is not intended to be a part of a circuit), with access to it synchronized usingawaitctx.tick().sample(...). Such state is visible in a waveform viewer,simplifying debugging.

Raises:

RuntimeError – If the simulation has been advanced since its creation or last reset.

run()

Run the simulation indefinitely.

This method advances the simulation while any critical testbenches or processes continueexecuting. It is equivalent to:

whileself.advance():pass
run_until(deadline)

Run the simulation until a specific point in time.

This method advances the simulation until the simulation time reachesdeadline,without regard for whether there are critical testbenches or processes executing.

advance()

Advance the simulation.

This method advances the simulation by one time step. After this method completes, all ofthe events scheduled for the current point in time will have taken effect, and the currentpoint in time was advanced to the closest point in the future for which any events arescheduled (which may be the same point in time).

The non-waiting testbenches are executed in the order they were added, and the processesare executed as necessary.

ReturnsTrue if the simulation contains any critical testbenches or processes, andFalse otherwise.

write_vcd(vcd_file,gtkw_file=None,*,traces=())

Capture waveforms to a file.

This context manager captures waveforms for each signal and memory that is referenced fromtoplevel, as well as any additional signals or memories specified intraces,and saves them tovcd_file. Ifgtkw_file is provided, it is populated witha GTKWave save file displayingtraces when opened.

Use this context manager to wrap a call torun() orrun_until():

withsim.write_vcd("simulation.vcd"):sim.run()

Thevcd_file andgtkw_file arguments accept either afile objector a filename. If a file object is provided, it is closed when exiting the context manager(once the simulation completes or encounters an error).

Thetraces argument accepts atrace specification, which can be one of:

Raises:

TypeError – If a trace specification refers to a signal with a private name.

reset()

Reset the simulation.

This method reverts the simulation to its initial state:

  • The value of each signal is changed to its initial value;

  • The contents of each memory is changed to its initial contents;

  • Each clock, testbench, and process is restarted.

classamaranth.sim.SimulatorContext(...)

Simulator context.

Simulator processes and testbenches areasync Python functions that interact withthe simulation using the only argument they receive: thecontext. Using a context, it ispossible to sample or update signals and wait for events to occur in the simulation.

The context has two kinds of methods:async methods and non-async methods. Callinganasync method may cause the caller to be preempted (be paused such that the simulationtime can advance), while calling non-async methods never causes that.

Note

While a testbench or process is executing without callingasync methods, no othertestbench or process will run, with one exception: if a testbench callsset(), allprocesses that wait (directly or indirectly) for the updated signals to change will executebefore the call returns.

get(expr)

Sample the value of an expression.

The behavior of this method depends on the type ofexpr:

This method is only available in testbenches.

Raises:

TypeError – If the caller is a process.

set(expr,value)

Update the value of an expression.

The behavior of this method depends on the type ofexpr:

This method is available in both processes and testbenches.

When used in a testbench, this method runs all processes that wait (directly orindirectly) for the signals inexpr to change, and returns only after the changepropagates through the simulated circuits.

critical()

Context manager that temporarily makes the caller critical.

Testbenches and processes may bebackground orcritical, where critical ones preventSimulator.run() from finishing. Processes are always created background, whiletestbenches are created critical by default, but may also be created background.This context manager makes the caller critical for the span of thewith statement.

This may be useful in cases where an operation (for example, a bus transaction) takesmultiple clock cycles to complete, and must be completed after starting, but the testbenchor process performing it never finishes, always waiting for the next operation to arrive.In this case, the caller would elevate itself to become critical only for the duration ofthe operation itself using this context manager, for example:

asyncdeftestbench_bus_transaction(ctx):# On every cycle, check whether the bus has an active transaction...asyncforclk_edge,rst_active,bus_active_valueinctx.tick().sample(bus.active):ifbus_active_value:# ... if it does...withctx.critical():# ... make this testbench critical...addr_value=ctx.get(bus.r_addr)ctx.set(bus.r_data,...)# ... perform the access...awaitctx.tick()ctx.set(bus.done,1)awaitctx.tick()ctx.set(bus.done,0)# ... and complete the transaction later.# The `run()` method could return at this point, but not before.
tick(domain='sync',*,context=None)

Wait until an active clock edge or an asynchronous reset occurs.

This method returns aTickTrigger object that, when awaited, pauses the executionof the calling process or testbench until the active edge of the clock, or the asynchronousreset (if applicable), occurs. The returned object may be used to repeatedly wait for oneof these events until a condition is satisfied or a specific number of times. Seethetick trigger reference for more details.

Thedomain may be either aClockDomain or astr. If it isastr, a clock domain with this name is looked up intheelaboratablecontext, or intoplevel ifcontext is not provided.

Raises:
  • ValueError – Ifdomain is"comb".

  • ValueError – Ifdomain is aClockDomain andcontext is provided and notNone.

  • ValueError – Ifcontext is an elaboratable that is not a direct or indirect submodule oftoplevel.

  • NameError – Ifdomain is astr, but there is no clock domain with this name incontext ortoplevel.

delay(interval)

Wait until a time interval has elapsed.

This method returns aTriggerCombination object that, when awaited, pausesthe execution of the calling process or testbench byinterval seconds. The returnedobject may be used to wait for multiple events.

The value captured by this trigger isTrue if the delay has expired when the wait hascompleted, andFalse otherwise.

Theinterval may be zero, in which case the caller will be scheduled for executionimmediately after all of the processes and testbenches scheduled for the current time stepfinish executing. In other words, if a call toSimulator.advance() schedules a processor testbench and it performsawaitctx.delay(0), this process or testbench willcontinue execution only during the next call toSimulator.advance().

Note

Although the behavior ofawaitctx.delay(0) is well-defined, it may make waveformsdifficult to understand and simulations hard to reason about.

Raises:

ValueError – Ifdelay is negative.

changed(*signals)

Asynchronously wait until one of the signals change.

This method returns aTriggerCombination object that, when awaited, pausesthe execution of the calling process or testbench until any of thesignals change.The returned object may be used to wait for multiple events.

The values captured by this trigger are the values ofsignals at the time the waithas completed.

Warning

The simulation may produceglitches: transient changes to signals (e.g. from 0 to 1and back to 0) during combinational propagation that are invisible in testbenches orwaveform captures. Glitches will wake upboth processes and testbenches that usethis method to wait for a signal to change, and both processes and testbenches must beprepared to handle such spurious wakeups. The presence, count, and sequence in whichglitches occur may also vary between simulation runs.

Testbenches that wait for a signal to change using anawait statement might onlyobserve the final value of the signal, and testbenches that wait for changes usinganasyncfor loop will crash with aBrokenTrigger exception if theyencounter a glitch.

Processes will observe all of the transient values of the signal.

edge(signal,polarity)

Asynchronously wait until a low-to-high or high-to-low transition of a signal occurs.

This method returns aTriggerCombination object that, when awaited, pausesthe execution of the calling process or testbench until the value ofsignal(a single-bit signal or a single-bit slice of a signal) changes fromnotpolaritytopolarity. The returned object may be used to wait for multiple events.

The value captured by this trigger isTrue if the relevant transition has occurredat the time the wait has completed, andFalse otherwise.

Warning

In most cases, this method should not be used to wait for a status signal to be assertedor deasserted in a testbench because it is likely to introduce a race condition.Whenever a suitable clock domain is available, useawaitctx.tick().until(signal==polarity) instead.

Raises:
  • TypeError – Ifsignal is neither a single-bitSignal nor a single-bit slice of aSignal.

  • TypeError – If the shape ofsignal is aShapeCastable.

  • ValueError – Ifpolarity is neither 0 nor 1.

posedge(signal)

Asynchronously wait until a signal is asserted.

Equivalent toedge(signal,1).

negedge(signal)

Asynchronously wait until a signal is deasserted.

Equivalent toedge(signal,0).

elapsed_time()

Return the currently elapsed simulation time.

exceptionamaranth.sim.BrokenTrigger

Exception raised when a trigger that is repeatedly awaited using anasyncfor loop hasa matching event occur while the body of theasyncfor loop is still executing.

exceptionamaranth.sim.DomainReset

Exception raised when a tick trigger is repeatedly awaited, and its domain has been reset.

classamaranth.sim.TickTrigger(...)

A trigger that wakes up the caller when the active edge of a clock domain occurs or the domainis asynchronously reset.

ATickTrigger is an immutable object that stores a reference to a clock domain anda list of expressions to sample.

TheSimulatorContext.tick() method creates a tick trigger with an empty list of sampledexpressions, and theTickTrigger.sample() method creates a tick trigger based on anothertick trigger that additionally samples the specified expressions.

To wait for a tick trigger to be activated once (aone-shot wait), a process or testbenchcallsawaittrigger, usually on a newly created tick trigger:

asyncdeftestbench(ctx):clk_hit,rst_active,a_value,b_value=awaitctx.tick().sample(dut.a,dut.b)

To repeatedly wait for a tick trigger to be activated (amulti-shot wait), a process ortestbenchasynchronously iterates the tick trigger,usually using theasyncfor loop:

asyncdeftestbench(ctx):asyncforclk_hit,rst_active,a_value,b_valueinctx.tick().sample(dut.a,dut.b):...

Both one-shot and multi-shot waits return the sametuple(clk_hit,rst_active,*values) of return values:

  1. clk_hit isTrue if there was an active clock edge at the moment the wait hascompleted, andFalse otherwise (that is, if the clock domain was asynchronously reset).

  2. rst_active isTrue if the clock domain is reset (synchronously or asynchronously)at the moment the wait has completed,False otherwise.

  3. All following return values correspond to the sampled expressions in the order in which theywere added.

Aside from the syntax, there are two differences between one-shot and multi-shot waits:

  1. A multi-shot wait continues to observe the tick trigger while the process or testbenchresponds to the event. If the tick trigger is activated again before the next iteration ofthe asynchronous iterator (such as while the body of theasyncfor loop is executing),the next iteration raises aBrokenTrigger exception to notify the caller of the missedevent.

  2. A repeated one-shot wait may be less efficient than a multi-shot wait.

Note

The exact behavior ofrst_active differs depending on whetherdomain usessynchronous or asynchronous reset; in both cases it isTrue if and only ifthe domain reset has been asserted. Reusable processes and testbenches, as well as theirbuilding blocks, should handle both cases.

sample(*exprs)

Sample expressions when this trigger is activated.

This method returns a newTickTrigger object. When awaited, this object returns,in addition to the values that would be otherwise returned byawaittrigger,the values ofexprs (anyValueLike) at exactly the moment at whichthe active clock edge, or the asynchronous reset (if applicable), has occurred.

Combiningtick() withsample() can be used to capturethe state of a circuit after the active clock edge, but before propagation of signal valuesthat have been updated by that clock edge:

asyncforclk_edge,rst_active,in_a_value,in_b_valuein \ctx.tick().sample(in_a,in_b):...

Chaining calls tosample() has the same effect as calling it once with the combinedlist of arguments. The code below has the same behavior as the code above:

asyncforclk_edge,rst_active,in_a_value,in_b_valuein \ctx.tick().sample(in_a).sample(in_b):...

Note

Chaining calls to this method is useful for defining reusable building blocks.The following (simplified for clarity) implementation ofuntil() takes advantageof it by first appendingcondition to the end of the list of captured expressions,checking if it holds, and then removing it from the list of sampled values:

asyncdefuntil(trigger,condition):asyncforclk_edge,rst_active,*values,doneintrigger.sample(condition):ifdone:returnvalues
asyncuntil(condition)

Repeat this trigger until a condition is met.

This method awaits this trigger at least once, and returns atuple of the valuesthat aresample()d whencondition evaluates to a non-zero value. Valuessampled during previous repeats are discarded.

Awaiting atrigger returns values indicating the state of the clock and reset signals,while awaitingtrigger.until(...) does not:

whileTrue:clk_edge,rst_active,*values,flag_value=awaittrigger.sample(flag)# never raisesifflag_value:break# `values` may be used after the loop finishes
values=awaittrigger.until(flag)# may raise `DomainReset`
Raises:
  • TypeError – If the shape ofcondition is aShapeCastable.

  • DomainReset – If the clock domain has been synchronously or asynchronously reset during the wait.

asyncrepeat(count)

Repeat this trigger a specific number of times.

This method awaits this trigger at least once, and returns atuple of the valuesthat aresample()d during the last repeat. Values sampled during previous repeatsare discarded.

Awaiting atrigger returns values indicating the state of the clock and reset signals,while awaitingtrigger.repeat(...) does not:

for_inrange(3):clk_edge,rst_active,*values=awaittrigger# never raises# `values` may be used after the loop finishes
values=awaittrigger.repeat(3)# may raise `DomainReset`
Raises:
  • ValueError – Ifcount is less than 1.

  • DomainReset – If the clock domain has been synchronously or asynchronously reset during the wait.

classamaranth.sim.TriggerCombination(...)

A list of triggers, the activation of any of which will wake up the caller.

ATriggerCombination is an immutable object that stores a list of triggers andexpressions to sample. The trigger combination wakes up the caller when any of these triggersactivate, and it samples all of the signals at the same moment.

TheSimulatorContext.delay(),SimulatorContext.changed(), andSimulatorContext.edge() methods create a trigger combination that consists of just thatone trigger, whileTriggerCombination.delay(),TriggerCombination.changed(), andTriggerCombination.edge() methods create a trigger combination based on another triggercombination by extending it with an additional trigger. TheTriggerCombination.sample()method creates a trigger combination based on another trigger combination that wakes upthe caller in the same conditions but additionally samples the specified expressions.

To wait for a trigger combination to be activated once (aone-shot wait), a process ortestbench callsawaittriggers, usually on a newly created trigger combination:

asyncdeftestbench(ctx):a_value,b_value=awaitctx.changed(dut.a,dut.b)

To repeatedly wait for a trigger combination to be activated (amulti-shot wait), a processor testbenchasynchronously iterates the triggercombination, usually using theasyncfor loop:

asyncdeftestbench(ctx):asyncfora_value,b_valueinctx.changed(dut.a,dut.b):...

Both one-shot and multi-shot waits return the sametuple of return values, the elementsof which are determined by the triggers and sampled expressions that have been added tothe trigger combination, in the order in which they were added. For a detailed description ofthe return values, refer toSimulatorContext.delay(),SimulatorContext.changed(),SimulatorContext.edge(), andTriggerCombination.sample().

Aside from the syntax, there are two differences between one-shot and multi-shot waits:

  1. A multi-shot wait continues to observe the trigger combination while the process or testbenchresponds to the event. If the trigger combination is activated again before the nextiteration of the asynchronous iterator (such as while the body of theasyncfor loop isexecuting), the next iteration raises aBrokenTrigger exception to notify the callerof the missed event.

  2. A repeated one-shot wait may be less efficient than a multi-shot wait.

sample(*exprs)

Sample signals when a trigger from this combination is activated.

This method returns a newTriggerCombination object. When awaited, this objectreturns, in addition to the values that would be returned byawaittrigger, the valuesofexprs at exactly the moment at which the wait has completed.

Combiningdelay(),changed(), oredge() withsample() can be used to capture the state ofa circuit at the moment of the event:

asyncforarst_edge,delay_expired,in_a_value,in_b_valuein \ctx.posedge(arst).delay(1e-3).sample(in_a,in_b):...

Chaining calls tosample() has the same effect as calling it once with the combinedlist of arguments. The code below has the same behavior as the code above:

asyncforarst_edge,delay_expired,in_a_value,in_b_valuein \ctx.posedge(arst).delay(1e-3).sample(in_a).sample(in_b):...

Note

Chaining calls to this method is useful for defining reusable building blocks. Seethe documentation forTickTrigger.sample() for a detailed example.

delay(interval)

Add a delay trigger to the list of triggers.

This method returns a newTriggerCombination object. When awaited, this objectalso waits for the same trigger asSimulatorContext.delay(), and returns,in addition to the values that would be returned byawaittrigger, the valuereturned bySimulatorContext.delay().

changed(*signals)

Add a signal change trigger to the list of triggers.

This method returns a newTriggerCombination object. When awaited, this objectalso waits for the same trigger asSimulatorContext.changed(), and returns,in addition to the values that would be returned byawaittrigger, the valuesreturned bySimulatorContext.changed().

edge(signal,polarity)

Add a low-to-high or high-to-low transition trigger to the list of triggers.

This method returns a newTriggerCombination object. When awaited, this objectalso waits for the same trigger asSimulatorContext.edge(), and returns,in addition to the values that would be returned byawaittrigger, the valuesreturned bySimulatorContext.edge().

posedge(signal)

Add a low-to-high transition trigger to the list of triggers.

Equivalent toedge(signal,1).

negedge(signal)

Add a high-to-low transition trigger to the list of triggers.

Equivalent toedge(signal,0).