Skip to content

Commit

Permalink
Nonexclusive wrapper (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
tilk authored Feb 6, 2025
1 parent 5444360 commit 7219ec1
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 0 deletions.
70 changes: 70 additions & 0 deletions test/lib/test_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from transactron import *
from transactron.lib.adapters import Adapter, AdapterTrans
from transactron.lib.transformers import *
from transactron.testing.testbenchio import CallTrigger
from transactron.utils._typing import ModuleLike, MethodStruct, RecordDict
from transactron.utils import ModuleConnector
from transactron.testing import (
Expand Down Expand Up @@ -294,3 +295,72 @@ def elaborate(self, platform):
m.submodules.method = self.method = TestbenchIO(AdapterTrans(product.use(m)))

return m


class NonexclusiveWrapperTestCircuit(Elaboratable):
def __init__(self, iosize: int, wrappers: int, callers: int):
self.iosize = iosize
self.wrappers = wrappers
self.callers = callers
self.sources: list[list[TestbenchIO]] = []

def elaborate(self, platform):
m = TModule()

layout = data_layout(self.iosize)

m.submodules.target = self.target = TestbenchIO(Adapter.create(i=layout, o=layout))

for i in range(self.wrappers):
nonex = NonexclusiveWrapper(self.target.adapter.iface).use(m)
sources = []
self.sources.append(sources)

for j in range(self.callers):
m.submodules[f"source_{i}_{j}"] = source = TestbenchIO(AdapterTrans(nonex))
sources.append(source)

return m


class TestNonexclusiveWrapper(TestCaseWithSimulator):
def test_nonexclusive_wrapper(self):
iosize = 4
wrappers = 2
callers = 2
iterations = 100
m = NonexclusiveWrapperTestCircuit(iosize, wrappers, callers)

def caller_process(i: int):
async def process(sim: TestbenchContext):
for _ in range(iterations):
j = random.randrange(callers)
data = random.randrange(2**iosize)
ret = await m.sources[i][j].call(sim, data=data)
assert ret.data == (data + 1) % (2**iosize)
await self.random_wait_geom(sim, 0.5)

return process

@def_method_mock(lambda: m.target)
def target(data):
return {"data": data + 1}

with self.run_simulation(m) as sim:
self.add_mock(sim, target())
for i in range(wrappers):
sim.add_testbench(caller_process(i))

def test_no_conflict(self):
m = NonexclusiveWrapperTestCircuit(1, 1, 2)

async def process(sim: TestbenchContext):
res1, res2 = await CallTrigger(sim).call(m.sources[0][0], data=1).call(m.sources[0][1], data=2).until_done()
assert res1 is not None and res2 is not None # there was no conflict, however the result is undefined

@def_method_mock(lambda: m.target)
def target(data):
return {"data": data + 1}

with self.run_simulation(m) as sim:
sim.add_testbench(process)
25 changes: 25 additions & 0 deletions transactron/lib/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"Collector",
"CatTrans",
"ConnectAndMapTrans",
"NonexclusiveWrapper",
]


Expand Down Expand Up @@ -453,3 +454,27 @@ def elaborate(self, platform):
m.submodules.connect = ConnectTrans(self.method1, transformer.method)

return m


class NonexclusiveWrapper(Elaboratable, Transformer):
"""Nonexclusive wrapper around a method.
Useful when you can assume, for external reasons, that a given method will
never be called more than once in a given clock cycle - even when the
call graph indicates it could.
Possible use case is unifying parallel pipelines with the same latency.
"""

def __init__(self, target: Method):
self.target = target
self.method = Method.like(target)

def elaborate(self, platform):
m = TModule()

@def_method(m, self.method, nonexclusive=True)
def _(arg):
return self.target(m, arg)

return m

0 comments on commit 7219ec1

Please sign in to comment.