transactron.testing package

Submodules

transactron.testing.functions module

transactron.testing.functions.data_const_to_dict(c: data.Const[data.Layout])

transactron.testing.input_generation module

class transactron.testing.input_generation.OpNOP

Bases: object

transactron.testing.input_generation.generate_based_on_layout(layout: MethodLayout) SearchStrategy[NameIntDict]
transactron.testing.input_generation.generate_method_input(args: list[tuple[str, MethodLayout]]) SearchStrategy[dict[str, NameIntDict]]
transactron.testing.input_generation.generate_nops_in_list(max_nops: int, generate_list: SearchStrategy[list[T]]) SearchStrategy[list[T | OpNOP]]
transactron.testing.input_generation.generate_process_input(elem_count: int, max_nops: int, layouts: list[tuple[str, MethodLayout]]) SearchStrategy[list[dict[str, NameIntDict] | OpNOP]]
transactron.testing.input_generation.generate_shrinkable_list(length: int, generator: SearchStrategy) SearchStrategy[list[T]]

Trick based on https://github.com/HypothesisWorks/hypothesis/blob/ 6867da71beae0e4ed004b54b92ef7c74d0722815/hypothesis-python/src/hypothesis/stateful.py#L143

transactron.testing.logging module

class transactron.testing.logging.HDLLogWrapper

Bases: Elaboratable

Wrapper for a module to enable utils.logging backend for printing in HDL simulation.

__init__(elaboratable: HasElaborate, *, print_cycle_separator=True, print_src_loc=False, level: int = 0, namespace_regexp: str = '.*')
class transactron.testing.logging.HDLLogWrapperComponent

Bases: HDLLogWrapper, Component

HDLLogWrapper variant for use with Component.

__init__(component: AbstractComponent, *, print_cycle_separator=True, print_src_loc=False, level: int = 0, namespace_regexp: str = '.*')
transactron.testing.logging.make_logging_process(level: int, namespace_regexp: str, on_error: Callable[[], Any])
transactron.testing.logging.parse_logging_level(str: str) int

Parse the log level from a string.

The level can be either a non-negative integer or a string representation of one of the predefined levels.

Raises an exception if the level cannot be parsed.

transactron.testing.method_mock module

class transactron.testing.method_mock.MethodMock

Bases: object

__init__(adapter: ~transactron.lib.adapters.AdapterBase, function: ~typing.Callable[[...], NameIntDict | None], *, validate_arguments: ~typing.Callable[[...], bool] | None = None, enable: ~typing.Callable[[], bool] = <function MethodMock.<lambda>>, delay: float = 0, **kwargs: ~typing.Unpack[~transactron.core.body.AdapterBodyParams])
static effect(effect: Callable[[], None])
async effect_process(sim: SimulatorContext) None
async output_process(sim: SimulatorContext) None
async validate_arguments_process(sim: SimulatorContext) None
transactron.testing.method_mock.def_method_mock(tb_getter: Callable[[], TestbenchIO] | Callable[[Any], TestbenchIO], **kwargs) Callable[[Callable[[...], NameIntDict | None]], Callable[[], MethodMock]]

Decorator function to create method mock handlers. It should be applied on a function which describes functionality which we want to invoke on method call. This function will be called on every clock cycle when the method is active, and also on combinational changes to inputs.

The decorated function can have a single argument arg, which receives the arguments passed to a method as a data.Const, or multiple named arguments, which correspond to named arguments of the method.

This decorator can be applied to function definitions or method definitions. When applied to a method definition, lambdas passed to def_method_mock need to take a self argument, which should be the first.

Mocks defined at class level or at test level are automatically discovered and don’t need to be manually added to the simulation.

Any side effects (state modification, assertions, etc.) need to be guarded using the MethodMock.effect decorator.

Make sure to defer accessing state, since decorators are evaluated eagerly during function declaration.

Parameters:
tb_getterCallable[[], TestbenchIO] | Callable[[Any], TestbenchIO]

Function to get the TestbenchIO of the mocked method.

enableCallable[[], bool] | Callable[[Any], bool]

Function which decides if the method is enabled in a given clock cycle.

validate_argumentsCallable[…, bool]

Function which validates call arguments. This applies only to Adapters with with_validate_arguments set to True.

delayfloat

Simulation time delay for method mock calling. Used for synchronization between different mocks and testbench processes.

transactron.testing.profiler module

transactron.testing.profiler.profiler_process(transaction_manager: TransactionManager, profile: Profile)

transactron.testing.simulator module

class transactron.testing.simulator.PysimSimulator

Bases: Simulator

__init__(module: HasElaborate, max_cycles: float = 100000.0, add_transaction_module=True, traces_file=None, clk_period=1e-06)
add_mock(val: MethodMock)
run() None

Run the simulation indefinitely.

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

while self.advance():
    pass
async transactron.testing.simulator.random_wait(ctx: SimulatorContext, max_cycle_cnt: int, *, min_cycle_cnt: int = 0)

Wait for a random amount of cycles in range [min_cycle_cnt, max_cycle_cnt]

async transactron.testing.simulator.random_wait_geom(ctx: SimulatorContext, prob: float = 0.5, max_cycle_cnt: int = 65536)

Wait till the first success, where there is prob probability for success in each cycle.

async transactron.testing.simulator.tick(ctx: SimulatorContext, cycle_cnt: int = 1)

Waits for the given number of cycles.

transactron.testing.test_case module

class transactron.testing.test_case.TestCaseWithSimulatorBase

Bases: object

ctx_testing_env(base_output_file_name: str)
ctx_testing_env_next()
dependency_manager: DependencyManager
async static random_wait(ctx: SimulatorContext, max_cycle_cnt: int, *, min_cycle_cnt: int = 0)

Wait for a random amount of cycles in range [min_cycle_cnt, max_cycle_cnt]

async static random_wait_geom(ctx: SimulatorContext, prob: float = 0.5, max_cycle_cnt: int = 65536)

Wait till the first success, where there is prob probability for success in each cycle.

run_simulation(module: HasElaborate, max_cycles: float = 100000.0, add_transaction_module=True)
async static tick(ctx: SimulatorContext, cycle_cnt: int = 1)

Waits for the given number of cycles.

static wrap_testing_env(base_output_file_name: str)
static wrap_testing_env_next(func: Callable[[Concatenate[S, P]], T])

transactron.testing.test_case_pytest module

class transactron.testing.test_case_pytest.TestCaseWithSimulator

Bases: TestCaseWithSimulatorBase

fixture_initialize_testing_env(request)

transactron.testing.test_circuit module

class transactron.testing.test_circuit.SimpleTestCircuit

Bases: Elaboratable, Generic

__init__(dut: T, *, exclude: Iterable[str] = ())
debug_signals()

transactron.testing.testbenchio module

class transactron.testing.testbenchio.CallTrigger

Bases: object

A trigger which allows to call multiple methods and sample signals.

The call() and call_try() methods on a TestbenchIO always wait at least one clock cycle. It follows that these methods can’t be used to perform calls to multiple methods in a single clock cycle. Usually this is not a problem, as different methods can be called from different simulation processes. But in cases when more control over the time when different calls happen is needed, this trigger class allows to call many methods in a single clock cycle.

__init__(sim: SimulatorContext, _calls: Iterable[Value | int | Enum | ValueCastable | tuple[TestbenchIO, dict[str, Any] | None]] = ())
Parameters:
sim: SimulatorContext

Amaranth simulator context.

call(tbio: TestbenchIO, data: dict[str, Any] = {}, /, **kwdata)

Call a method and sample its result.

Adds a method call to the trigger. The method result is sampled on a clock edge. If the call did not succeed, the sampled value is None.

Parameters:
tbio: TestbenchIO

The method to call.

data: dict[str, Any]

Method call arguments stored in a dict.

**kwdata: Any

Method call arguments passed as keyword arguments. If keyword arguments are used, the data argument should not be provided.

sample(*values: Value | int | Enum | ValueCastable | TestbenchIO)

Sample a signal or a method result on a clock edge.

Values are sampled like in standard Amaranth TickTrigger. Sampling a method result works like call(), but the method is not called - another process can do that instead. If the method was not called, the sampled value is None.

Parameters:
*values: ValueLike | TestbenchIO

Value or method to sample.

async until_all_done() Any

Same as until_done but wait for all results instead of any result.

async until_done() Any

Wait until at least one of the calls succeeds.

The CallTrigger normally acts like TickTrigger, e.g. awaiting on it advances the clock to the next clock edge. It is possible that none of the calls could not be performed, for example because the called methods were not enabled. In case we only want to focus on the cycles when one of the calls succeeded, until_done can be used. This works like until() in TickTrigger.

class transactron.testing.testbenchio.TestbenchIO

Bases: Elaboratable

__init__(adapter: AdapterBase)
async call(sim: SimulatorContext, data={}, /, **kwdata) data.Const[data.StructLayout]
async call_do(sim: SimulatorContext) data.Const[data.StructLayout]
call_init(sim: SimulatorContext, data={}, /, **kwdata)
async call_result(sim: SimulatorContext) data.Const[data.StructLayout] | None
async call_try(sim: SimulatorContext, data={}, /, **kwdata) data.Const[data.StructLayout] | None
disable(sim: SimulatorContext)
property done
enable(sim: SimulatorContext)
get_call_result(sim: TestbenchContext) data.Const[data.StructLayout] | None
get_done(sim: TestbenchContext)
get_outputs(sim: TestbenchContext) data.Const[data.StructLayout]
property outputs
sample_outputs(sim: SimulatorContext)
sample_outputs_done(sim: SimulatorContext)
sample_outputs_until_done(sim: SimulatorContext)
set_enable(sim: SimulatorContext, en)
set_inputs(sim: SimulatorContext, data)

transactron.testing.tick_count module

class transactron.testing.tick_count.TicksKey

Bases: SimpleKey[Signal]

__init__() None
transactron.testing.tick_count.make_tick_count_process()

Module contents

class transactron.testing.CallTrigger

Bases: object

A trigger which allows to call multiple methods and sample signals.

The call() and call_try() methods on a TestbenchIO always wait at least one clock cycle. It follows that these methods can’t be used to perform calls to multiple methods in a single clock cycle. Usually this is not a problem, as different methods can be called from different simulation processes. But in cases when more control over the time when different calls happen is needed, this trigger class allows to call many methods in a single clock cycle.

__init__(sim: SimulatorContext, _calls: Iterable[Value | int | Enum | ValueCastable | tuple[TestbenchIO, dict[str, Any] | None]] = ())
Parameters:
sim: SimulatorContext

Amaranth simulator context.

call(tbio: TestbenchIO, data: dict[str, Any] = {}, /, **kwdata)

Call a method and sample its result.

Adds a method call to the trigger. The method result is sampled on a clock edge. If the call did not succeed, the sampled value is None.

Parameters:
tbio: TestbenchIO

The method to call.

data: dict[str, Any]

Method call arguments stored in a dict.

**kwdata: Any

Method call arguments passed as keyword arguments. If keyword arguments are used, the data argument should not be provided.

sample(*values: Value | int | Enum | ValueCastable | TestbenchIO)

Sample a signal or a method result on a clock edge.

Values are sampled like in standard Amaranth TickTrigger. Sampling a method result works like call(), but the method is not called - another process can do that instead. If the method was not called, the sampled value is None.

Parameters:
*values: ValueLike | TestbenchIO

Value or method to sample.

async until_all_done() Any

Same as until_done but wait for all results instead of any result.

async until_done() Any

Wait until at least one of the calls succeeds.

The CallTrigger normally acts like TickTrigger, e.g. awaiting on it advances the clock to the next clock edge. It is possible that none of the calls could not be performed, for example because the called methods were not enabled. In case we only want to focus on the cycles when one of the calls succeeded, until_done can be used. This works like until() in TickTrigger.

class transactron.testing.MethodMock

Bases: object

__init__(adapter: ~transactron.lib.adapters.AdapterBase, function: ~typing.Callable[[...], NameIntDict | None], *, validate_arguments: ~typing.Callable[[...], bool] | None = None, enable: ~typing.Callable[[], bool] = <function MethodMock.<lambda>>, delay: float = 0, **kwargs: ~typing.Unpack[~transactron.core.body.AdapterBodyParams])
static effect(effect: Callable[[], None])
async effect_process(sim: SimulatorContext) None
async output_process(sim: SimulatorContext) None
async validate_arguments_process(sim: SimulatorContext) None
class transactron.testing.ProcessContext

Bases: SimulatorContext

get(expr: ValueLike) Never

Sample the value of an expression.

The behavior of this method depends on the type of expr:

  • If it is a ValueCastable whose shape is a ShapeCastable, its numeric value is converted to a higher-level representation using from_bits() and then returned.

  • If it is a Value or a ValueCastable whose shape is a Shape, the numeric value is returned as an int.

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 of expr:

  • If it is a ValueCastable whose shape is a ShapeCastable, value is converted to a numeric representation using const() and then assigned.

  • If it is a Value or a ValueCastable whose shape is a Shape, value is assigned as-is.

This method is available in both processes and testbenches.

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

class transactron.testing.PysimSimulator

Bases: Simulator

__init__(module: HasElaborate, max_cycles: float = 100000.0, add_transaction_module=True, traces_file=None, clk_period=1e-06)
add_mock(val: MethodMock)
run() None

Run the simulation indefinitely.

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

while self.advance():
    pass
class transactron.testing.SimpleTestCircuit

Bases: Elaboratable, Generic

__init__(dut: T, *, exclude: Iterable[str] = ())
debug_signals()
class transactron.testing.SimulatorContext

Bases: object

Simulator context.

Simulator processes and testbenches are async Python functions that interact with the simulation using the only argument they receive: the context. Using a context, it is possible 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. Calling an async method may cause the caller to be preempted (be paused such that the simulation time can advance), while calling non-async methods never causes that.

Note

While a testbench or process is executing without calling async methods, no other testbench or process will run, with one exception: if a testbench calls set(), all processes that wait (directly or indirectly) for the updated signals to change will execute before the call returns.

__init__(design, engine: BaseEngine, process: BaseProcess)
changed(*signals) TriggerCombination

Asynchronously wait until one of the signals change.

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

The values captured by this trigger are the values of signals at the time the wait has completed.

Warning

The simulation may produce glitches: transient changes to signals (e.g. from 0 to 1 and back to 0) during combinational propagation that are invisible in testbenches or waveform captures. Glitches will wake up both processes and testbenches that use this method to wait for a signal to change, and both processes and testbenches must be prepared to handle such spurious wakeups. The presence, count, and sequence in which glitches occur may also vary between simulation runs.

Testbenches that wait for a signal to change using an await statement might only observe the final value of the signal, and testbenches that wait for changes using an async for loop will crash with a BrokenTrigger exception if they encounter a glitch.

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

critical()

Context manager that temporarily makes the caller critical.

Testbenches and processes may be background or critical, where critical ones prevent Simulator.run() from finishing. Processes are always created background, while testbenches are created critical by default, but may also be created background. This context manager makes the caller critical for the span of the with statement.

This may be useful in cases where an operation (for example, a bus transaction) takes multiple clock cycles to complete, and must be completed after starting, but the testbench or 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 of the operation itself using this context manager, for example:

async def testbench_bus_transaction(ctx):
    # On every cycle, check whether the bus has an active transaction...
    async for clk_edge, rst_active, bus_active_value in ctx.tick().sample(bus.active):
        if bus_active_value: # ... if it does...
            with ctx.critical(): # ... make this testbench critical...
                addr_value = ctx.get(bus.r_addr)
                ctx.set(bus.r_data, ...) # ... perform the access...
                await ctx.tick()
                ctx.set(bus.done, 1)
                await ctx.tick()
                ctx.set(bus.done, 0) # ... and complete the transaction later.
            # The `run()` method could return at this point, but not before.
delay(interval) TriggerCombination

Wait until a time interval has elapsed.

This method returns a TriggerCombination object that, when awaited, pauses the execution of the calling process or testbench by interval seconds. The returned object may be used to wait for multiple events.

The value captured by this trigger is True if the delay has expired when the wait has completed, and False otherwise.

The interval may be zero, in which case the caller will be scheduled for execution immediately after all of the processes and testbenches scheduled for the current time step finish executing. In other words, if a call to Simulator.advance() schedules a process or testbench and it performs await ctx.delay(0), this process or testbench will continue execution only during the next call to Simulator.advance().

Note

Although the behavior of await ctx.delay(0) is well-defined, it may make waveforms difficult to understand and simulations hard to reason about.

Raises:
ValueError

If delay is negative.

edge(signal, polarity) TriggerCombination

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

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

The value captured by this trigger is True if the relevant transition has occurred at the time the wait has completed, and False otherwise.

Warning

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

Raises:
TypeError

If signal is neither a single-bit Signal nor a single-bit slice of a Signal.

TypeError

If the shape of signal is a ShapeCastable.

ValueError

If polarity is neither 0 nor 1.

get(expr)

Sample the value of an expression.

The behavior of this method depends on the type of expr:

  • If it is a ValueCastable whose shape is a ShapeCastable, its numeric value is converted to a higher-level representation using from_bits() and then returned.

  • If it is a Value or a ValueCastable whose shape is a Shape, the numeric value is returned as an int.

This method is only available in testbenches.

Raises:
TypeError

If the caller is a process.

negedge(signal) TriggerCombination

Asynchronously wait until a signal is deasserted.

Equivalent to edge(signal, 0).

posedge(signal) TriggerCombination

Asynchronously wait until a signal is asserted.

Equivalent to edge(signal, 1).

set(expr, value)

Update the value of an expression.

The behavior of this method depends on the type of expr:

  • If it is a ValueCastable whose shape is a ShapeCastable, value is converted to a numeric representation using const() and then assigned.

  • If it is a Value or a ValueCastable whose shape is a Shape, value is assigned as-is.

This method is available in both processes and testbenches.

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

tick(domain='sync', *, context=None)

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

This method returns a TickTrigger object that, when awaited, pauses the execution of the calling process or testbench until the active edge of the clock, or the asynchronous reset (if applicable), occurs. The returned object may be used to repeatedly wait for one of these events until a condition is satisfied or a specific number of times. See the tick trigger reference for more details.

The domain may be either a ClockDomain or a str. If it is a str, a clock domain with this name is looked up in the elaboratable context, or in toplevel if context is not provided.

Raises:
ValueError

If domain is "comb".

ValueError

If domain is a ClockDomain and context is provided and not None.

ValueError

If context is an elaboratable that is not a direct or indirect submodule of toplevel.

NameError

If domain is a str, but there is no clock domain with this name in context or toplevel.

class transactron.testing.TestCaseWithSimulatorBase

Bases: object

ctx_testing_env(base_output_file_name: str)
ctx_testing_env_next()
dependency_manager: DependencyManager
async static random_wait(ctx: SimulatorContext, max_cycle_cnt: int, *, min_cycle_cnt: int = 0)

Wait for a random amount of cycles in range [min_cycle_cnt, max_cycle_cnt]

async static random_wait_geom(ctx: SimulatorContext, prob: float = 0.5, max_cycle_cnt: int = 65536)

Wait till the first success, where there is prob probability for success in each cycle.

run_simulation(module: HasElaborate, max_cycles: float = 100000.0, add_transaction_module=True)
async static tick(ctx: SimulatorContext, cycle_cnt: int = 1)

Waits for the given number of cycles.

static wrap_testing_env(base_output_file_name: str)
static wrap_testing_env_next(func: Callable[[Concatenate[S, P]], T])
class transactron.testing.TestbenchContext

Bases: SimulatorContext

get(expr)

Sample the value of an expression.

The behavior of this method depends on the type of expr:

  • If it is a ValueCastable whose shape is a ShapeCastable, its numeric value is converted to a higher-level representation using from_bits() and then returned.

  • If it is a Value or a ValueCastable whose shape is a Shape, the numeric value is returned as an int.

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 of expr:

  • If it is a ValueCastable whose shape is a ShapeCastable, value is converted to a numeric representation using const() and then assigned.

  • If it is a Value or a ValueCastable whose shape is a Shape, value is assigned as-is.

This method is available in both processes and testbenches.

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

class transactron.testing.TestbenchIO

Bases: Elaboratable

__init__(adapter: AdapterBase)
async call(sim: SimulatorContext, data={}, /, **kwdata) data.Const[data.StructLayout]
async call_do(sim: SimulatorContext) data.Const[data.StructLayout]
call_init(sim: SimulatorContext, data={}, /, **kwdata)
async call_result(sim: SimulatorContext) data.Const[data.StructLayout] | None
async call_try(sim: SimulatorContext, data={}, /, **kwdata) data.Const[data.StructLayout] | None
disable(sim: SimulatorContext)
property done
enable(sim: SimulatorContext)
get_call_result(sim: TestbenchContext) data.Const[data.StructLayout] | None
get_done(sim: TestbenchContext)
get_outputs(sim: TestbenchContext) data.Const[data.StructLayout]
property outputs
sample_outputs(sim: SimulatorContext)
sample_outputs_done(sim: SimulatorContext)
sample_outputs_until_done(sim: SimulatorContext)
set_enable(sim: SimulatorContext, en)
set_inputs(sim: SimulatorContext, data)
transactron.testing.data_const_to_dict(c: data.Const[data.Layout])
transactron.testing.data_layout(val: Shape | int | range | type[Enum] | ShapeCastable) MethodLayout
transactron.testing.def_method_mock(tb_getter: Callable[[], TestbenchIO] | Callable[[Any], TestbenchIO], **kwargs) Callable[[Callable[[...], NameIntDict | None]], Callable[[], MethodMock]]

Decorator function to create method mock handlers. It should be applied on a function which describes functionality which we want to invoke on method call. This function will be called on every clock cycle when the method is active, and also on combinational changes to inputs.

The decorated function can have a single argument arg, which receives the arguments passed to a method as a data.Const, or multiple named arguments, which correspond to named arguments of the method.

This decorator can be applied to function definitions or method definitions. When applied to a method definition, lambdas passed to def_method_mock need to take a self argument, which should be the first.

Mocks defined at class level or at test level are automatically discovered and don’t need to be manually added to the simulation.

Any side effects (state modification, assertions, etc.) need to be guarded using the MethodMock.effect decorator.

Make sure to defer accessing state, since decorators are evaluated eagerly during function declaration.

Parameters:
tb_getterCallable[[], TestbenchIO] | Callable[[Any], TestbenchIO]

Function to get the TestbenchIO of the mocked method.

enableCallable[[], bool] | Callable[[Any], bool]

Function which decides if the method is enabled in a given clock cycle.

validate_argumentsCallable[…, bool]

Function which validates call arguments. This applies only to Adapters with with_validate_arguments set to True.

delayfloat

Simulation time delay for method mock calling. Used for synchronization between different mocks and testbench processes.