From b060029bf3da13a1752fd074aaaabaca63877fcc Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Tue, 5 Dec 2023 22:42:51 +0100 Subject: [PATCH 1/6] sim.core: Add `passive` argument to `add_*`. --- amaranth/sim/core.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index 67003d1d7..4b80b6886 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -80,18 +80,22 @@ def _check_process(self, process): .format(process)) return process - def add_process(self, process): + def add_process(self, process, *, passive=False): process = self._check_process(process) def wrapper(): + if passive: + yield Passive() # Only start a bench process after comb settling, so that the initial values are correct. yield object.__new__(Settle) yield from process() self._engine.add_coroutine_process(wrapper, default_cmd=None) @deprecated("The `add_sync_process` method is deprecated per RFC 47. Use `add_process` or `add_testbench` instead.") - def add_sync_process(self, process, *, domain="sync"): + def add_sync_process(self, process, *, domain="sync", passive=False): process = self._check_process(process) def wrapper(): + if passive: + yield Passive() # Only start a sync process after the first clock edge (or reset edge, if the domain # uses an asynchronous reset). This matches the behavior of synchronous FFs. generator = process() @@ -114,9 +118,11 @@ def wrapper(): exception = e self._engine.add_coroutine_process(wrapper, default_cmd=Tick(domain)) - def add_testbench(self, process): + def add_testbench(self, process, *, passive=False): process = self._check_process(process) def wrapper(): + if passive: + yield Passive() generator = process() # Only start a bench process after power-on reset finishes. Use object.__new__ to # avoid deprecation warning. From 080d00c8f13cdf0124526dab00022902171f69db Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Wed, 6 Dec 2023 21:34:20 +0100 Subject: [PATCH 2/6] sim.core: Allow async testbenches. --- amaranth/sim/core.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index 4b80b6886..b7c0d6c43 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -87,7 +87,10 @@ def wrapper(): yield Passive() # Only start a bench process after comb settling, so that the initial values are correct. yield object.__new__(Settle) - yield from process() + generator = process() + if inspect.isawaitable(generator): + generator = generator.__await__() + yield from generator self._engine.add_coroutine_process(wrapper, default_cmd=None) @deprecated("The `add_sync_process` method is deprecated per RFC 47. Use `add_process` or `add_testbench` instead.") @@ -99,6 +102,8 @@ def wrapper(): # Only start a sync process after the first clock edge (or reset edge, if the domain # uses an asynchronous reset). This matches the behavior of synchronous FFs. generator = process() + if inspect.isawaitable(generator): + generator = generator.__await__() result = None exception = None yield Tick(domain) @@ -124,6 +129,8 @@ def wrapper(): if passive: yield Passive() generator = process() + if inspect.isawaitable(generator): + generator = generator.__await__() # Only start a bench process after power-on reset finishes. Use object.__new__ to # avoid deprecation warning. yield object.__new__(Settle) From a3ff5bfa03ea356bc8fb8bdb6c3dc71eb74741b5 Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Wed, 6 Dec 2023 21:36:05 +0100 Subject: [PATCH 3/6] sim.core: Add `sim` argument to testbench functions. --- amaranth/sim/_pycoro.py | 6 +++++- amaranth/sim/core.py | 44 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/amaranth/sim/_pycoro.py b/amaranth/sim/_pycoro.py index 085eb009c..97d38abcf 100644 --- a/amaranth/sim/_pycoro.py +++ b/amaranth/sim/_pycoro.py @@ -3,7 +3,7 @@ from ..hdl import * from ..hdl._ast import Statement, SignalSet, ValueCastable from ..hdl._mem import MemorySimRead, MemorySimWrite -from .core import Tick, Settle, Delay, Passive, Active +from .core import Tick, Settle, Delay, Passive, Active, _Changed from ._base import BaseProcess, BaseMemoryState from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler @@ -104,6 +104,10 @@ def run(self): self.add_trigger(domain.rst, trigger=1) return + elif type(command) is _Changed: + self.add_trigger(command.signal, trigger=command.value) + return + elif type(command) is Settle: self.state.wait_interval(self, None) return diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index b7c0d6c43..4082cc4ca 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -58,6 +58,35 @@ def __repr__(self): return "(active)" +class _Changed(Command): + def __init__(self, signal, value=None): + self.signal = signal + self.value = value + + +class _AsyncTrigger: + def __init__(self, cmd): + self.cmd = cmd + + def __await__(self): + yield self.cmd + + async def until(self, condition): + while not (await condition.get()): + await self + + +class SimulatorContext: + def delay(self, interval=None): + return _AsyncTrigger(Delay(interval)) + + def tick(self, domain="sync"): + return _AsyncTrigger(Tick(domain)) + + def changed(self, signal, value=None): + return _AsyncTrigger(_Changed(signal, value)) + + class Simulator: def __init__(self, fragment, *, engine="pysim"): if isinstance(engine, type) and issubclass(engine, BaseEngine): @@ -87,7 +116,10 @@ def wrapper(): yield Passive() # Only start a bench process after comb settling, so that the initial values are correct. yield object.__new__(Settle) - generator = process() + if "sim" in inspect.signature(process).parameters: + generator = process(sim=SimulatorContext()) + else: + generator = process() if inspect.isawaitable(generator): generator = generator.__await__() yield from generator @@ -101,7 +133,10 @@ def wrapper(): yield Passive() # Only start a sync process after the first clock edge (or reset edge, if the domain # uses an asynchronous reset). This matches the behavior of synchronous FFs. - generator = process() + if "sim" in inspect.signature(process).parameters: + generator = process(sim=SimulatorContext()) + else: + generator = process() if inspect.isawaitable(generator): generator = generator.__await__() result = None @@ -128,7 +163,10 @@ def add_testbench(self, process, *, passive=False): def wrapper(): if passive: yield Passive() - generator = process() + if "sim" in inspect.signature(process).parameters: + generator = process(sim=SimulatorContext()) + else: + generator = process() if inspect.isawaitable(generator): generator = generator.__await__() # Only start a bench process after power-on reset finishes. Use object.__new__ to From 55b828b2a889bdc67b1e84c00ef2784c3657049d Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Mon, 18 Mar 2024 23:43:02 +0100 Subject: [PATCH 4/6] sim.core: Add `sim.get()`/`sim.set()`. --- amaranth/sim/core.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index 4082cc4ca..0f19a65d8 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -64,6 +64,14 @@ def __init__(self, signal, value=None): self.value = value +class _AwaitableCmd: + def __init__(self, obj): + self.obj = obj + + def __await__(self): + return (yield self.obj) + + class _AsyncTrigger: def __init__(self, cmd): self.cmd = cmd @@ -77,6 +85,12 @@ async def until(self, condition): class SimulatorContext: + def get(self, expr): + return _AwaitableCmd(expr) + + def set(self, expr, value): + return _AwaitableCmd(expr.eq(value)) + def delay(self, interval=None): return _AsyncTrigger(Delay(interval)) From e74ccbb400b338f9b8898e8c1369dffa275df064 Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Mon, 18 Mar 2024 23:45:29 +0100 Subject: [PATCH 5/6] sim.core: Add `sim.memory_read()`/`sim.memory_write()`. --- amaranth/hdl/_mem.py | 3 ++- amaranth/sim/_pycoro.py | 3 ++- amaranth/sim/core.py | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/amaranth/hdl/_mem.py b/amaranth/hdl/_mem.py index 20bcd9a4d..0f9c80601 100644 --- a/amaranth/hdl/_mem.py +++ b/amaranth/hdl/_mem.py @@ -25,11 +25,12 @@ def eq(self, value): class MemorySimWrite: - def __init__(self, identity, addr, data): + def __init__(self, identity, addr, data, mask = None): assert isinstance(identity, MemoryIdentity) self._identity = identity self._addr = Value.cast(addr) self._data = Value.cast(data) + self._mask = mask class MemoryInstance(Fragment): diff --git a/amaranth/sim/_pycoro.py b/amaranth/sim/_pycoro.py index 97d38abcf..e6e276a55 100644 --- a/amaranth/sim/_pycoro.py +++ b/amaranth/sim/_pycoro.py @@ -140,10 +140,11 @@ def run(self): exec(_RHSValueCompiler.compile(self.state, command._data, mode="curr"), self.exec_locals) data = Const(self.exec_locals["result"], command._data.shape()).value + mask = command._mask index = self.state.memories[command._identity] state = self.state.slots[index] assert isinstance(state, BaseMemoryState) - state.write(addr, data) + state.write(addr, data, mask) elif command is None: # only possible if self.default_cmd is None raise TypeError("Received default command from process {!r} that was added " diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index 0f19a65d8..b37d61bcc 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -4,6 +4,7 @@ from .._utils import deprecated from ..hdl._cd import * from ..hdl._ir import * +from ..hdl._mem import MemorySimRead, MemorySimWrite from ._base import BaseEngine @@ -91,6 +92,12 @@ def get(self, expr): def set(self, expr, value): return _AwaitableCmd(expr.eq(value)) + def memory_read(self, instance, address): + return _AwaitableCmd(MemorySimRead(instance, address)) + + def memory_write(self, instance, address, value, mask=None): + return _AwaitableCmd(MemorySimWrite(instance, address, value, mask)) + def delay(self, interval=None): return _AsyncTrigger(Delay(interval)) From 6faab6ee9a0b9b5c5b2973112235ea72a10d62c7 Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Mon, 18 Mar 2024 23:49:24 +0100 Subject: [PATCH 6/6] sim.core: Update triggers per RFC 36. --- amaranth/sim/_pycoro.py | 19 ++++++++++-- amaranth/sim/core.py | 68 +++++++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/amaranth/sim/_pycoro.py b/amaranth/sim/_pycoro.py index e6e276a55..121576d9a 100644 --- a/amaranth/sim/_pycoro.py +++ b/amaranth/sim/_pycoro.py @@ -3,7 +3,7 @@ from ..hdl import * from ..hdl._ast import Statement, SignalSet, ValueCastable from ..hdl._mem import MemorySimRead, MemorySimWrite -from .core import Tick, Settle, Delay, Passive, Active, _Changed +from .core import Tick, Settle, Delay, Passive, Active, _CombinableTrigger from ._base import BaseProcess, BaseMemoryState from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler @@ -104,8 +104,21 @@ def run(self): self.add_trigger(domain.rst, trigger=1) return - elif type(command) is _Changed: - self.add_trigger(command.signal, trigger=command.value) + elif type(command) is _CombinableTrigger: + delay_interval = None + for trigger, *args in command._triggers: + if trigger == 'edge': + signal, value = args + self.add_trigger(signal, trigger=value) + elif trigger == 'changed': + for signal in args: + self.add_trigger(signal) + elif trigger == 'delay': + interval, = args + if delay_interval is None or delay_interval > interval: + delay_interval = interval + if delay_interval is not None: + self.state.wait_interval(self, delay_interval * 1e12) return elif type(command) is Settle: diff --git a/amaranth/sim/core.py b/amaranth/sim/core.py index b37d61bcc..eba087e5d 100644 --- a/amaranth/sim/core.py +++ b/amaranth/sim/core.py @@ -59,12 +59,6 @@ def __repr__(self): return "(active)" -class _Changed(Command): - def __init__(self, signal, value=None): - self.signal = signal - self.value = value - - class _AwaitableCmd: def __init__(self, obj): self.obj = obj @@ -73,17 +67,50 @@ def __await__(self): return (yield self.obj) -class _AsyncTrigger: - def __init__(self, cmd): - self.cmd = cmd +class _DomainTrigger: + def __init__(self, sim, domain, context): + self._sim = sim + self._domain = domain + self._context = context def __await__(self): - yield self.cmd + yield Tick(self.domain) async def until(self, condition): - while not (await condition.get()): + while not await self._sim.get(condition): await self + async def repeat(self, times): + for _ in range(times): + await self + + +class _CombinableTrigger: + def __init__(self, triggers=None): + self._triggers = [] if triggers is None else triggers + + def __await__(self): + yield self + + async def __aiter__(self): + while True: + yield await self + + def delay(self, interval): + return _CombinableTrigger(self._triggers + [('delay', interval)]) + + def changed(self, *signals): + return _CombinableTrigger(self._triggers + [('changed', signals)]) + + def edge(self, signal, value): + return _CombinableTrigger(self._triggers + [('edge', signal, value)]) + + def posedge(self, signal): + return self.edge(signal, 1) + + def negedge(self, signal): + return self.edge(signal, 0) + class SimulatorContext: def get(self, expr): @@ -98,14 +125,23 @@ def memory_read(self, instance, address): def memory_write(self, instance, address, value, mask=None): return _AwaitableCmd(MemorySimWrite(instance, address, value, mask)) + def tick(self, domain="sync", context=None): + return _DomainTrigger(self, domain, context) + def delay(self, interval=None): - return _AsyncTrigger(Delay(interval)) + return _CombinableTrigger().delay(interval) + + def changed(self, *signals): + return _CombinableTrigger().changed(*signals) + + def edge(self, signal, value): + return _CombinableTrigger().edge(signal, value) - def tick(self, domain="sync"): - return _AsyncTrigger(Tick(domain)) + def posedge(self, signal): + return _CombinableTrigger().posedge(signal) - def changed(self, signal, value=None): - return _AsyncTrigger(_Changed(signal, value)) + def negedge(self, signal): + return _CombinableTrigger().negedge(signal) class Simulator: