From 8b5a971021ee0b0bb645dbf580feb06d7e40f109 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Tue, 17 Oct 2023 18:45:35 +0200 Subject: [PATCH 1/8] Introduce ready function. --- transactron/core.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/transactron/core.py b/transactron/core.py index c4c34f8cc..3ec6158ac 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -63,15 +63,17 @@ class MethodMap: def __init__(self, transactions: Iterable["Transaction"]): self.methods_by_transaction = dict[Transaction, list[Method]]() self.transactions_by_method = defaultdict[Method, list[Transaction]](list) + self.arguments_by_method_by_transaction = defaultdict[Transaction, dict[Method, RecordDict]](dict) def rec(transaction: Transaction, source: TransactionBase): - for method in source.method_uses.keys(): + for method, (arg_rec, _) in source.method_uses.items(): if not method.defined: raise RuntimeError(f"Trying to use method '{method.name}' which is not defined yet") if method in self.methods_by_transaction[transaction]: raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction") self.methods_by_transaction[transaction].append(method) self.transactions_by_method[method].append(transaction) + self.arguments_by_method_by_transaction[transaction][method]=arg_rec rec(transaction, method) for transaction in transactions: @@ -127,6 +129,8 @@ def eager_deterministic_cc_scheduler( ccl = list(cc) ccl.sort(key=lambda transaction: porder[transaction]) for k, transaction in enumerate(ccl): + for method in method_map.methods_by_transaction[transaction]: + method.ready_function(method_map.arguments_by_method_by_transaction[transaction][method]) ready = [method.ready for method in method_map.methods_by_transaction[transaction]] runnable = Cat(ready).all() conflicts = [ccl[j].grant for j in range(k) if ccl[j] in gr[transaction]] @@ -1076,6 +1080,9 @@ def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0)) finally: self.defined = True + def ready_function(self, arg_rec): + pass + def __call__( self, m: TModule, arg: Optional[RecordDict] = None, enable: ValueLike = C(1), /, **kwargs: RecordDict ) -> Record: From 6871a870f4a8975dcc4ec979a61f9d962369dec1 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Thu, 19 Oct 2023 19:01:13 +0200 Subject: [PATCH 2/8] First version of test --- test/transactions/test_methods.py | 80 +++++++++++++++++++++++++++++++ transactron/core.py | 24 +++++----- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/test/transactions/test_methods.py b/test/transactions/test_methods.py index 971eaaa00..584862903 100644 --- a/test/transactions/test_methods.py +++ b/test/transactions/test_methods.py @@ -1,3 +1,4 @@ +import random from amaranth import * from amaranth.sim import * @@ -529,3 +530,82 @@ def process(): with self.run_simulation(circ) as sim: sim.add_sync_process(process) + +class DataDependentConditionalCircuit(Elaboratable): + def __init__(self, n = 2, bad_number = 3): + self.bad_number = bad_number + self.method = Method(i=data_layout(n)) + + self.in_t1 = Record(data_layout(n)) + self.in_t2 = Record(data_layout(n)) + self.ready = Signal() + self.req_t1 = Signal() + self.req_t2 = Signal() + + self.out_m = Signal() + self.out_t1 = Signal() + self.out_t2 = Signal() + + def elaborate(self, platform): + m = TModule() + + @def_method(m, self.method, self.ready, ready_function = lambda rec: rec.data!=self.bad_number) + def _(data): + m.d.comb += self.out_m.eq(1) + + with Transaction().body(m, request = self.req_t1): + m.d.comb += self.out_t1.eq(1) + self.method(m, self.in_t1) + + with Transaction().body(m, request = self.req_t2): + m.d.comb += self.out_t2.eq(1) + self.method(m, self.in_t2) + + return m + +class TestDataDependentConditionalMethod(TestCaseWithSimulator): + def setUp(self): + self.test_number=150 + self.bad_number = 3 + self.n = 2 + self.circ = DataDependentConditionalCircuit(self.n, self.bad_number) + + def test_random(self): + random.seed(14) + def process(): + for _ in range(self.test_number): + in1 = random.randrange(0, 2**self.n) + in2 = random.randrange(0, 2**self.n) + m_ready = random.randrange(2) + req_t1 = random.randrange(2) + req_t2 = random.randrange(2) + + yield self.circ.in_t1.eq(in1) + yield self.circ.in_t2.eq(in2) + yield self.circ.req_t1.eq(req_t1) + yield self.circ.req_t2.eq(req_t2) + yield self.circ.ready.eq(m_ready) + yield Settle() + yield Delay(1e-8) + + out_m = yield self.circ.out_m + out_t1 = yield self.circ.out_t1 + out_t2 = yield self.circ.out_t2 + + if not m_ready or not (req_t1 or req_t2) or (in1 == self.bad_number and in2 == self.bad_number): + self.assertEqual(out_m, 0) + self.assertEqual(out_t1, 0) + self.assertEqual(out_t2, 0) + continue + # Here method global ready signal is high and we requested one of the transactions + # we also know that one of the transactions request correct input data + + self.assertEqual(out_m, 1) + self.assertEqual(out_t1 ^ out_t2, 1) + # inX == self.bad_number implies out_tX==0 + self.assertTrue(in1 != self.bad_number or not out_t1) + self.assertTrue(in2 != self.bad_number or not out_t2) + + with self.run_simulation(self.circ, 100) as sim: + sim.add_process(process) + diff --git a/transactron/core.py b/transactron/core.py index 3ec6158ac..fee6c5cf1 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -63,7 +63,7 @@ class MethodMap: def __init__(self, transactions: Iterable["Transaction"]): self.methods_by_transaction = dict[Transaction, list[Method]]() self.transactions_by_method = defaultdict[Method, list[Transaction]](list) - self.arguments_by_method_by_transaction = defaultdict[Transaction, dict[Method, RecordDict]](dict) + self.arguments_by_method_by_transaction = defaultdict[Transaction, dict[Method, Record]](dict) def rec(transaction: Transaction, source: TransactionBase): for method, (arg_rec, _) in source.method_uses.items(): @@ -129,9 +129,7 @@ def eager_deterministic_cc_scheduler( ccl = list(cc) ccl.sort(key=lambda transaction: porder[transaction]) for k, transaction in enumerate(ccl): - for method in method_map.methods_by_transaction[transaction]: - method.ready_function(method_map.arguments_by_method_by_transaction[transaction][method]) - ready = [method.ready for method in method_map.methods_by_transaction[transaction]] + ready = [method.ready_function(method_map.arguments_by_method_by_transaction[transaction][method]) for method in method_map.methods_by_transaction[transaction]] runnable = Cat(ready).all() conflicts = [ccl[j].grant for j in range(k) if ccl[j] in gr[transaction]] noconflict = ~Cat(conflicts).any() @@ -682,7 +680,7 @@ class TransactionBase(Owned): name: str def __init__(self): - self.method_uses: dict[Method, Tuple[ValueLike, ValueLike]] = dict() + self.method_uses: dict[Method, Tuple[Record, ValueLike]] = dict() self.relations: list[RelationBase] = [] self.simultaneous_list: list[TransactionOrMethod] = [] self.independent_list: list[TransactionOrMethod] = [] @@ -718,7 +716,7 @@ def schedule_before(self, end: TransactionOrMethod) -> None: """ self.relations.append(RelationBase(end=end, priority=Priority.LEFT, conflict=False)) - def use_method(self, method: "Method", arg: ValueLike, enable: ValueLike): + def use_method(self, method: "Method", arg: Record, enable: ValueLike): if method in self.method_uses: raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction '{self.name}'") self.method_uses[method] = (arg, enable) @@ -983,6 +981,7 @@ def __init__( self.data_out = Record(o) self.nonexclusive = nonexclusive self.single_caller = single_caller + self.user_ready_function : Optional[Callable[[Record], ValueLike]] = None if nonexclusive: assert len(self.data_in) == 0 @@ -1026,7 +1025,7 @@ def _(arg): return method(m, arg) @contextmanager - def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0)) -> Iterator[Record]: + def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0), user_ready_function : Optional[Callable[[Record], ValueLike]] = None) -> Iterator[Record]: """Define method body The `body` context manager can be used to define the actions @@ -1070,6 +1069,7 @@ def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0)) if self.defined: raise RuntimeError(f"Method '{self.name}' already defined") self.def_order = next(TransactionBase.def_counter) + self.user_ready_function=user_ready_function try: m.d.av_comb += self.ready.eq(ready) @@ -1080,8 +1080,10 @@ def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0)) finally: self.defined = True - def ready_function(self, arg_rec): - pass + def ready_function(self, arg_rec : Record) -> ValueLike: + if self.user_ready_function is not None: + return self.ready & self.user_ready_function(arg_rec) + return self.ready def __call__( self, m: TModule, arg: Optional[RecordDict] = None, enable: ValueLike = C(1), /, **kwargs: RecordDict @@ -1154,7 +1156,7 @@ def debug_signals(self) -> SignalBundle: return [self.ready, self.run, self.data_in, self.data_out] -def def_method(m: TModule, method: Method, ready: ValueLike = C(1)): +def def_method(m: TModule, method: Method, ready: ValueLike = C(1), ready_function : Optional[Callable[[Record], ValueLike]] = None): """Define a method. This decorator allows to define transactional methods in an @@ -1214,7 +1216,7 @@ def decorator(func: Callable[..., Optional[RecordDict]]): out = Record.like(method.data_out) ret_out = None - with method.body(m, ready=ready, out=out) as arg: + with method.body(m, ready=ready, out=out, user_ready_function=ready_function) as arg: ret_out = method_def_helper(method, func, arg, **arg.fields) if ret_out is not None: From bbc2049816bfad36c645dcd83ac19386d654e2d4 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Thu, 19 Oct 2023 19:07:07 +0200 Subject: [PATCH 3/8] Fix test. --- test/transactions/test_methods.py | 20 +++++++++++--------- transactron/core.py | 26 +++++++++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/test/transactions/test_methods.py b/test/transactions/test_methods.py index 584862903..e5de2ce1f 100644 --- a/test/transactions/test_methods.py +++ b/test/transactions/test_methods.py @@ -531,8 +531,9 @@ def process(): with self.run_simulation(circ) as sim: sim.add_sync_process(process) + class DataDependentConditionalCircuit(Elaboratable): - def __init__(self, n = 2, bad_number = 3): + def __init__(self, n=2, bad_number=3): self.bad_number = bad_number self.method = Method(i=data_layout(n)) @@ -549,29 +550,31 @@ def __init__(self, n = 2, bad_number = 3): def elaborate(self, platform): m = TModule() - @def_method(m, self.method, self.ready, ready_function = lambda rec: rec.data!=self.bad_number) + @def_method(m, self.method, self.ready, ready_function=lambda rec: rec.data != self.bad_number) def _(data): m.d.comb += self.out_m.eq(1) - with Transaction().body(m, request = self.req_t1): + with Transaction().body(m, request=self.req_t1): m.d.comb += self.out_t1.eq(1) self.method(m, self.in_t1) - with Transaction().body(m, request = self.req_t2): + with Transaction().body(m, request=self.req_t2): m.d.comb += self.out_t2.eq(1) self.method(m, self.in_t2) return m + class TestDataDependentConditionalMethod(TestCaseWithSimulator): def setUp(self): - self.test_number=150 + self.test_number = 200 self.bad_number = 3 self.n = 2 self.circ = DataDependentConditionalCircuit(self.n, self.bad_number) def test_random(self): random.seed(14) + def process(): for _ in range(self.test_number): in1 = random.randrange(0, 2**self.n) @@ -587,12 +590,12 @@ def process(): yield self.circ.ready.eq(m_ready) yield Settle() yield Delay(1e-8) - + out_m = yield self.circ.out_m out_t1 = yield self.circ.out_t1 out_t2 = yield self.circ.out_t2 - - if not m_ready or not (req_t1 or req_t2) or (in1 == self.bad_number and in2 == self.bad_number): + + if not m_ready or (not req_t1 or in1 == self.bad_number) and (not req_t2 or in2 == self.bad_number): self.assertEqual(out_m, 0) self.assertEqual(out_t1, 0) self.assertEqual(out_t2, 0) @@ -608,4 +611,3 @@ def process(): with self.run_simulation(self.circ, 100) as sim: sim.add_process(process) - diff --git a/transactron/core.py b/transactron/core.py index fee6c5cf1..e47b640b5 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -73,7 +73,7 @@ def rec(transaction: Transaction, source: TransactionBase): raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction") self.methods_by_transaction[transaction].append(method) self.transactions_by_method[method].append(transaction) - self.arguments_by_method_by_transaction[transaction][method]=arg_rec + self.arguments_by_method_by_transaction[transaction][method] = arg_rec rec(transaction, method) for transaction in transactions: @@ -129,7 +129,10 @@ def eager_deterministic_cc_scheduler( ccl = list(cc) ccl.sort(key=lambda transaction: porder[transaction]) for k, transaction in enumerate(ccl): - ready = [method.ready_function(method_map.arguments_by_method_by_transaction[transaction][method]) for method in method_map.methods_by_transaction[transaction]] + ready = [ + method.ready_function(method_map.arguments_by_method_by_transaction[transaction][method]) + for method in method_map.methods_by_transaction[transaction] + ] runnable = Cat(ready).all() conflicts = [ccl[j].grant for j in range(k) if ccl[j] in gr[transaction]] noconflict = ~Cat(conflicts).any() @@ -981,7 +984,7 @@ def __init__( self.data_out = Record(o) self.nonexclusive = nonexclusive self.single_caller = single_caller - self.user_ready_function : Optional[Callable[[Record], ValueLike]] = None + self.user_ready_function: Optional[Callable[[Record], ValueLike]] = None if nonexclusive: assert len(self.data_in) == 0 @@ -1025,7 +1028,14 @@ def _(arg): return method(m, arg) @contextmanager - def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0), user_ready_function : Optional[Callable[[Record], ValueLike]] = None) -> Iterator[Record]: + def body( + self, + m: TModule, + *, + ready: ValueLike = C(1), + out: ValueLike = C(0, 0), + user_ready_function: Optional[Callable[[Record], ValueLike]] = None, + ) -> Iterator[Record]: """Define method body The `body` context manager can be used to define the actions @@ -1069,7 +1079,7 @@ def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0), if self.defined: raise RuntimeError(f"Method '{self.name}' already defined") self.def_order = next(TransactionBase.def_counter) - self.user_ready_function=user_ready_function + self.user_ready_function = user_ready_function try: m.d.av_comb += self.ready.eq(ready) @@ -1080,7 +1090,7 @@ def body(self, m: TModule, *, ready: ValueLike = C(1), out: ValueLike = C(0, 0), finally: self.defined = True - def ready_function(self, arg_rec : Record) -> ValueLike: + def ready_function(self, arg_rec: Record) -> ValueLike: if self.user_ready_function is not None: return self.ready & self.user_ready_function(arg_rec) return self.ready @@ -1156,7 +1166,9 @@ def debug_signals(self) -> SignalBundle: return [self.ready, self.run, self.data_in, self.data_out] -def def_method(m: TModule, method: Method, ready: ValueLike = C(1), ready_function : Optional[Callable[[Record], ValueLike]] = None): +def def_method( + m: TModule, method: Method, ready: ValueLike = C(1), ready_function: Optional[Callable[[Record], ValueLike]] = None +): """Define a method. This decorator allows to define transactional methods in an From 671cf7ea372bab9e2ddace555367632f002819b6 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Tue, 24 Oct 2023 17:35:33 +0200 Subject: [PATCH 4/8] Generalize method readiness --- transactron/core.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/transactron/core.py b/transactron/core.py index e47b640b5..5ff25f978 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -63,7 +63,7 @@ class MethodMap: def __init__(self, transactions: Iterable["Transaction"]): self.methods_by_transaction = dict[Transaction, list[Method]]() self.transactions_by_method = defaultdict[Method, list[Transaction]](list) - self.arguments_by_method_by_transaction = defaultdict[Transaction, dict[Method, Record]](dict) + self.readiness_by_method_and_transaction = dict[tuple[Transaction, Method], ValueLike]() def rec(transaction: Transaction, source: TransactionBase): for method, (arg_rec, _) in source.method_uses.items(): @@ -73,7 +73,7 @@ def rec(transaction: Transaction, source: TransactionBase): raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction") self.methods_by_transaction[transaction].append(method) self.transactions_by_method[method].append(transaction) - self.arguments_by_method_by_transaction[transaction][method] = arg_rec + self.readiness_by_method_and_transaction[(transaction,method)] = method.ready_function(arg_rec) rec(transaction, method) for transaction in transactions: @@ -130,7 +130,7 @@ def eager_deterministic_cc_scheduler( ccl.sort(key=lambda transaction: porder[transaction]) for k, transaction in enumerate(ccl): ready = [ - method.ready_function(method_map.arguments_by_method_by_transaction[transaction][method]) + method_map.readiness_by_method_and_transaction[(transaction,method)] for method in method_map.methods_by_transaction[transaction] ] runnable = Cat(ready).all() @@ -168,11 +168,11 @@ def trivial_roundrobin_cc_scheduler( sched = Scheduler(len(cc)) m.submodules.scheduler = sched for k, transaction in enumerate(cc): - methods = method_map.methods_by_transaction[transaction] - ready = Signal(len(methods)) - for n, method in enumerate(methods): - m.d.comb += ready[n].eq(method.ready) - runnable = ready.all() + ready = [ + method_map.readiness_by_method_and_transaction[(transaction,method)] + for method in method_map.methods_by_transaction[transaction] + ] + runnable = Cat(ready).all() m.d.comb += sched.requests[k].eq(transaction.request & runnable) m.d.comb += transaction.grant.eq(sched.grant[k] & sched.valid) return m From fe25ecb5da3d5493fe402660324772b8f2ca1bd5 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Tue, 24 Oct 2023 17:58:11 +0200 Subject: [PATCH 5/8] Support kwargs. --- test/transactions/test_methods.py | 16 +++++++++++----- transactron/core.py | 6 +++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/test/transactions/test_methods.py b/test/transactions/test_methods.py index e5de2ce1f..7270f6f57 100644 --- a/test/transactions/test_methods.py +++ b/test/transactions/test_methods.py @@ -533,9 +533,9 @@ def process(): class DataDependentConditionalCircuit(Elaboratable): - def __init__(self, n=2, bad_number=3): - self.bad_number = bad_number + def __init__(self, n=2, ready_function = lambda arg: arg.data != 3): self.method = Method(i=data_layout(n)) + self.ready_function = ready_function self.in_t1 = Record(data_layout(n)) self.in_t2 = Record(data_layout(n)) @@ -550,7 +550,7 @@ def __init__(self, n=2, bad_number=3): def elaborate(self, platform): m = TModule() - @def_method(m, self.method, self.ready, ready_function=lambda rec: rec.data != self.bad_number) + @def_method(m, self.method, self.ready, ready_function=self.ready_function) def _(data): m.d.comb += self.out_m.eq(1) @@ -570,10 +570,10 @@ def setUp(self): self.test_number = 200 self.bad_number = 3 self.n = 2 - self.circ = DataDependentConditionalCircuit(self.n, self.bad_number) - def test_random(self): + def base_random(self, f): random.seed(14) + self.circ = DataDependentConditionalCircuit(n = self.n, ready_function = f) def process(): for _ in range(self.test_number): @@ -611,3 +611,9 @@ def process(): with self.run_simulation(self.circ, 100) as sim: sim.add_process(process) + + def test_random_arg(self): + self.base_random(lambda arg: arg.data != self.bad_number) + + def test_random_kwarg(self): + self.base_random(lambda data: data != self.bad_number) diff --git a/transactron/core.py b/transactron/core.py index 5ff25f978..9e3c03e31 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -73,7 +73,7 @@ def rec(transaction: Transaction, source: TransactionBase): raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction") self.methods_by_transaction[transaction].append(method) self.transactions_by_method[method].append(transaction) - self.readiness_by_method_and_transaction[(transaction,method)] = method.ready_function(arg_rec) + self.readiness_by_method_and_transaction[(transaction,method)] = method._ready_function(arg_rec) rec(transaction, method) for transaction in transactions: @@ -1090,9 +1090,9 @@ def body( finally: self.defined = True - def ready_function(self, arg_rec: Record) -> ValueLike: + def _ready_function(self, arg_rec: Record) -> ValueLike: if self.user_ready_function is not None: - return self.ready & self.user_ready_function(arg_rec) + return self.ready & method_def_helper(self, self.user_ready_function, arg_rec, **arg_rec.fields) return self.ready def __call__( From 8e79cc7a6e5154853de54d3a18b1c95f490a6e9d Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Tue, 24 Oct 2023 18:01:01 +0200 Subject: [PATCH 6/8] Change name of user_ready_function --- transactron/core.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/transactron/core.py b/transactron/core.py index 9e3c03e31..b00ff9b62 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -984,7 +984,7 @@ def __init__( self.data_out = Record(o) self.nonexclusive = nonexclusive self.single_caller = single_caller - self.user_ready_function: Optional[Callable[[Record], ValueLike]] = None + self.ready_function: Optional[Callable[[Record], ValueLike]] = None if nonexclusive: assert len(self.data_in) == 0 @@ -1034,7 +1034,7 @@ def body( *, ready: ValueLike = C(1), out: ValueLike = C(0, 0), - user_ready_function: Optional[Callable[[Record], ValueLike]] = None, + ready_function: Optional[Callable[[Record], ValueLike]] = None, ) -> Iterator[Record]: """Define method body @@ -1079,7 +1079,7 @@ def body( if self.defined: raise RuntimeError(f"Method '{self.name}' already defined") self.def_order = next(TransactionBase.def_counter) - self.user_ready_function = user_ready_function + self.ready_function = ready_function try: m.d.av_comb += self.ready.eq(ready) @@ -1091,8 +1091,8 @@ def body( self.defined = True def _ready_function(self, arg_rec: Record) -> ValueLike: - if self.user_ready_function is not None: - return self.ready & method_def_helper(self, self.user_ready_function, arg_rec, **arg_rec.fields) + if self.ready_function is not None: + return self.ready & method_def_helper(self, self.ready_function, arg_rec, **arg_rec.fields) return self.ready def __call__( @@ -1228,7 +1228,7 @@ def decorator(func: Callable[..., Optional[RecordDict]]): out = Record.like(method.data_out) ret_out = None - with method.body(m, ready=ready, out=out, user_ready_function=ready_function) as arg: + with method.body(m, ready=ready, out=out, ready_function=ready_function) as arg: ret_out = method_def_helper(method, func, arg, **arg.fields) if ret_out is not None: From 4e39bfc593af93f6db5abc3896a39c0a8703a370 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Tue, 24 Oct 2023 18:09:03 +0200 Subject: [PATCH 7/8] Update doc strings. --- test/transactions/test_methods.py | 4 ++-- transactron/core.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/test/transactions/test_methods.py b/test/transactions/test_methods.py index 7270f6f57..a3bad4f67 100644 --- a/test/transactions/test_methods.py +++ b/test/transactions/test_methods.py @@ -533,7 +533,7 @@ def process(): class DataDependentConditionalCircuit(Elaboratable): - def __init__(self, n=2, ready_function = lambda arg: arg.data != 3): + def __init__(self, n=2, ready_function=lambda arg: arg.data != 3): self.method = Method(i=data_layout(n)) self.ready_function = ready_function @@ -573,7 +573,7 @@ def setUp(self): def base_random(self, f): random.seed(14) - self.circ = DataDependentConditionalCircuit(n = self.n, ready_function = f) + self.circ = DataDependentConditionalCircuit(n=self.n, ready_function=f) def process(): for _ in range(self.test_number): diff --git a/transactron/core.py b/transactron/core.py index b00ff9b62..4e9c8acfe 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -73,7 +73,7 @@ def rec(transaction: Transaction, source: TransactionBase): raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction") self.methods_by_transaction[transaction].append(method) self.transactions_by_method[method].append(transaction) - self.readiness_by_method_and_transaction[(transaction,method)] = method._ready_function(arg_rec) + self.readiness_by_method_and_transaction[(transaction, method)] = method._ready_function(arg_rec) rec(transaction, method) for transaction in transactions: @@ -130,7 +130,7 @@ def eager_deterministic_cc_scheduler( ccl.sort(key=lambda transaction: porder[transaction]) for k, transaction in enumerate(ccl): ready = [ - method_map.readiness_by_method_and_transaction[(transaction,method)] + method_map.readiness_by_method_and_transaction[(transaction, method)] for method in method_map.methods_by_transaction[transaction] ] runnable = Cat(ready).all() @@ -169,7 +169,7 @@ def trivial_roundrobin_cc_scheduler( m.submodules.scheduler = sched for k, transaction in enumerate(cc): ready = [ - method_map.readiness_by_method_and_transaction[(transaction,method)] + method_map.readiness_by_method_and_transaction[(transaction, method)] for method in method_map.methods_by_transaction[transaction] ] runnable = Cat(ready).all() @@ -1058,6 +1058,11 @@ def body( Data generated by the `Method`, which will be passed to the caller (a `Transaction` or another `Method`). Assigned combinationally to the `data_out` attribute. + ready_function: Optional[Callable[[Record], ValueLike]] + Function to instantiate a combinational circuit for each + method caller. It should take input arguments and return + if the method can be called with those arguments. By default + there is no function, so all arguments are accepted. Returns ------- @@ -1193,6 +1198,11 @@ def def_method( Signal to indicate if the method is ready to be run. By default it is `Const(1)`, so the method is always ready. Assigned combinationally to the `ready` attribute. + ready_function: Optional[Callable[[Record], ValueLike]] + Function to instantiate a combinational circuit for each + method caller. It should take input arguments and return + if the method can be called with those arguments. By default + there is no function, so all arguments are accepted. Examples -------- From b54aeb35ad0dfe17ca096d5d39051c51a4f622d5 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sat, 28 Oct 2023 12:13:54 +0200 Subject: [PATCH 8/8] Fix comments from review. --- test/transactions/test_methods.py | 2 +- transactron/core.py | 43 +++++++++++++++++-------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/test/transactions/test_methods.py b/test/transactions/test_methods.py index a3bad4f67..df05d259f 100644 --- a/test/transactions/test_methods.py +++ b/test/transactions/test_methods.py @@ -550,7 +550,7 @@ def __init__(self, n=2, ready_function=lambda arg: arg.data != 3): def elaborate(self, platform): m = TModule() - @def_method(m, self.method, self.ready, ready_function=self.ready_function) + @def_method(m, self.method, self.ready, validate_arguments=self.ready_function) def _(data): m.d.comb += self.out_m.eq(1) diff --git a/transactron/core.py b/transactron/core.py index 5d8a80eab..aa24f21a7 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -85,7 +85,7 @@ def rec(transaction: Transaction, source: TransactionBase): raise RuntimeError(f"Method '{method.name}' can't be called twice from the same transaction") self.methods_by_transaction[transaction].append(method) self.transactions_by_method[method].append(transaction) - self.readiness_by_method_and_transaction[(transaction, method)] = method._ready_function(arg_rec) + self.readiness_by_method_and_transaction[(transaction, method)] = method._validate_arguments(arg_rec) rec(transaction, method) for transaction in transactions: @@ -999,7 +999,7 @@ def __init__( self.data_out = Record(o) self.nonexclusive = nonexclusive self.single_caller = single_caller - self.ready_function: Optional[Callable[[Record], ValueLike]] = None + self.validate_arguments: Optional[Callable[..., ValueLike]] = None if nonexclusive: assert len(self.data_in) == 0 @@ -1049,7 +1049,7 @@ def body( *, ready: ValueLike = C(1), out: ValueLike = C(0, 0), - ready_function: Optional[Callable[[Record], ValueLike]] = None, + validate_arguments: Optional[Callable[..., ValueLike]] = None, ) -> Iterator[Record]: """Define method body @@ -1073,11 +1073,12 @@ def body( Data generated by the `Method`, which will be passed to the caller (a `Transaction` or another `Method`). Assigned combinationally to the `data_out` attribute. - ready_function: Optional[Callable[[Record], ValueLike]] - Function to instantiate a combinational circuit for each - method caller. It should take input arguments and return - if the method can be called with those arguments. By default - there is no function, so all arguments are accepted. + validate_arguments: Optional[Callable[..., ValueLike]] + Function that takes input arguments used to call the method + and checks whether the method can be called with those arguments. + It instantiates a combinational circuit for each + method caller. By default, there is no function, so all arguments + are accepted. Returns ------- @@ -1099,7 +1100,7 @@ def body( if self.defined: raise RuntimeError(f"Method '{self.name}' already defined") self.def_order = next(TransactionBase.def_counter) - self.ready_function = ready_function + self.validate_arguments = validate_arguments try: m.d.av_comb += self.ready.eq(ready) @@ -1110,9 +1111,9 @@ def body( finally: self.defined = True - def _ready_function(self, arg_rec: Record) -> ValueLike: - if self.ready_function is not None: - return self.ready & method_def_helper(self, self.ready_function, arg_rec, **arg_rec.fields) + def _validate_arguments(self, arg_rec: Record) -> ValueLike: + if self.validate_arguments is not None: + return self.ready & method_def_helper(self, self.validate_arguments, arg_rec, **arg_rec.fields) return self.ready def __call__( @@ -1187,7 +1188,10 @@ def debug_signals(self) -> SignalBundle: def def_method( - m: TModule, method: Method, ready: ValueLike = C(1), ready_function: Optional[Callable[[Record], ValueLike]] = None + m: TModule, + method: Method, + ready: ValueLike = C(1), + validate_arguments: Optional[Callable[..., ValueLike]] = None, ): """Define a method. @@ -1213,11 +1217,12 @@ def def_method( Signal to indicate if the method is ready to be run. By default it is `Const(1)`, so the method is always ready. Assigned combinationally to the `ready` attribute. - ready_function: Optional[Callable[[Record], ValueLike]] - Function to instantiate a combinational circuit for each - method caller. It should take input arguments and return - if the method can be called with those arguments. By default - there is no function, so all arguments are accepted. + validate_arguments: Optional[Callable[..., ValueLike]] + Function that takes input arguments used to call the method + and checks whether the method can be called with those arguments. + It instantiates a combinational circuit for each + method caller. By default, there is no function, so all arguments + are accepted. Examples -------- @@ -1253,7 +1258,7 @@ def decorator(func: Callable[..., Optional[RecordDict]]): out = Record.like(method.data_out) ret_out = None - with method.body(m, ready=ready, out=out, ready_function=ready_function) as arg: + with method.body(m, ready=ready, out=out, validate_arguments=validate_arguments) as arg: ret_out = method_def_helper(method, func, arg, **arg.fields) if ret_out is not None: