From 044b12533009ae5e9dff9a3f7c0176ce249cee9b Mon Sep 17 00:00:00 2001 From: lekcyjna123 <34948061+lekcyjna123@users.noreply.github.com> Date: Sun, 17 Mar 2024 14:16:49 +0100 Subject: [PATCH 1/6] Split transactron core (#617) --- coreblocks/structs_common/rs.py | 2 +- scripts/core_graph.py | 3 +- test/regression/pysim.py | 2 +- test/transactions/test_branches.py | 4 +- test/transactions/test_transaction_lib.py | 3 +- test/transactions/test_transactions.py | 9 +- transactron/__init__.py | 11 +- transactron/core.py | 1514 --------------------- transactron/core/__init__.py | 6 + transactron/core/keys.py | 13 + transactron/core/manager.py | 486 +++++++ transactron/core/method.py | 299 ++++ transactron/core/schedulers.py | 77 ++ transactron/core/sugar.py | 90 ++ transactron/core/tmodule.py | 286 ++++ transactron/core/transaction.py | 115 ++ transactron/core/transaction_base.py | 209 +++ transactron/lib/adapters.py | 3 +- transactron/lib/buttons.py | 2 +- transactron/lib/connectors.py | 2 +- transactron/lib/reqres.py | 2 +- transactron/lib/transformers.py | 12 +- transactron/profiler.py | 3 +- transactron/testing/infrastructure.py | 3 +- transactron/testing/profiler.py | 3 +- transactron/testing/testbenchio.py | 3 +- transactron/utils/_typing.py | 2 + transactron/utils/gen.py | 4 +- 28 files changed, 1618 insertions(+), 1550 deletions(-) delete mode 100644 transactron/core.py create mode 100644 transactron/core/__init__.py create mode 100644 transactron/core/keys.py create mode 100644 transactron/core/manager.py create mode 100644 transactron/core/method.py create mode 100644 transactron/core/schedulers.py create mode 100644 transactron/core/sugar.py create mode 100644 transactron/core/tmodule.py create mode 100644 transactron/core/transaction.py create mode 100644 transactron/core/transaction_base.py diff --git a/coreblocks/structs_common/rs.py b/coreblocks/structs_common/rs.py index fe8d04ba4..6af0b5e2a 100644 --- a/coreblocks/structs_common/rs.py +++ b/coreblocks/structs_common/rs.py @@ -4,7 +4,7 @@ from amaranth.lib.coding import PriorityEncoder from transactron import Method, def_method, TModule from coreblocks.params import RSLayouts, GenParams, OpType -from transactron.core import RecordDict +from transactron.utils import RecordDict from transactron.utils.transactron_helpers import make_layout __all__ = ["RS"] diff --git a/scripts/core_graph.py b/scripts/core_graph.py index 6818f6dd0..5024a88f8 100755 --- a/scripts/core_graph.py +++ b/scripts/core_graph.py @@ -17,7 +17,8 @@ from transactron.graph import TracingFragment # noqa: E402 from test.test_core import CoreTestElaboratable # noqa: E402 from coreblocks.params.configurations import basic_core_config # noqa: E402 -from transactron.core import TransactionManagerKey, TransactionModule # noqa: E402 +from transactron.core import TransactionModule # noqa: E402 +from transactron.core.keys import TransactionManagerKey # noqa: E402 gp = GenParams(basic_core_config) elaboratable = CoreTestElaboratable(gp) diff --git a/test/regression/pysim.py b/test/regression/pysim.py index 804687bba..796c1ab63 100644 --- a/test/regression/pysim.py +++ b/test/regression/pysim.py @@ -6,7 +6,7 @@ from amaranth.utils import exact_log2 from amaranth import * -from transactron.core import TransactionManagerKey +from transactron.core.keys import TransactionManagerKey from .memory import * from .common import SimulationBackend, SimulationExecutionResult diff --git a/test/transactions/test_branches.py b/test/transactions/test_branches.py index f66b954b7..2b38636a3 100644 --- a/test/transactions/test_branches.py +++ b/test/transactions/test_branches.py @@ -1,8 +1,6 @@ from amaranth import * from itertools import product from transactron.core import ( - CtrlPath, - MethodMap, TModule, Method, Transaction, @@ -10,6 +8,8 @@ TransactionModule, def_method, ) +from transactron.core.tmodule import CtrlPath +from transactron.core.manager import MethodMap from unittest import TestCase from transactron.testing import TestCaseWithSimulator from transactron.utils.dependencies import DependencyContext diff --git a/test/transactions/test_transaction_lib.py b/test/transactions/test_transaction_lib.py index 058557f22..dd3899964 100644 --- a/test/transactions/test_transaction_lib.py +++ b/test/transactions/test_transaction_lib.py @@ -10,10 +10,9 @@ from amaranth import * from transactron import * -from transactron.core import RecordDict from transactron.lib import * from coreblocks.utils import * -from transactron.utils._typing import ModuleLike, MethodStruct +from transactron.utils._typing import ModuleLike, MethodStruct, RecordDict from transactron.utils import ModuleConnector from transactron.testing import ( SimpleTestCircuit, diff --git a/test/transactions/test_transactions.py b/test/transactions/test_transactions.py index c73a1642b..53dec0821 100644 --- a/test/transactions/test_transactions.py +++ b/test/transactions/test_transactions.py @@ -15,12 +15,9 @@ from transactron.lib import Adapter, AdapterTrans from transactron.utils import Scheduler -from transactron.core import ( - Priority, - TransactionScheduler, - trivial_roundrobin_cc_scheduler, - eager_deterministic_cc_scheduler, -) +from transactron.core import Priority +from transactron.core.schedulers import trivial_roundrobin_cc_scheduler, eager_deterministic_cc_scheduler +from transactron.core.manager import TransactionScheduler from transactron.utils.dependencies import DependencyContext diff --git a/transactron/__init__.py b/transactron/__init__.py index de27375ac..c162fe991 100644 --- a/transactron/__init__.py +++ b/transactron/__init__.py @@ -1,10 +1 @@ -from .core import * - -__all__ = [ - "TModule", - "TransactionManager", - "TransactionModule", - "Transaction", - "Method", - "def_method", -] +from .core import * # noqa: F401 diff --git a/transactron/core.py b/transactron/core.py deleted file mode 100644 index e85627437..000000000 --- a/transactron/core.py +++ /dev/null @@ -1,1514 +0,0 @@ -from collections import defaultdict, deque -from collections.abc import Collection, Sequence, Iterable, Callable, Mapping, Iterator -from contextlib import contextmanager -from enum import Enum, auto -from typing import ( - ClassVar, - NoReturn, - TypeAlias, - TypedDict, - Union, - Optional, - Tuple, - TypeVar, - Protocol, - Self, - runtime_checkable, -) -from os import environ -from graphlib import TopologicalSorter -from dataclasses import dataclass, replace -from amaranth import * -from amaranth import tracer -from itertools import count, chain, filterfalse, product -from amaranth.hdl._dsl import FSM - -from transactron.utils.assign import AssignArg - -from .graph import Owned, OwnershipGraph, Direction -from transactron.utils import * -from transactron.utils.transactron_helpers import _graph_ccs - -__all__ = [ - "MethodLayout", - "Priority", - "TModule", - "TransactionManager", - "TransactionManagerKey", - "TransactionModule", - "Transaction", - "Method", - "eager_deterministic_cc_scheduler", - "trivial_roundrobin_cc_scheduler", - "def_method", -] - - -TransactionGraph: TypeAlias = Graph["Transaction"] -TransactionGraphCC: TypeAlias = GraphCC["Transaction"] -PriorityOrder: TypeAlias = dict["Transaction", int] -TransactionScheduler: TypeAlias = Callable[["MethodMap", TransactionGraph, TransactionGraphCC, PriorityOrder], Module] -RecordDict: TypeAlias = ValueLike | Mapping[str, "RecordDict"] -TransactionOrMethod: TypeAlias = Union["Transaction", "Method"] -TransactionOrMethodBound = TypeVar("TransactionOrMethodBound", "Transaction", "Method") - - -class Priority(Enum): - #: Conflicting transactions/methods don't have a priority order. - UNDEFINED = auto() - #: Left transaction/method is prioritized over the right one. - LEFT = auto() - #: Right transaction/method is prioritized over the left one. - RIGHT = auto() - - -class RelationBase(TypedDict): - end: TransactionOrMethod - priority: Priority - conflict: bool - silence_warning: bool - - -class Relation(RelationBase): - start: TransactionOrMethod - - -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.readiness_by_method_and_transaction = dict[tuple[Transaction, Method], ValueLike]() - self.method_parents = defaultdict[Method, list[TransactionBase]](list) - - def rec(transaction: Transaction, source: TransactionBase): - 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.readiness_by_method_and_transaction[(transaction, method)] = method._validate_arguments(arg_rec) - rec(transaction, method) - - for transaction in transactions: - self.methods_by_transaction[transaction] = [] - rec(transaction, transaction) - - for transaction_or_method in self.methods_and_transactions: - for method in transaction_or_method.method_uses.keys(): - self.method_parents[method].append(transaction_or_method) - - def transactions_for(self, elem: TransactionOrMethod) -> Collection["Transaction"]: - if isinstance(elem, Transaction): - return [elem] - else: - return self.transactions_by_method[elem] - - @property - def methods(self) -> Collection["Method"]: - return self.transactions_by_method.keys() - - @property - def transactions(self) -> Collection["Transaction"]: - return self.methods_by_transaction.keys() - - @property - def methods_and_transactions(self) -> Iterable[TransactionOrMethod]: - return chain(self.methods, self.transactions) - - -def eager_deterministic_cc_scheduler( - method_map: MethodMap, gr: TransactionGraph, cc: TransactionGraphCC, porder: PriorityOrder -) -> Module: - """eager_deterministic_cc_scheduler - - This function generates an eager scheduler for the transaction - subsystem. It isn't fair, because it starts transactions using - transaction index in `cc` as a priority. Transaction with the lowest - index has the highest priority. - - If there are two different transactions which have no conflicts then - they will be started concurrently. - - Parameters - ---------- - manager : TransactionManager - TransactionManager which uses this instance of scheduler for - arbitrating which agent should get a grant signal. - gr : TransactionGraph - Graph of conflicts between transactions, where vertices are transactions and edges are conflicts. - cc : Set[Transaction] - Connected components of the graph `gr` for which scheduler - should be generated. - porder : PriorityOrder - Linear ordering of transactions which is consistent with priority constraints. - """ - m = Module() - ccl = list(cc) - ccl.sort(key=lambda transaction: porder[transaction]) - for k, transaction in enumerate(ccl): - conflicts = [ccl[j].grant for j in range(k) if ccl[j] in gr[transaction]] - noconflict = ~Cat(conflicts).any() - m.d.comb += transaction.grant.eq(transaction.request & transaction.runnable & noconflict) - return m - - -def trivial_roundrobin_cc_scheduler( - method_map: MethodMap, gr: TransactionGraph, cc: TransactionGraphCC, porder: PriorityOrder -) -> Module: - """trivial_roundrobin_cc_scheduler - - This function generates a simple round-robin scheduler for the transaction - subsystem. In a one cycle there will be at most one transaction granted - (in a given connected component of the conflict graph), even if there is - another ready, non-conflicting, transaction. It is mainly for testing - purposes. - - Parameters - ---------- - manager : TransactionManager - TransactionManager which uses this instance of scheduler for - arbitrating which agent should get grant signal. - gr : TransactionGraph - Graph of conflicts between transactions, where vertices are transactions and edges are conflicts. - cc : Set[Transaction] - Connected components of the graph `gr` for which scheduler - should be generated. - porder : PriorityOrder - Linear ordering of transactions which is consistent with priority constraints. - """ - m = Module() - sched = Scheduler(len(cc)) - m.submodules.scheduler = sched - for k, transaction in enumerate(cc): - m.d.comb += sched.requests[k].eq(transaction.request & transaction.runnable) - m.d.comb += transaction.grant.eq(sched.grant[k] & sched.valid) - return m - - -class TransactionManager(Elaboratable): - """Transaction manager - - This module is responsible for granting `Transaction`\\s and running - `Method`\\s. It takes care that two conflicting `Transaction`\\s - are never granted in the same clock cycle. - """ - - def __init__(self, cc_scheduler: TransactionScheduler = eager_deterministic_cc_scheduler): - self.transactions: list[Transaction] = [] - self.cc_scheduler = cc_scheduler - - def add_transaction(self, transaction: "Transaction"): - self.transactions.append(transaction) - - @staticmethod - def _conflict_graph(method_map: MethodMap) -> Tuple[TransactionGraph, TransactionGraph, PriorityOrder]: - """_conflict_graph - - This function generates the graph of transaction conflicts. Conflicts - between transactions can be explicit or implicit. Two transactions - conflict explicitly, if a conflict was added between the transactions - or the methods used by them via `add_conflict`. Two transactions - conflict implicitly if they are both using the same method. - - Created graph is undirected. Transactions are nodes in that graph - and conflict between two transactions is marked as an edge. In such - representation connected components are sets of transactions which can - potentially conflict so there is a need to arbitrate between them. - On the other hand when two transactions are in different connected - components, then they can be scheduled independently, because they - will have no conflicts. - - This function also computes a linear ordering of transactions - which is consistent with conflict priorities of methods and - transactions. When priority constraints cannot be satisfied, - an exception is thrown. - - Returns - ------- - cgr : TransactionGraph - Graph of conflicts between transactions, where vertices are transactions and edges are conflicts. - rgr : TransactionGraph - Graph of relations between transactions, which includes conflicts and orderings. - porder : PriorityOrder - Linear ordering of transactions which is consistent with priority constraints. - """ - - def transactions_exclusive(trans1: Transaction, trans2: Transaction): - tms1 = [trans1] + method_map.methods_by_transaction[trans1] - tms2 = [trans2] + method_map.methods_by_transaction[trans2] - - # if first transaction is exclusive with the second transaction, or this is true for - # any called methods, the transactions will never run at the same time - for tm1, tm2 in product(tms1, tms2): - if tm1.ctrl_path.exclusive_with(tm2.ctrl_path): - return True - - return False - - cgr: TransactionGraph = {} # Conflict graph - pgr: TransactionGraph = {} # Priority graph - rgr: TransactionGraph = {} # Relation graph - - def add_edge(begin: Transaction, end: Transaction, priority: Priority, conflict: bool): - rgr[begin].add(end) - rgr[end].add(begin) - if conflict: - cgr[begin].add(end) - cgr[end].add(begin) - match priority: - case Priority.LEFT: - pgr[end].add(begin) - case Priority.RIGHT: - pgr[begin].add(end) - - for transaction in method_map.transactions: - cgr[transaction] = set() - pgr[transaction] = set() - rgr[transaction] = set() - - for method in method_map.methods: - if method.nonexclusive: - continue - for transaction1 in method_map.transactions_for(method): - for transaction2 in method_map.transactions_for(method): - if transaction1 is not transaction2 and not transactions_exclusive(transaction1, transaction2): - add_edge(transaction1, transaction2, Priority.UNDEFINED, True) - - relations = [ - Relation(**relation, start=elem) - for elem in method_map.methods_and_transactions - for relation in elem.relations - ] - - for relation in relations: - start = relation["start"] - end = relation["end"] - if not relation["conflict"]: # relation added with schedule_before - if end.def_order < start.def_order and not relation["silence_warning"]: - raise RuntimeError(f"{start.name!r} scheduled before {end.name!r}, but defined afterwards") - - for trans_start in method_map.transactions_for(start): - for trans_end in method_map.transactions_for(end): - conflict = relation["conflict"] and not transactions_exclusive(trans_start, trans_end) - add_edge(trans_start, trans_end, relation["priority"], conflict) - - porder: PriorityOrder = {} - - for k, transaction in enumerate(TopologicalSorter(pgr).static_order()): - porder[transaction] = k - - return cgr, rgr, porder - - @staticmethod - def _method_enables(method_map: MethodMap) -> Mapping["Transaction", Mapping["Method", ValueLike]]: - method_enables = defaultdict[Transaction, dict[Method, ValueLike]](dict) - enables: list[ValueLike] = [] - - def rec(transaction: Transaction, source: TransactionOrMethod): - for method, (_, enable) in source.method_uses.items(): - enables.append(enable) - rec(transaction, method) - method_enables[transaction][method] = Cat(*enables).all() - enables.pop() - - for transaction in method_map.transactions: - rec(transaction, transaction) - - return method_enables - - @staticmethod - def _method_calls( - m: Module, method_map: MethodMap - ) -> tuple[Mapping["Method", Sequence[ValueLike]], Mapping["Method", Sequence[ValueLike]]]: - args = defaultdict[Method, list[ValueLike]](list) - runs = defaultdict[Method, list[ValueLike]](list) - - for source in method_map.methods_and_transactions: - if isinstance(source, Method): - run_val = Cat(transaction.grant for transaction in method_map.transactions_by_method[source]).any() - run = Signal() - m.d.comb += run.eq(run_val) - else: - run = source.grant - for method, (arg, _) in source.method_uses.items(): - args[method].append(arg) - runs[method].append(run) - - return (args, runs) - - def _simultaneous(self): - method_map = MethodMap(self.transactions) - - # remove orderings between simultaneous methods/transactions - # TODO: can it be done after transitivity, possibly catching more cases? - for elem in method_map.methods_and_transactions: - all_sims = frozenset(elem.simultaneous_list) - elem.relations = list( - filterfalse( - lambda relation: not relation["conflict"] - and relation["priority"] != Priority.UNDEFINED - and relation["end"] in all_sims, - elem.relations, - ) - ) - - # step 1: simultaneous and independent sets generation - independents = defaultdict[Transaction, set[Transaction]](set) - - for elem in method_map.methods_and_transactions: - indeps = frozenset[Transaction]().union( - *(frozenset(method_map.transactions_for(ind)) for ind in chain([elem], elem.independent_list)) - ) - for transaction1, transaction2 in product(indeps, indeps): - independents[transaction1].add(transaction2) - - simultaneous = set[frozenset[Transaction]]() - - for elem in method_map.methods_and_transactions: - for sim_elem in elem.simultaneous_list: - for tr1, tr2 in product(method_map.transactions_for(elem), method_map.transactions_for(sim_elem)): - if tr1 in independents[tr2]: - raise RuntimeError( - f"Unsatisfiable simultaneity constraints for '{elem.name}' and '{sim_elem.name}'" - ) - simultaneous.add(frozenset({tr1, tr2})) - - # step 2: transitivity computation - tr_simultaneous = set[frozenset[Transaction]]() - - def conflicting(group: frozenset[Transaction]): - return any(tr1 != tr2 and tr1 in independents[tr2] for tr1 in group for tr2 in group) - - q = deque[frozenset[Transaction]](simultaneous) - - while q: - new_group = q.popleft() - if new_group in tr_simultaneous or conflicting(new_group): - continue - q.extend(new_group | other_group for other_group in simultaneous if new_group & other_group) - tr_simultaneous.add(new_group) - - # step 3: maximal group selection - def maximal(group: frozenset[Transaction]): - return not any(group.issubset(group2) and group != group2 for group2 in tr_simultaneous) - - final_simultaneous = set(filter(maximal, tr_simultaneous)) - - # step 4: convert transactions to methods - joined_transactions = set[Transaction]().union(*final_simultaneous) - - self.transactions = list(filter(lambda t: t not in joined_transactions, self.transactions)) - methods = dict[Transaction, Method]() - - for transaction in joined_transactions: - # TODO: some simpler way? - method = Method(name=transaction.name) - method.owner = transaction.owner - method.src_loc = transaction.src_loc - method.ready = transaction.request - method.run = transaction.grant - method.defined = transaction.defined - method.method_calls = transaction.method_calls - method.method_uses = transaction.method_uses - method.relations = transaction.relations - method.def_order = transaction.def_order - method.ctrl_path = transaction.ctrl_path - methods[transaction] = method - - for elem in method_map.methods_and_transactions: - # I guess method/transaction unification is really needed - for relation in elem.relations: - if relation["end"] in methods: - relation["end"] = methods[relation["end"]] - - # step 5: construct merged transactions - m = TModule() - m._MustUse__silence = True # type: ignore - - for group in final_simultaneous: - name = "_".join([t.name for t in group]) - with Transaction(manager=self, name=name).body(m): - for transaction in group: - methods[transaction](m) - - return m - - def elaborate(self, platform): - # In the following, various problems in the transaction set-up are detected. - # The exception triggers an unused Elaboratable warning. - with silence_mustuse(self): - merge_manager = self._simultaneous() - - method_map = MethodMap(self.transactions) - cgr, rgr, porder = TransactionManager._conflict_graph(method_map) - - m = Module() - m.submodules.merge_manager = merge_manager - - for elem in method_map.methods_and_transactions: - elem._set_method_uses(m) - - for transaction in self.transactions: - ready = [ - method_map.readiness_by_method_and_transaction[transaction, method] - for method in method_map.methods_by_transaction[transaction] - ] - m.d.comb += transaction.runnable.eq(Cat(ready).all()) - - ccs = _graph_ccs(rgr) - m.submodules._transactron_schedulers = ModuleConnector( - *[self.cc_scheduler(method_map, cgr, cc, porder) for cc in ccs] - ) - - method_enables = self._method_enables(method_map) - - for method, transactions in method_map.transactions_by_method.items(): - granted = Cat(transaction.grant & method_enables[transaction][method] for transaction in transactions) - m.d.comb += method.run.eq(granted.any()) - - (method_args, method_runs) = self._method_calls(m, method_map) - - for method in method_map.methods: - if len(method_args[method]) == 1: - m.d.comb += method.data_in.eq(method_args[method][0]) - else: - if method.single_caller: - raise RuntimeError(f"Single-caller method '{method.name}' called more than once") - - runs = Cat(method_runs[method]) - for i in OneHotSwitchDynamic(m, runs): - m.d.comb += method.data_in.eq(method_args[method][i]) - - if "TRANSACTRON_VERBOSE" in environ: - self.print_info(cgr, porder, ccs, method_map) - - return m - - def print_info( - self, cgr: TransactionGraph, porder: PriorityOrder, ccs: list[GraphCC["Transaction"]], method_map: MethodMap - ): - print("Transactron statistics") - print(f"\tMethods: {len(method_map.methods)}") - print(f"\tTransactions: {len(method_map.transactions)}") - print(f"\tIndependent subgraphs: {len(ccs)}") - print(f"\tAvg callers per method: {average_dict_of_lists(method_map.transactions_by_method):.2f}") - print(f"\tAvg conflicts per transaction: {average_dict_of_lists(cgr):.2f}") - print("") - print("Transaction subgraphs") - for cc in ccs: - ccl = list(cc) - ccl.sort(key=lambda t: porder[t]) - for t in ccl: - print(f"\t{t.name}") - print("") - print("Calling transactions per method") - for m, ts in method_map.transactions_by_method.items(): - print(f"\t{m.owned_name}: {m.src_loc[0]}:{m.src_loc[1]}") - for t in ts: - print(f"\t\t{t.name}: {t.src_loc[0]}:{t.src_loc[1]}") - print("") - print("Called methods per transaction") - for t, ms in method_map.methods_by_transaction.items(): - print(f"\t{t.name}: {t.src_loc[0]}:{t.src_loc[1]}") - for m in ms: - print(f"\t\t{m.owned_name}: {m.src_loc[0]}:{m.src_loc[1]}") - print("") - - def visual_graph(self, fragment): - graph = OwnershipGraph(fragment) - method_map = MethodMap(self.transactions) - for method, transactions in method_map.transactions_by_method.items(): - if len(method.data_in.as_value()) > len(method.data_out.as_value()): - direction = Direction.IN - elif method.data_in.shape().size < method.data_out.shape().size: - direction = Direction.OUT - else: - direction = Direction.INOUT - graph.insert_node(method) - for transaction in transactions: - graph.insert_node(transaction) - graph.insert_edge(transaction, method, direction) - - return graph - - def debug_signals(self) -> SignalBundle: - method_map = MethodMap(self.transactions) - cgr, _, _ = TransactionManager._conflict_graph(method_map) - - def transaction_debug(t: Transaction): - return ( - [t.request, t.grant] - + [m.ready for m in method_map.methods_by_transaction[t]] - + [t2.grant for t2 in cgr[t]] - ) - - def method_debug(m: Method): - return [m.ready, m.run, {t.name: transaction_debug(t) for t in method_map.transactions_by_method[m]}] - - return { - "transactions": {t.name: transaction_debug(t) for t in method_map.transactions}, - "methods": {m.owned_name: method_debug(m) for m in method_map.methods}, - } - - -@dataclass(frozen=True) -class TransactionManagerKey(SimpleKey[TransactionManager]): - pass - - -class TransactionModule(Elaboratable): - """ - `TransactionModule` is used as wrapper on `Elaboratable` classes, - which adds support for transactions. It creates a - `TransactionManager` which will handle transaction scheduling - and can be used in definition of `Method`\\s and `Transaction`\\s. - The `TransactionManager` is stored in a `DependencyManager`. - """ - - def __init__( - self, - elaboratable: HasElaborate, - dependency_manager: Optional[DependencyManager] = None, - transaction_manager: Optional[TransactionManager] = None, - ): - """ - Parameters - ---------- - elaboratable: HasElaborate - The `Elaboratable` which should be wrapped to add support for - transactions and methods. - dependency_manager: DependencyManager, optional - The `DependencyManager` to use inside the transaction module. - If omitted, a new one is created. - transaction_manager: TransactionManager, optional - The `TransactionManager` to use inside the transaction module. - If omitted, a new one is created. - """ - if transaction_manager is None: - transaction_manager = TransactionManager() - if dependency_manager is None: - dependency_manager = DependencyManager() - self.manager = dependency_manager - self.manager.add_dependency(TransactionManagerKey(), transaction_manager) - self.elaboratable = elaboratable - - def context(self) -> DependencyContext: - return DependencyContext(self.manager) - - def elaborate(self, platform): - with silence_mustuse(self.manager.get_dependency(TransactionManagerKey())): - with self.context(): - elaboratable = Fragment.get(self.elaboratable, platform) - - m = Module() - - m.submodules.main_module = elaboratable - m.submodules.transactionManager = self.manager.get_dependency(TransactionManagerKey()) - - return m - - -class _AvoidingModuleBuilderDomain: - """ - A wrapper over Amaranth domain to abstract away internal Amaranth implementation. - It is needed to allow for correctness check in `__setattr__` which uses `isinstance`. - """ - - def __init__(self, amaranth_module_domain): - self._domain = amaranth_module_domain - - def __iadd__(self, assigns: StatementLike) -> Self: - self._domain.__iadd__(assigns) - return self - - -class _AvoidingModuleBuilderDomains: - _m: "TModule" - - def __init__(self, m: "TModule"): - object.__setattr__(self, "_m", m) - - def __getattr__(self, name: str) -> _AvoidingModuleBuilderDomain: - if name == "av_comb": - return _AvoidingModuleBuilderDomain(self._m.avoiding_module.d["comb"]) - elif name == "top_comb": - return _AvoidingModuleBuilderDomain(self._m.top_module.d["comb"]) - else: - return _AvoidingModuleBuilderDomain(self._m.main_module.d[name]) - - def __getitem__(self, name: str) -> _AvoidingModuleBuilderDomain: - return self.__getattr__(name) - - def __setattr__(self, name: str, value): - if not isinstance(value, _AvoidingModuleBuilderDomain): - raise AttributeError(f"Cannot assign 'd.{name}' attribute; did you mean 'd.{name} +='?") - - def __setitem__(self, name: str, value): - return self.__setattr__(name, value) - - -class EnterType(Enum): - """Characterizes stack behavior of Amaranth's context managers for control structures.""" - - #: Used for `m.If`, `m.Switch` and `m.FSM`. - PUSH = auto() - #: Used for `m.Elif` and `m.Else`. - ADD = auto() - #: Used for `m.Case`, `m.Default` and `m.State`. - ENTRY = auto() - - -@dataclass(frozen=True) -class PathEdge: - """Describes an edge in Amaranth's control tree. - - Attributes - ---------- - alt : int - Which alternative (e.g. case of `m.If` or m.Switch`) is described. - par : int - Which parallel control structure (e.g. `m.If` at the same level) is described. - """ - - alt: int = 0 - par: int = 0 - - -@dataclass -class CtrlPath: - """Describes a path in Amaranth's control tree. - - Attributes - ---------- - module : int - Unique number of the module the path refers to. - path : list[PathEdge] - Path in the control tree, starting from the root. - """ - - module: int - path: list[PathEdge] - - def exclusive_with(self, other: "CtrlPath"): - """Decides if this path is mutually exclusive with some other path. - - Paths are mutually exclusive if they refer to the same module and - diverge on different alternatives of the same control structure. - - Arguments - --------- - other : CtrlPath - The other path this path is compared to. - """ - common_prefix = [] - for a, b in zip(self.path, other.path): - if a == b: - common_prefix.append(a) - elif a.par != b.par: - return False - else: - break - - return ( - self.module == other.module - and len(common_prefix) != len(self.path) - and len(common_prefix) != len(other.path) - ) - - -class CtrlPathBuilder: - """Constructs control paths. - - Used internally by `TModule`.""" - - def __init__(self, module: int): - """ - Parameters - ---------- - module: int - Unique module identifier. - """ - self.module = module - self.ctrl_path: list[PathEdge] = [] - self.previous: Optional[PathEdge] = None - - @contextmanager - def enter(self, enter_type=EnterType.PUSH): - et = EnterType - - match enter_type: - case et.ADD: - assert self.previous is not None - self.ctrl_path.append(replace(self.previous, alt=self.previous.alt + 1)) - case et.ENTRY: - self.ctrl_path[-1] = replace(self.ctrl_path[-1], alt=self.ctrl_path[-1].alt + 1) - case et.PUSH: - if self.previous is not None: - self.ctrl_path.append(PathEdge(par=self.previous.par + 1)) - else: - self.ctrl_path.append(PathEdge()) - self.previous = None - try: - yield - finally: - if enter_type in [et.PUSH, et.ADD]: - self.previous = self.ctrl_path.pop() - - def build_ctrl_path(self): - """Returns the current control path.""" - return CtrlPath(self.module, self.ctrl_path[:]) - - -class TModule(ModuleLike, Elaboratable): - """Extended Amaranth module for use with transactions. - - It includes three different combinational domains: - - * `comb` domain, works like the `comb` domain in plain Amaranth modules. - Statements in `comb` are guarded by every condition, including - `AvoidedIf`. This means they are guarded by transaction and method - bodies: they don't execute if the given transaction/method is not run. - * `av_comb` is guarded by all conditions except `AvoidedIf`. This means - they are not guarded by transaction and method bodies. This allows to - reduce the amount of useless multplexers due to transaction use, while - still allowing the use of conditions in transaction/method bodies. - * `top_comb` is unguarded: statements added to this domain always - execute. It can be used to reduce combinational path length due to - multplexers while keeping related combinational and synchronous - statements together. - """ - - __next_uid = 0 - - def __init__(self): - self.main_module = Module() - self.avoiding_module = Module() - self.top_module = Module() - self.d = _AvoidingModuleBuilderDomains(self) - self.submodules = self.main_module.submodules - self.domains = self.main_module.domains - self.fsm: Optional[FSM] = None - self.uid = TModule.__next_uid - self.path_builder = CtrlPathBuilder(self.uid) - TModule.__next_uid += 1 - - @contextmanager - def AvoidedIf(self, cond: ValueLike): # noqa: N802 - with self.main_module.If(cond): - with self.path_builder.enter(EnterType.PUSH): - yield - - @contextmanager - def If(self, cond: ValueLike): # noqa: N802 - with self.main_module.If(cond): - with self.avoiding_module.If(cond): - with self.path_builder.enter(EnterType.PUSH): - yield - - @contextmanager - def Elif(self, cond): # noqa: N802 - with self.main_module.Elif(cond): - with self.avoiding_module.Elif(cond): - with self.path_builder.enter(EnterType.ADD): - yield - - @contextmanager - def Else(self): # noqa: N802 - with self.main_module.Else(): - with self.avoiding_module.Else(): - with self.path_builder.enter(EnterType.ADD): - yield - - @contextmanager - def Switch(self, test: ValueLike): # noqa: N802 - with self.main_module.Switch(test): - with self.avoiding_module.Switch(test): - with self.path_builder.enter(EnterType.PUSH): - yield - - @contextmanager - def Case(self, *patterns: SwitchKey): # noqa: N802 - with self.main_module.Case(*patterns): - with self.avoiding_module.Case(*patterns): - with self.path_builder.enter(EnterType.ENTRY): - yield - - @contextmanager - def Default(self): # noqa: N802 - with self.main_module.Default(): - with self.avoiding_module.Default(): - with self.path_builder.enter(EnterType.ENTRY): - yield - - @contextmanager - def FSM(self, reset: Optional[str] = None, domain: str = "sync", name: str = "fsm"): # noqa: N802 - old_fsm = self.fsm - with self.main_module.FSM(reset, domain, name) as fsm: - self.fsm = fsm - with self.path_builder.enter(EnterType.PUSH): - yield fsm - self.fsm = old_fsm - - @contextmanager - def State(self, name: str): # noqa: N802 - assert self.fsm is not None - with self.main_module.State(name): - with self.avoiding_module.If(self.fsm.ongoing(name)): - with self.path_builder.enter(EnterType.ENTRY): - yield - - @property - def next(self) -> NoReturn: - raise NotImplementedError - - @next.setter - def next(self, name: str): - self.main_module.next = name - - @property - def ctrl_path(self): - return self.path_builder.build_ctrl_path() - - @property - def _MustUse__silence(self): # noqa: N802 - return self.main_module._MustUse__silence - - @_MustUse__silence.setter - def _MustUse__silence(self, value): # noqa: N802 - self.main_module._MustUse__silence = value # type: ignore - self.avoiding_module._MustUse__silence = value # type: ignore - self.top_module._MustUse__silence = value # type: ignore - - def elaborate(self, platform): - self.main_module.submodules._avoiding_module = self.avoiding_module - self.main_module.submodules._top_module = self.top_module - return self.main_module - - -@runtime_checkable -class TransactionBase(Owned, Protocol): - stack: ClassVar[list[Union["Transaction", "Method"]]] = [] - def_counter: ClassVar[count] = count() - def_order: int - defined: bool = False - name: str - src_loc: SrcLoc - method_uses: dict["Method", tuple[MethodStruct, Signal]] - method_calls: defaultdict["Method", list[tuple[CtrlPath, MethodStruct, ValueLike]]] - relations: list[RelationBase] - simultaneous_list: list[TransactionOrMethod] - independent_list: list[TransactionOrMethod] - ctrl_path: CtrlPath = CtrlPath(-1, []) - - def __init__(self, *, src_loc: int | SrcLoc): - self.src_loc = get_src_loc(src_loc) - self.method_uses = {} - self.method_calls = defaultdict(list) - self.relations = [] - self.simultaneous_list = [] - self.independent_list = [] - - def add_conflict(self, end: TransactionOrMethod, priority: Priority = Priority.UNDEFINED) -> None: - """Registers a conflict. - - Record that that the given `Transaction` or `Method` cannot execute - simultaneously with this `Method` or `Transaction`. Typical reason - is using a common resource (register write or memory port). - - Parameters - ---------- - end: Transaction or Method - The conflicting `Transaction` or `Method` - priority: Priority, optional - Is one of conflicting `Transaction`\\s or `Method`\\s prioritized? - Defaults to undefined priority relation. - """ - self.relations.append( - RelationBase(end=end, priority=priority, conflict=True, silence_warning=self.owner != end.owner) - ) - - def schedule_before(self, end: TransactionOrMethod) -> None: - """Adds a priority relation. - - Record that that the given `Transaction` or `Method` needs to be - scheduled before this `Method` or `Transaction`, without adding - a conflict. Typical reason is data forwarding. - - Parameters - ---------- - end: Transaction or Method - The other `Transaction` or `Method` - """ - self.relations.append( - RelationBase(end=end, priority=Priority.LEFT, conflict=False, silence_warning=self.owner != end.owner) - ) - - def simultaneous(self, *others: TransactionOrMethod) -> None: - """Adds simultaneity relations. - - The given `Transaction`\\s or `Method``\\s will execute simultaneously - (in the same clock cycle) with this `Transaction` or `Method`. - - Parameters - ---------- - *others: Transaction or Method - The `Transaction`\\s or `Method`\\s to be executed simultaneously. - """ - self.simultaneous_list += others - - def simultaneous_alternatives(self, *others: TransactionOrMethod) -> None: - """Adds exclusive simultaneity relations. - - Each of the given `Transaction`\\s or `Method``\\s will execute - simultaneously (in the same clock cycle) with this `Transaction` or - `Method`. However, each of the given `Transaction`\\s or `Method`\\s - will be separately considered for execution. - - Parameters - ---------- - *others: Transaction or Method - The `Transaction`\\s or `Method`\\s to be executed simultaneously, - but mutually exclusive, with this `Transaction` or `Method`. - """ - self.simultaneous(*others) - others[0]._independent(*others[1:]) - - def _independent(self, *others: TransactionOrMethod) -> None: - """Adds independence relations. - - This `Transaction` or `Method`, together with all the given - `Transaction`\\s or `Method`\\s, will never be considered (pairwise) - for simultaneous execution. - - Warning: this function is an implementation detail, do not use in - user code. - - Parameters - ---------- - *others: Transaction or Method - The `Transaction`\\s or `Method`\\s which, together with this - `Transaction` or `Method`, need to be independently considered - for execution. - """ - self.independent_list += others - - @contextmanager - def context(self: TransactionOrMethodBound, m: TModule) -> Iterator[TransactionOrMethodBound]: - self.ctrl_path = m.ctrl_path - - parent = TransactionBase.peek() - if parent is not None: - parent.schedule_before(self) - - TransactionBase.stack.append(self) - - try: - yield self - finally: - TransactionBase.stack.pop() - self.defined = True - - def _set_method_uses(self, m: ModuleLike): - for method, calls in self.method_calls.items(): - arg_rec, enable_sig = self.method_uses[method] - if len(calls) == 1: - m.d.comb += arg_rec.eq(calls[0][1]) - m.d.comb += enable_sig.eq(calls[0][2]) - else: - call_ens = Cat([en for _, _, en in calls]) - - for i in OneHotSwitchDynamic(m, call_ens): - m.d.comb += arg_rec.eq(calls[i][1]) - m.d.comb += enable_sig.eq(1) - - @classmethod - def get(cls) -> Self: - ret = cls.peek() - if ret is None: - raise RuntimeError("No current body") - return ret - - @classmethod - def peek(cls) -> Optional[Self]: - if not TransactionBase.stack: - return None - if not isinstance(TransactionBase.stack[-1], cls): - raise RuntimeError(f"Current body not a {cls.__name__}") - return TransactionBase.stack[-1] - - @property - def owned_name(self): - if self.owner is not None and self.owner.__class__.__name__ != self.name: - return f"{self.owner.__class__.__name__}_{self.name}" - else: - return self.name - - -class Transaction(TransactionBase): - """Transaction. - - A `Transaction` represents a task which needs to be regularly done. - Execution of a `Transaction` always lasts a single clock cycle. - A `Transaction` signals readiness for execution by setting the - `request` signal. If the conditions for its execution are met, it - can be granted by the `TransactionManager`. - - A `Transaction` can, as part of its execution, call a number of - `Method`\\s. A `Transaction` can be granted only if every `Method` - it runs is ready. - - A `Transaction` cannot execute concurrently with another, conflicting - `Transaction`. Conflicts between `Transaction`\\s are either explicit - or implicit. An explicit conflict is added using the `add_conflict` - method. Implicit conflicts arise between pairs of `Transaction`\\s - which use the same `Method`. - - A module which defines a `Transaction` should use `body` to - describe used methods and the transaction's effect on the module state. - The used methods should be called inside the `body`'s - `with` block. - - Attributes - ---------- - name: str - Name of this `Transaction`. - request: Signal, in - Signals that the transaction wants to run. If omitted, the transaction - is always ready. Defined in the constructor. - runnable: Signal, out - Signals that all used methods are ready. - grant: Signal, out - Signals that the transaction is granted by the `TransactionManager`, - and all used methods are called. - """ - - def __init__( - self, *, name: Optional[str] = None, manager: Optional[TransactionManager] = None, src_loc: int | SrcLoc = 0 - ): - """ - Parameters - ---------- - name: str or None - Name hint for this `Transaction`. If `None` (default) the name is - inferred from the variable name this `Transaction` is assigned to. - If the `Transaction` was not assigned, the name is inferred from - the class name where the `Transaction` was constructed. - manager: TransactionManager - The `TransactionManager` controlling this `Transaction`. - If omitted, the manager is received from `TransactionContext`. - src_loc: int | SrcLoc - How many stack frames deep the source location is taken from. - Alternatively, the source location to use instead of the default. - """ - super().__init__(src_loc=get_src_loc(src_loc)) - self.owner, owner_name = get_caller_class_name(default="$transaction") - self.name = name or tracer.get_var_name(depth=2, default=owner_name) - if manager is None: - manager = DependencyContext.get().get_dependency(TransactionManagerKey()) - manager.add_transaction(self) - self.request = Signal(name=self.owned_name + "_request") - self.runnable = Signal(name=self.owned_name + "_runnable") - self.grant = Signal(name=self.owned_name + "_grant") - - @contextmanager - def body(self, m: TModule, *, request: ValueLike = C(1)) -> Iterator["Transaction"]: - """Defines the `Transaction` body. - - This context manager allows to conveniently define the actions - performed by a `Transaction` when it's granted. Each assignment - added to a domain under `body` is guarded by the `grant` signal. - Combinational assignments which do not need to be guarded by - `grant` can be added to `m.d.top_comb` or `m.d.av_comb` instead of - `m.d.comb`. `Method` calls can be performed under `body`. - - Parameters - ---------- - m: TModule - The module where the `Transaction` is defined. - request: Signal - Indicates that the `Transaction` wants to be executed. By - default it is `Const(1)`, so it wants to be executed in - every clock cycle. - """ - if self.defined: - raise RuntimeError(f"Transaction '{self.name}' already defined") - self.def_order = next(TransactionBase.def_counter) - - m.d.av_comb += self.request.eq(request) - with self.context(m): - with m.AvoidedIf(self.grant): - yield self - - def __repr__(self) -> str: - return "(transaction {})".format(self.name) - - def debug_signals(self) -> SignalBundle: - return [self.request, self.runnable, self.grant] - - -class Method(TransactionBase): - """Transactional method. - - A `Method` serves to interface a module with external `Transaction`\\s - or `Method`\\s. It can be called by at most once in a given clock cycle. - When a given `Method` is required by multiple `Transaction`\\s - (either directly, or indirectly via another `Method`) simultenaously, - at most one of them is granted by the `TransactionManager`, and the rest - of them must wait. (Non-exclusive methods are an exception to this - behavior.) Calling a `Method` always takes a single clock cycle. - - Data is combinationally transferred between to and from `Method`\\s - using Amaranth structures (`View` with a `StructLayout`). The transfer - can take place in both directions at the same time: from the called - `Method` to the caller (`data_out`) and from the caller to the called - `Method` (`data_in`). - - A module which defines a `Method` should use `body` or `def_method` - to describe the method's effect on the module state. - - Attributes - ---------- - name: str - Name of this `Method`. - ready: Signal, in - Signals that the method is ready to run in the current cycle. - Typically defined by calling `body`. - run: Signal, out - Signals that the method is called in the current cycle by some - `Transaction`. Defined by the `TransactionManager`. - data_in: MethodStruct, out - Contains the data passed to the `Method` by the caller - (a `Transaction` or another `Method`). - data_out: MethodStruct, in - Contains the data passed from the `Method` to the caller - (a `Transaction` or another `Method`). Typically defined by - calling `body`. - """ - - def __init__( - self, - *, - name: Optional[str] = None, - i: MethodLayout = (), - o: MethodLayout = (), - nonexclusive: bool = False, - single_caller: bool = False, - src_loc: int | SrcLoc = 0, - ): - """ - Parameters - ---------- - name: str or None - Name hint for this `Method`. If `None` (default) the name is - inferred from the variable name this `Method` is assigned to. - i: method layout - The format of `data_in`. - o: method layout - The format of `data_out`. - nonexclusive: bool - If true, the method is non-exclusive: it can be called by multiple - transactions in the same clock cycle. If such a situation happens, - the method still is executed only once, and each of the callers - receive its output. Nonexclusive methods cannot have inputs. - single_caller: bool - If true, this method is intended to be called from a single - transaction. An error will be thrown if called from multiple - transactions. - src_loc: int | SrcLoc - How many stack frames deep the source location is taken from. - Alternatively, the source location to use instead of the default. - """ - super().__init__(src_loc=get_src_loc(src_loc)) - self.owner, owner_name = get_caller_class_name(default="$method") - self.name = name or tracer.get_var_name(depth=2, default=owner_name) - self.ready = Signal(name=self.owned_name + "_ready") - self.run = Signal(name=self.owned_name + "_run") - self.data_in: MethodStruct = Signal(from_method_layout(i)) - self.data_out: MethodStruct = Signal(from_method_layout(o)) - self.nonexclusive = nonexclusive - self.single_caller = single_caller - self.validate_arguments: Optional[Callable[..., ValueLike]] = None - if nonexclusive: - assert len(self.data_in.as_value()) == 0 - - @property - def layout_in(self): - return self.data_in.shape() - - @property - def layout_out(self): - return self.data_out.shape() - - @staticmethod - def like(other: "Method", *, name: Optional[str] = None, src_loc: int | SrcLoc = 0) -> "Method": - """Constructs a new `Method` based on another. - - The returned `Method` has the same input/output data layouts as the - `other` `Method`. - - Parameters - ---------- - other : Method - The `Method` which serves as a blueprint for the new `Method`. - name : str, optional - Name of the new `Method`. - src_loc: int | SrcLoc - How many stack frames deep the source location is taken from. - Alternatively, the source location to use instead of the default. - - Returns - ------- - Method - The freshly constructed `Method`. - """ - return Method(name=name, i=other.layout_in, o=other.layout_out, src_loc=get_src_loc(src_loc)) - - def proxy(self, m: TModule, method: "Method"): - """Define as a proxy for another method. - - The calls to this method will be forwarded to `method`. - - Parameters - ---------- - m : TModule - Module in which operations on signals should be executed, - `proxy` uses the combinational domain only. - method : Method - Method for which this method is a proxy for. - """ - - @def_method(m, self) - def _(arg): - return method(m, arg) - - @contextmanager - def body( - self, - m: TModule, - *, - ready: ValueLike = C(1), - out: ValueLike = C(0, 0), - validate_arguments: Optional[Callable[..., ValueLike]] = None, - ) -> Iterator[MethodStruct]: - """Define method body - - The `body` context manager can be used to define the actions - performed by a `Method` when it's run. Each assignment added to - a domain under `body` is guarded by the `run` signal. - Combinational assignments which do not need to be guarded by `run` - can be added to `m.d.av_comb` or `m.d.top_comb` instead of `m.d.comb`. - `Method` calls can be performed under `body`. - - Parameters - ---------- - m : TModule - Module in which operations on signals should be executed, - `body` uses the combinational domain only. - ready : Signal, in - Signal to indicate if the method is ready to be run. By - default it is `Const(1)`, so the method is always ready. - Assigned combinationially to the `ready` attribute. - out : Value, in - Data generated by the `Method`, which will be passed to - the caller (a `Transaction` or another `Method`). Assigned - combinationally to the `data_out` attribute. - 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 - ------- - data_in : Record, out - Data passed from the caller (a `Transaction` or another - `Method`) to this `Method`. - - Examples - -------- - .. highlight:: python - .. code-block:: python - - m = Module() - my_sum_method = Method(i = Layout([("arg1",8),("arg2",8)])) - sum = Signal(16) - with my_sum_method.body(m, out = sum) as data_in: - m.d.comb += sum.eq(data_in.arg1 + data_in.arg2) - """ - if self.defined: - raise RuntimeError(f"Method '{self.name}' already defined") - self.def_order = next(TransactionBase.def_counter) - self.validate_arguments = validate_arguments - - m.d.av_comb += self.ready.eq(ready) - m.d.top_comb += self.data_out.eq(out) - with self.context(m): - with m.AvoidedIf(self.run): - yield self.data_in - - def _validate_arguments(self, arg_rec: MethodStruct) -> ValueLike: - if self.validate_arguments is not None: - return self.ready & method_def_helper(self, self.validate_arguments, arg_rec) - return self.ready - - def __call__( - self, m: TModule, arg: Optional[AssignArg] = None, enable: ValueLike = C(1), /, **kwargs: AssignArg - ) -> MethodStruct: - """Call a method. - - Methods can only be called from transaction and method bodies. - Calling a `Method` marks, for the purpose of transaction scheduling, - the dependency between the calling context and the called `Method`. - It also connects the method's inputs to the parameters and the - method's outputs to the return value. - - Parameters - ---------- - m : TModule - Module in which operations on signals should be executed, - arg : Value or dict of Values - Call argument. Can be passed as a `View` of the method's - input layout or as a dictionary. Alternative syntax uses - keyword arguments. - enable : Value - Configures the call as enabled in the current clock cycle. - Disabled calls still lock the called method in transaction - scheduling. Calls are by default enabled. - **kwargs : Value or dict of Values - Allows to pass method arguments using keyword argument - syntax. Equivalent to passing a dict as the argument. - - Returns - ------- - data_out : MethodStruct - The result of the method call. - - Examples - -------- - .. highlight:: python - .. code-block:: python - - m = Module() - with Transaction().body(m): - ret = my_sum_method(m, arg1=2, arg2=3) - - Alternative syntax: - - .. highlight:: python - .. code-block:: python - - with Transaction().body(m): - ret = my_sum_method(m, {"arg1": 2, "arg2": 3}) - """ - arg_rec = Signal.like(self.data_in) - - if arg is not None and kwargs: - raise ValueError(f"Method '{self.name}' call with both keyword arguments and legacy record argument") - - if arg is None: - arg = kwargs - - enable_sig = Signal(name=self.owned_name + "_enable") - m.d.av_comb += enable_sig.eq(enable) - m.d.top_comb += assign(arg_rec, arg, fields=AssignType.ALL) - - caller = TransactionBase.get() - if not all(ctrl_path.exclusive_with(m.ctrl_path) for ctrl_path, _, _ in caller.method_calls[self]): - raise RuntimeError(f"Method '{self.name}' can't be called twice from the same caller '{caller.name}'") - caller.method_calls[self].append((m.ctrl_path, arg_rec, enable_sig)) - - if self not in caller.method_uses: - arg_rec_use = Signal(self.layout_in) - arg_rec_enable_sig = Signal() - caller.method_uses[self] = (arg_rec_use, arg_rec_enable_sig) - - return self.data_out - - def __repr__(self) -> str: - return "(method {})".format(self.name) - - 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), - validate_arguments: Optional[Callable[..., ValueLike]] = None, -): - """Define a method. - - This decorator allows to define transactional methods in an - elegant way using Python's `def` syntax. Internally, `def_method` - uses `Method.body`. - - The decorated function should take keyword arguments corresponding to the - fields of the method's input layout. The `**kwargs` syntax is supported. - Alternatively, it can take one argument named `arg`, which will be a - structure with input signals. - - The returned value can be either a structure with the method's output layout - or a dictionary of outputs. - - Parameters - ---------- - m: TModule - Module in which operations on signals should be executed. - method: Method - The method whose body is going to be defined. - ready: Signal - 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. - 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 - -------- - .. highlight:: python - .. code-block:: python - - m = Module() - my_sum_method = Method(i=[("arg1",8),("arg2",8)], o=[("res",8)]) - @def_method(m, my_sum_method) - def _(arg1, arg2): - return arg1 + arg2 - - Alternative syntax (keyword args in dictionary): - - .. highlight:: python - .. code-block:: python - - @def_method(m, my_sum_method) - def _(**args): - return args["arg1"] + args["arg2"] - - Alternative syntax (arg structure): - - .. highlight:: python - .. code-block:: python - - @def_method(m, my_sum_method) - def _(arg): - return {"res": arg.arg1 + arg.arg2} - """ - - def decorator(func: Callable[..., Optional[AssignArg]]): - out = Signal(method.layout_out) - ret_out = None - - with method.body(m, ready=ready, out=out, validate_arguments=validate_arguments) as arg: - ret_out = method_def_helper(method, func, arg) - - if ret_out is not None: - m.d.top_comb += assign(out, ret_out, fields=AssignType.ALL) - - return decorator diff --git a/transactron/core/__init__.py b/transactron/core/__init__.py new file mode 100644 index 000000000..6ead593f8 --- /dev/null +++ b/transactron/core/__init__.py @@ -0,0 +1,6 @@ +from .tmodule import * # noqa: F401 +from .transaction_base import * # noqa: F401 +from .method import * # noqa: F401 +from .transaction import * # noqa: F401 +from .manager import * # noqa: F401 +from .sugar import * # noqa: F401 diff --git a/transactron/core/keys.py b/transactron/core/keys.py new file mode 100644 index 000000000..9444dce34 --- /dev/null +++ b/transactron/core/keys.py @@ -0,0 +1,13 @@ +from transactron.utils import * +from typing import TYPE_CHECKING +from dataclasses import dataclass + +if TYPE_CHECKING: + from .manager import TransactionManager # noqa: F401 because of https://github.com/PyCQA/pyflakes/issues/571 + +__all__ = ["TransactionManagerKey"] + + +@dataclass(frozen=True) +class TransactionManagerKey(SimpleKey["TransactionManager"]): + pass diff --git a/transactron/core/manager.py b/transactron/core/manager.py new file mode 100644 index 000000000..0b3c55090 --- /dev/null +++ b/transactron/core/manager.py @@ -0,0 +1,486 @@ +from collections import defaultdict, deque +from typing import Callable, Iterable, Sequence, TypeAlias, Tuple +from os import environ +from graphlib import TopologicalSorter +from amaranth import * +from itertools import chain, filterfalse, product + +from transactron.utils import * +from transactron.utils.transactron_helpers import _graph_ccs +from transactron.graph import OwnershipGraph, Direction + +from .transaction_base import TransactionBase, TransactionOrMethod, Priority, Relation +from .method import Method +from .transaction import Transaction, TransactionManagerKey +from .tmodule import TModule +from .schedulers import eager_deterministic_cc_scheduler + +__all__ = ["TransactionManager", "TransactionModule"] + +TransactionGraph: TypeAlias = Graph["Transaction"] +TransactionGraphCC: TypeAlias = GraphCC["Transaction"] +PriorityOrder: TypeAlias = dict["Transaction", int] +TransactionScheduler: TypeAlias = Callable[["MethodMap", TransactionGraph, TransactionGraphCC, PriorityOrder], Module] + + +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.readiness_by_method_and_transaction = dict[tuple[Transaction, Method], ValueLike]() + self.method_parents = defaultdict[Method, list[TransactionBase]](list) + + def rec(transaction: Transaction, source: TransactionBase): + 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.readiness_by_method_and_transaction[(transaction, method)] = method._validate_arguments(arg_rec) + rec(transaction, method) + + for transaction in transactions: + self.methods_by_transaction[transaction] = [] + rec(transaction, transaction) + + for transaction_or_method in self.methods_and_transactions: + for method in transaction_or_method.method_uses.keys(): + self.method_parents[method].append(transaction_or_method) + + def transactions_for(self, elem: TransactionOrMethod) -> Collection["Transaction"]: + if isinstance(elem, Transaction): + return [elem] + else: + return self.transactions_by_method[elem] + + @property + def methods(self) -> Collection["Method"]: + return self.transactions_by_method.keys() + + @property + def transactions(self) -> Collection["Transaction"]: + return self.methods_by_transaction.keys() + + @property + def methods_and_transactions(self) -> Iterable[TransactionOrMethod]: + return chain(self.methods, self.transactions) + + +class TransactionManager(Elaboratable): + """Transaction manager + + This module is responsible for granting `Transaction`\\s and running + `Method`\\s. It takes care that two conflicting `Transaction`\\s + are never granted in the same clock cycle. + """ + + def __init__(self, cc_scheduler: TransactionScheduler = eager_deterministic_cc_scheduler): + self.transactions: list[Transaction] = [] + self.cc_scheduler = cc_scheduler + + def add_transaction(self, transaction: "Transaction"): + self.transactions.append(transaction) + + @staticmethod + def _conflict_graph(method_map: MethodMap) -> Tuple[TransactionGraph, TransactionGraph, PriorityOrder]: + """_conflict_graph + + This function generates the graph of transaction conflicts. Conflicts + between transactions can be explicit or implicit. Two transactions + conflict explicitly, if a conflict was added between the transactions + or the methods used by them via `add_conflict`. Two transactions + conflict implicitly if they are both using the same method. + + Created graph is undirected. Transactions are nodes in that graph + and conflict between two transactions is marked as an edge. In such + representation connected components are sets of transactions which can + potentially conflict so there is a need to arbitrate between them. + On the other hand when two transactions are in different connected + components, then they can be scheduled independently, because they + will have no conflicts. + + This function also computes a linear ordering of transactions + which is consistent with conflict priorities of methods and + transactions. When priority constraints cannot be satisfied, + an exception is thrown. + + Returns + ------- + cgr : TransactionGraph + Graph of conflicts between transactions, where vertices are transactions and edges are conflicts. + rgr : TransactionGraph + Graph of relations between transactions, which includes conflicts and orderings. + porder : PriorityOrder + Linear ordering of transactions which is consistent with priority constraints. + """ + + def transactions_exclusive(trans1: Transaction, trans2: Transaction): + tms1 = [trans1] + method_map.methods_by_transaction[trans1] + tms2 = [trans2] + method_map.methods_by_transaction[trans2] + + # if first transaction is exclusive with the second transaction, or this is true for + # any called methods, the transactions will never run at the same time + for tm1, tm2 in product(tms1, tms2): + if tm1.ctrl_path.exclusive_with(tm2.ctrl_path): + return True + + return False + + cgr: TransactionGraph = {} # Conflict graph + pgr: TransactionGraph = {} # Priority graph + rgr: TransactionGraph = {} # Relation graph + + def add_edge(begin: Transaction, end: Transaction, priority: Priority, conflict: bool): + rgr[begin].add(end) + rgr[end].add(begin) + if conflict: + cgr[begin].add(end) + cgr[end].add(begin) + match priority: + case Priority.LEFT: + pgr[end].add(begin) + case Priority.RIGHT: + pgr[begin].add(end) + + for transaction in method_map.transactions: + cgr[transaction] = set() + pgr[transaction] = set() + rgr[transaction] = set() + + for method in method_map.methods: + if method.nonexclusive: + continue + for transaction1 in method_map.transactions_for(method): + for transaction2 in method_map.transactions_for(method): + if transaction1 is not transaction2 and not transactions_exclusive(transaction1, transaction2): + add_edge(transaction1, transaction2, Priority.UNDEFINED, True) + + relations = [ + Relation(**relation, start=elem) + for elem in method_map.methods_and_transactions + for relation in elem.relations + ] + + for relation in relations: + start = relation["start"] + end = relation["end"] + if not relation["conflict"]: # relation added with schedule_before + if end.def_order < start.def_order and not relation["silence_warning"]: + raise RuntimeError(f"{start.name!r} scheduled before {end.name!r}, but defined afterwards") + + for trans_start in method_map.transactions_for(start): + for trans_end in method_map.transactions_for(end): + conflict = relation["conflict"] and not transactions_exclusive(trans_start, trans_end) + add_edge(trans_start, trans_end, relation["priority"], conflict) + + porder: PriorityOrder = {} + + for k, transaction in enumerate(TopologicalSorter(pgr).static_order()): + porder[transaction] = k + + return cgr, rgr, porder + + @staticmethod + def _method_enables(method_map: MethodMap) -> Mapping["Transaction", Mapping["Method", ValueLike]]: + method_enables = defaultdict[Transaction, dict[Method, ValueLike]](dict) + enables: list[ValueLike] = [] + + def rec(transaction: Transaction, source: TransactionOrMethod): + for method, (_, enable) in source.method_uses.items(): + enables.append(enable) + rec(transaction, method) + method_enables[transaction][method] = Cat(*enables).all() + enables.pop() + + for transaction in method_map.transactions: + rec(transaction, transaction) + + return method_enables + + @staticmethod + def _method_calls( + m: Module, method_map: MethodMap + ) -> tuple[Mapping["Method", Sequence[ValueLike]], Mapping["Method", Sequence[ValueLike]]]: + args = defaultdict[Method, list[ValueLike]](list) + runs = defaultdict[Method, list[ValueLike]](list) + + for source in method_map.methods_and_transactions: + if isinstance(source, Method): + run_val = Cat(transaction.grant for transaction in method_map.transactions_by_method[source]).any() + run = Signal() + m.d.comb += run.eq(run_val) + else: + run = source.grant + for method, (arg, _) in source.method_uses.items(): + args[method].append(arg) + runs[method].append(run) + + return (args, runs) + + def _simultaneous(self): + method_map = MethodMap(self.transactions) + + # remove orderings between simultaneous methods/transactions + # TODO: can it be done after transitivity, possibly catching more cases? + for elem in method_map.methods_and_transactions: + all_sims = frozenset(elem.simultaneous_list) + elem.relations = list( + filterfalse( + lambda relation: not relation["conflict"] + and relation["priority"] != Priority.UNDEFINED + and relation["end"] in all_sims, + elem.relations, + ) + ) + + # step 1: simultaneous and independent sets generation + independents = defaultdict[Transaction, set[Transaction]](set) + + for elem in method_map.methods_and_transactions: + indeps = frozenset[Transaction]().union( + *(frozenset(method_map.transactions_for(ind)) for ind in chain([elem], elem.independent_list)) + ) + for transaction1, transaction2 in product(indeps, indeps): + independents[transaction1].add(transaction2) + + simultaneous = set[frozenset[Transaction]]() + + for elem in method_map.methods_and_transactions: + for sim_elem in elem.simultaneous_list: + for tr1, tr2 in product(method_map.transactions_for(elem), method_map.transactions_for(sim_elem)): + if tr1 in independents[tr2]: + raise RuntimeError( + f"Unsatisfiable simultaneity constraints for '{elem.name}' and '{sim_elem.name}'" + ) + simultaneous.add(frozenset({tr1, tr2})) + + # step 2: transitivity computation + tr_simultaneous = set[frozenset[Transaction]]() + + def conflicting(group: frozenset[Transaction]): + return any(tr1 != tr2 and tr1 in independents[tr2] for tr1 in group for tr2 in group) + + q = deque[frozenset[Transaction]](simultaneous) + + while q: + new_group = q.popleft() + if new_group in tr_simultaneous or conflicting(new_group): + continue + q.extend(new_group | other_group for other_group in simultaneous if new_group & other_group) + tr_simultaneous.add(new_group) + + # step 3: maximal group selection + def maximal(group: frozenset[Transaction]): + return not any(group.issubset(group2) and group != group2 for group2 in tr_simultaneous) + + final_simultaneous = set(filter(maximal, tr_simultaneous)) + + # step 4: convert transactions to methods + joined_transactions = set[Transaction]().union(*final_simultaneous) + + self.transactions = list(filter(lambda t: t not in joined_transactions, self.transactions)) + methods = dict[Transaction, Method]() + + for transaction in joined_transactions: + # TODO: some simpler way? + method = Method(name=transaction.name) + method.owner = transaction.owner + method.src_loc = transaction.src_loc + method.ready = transaction.request + method.run = transaction.grant + method.defined = transaction.defined + method.method_calls = transaction.method_calls + method.method_uses = transaction.method_uses + method.relations = transaction.relations + method.def_order = transaction.def_order + method.ctrl_path = transaction.ctrl_path + methods[transaction] = method + + for elem in method_map.methods_and_transactions: + # I guess method/transaction unification is really needed + for relation in elem.relations: + if relation["end"] in methods: + relation["end"] = methods[relation["end"]] + + # step 5: construct merged transactions + m = TModule() + m._MustUse__silence = True # type: ignore + + for group in final_simultaneous: + name = "_".join([t.name for t in group]) + with Transaction(manager=self, name=name).body(m): + for transaction in group: + methods[transaction](m) + + return m + + def elaborate(self, platform): + # In the following, various problems in the transaction set-up are detected. + # The exception triggers an unused Elaboratable warning. + with silence_mustuse(self): + merge_manager = self._simultaneous() + + method_map = MethodMap(self.transactions) + cgr, rgr, porder = TransactionManager._conflict_graph(method_map) + + m = Module() + m.submodules.merge_manager = merge_manager + + for elem in method_map.methods_and_transactions: + elem._set_method_uses(m) + + for transaction in self.transactions: + ready = [ + method_map.readiness_by_method_and_transaction[transaction, method] + for method in method_map.methods_by_transaction[transaction] + ] + m.d.comb += transaction.runnable.eq(Cat(ready).all()) + + ccs = _graph_ccs(rgr) + m.submodules._transactron_schedulers = ModuleConnector( + *[self.cc_scheduler(method_map, cgr, cc, porder) for cc in ccs] + ) + + method_enables = self._method_enables(method_map) + + for method, transactions in method_map.transactions_by_method.items(): + granted = Cat(transaction.grant & method_enables[transaction][method] for transaction in transactions) + m.d.comb += method.run.eq(granted.any()) + + (method_args, method_runs) = self._method_calls(m, method_map) + + for method in method_map.methods: + if len(method_args[method]) == 1: + m.d.comb += method.data_in.eq(method_args[method][0]) + else: + if method.single_caller: + raise RuntimeError(f"Single-caller method '{method.name}' called more than once") + + runs = Cat(method_runs[method]) + for i in OneHotSwitchDynamic(m, runs): + m.d.comb += method.data_in.eq(method_args[method][i]) + + if "TRANSACTRON_VERBOSE" in environ: + self.print_info(cgr, porder, ccs, method_map) + + return m + + def print_info( + self, cgr: TransactionGraph, porder: PriorityOrder, ccs: list[GraphCC["Transaction"]], method_map: MethodMap + ): + print("Transactron statistics") + print(f"\tMethods: {len(method_map.methods)}") + print(f"\tTransactions: {len(method_map.transactions)}") + print(f"\tIndependent subgraphs: {len(ccs)}") + print(f"\tAvg callers per method: {average_dict_of_lists(method_map.transactions_by_method):.2f}") + print(f"\tAvg conflicts per transaction: {average_dict_of_lists(cgr):.2f}") + print("") + print("Transaction subgraphs") + for cc in ccs: + ccl = list(cc) + ccl.sort(key=lambda t: porder[t]) + for t in ccl: + print(f"\t{t.name}") + print("") + print("Calling transactions per method") + for m, ts in method_map.transactions_by_method.items(): + print(f"\t{m.owned_name}: {m.src_loc[0]}:{m.src_loc[1]}") + for t in ts: + print(f"\t\t{t.name}: {t.src_loc[0]}:{t.src_loc[1]}") + print("") + print("Called methods per transaction") + for t, ms in method_map.methods_by_transaction.items(): + print(f"\t{t.name}: {t.src_loc[0]}:{t.src_loc[1]}") + for m in ms: + print(f"\t\t{m.owned_name}: {m.src_loc[0]}:{m.src_loc[1]}") + print("") + + def visual_graph(self, fragment): + graph = OwnershipGraph(fragment) + method_map = MethodMap(self.transactions) + for method, transactions in method_map.transactions_by_method.items(): + if len(method.data_in.as_value()) > len(method.data_out.as_value()): + direction = Direction.IN + elif method.data_in.shape().size < method.data_out.shape().size: + direction = Direction.OUT + else: + direction = Direction.INOUT + graph.insert_node(method) + for transaction in transactions: + graph.insert_node(transaction) + graph.insert_edge(transaction, method, direction) + + return graph + + def debug_signals(self) -> SignalBundle: + method_map = MethodMap(self.transactions) + cgr, _, _ = TransactionManager._conflict_graph(method_map) + + def transaction_debug(t: Transaction): + return ( + [t.request, t.grant] + + [m.ready for m in method_map.methods_by_transaction[t]] + + [t2.grant for t2 in cgr[t]] + ) + + def method_debug(m: Method): + return [m.ready, m.run, {t.name: transaction_debug(t) for t in method_map.transactions_by_method[m]}] + + return { + "transactions": {t.name: transaction_debug(t) for t in method_map.transactions}, + "methods": {m.owned_name: method_debug(m) for m in method_map.methods}, + } + + +class TransactionModule(Elaboratable): + """ + `TransactionModule` is used as wrapper on `Elaboratable` classes, + which adds support for transactions. It creates a + `TransactionManager` which will handle transaction scheduling + and can be used in definition of `Method`\\s and `Transaction`\\s. + The `TransactionManager` is stored in a `DependencyManager`. + """ + + def __init__( + self, + elaboratable: HasElaborate, + dependency_manager: Optional[DependencyManager] = None, + transaction_manager: Optional[TransactionManager] = None, + ): + """ + Parameters + ---------- + elaboratable: HasElaborate + The `Elaboratable` which should be wrapped to add support for + transactions and methods. + dependency_manager: DependencyManager, optional + The `DependencyManager` to use inside the transaction module. + If omitted, a new one is created. + transaction_manager: TransactionManager, optional + The `TransactionManager` to use inside the transaction module. + If omitted, a new one is created. + """ + if transaction_manager is None: + transaction_manager = TransactionManager() + if dependency_manager is None: + dependency_manager = DependencyManager() + self.manager = dependency_manager + self.manager.add_dependency(TransactionManagerKey(), transaction_manager) + self.elaboratable = elaboratable + + def context(self) -> DependencyContext: + return DependencyContext(self.manager) + + def elaborate(self, platform): + with silence_mustuse(self.manager.get_dependency(TransactionManagerKey())): + with self.context(): + elaboratable = Fragment.get(self.elaboratable, platform) + + m = Module() + + m.submodules.main_module = elaboratable + m.submodules.transactionManager = self.manager.get_dependency(TransactionManagerKey()) + + return m diff --git a/transactron/core/method.py b/transactron/core/method.py new file mode 100644 index 000000000..98fb59f3d --- /dev/null +++ b/transactron/core/method.py @@ -0,0 +1,299 @@ +from transactron.utils import * +from amaranth import * +from amaranth import tracer +from typing import Optional, Callable, Iterator, TYPE_CHECKING +from .transaction_base import * +from .sugar import def_method +from contextlib import contextmanager +from transactron.utils.assign import AssignArg + +if TYPE_CHECKING: + from .tmodule import TModule + +__all__ = ["Method"] + + +class Method(TransactionBase): + """Transactional method. + + A `Method` serves to interface a module with external `Transaction`\\s + or `Method`\\s. It can be called by at most once in a given clock cycle. + When a given `Method` is required by multiple `Transaction`\\s + (either directly, or indirectly via another `Method`) simultenaously, + at most one of them is granted by the `TransactionManager`, and the rest + of them must wait. (Non-exclusive methods are an exception to this + behavior.) Calling a `Method` always takes a single clock cycle. + + Data is combinationally transferred between to and from `Method`\\s + using Amaranth structures (`View` with a `StructLayout`). The transfer + can take place in both directions at the same time: from the called + `Method` to the caller (`data_out`) and from the caller to the called + `Method` (`data_in`). + + A module which defines a `Method` should use `body` or `def_method` + to describe the method's effect on the module state. + + Attributes + ---------- + name: str + Name of this `Method`. + ready: Signal, in + Signals that the method is ready to run in the current cycle. + Typically defined by calling `body`. + run: Signal, out + Signals that the method is called in the current cycle by some + `Transaction`. Defined by the `TransactionManager`. + data_in: MethodStruct, out + Contains the data passed to the `Method` by the caller + (a `Transaction` or another `Method`). + data_out: MethodStruct, in + Contains the data passed from the `Method` to the caller + (a `Transaction` or another `Method`). Typically defined by + calling `body`. + """ + + def __init__( + self, + *, + name: Optional[str] = None, + i: MethodLayout = (), + o: MethodLayout = (), + nonexclusive: bool = False, + single_caller: bool = False, + src_loc: int | SrcLoc = 0, + ): + """ + Parameters + ---------- + name: str or None + Name hint for this `Method`. If `None` (default) the name is + inferred from the variable name this `Method` is assigned to. + i: method layout + The format of `data_in`. + o: method layout + The format of `data_out`. + nonexclusive: bool + If true, the method is non-exclusive: it can be called by multiple + transactions in the same clock cycle. If such a situation happens, + the method still is executed only once, and each of the callers + receive its output. Nonexclusive methods cannot have inputs. + single_caller: bool + If true, this method is intended to be called from a single + transaction. An error will be thrown if called from multiple + transactions. + src_loc: int | SrcLoc + How many stack frames deep the source location is taken from. + Alternatively, the source location to use instead of the default. + """ + super().__init__(src_loc=get_src_loc(src_loc)) + self.owner, owner_name = get_caller_class_name(default="$method") + self.name = name or tracer.get_var_name(depth=2, default=owner_name) + self.ready = Signal(name=self.owned_name + "_ready") + self.run = Signal(name=self.owned_name + "_run") + self.data_in: MethodStruct = Signal(from_method_layout(i)) + self.data_out: MethodStruct = Signal(from_method_layout(o)) + self.nonexclusive = nonexclusive + self.single_caller = single_caller + self.validate_arguments: Optional[Callable[..., ValueLike]] = None + if nonexclusive: + assert len(self.data_in.as_value()) == 0 + + @property + def layout_in(self): + return self.data_in.shape() + + @property + def layout_out(self): + return self.data_out.shape() + + @staticmethod + def like(other: "Method", *, name: Optional[str] = None, src_loc: int | SrcLoc = 0) -> "Method": + """Constructs a new `Method` based on another. + + The returned `Method` has the same input/output data layouts as the + `other` `Method`. + + Parameters + ---------- + other : Method + The `Method` which serves as a blueprint for the new `Method`. + name : str, optional + Name of the new `Method`. + src_loc: int | SrcLoc + How many stack frames deep the source location is taken from. + Alternatively, the source location to use instead of the default. + + Returns + ------- + Method + The freshly constructed `Method`. + """ + return Method(name=name, i=other.layout_in, o=other.layout_out, src_loc=get_src_loc(src_loc)) + + def proxy(self, m: "TModule", method: "Method"): + """Define as a proxy for another method. + + The calls to this method will be forwarded to `method`. + + Parameters + ---------- + m : TModule + Module in which operations on signals should be executed, + `proxy` uses the combinational domain only. + method : Method + Method for which this method is a proxy for. + """ + + @def_method(m, self) + def _(arg): + return method(m, arg) + + @contextmanager + def body( + self, + m: "TModule", + *, + ready: ValueLike = C(1), + out: ValueLike = C(0, 0), + validate_arguments: Optional[Callable[..., ValueLike]] = None, + ) -> Iterator[MethodStruct]: + """Define method body + + The `body` context manager can be used to define the actions + performed by a `Method` when it's run. Each assignment added to + a domain under `body` is guarded by the `run` signal. + Combinational assignments which do not need to be guarded by `run` + can be added to `m.d.av_comb` or `m.d.top_comb` instead of `m.d.comb`. + `Method` calls can be performed under `body`. + + Parameters + ---------- + m : TModule + Module in which operations on signals should be executed, + `body` uses the combinational domain only. + ready : Signal, in + Signal to indicate if the method is ready to be run. By + default it is `Const(1)`, so the method is always ready. + Assigned combinationially to the `ready` attribute. + out : Value, in + Data generated by the `Method`, which will be passed to + the caller (a `Transaction` or another `Method`). Assigned + combinationally to the `data_out` attribute. + 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 + ------- + data_in : Record, out + Data passed from the caller (a `Transaction` or another + `Method`) to this `Method`. + + Examples + -------- + .. highlight:: python + .. code-block:: python + + m = Module() + my_sum_method = Method(i = Layout([("arg1",8),("arg2",8)])) + sum = Signal(16) + with my_sum_method.body(m, out = sum) as data_in: + m.d.comb += sum.eq(data_in.arg1 + data_in.arg2) + """ + if self.defined: + raise RuntimeError(f"Method '{self.name}' already defined") + self.def_order = next(TransactionBase.def_counter) + self.validate_arguments = validate_arguments + + m.d.av_comb += self.ready.eq(ready) + m.d.top_comb += self.data_out.eq(out) + with self.context(m): + with m.AvoidedIf(self.run): + yield self.data_in + + def _validate_arguments(self, arg_rec: MethodStruct) -> ValueLike: + if self.validate_arguments is not None: + return self.ready & method_def_helper(self, self.validate_arguments, arg_rec) + return self.ready + + def __call__( + self, m: "TModule", arg: Optional[AssignArg] = None, enable: ValueLike = C(1), /, **kwargs: AssignArg + ) -> MethodStruct: + """Call a method. + + Methods can only be called from transaction and method bodies. + Calling a `Method` marks, for the purpose of transaction scheduling, + the dependency between the calling context and the called `Method`. + It also connects the method's inputs to the parameters and the + method's outputs to the return value. + + Parameters + ---------- + m : TModule + Module in which operations on signals should be executed, + arg : Value or dict of Values + Call argument. Can be passed as a `View` of the method's + input layout or as a dictionary. Alternative syntax uses + keyword arguments. + enable : Value + Configures the call as enabled in the current clock cycle. + Disabled calls still lock the called method in transaction + scheduling. Calls are by default enabled. + **kwargs : Value or dict of Values + Allows to pass method arguments using keyword argument + syntax. Equivalent to passing a dict as the argument. + + Returns + ------- + data_out : MethodStruct + The result of the method call. + + Examples + -------- + .. highlight:: python + .. code-block:: python + + m = Module() + with Transaction().body(m): + ret = my_sum_method(m, arg1=2, arg2=3) + + Alternative syntax: + + .. highlight:: python + .. code-block:: python + + with Transaction().body(m): + ret = my_sum_method(m, {"arg1": 2, "arg2": 3}) + """ + arg_rec = Signal.like(self.data_in) + + if arg is not None and kwargs: + raise ValueError(f"Method '{self.name}' call with both keyword arguments and legacy record argument") + + if arg is None: + arg = kwargs + + enable_sig = Signal(name=self.owned_name + "_enable") + m.d.av_comb += enable_sig.eq(enable) + m.d.top_comb += assign(arg_rec, arg, fields=AssignType.ALL) + + caller = TransactionBase.get() + if not all(ctrl_path.exclusive_with(m.ctrl_path) for ctrl_path, _, _ in caller.method_calls[self]): + raise RuntimeError(f"Method '{self.name}' can't be called twice from the same caller '{caller.name}'") + caller.method_calls[self].append((m.ctrl_path, arg_rec, enable_sig)) + + if self not in caller.method_uses: + arg_rec_use = Signal(self.layout_in) + arg_rec_enable_sig = Signal() + caller.method_uses[self] = (arg_rec_use, arg_rec_enable_sig) + + return self.data_out + + def __repr__(self) -> str: + return "(method {})".format(self.name) + + def debug_signals(self) -> SignalBundle: + return [self.ready, self.run, self.data_in, self.data_out] diff --git a/transactron/core/schedulers.py b/transactron/core/schedulers.py new file mode 100644 index 000000000..856d4450b --- /dev/null +++ b/transactron/core/schedulers.py @@ -0,0 +1,77 @@ +from amaranth import * +from typing import TYPE_CHECKING +from transactron.utils import * + +if TYPE_CHECKING: + from .manager import MethodMap, TransactionGraph, TransactionGraphCC, PriorityOrder + +__all__ = ["eager_deterministic_cc_scheduler", "trivial_roundrobin_cc_scheduler"] + + +def eager_deterministic_cc_scheduler( + method_map: "MethodMap", gr: "TransactionGraph", cc: "TransactionGraphCC", porder: "PriorityOrder" +) -> Module: + """eager_deterministic_cc_scheduler + + This function generates an eager scheduler for the transaction + subsystem. It isn't fair, because it starts transactions using + transaction index in `cc` as a priority. Transaction with the lowest + index has the highest priority. + + If there are two different transactions which have no conflicts then + they will be started concurrently. + + Parameters + ---------- + manager : TransactionManager + TransactionManager which uses this instance of scheduler for + arbitrating which agent should get a grant signal. + gr : TransactionGraph + Graph of conflicts between transactions, where vertices are transactions and edges are conflicts. + cc : Set[Transaction] + Connected components of the graph `gr` for which scheduler + should be generated. + porder : PriorityOrder + Linear ordering of transactions which is consistent with priority constraints. + """ + m = Module() + ccl = list(cc) + ccl.sort(key=lambda transaction: porder[transaction]) + for k, transaction in enumerate(ccl): + conflicts = [ccl[j].grant for j in range(k) if ccl[j] in gr[transaction]] + noconflict = ~Cat(conflicts).any() + m.d.comb += transaction.grant.eq(transaction.request & transaction.runnable & noconflict) + return m + + +def trivial_roundrobin_cc_scheduler( + method_map: "MethodMap", gr: "TransactionGraph", cc: "TransactionGraphCC", porder: "PriorityOrder" +) -> Module: + """trivial_roundrobin_cc_scheduler + + This function generates a simple round-robin scheduler for the transaction + subsystem. In a one cycle there will be at most one transaction granted + (in a given connected component of the conflict graph), even if there is + another ready, non-conflicting, transaction. It is mainly for testing + purposes. + + Parameters + ---------- + manager : TransactionManager + TransactionManager which uses this instance of scheduler for + arbitrating which agent should get grant signal. + gr : TransactionGraph + Graph of conflicts between transactions, where vertices are transactions and edges are conflicts. + cc : Set[Transaction] + Connected components of the graph `gr` for which scheduler + should be generated. + porder : PriorityOrder + Linear ordering of transactions which is consistent with priority constraints. + """ + m = Module() + sched = Scheduler(len(cc)) + m.submodules.scheduler = sched + for k, transaction in enumerate(cc): + m.d.comb += sched.requests[k].eq(transaction.request & transaction.runnable) + m.d.comb += transaction.grant.eq(sched.grant[k] & sched.valid) + return m diff --git a/transactron/core/sugar.py b/transactron/core/sugar.py new file mode 100644 index 000000000..49ca6e5cd --- /dev/null +++ b/transactron/core/sugar.py @@ -0,0 +1,90 @@ +from amaranth import * +from typing import TYPE_CHECKING, Optional, Callable +from transactron.utils import * +from transactron.utils.assign import AssignArg + +if TYPE_CHECKING: + from .tmodule import TModule + from .method import Method + +__all__ = ["def_method"] + + +def def_method( + m: "TModule", + method: "Method", + ready: ValueLike = C(1), + validate_arguments: Optional[Callable[..., ValueLike]] = None, +): + """Define a method. + + This decorator allows to define transactional methods in an + elegant way using Python's `def` syntax. Internally, `def_method` + uses `Method.body`. + + The decorated function should take keyword arguments corresponding to the + fields of the method's input layout. The `**kwargs` syntax is supported. + Alternatively, it can take one argument named `arg`, which will be a + structure with input signals. + + The returned value can be either a structure with the method's output layout + or a dictionary of outputs. + + Parameters + ---------- + m: TModule + Module in which operations on signals should be executed. + method: Method + The method whose body is going to be defined. + ready: Signal + 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. + 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 + -------- + .. highlight:: python + .. code-block:: python + + m = Module() + my_sum_method = Method(i=[("arg1",8),("arg2",8)], o=[("res",8)]) + @def_method(m, my_sum_method) + def _(arg1, arg2): + return arg1 + arg2 + + Alternative syntax (keyword args in dictionary): + + .. highlight:: python + .. code-block:: python + + @def_method(m, my_sum_method) + def _(**args): + return args["arg1"] + args["arg2"] + + Alternative syntax (arg structure): + + .. highlight:: python + .. code-block:: python + + @def_method(m, my_sum_method) + def _(arg): + return {"res": arg.arg1 + arg.arg2} + """ + + def decorator(func: Callable[..., Optional[AssignArg]]): + out = Signal(method.layout_out) + ret_out = None + + with method.body(m, ready=ready, out=out, validate_arguments=validate_arguments) as arg: + ret_out = method_def_helper(method, func, arg) + + if ret_out is not None: + m.d.top_comb += assign(out, ret_out, fields=AssignType.ALL) + + return decorator diff --git a/transactron/core/tmodule.py b/transactron/core/tmodule.py new file mode 100644 index 000000000..a9ee030b1 --- /dev/null +++ b/transactron/core/tmodule.py @@ -0,0 +1,286 @@ +from enum import Enum, auto +from dataclasses import dataclass, replace +from amaranth import * +from typing import Optional, Self, NoReturn +from contextlib import contextmanager +from amaranth.hdl._dsl import FSM +from transactron.utils import * + +__all__ = ["TModule"] + + +class _AvoidingModuleBuilderDomain: + """ + A wrapper over Amaranth domain to abstract away internal Amaranth implementation. + It is needed to allow for correctness check in `__setattr__` which uses `isinstance`. + """ + + def __init__(self, amaranth_module_domain): + self._domain = amaranth_module_domain + + def __iadd__(self, assigns: StatementLike) -> Self: + self._domain.__iadd__(assigns) + return self + + +class _AvoidingModuleBuilderDomains: + _m: "TModule" + + def __init__(self, m: "TModule"): + object.__setattr__(self, "_m", m) + + def __getattr__(self, name: str) -> _AvoidingModuleBuilderDomain: + if name == "av_comb": + return _AvoidingModuleBuilderDomain(self._m.avoiding_module.d["comb"]) + elif name == "top_comb": + return _AvoidingModuleBuilderDomain(self._m.top_module.d["comb"]) + else: + return _AvoidingModuleBuilderDomain(self._m.main_module.d[name]) + + def __getitem__(self, name: str) -> _AvoidingModuleBuilderDomain: + return self.__getattr__(name) + + def __setattr__(self, name: str, value): + if not isinstance(value, _AvoidingModuleBuilderDomain): + raise AttributeError(f"Cannot assign 'd.{name}' attribute; did you mean 'd.{name} +='?") + + def __setitem__(self, name: str, value): + return self.__setattr__(name, value) + + +class EnterType(Enum): + """Characterizes stack behavior of Amaranth's context managers for control structures.""" + + #: Used for `m.If`, `m.Switch` and `m.FSM`. + PUSH = auto() + #: Used for `m.Elif` and `m.Else`. + ADD = auto() + #: Used for `m.Case`, `m.Default` and `m.State`. + ENTRY = auto() + + +@dataclass(frozen=True) +class PathEdge: + """Describes an edge in Amaranth's control tree. + + Attributes + ---------- + alt : int + Which alternative (e.g. case of `m.If` or m.Switch`) is described. + par : int + Which parallel control structure (e.g. `m.If` at the same level) is described. + """ + + alt: int = 0 + par: int = 0 + + +@dataclass +class CtrlPath: + """Describes a path in Amaranth's control tree. + + Attributes + ---------- + module : int + Unique number of the module the path refers to. + path : list[PathEdge] + Path in the control tree, starting from the root. + """ + + module: int + path: list[PathEdge] + + def exclusive_with(self, other: "CtrlPath"): + """Decides if this path is mutually exclusive with some other path. + + Paths are mutually exclusive if they refer to the same module and + diverge on different alternatives of the same control structure. + + Arguments + --------- + other : CtrlPath + The other path this path is compared to. + """ + common_prefix = [] + for a, b in zip(self.path, other.path): + if a == b: + common_prefix.append(a) + elif a.par != b.par: + return False + else: + break + + return ( + self.module == other.module + and len(common_prefix) != len(self.path) + and len(common_prefix) != len(other.path) + ) + + +class CtrlPathBuilder: + """Constructs control paths. + + Used internally by `TModule`.""" + + def __init__(self, module: int): + """ + Parameters + ---------- + module: int + Unique module identifier. + """ + self.module = module + self.ctrl_path: list[PathEdge] = [] + self.previous: Optional[PathEdge] = None + + @contextmanager + def enter(self, enter_type=EnterType.PUSH): + et = EnterType + + match enter_type: + case et.ADD: + assert self.previous is not None + self.ctrl_path.append(replace(self.previous, alt=self.previous.alt + 1)) + case et.ENTRY: + self.ctrl_path[-1] = replace(self.ctrl_path[-1], alt=self.ctrl_path[-1].alt + 1) + case et.PUSH: + if self.previous is not None: + self.ctrl_path.append(PathEdge(par=self.previous.par + 1)) + else: + self.ctrl_path.append(PathEdge()) + self.previous = None + try: + yield + finally: + if enter_type in [et.PUSH, et.ADD]: + self.previous = self.ctrl_path.pop() + + def build_ctrl_path(self): + """Returns the current control path.""" + return CtrlPath(self.module, self.ctrl_path[:]) + + +class TModule(ModuleLike, Elaboratable): + """Extended Amaranth module for use with transactions. + + It includes three different combinational domains: + + * `comb` domain, works like the `comb` domain in plain Amaranth modules. + Statements in `comb` are guarded by every condition, including + `AvoidedIf`. This means they are guarded by transaction and method + bodies: they don't execute if the given transaction/method is not run. + * `av_comb` is guarded by all conditions except `AvoidedIf`. This means + they are not guarded by transaction and method bodies. This allows to + reduce the amount of useless multplexers due to transaction use, while + still allowing the use of conditions in transaction/method bodies. + * `top_comb` is unguarded: statements added to this domain always + execute. It can be used to reduce combinational path length due to + multplexers while keeping related combinational and synchronous + statements together. + """ + + __next_uid = 0 + + def __init__(self): + self.main_module = Module() + self.avoiding_module = Module() + self.top_module = Module() + self.d = _AvoidingModuleBuilderDomains(self) + self.submodules = self.main_module.submodules + self.domains = self.main_module.domains + self.fsm: Optional[FSM] = None + self.uid = TModule.__next_uid + self.path_builder = CtrlPathBuilder(self.uid) + TModule.__next_uid += 1 + + @contextmanager + def AvoidedIf(self, cond: ValueLike): # noqa: N802 + with self.main_module.If(cond): + with self.path_builder.enter(EnterType.PUSH): + yield + + @contextmanager + def If(self, cond: ValueLike): # noqa: N802 + with self.main_module.If(cond): + with self.avoiding_module.If(cond): + with self.path_builder.enter(EnterType.PUSH): + yield + + @contextmanager + def Elif(self, cond): # noqa: N802 + with self.main_module.Elif(cond): + with self.avoiding_module.Elif(cond): + with self.path_builder.enter(EnterType.ADD): + yield + + @contextmanager + def Else(self): # noqa: N802 + with self.main_module.Else(): + with self.avoiding_module.Else(): + with self.path_builder.enter(EnterType.ADD): + yield + + @contextmanager + def Switch(self, test: ValueLike): # noqa: N802 + with self.main_module.Switch(test): + with self.avoiding_module.Switch(test): + with self.path_builder.enter(EnterType.PUSH): + yield + + @contextmanager + def Case(self, *patterns: SwitchKey): # noqa: N802 + with self.main_module.Case(*patterns): + with self.avoiding_module.Case(*patterns): + with self.path_builder.enter(EnterType.ENTRY): + yield + + @contextmanager + def Default(self): # noqa: N802 + with self.main_module.Default(): + with self.avoiding_module.Default(): + with self.path_builder.enter(EnterType.ENTRY): + yield + + @contextmanager + def FSM(self, reset: Optional[str] = None, domain: str = "sync", name: str = "fsm"): # noqa: N802 + old_fsm = self.fsm + with self.main_module.FSM(reset, domain, name) as fsm: + self.fsm = fsm + with self.path_builder.enter(EnterType.PUSH): + yield fsm + self.fsm = old_fsm + + @contextmanager + def State(self, name: str): # noqa: N802 + assert self.fsm is not None + with self.main_module.State(name): + with self.avoiding_module.If(self.fsm.ongoing(name)): + with self.path_builder.enter(EnterType.ENTRY): + yield + + @property + def next(self) -> NoReturn: + raise NotImplementedError + + @next.setter + def next(self, name: str): + self.main_module.next = name + + @property + def ctrl_path(self): + return self.path_builder.build_ctrl_path() + + @property + def _MustUse__silence(self): # noqa: N802 + return self.main_module._MustUse__silence + + @_MustUse__silence.setter + def _MustUse__silence(self, value): # noqa: N802 + self.main_module._MustUse__silence = value # type: ignore + self.avoiding_module._MustUse__silence = value # type: ignore + self.top_module._MustUse__silence = value # type: ignore + + def elaborate(self, platform): + self.main_module.submodules._avoiding_module = self.avoiding_module + self.main_module.submodules._top_module = self.top_module + return self.main_module diff --git a/transactron/core/transaction.py b/transactron/core/transaction.py new file mode 100644 index 000000000..c6f4176ab --- /dev/null +++ b/transactron/core/transaction.py @@ -0,0 +1,115 @@ +from transactron.utils import * +from amaranth import * +from amaranth import tracer +from typing import Optional, Iterator, TYPE_CHECKING +from .transaction_base import * +from .keys import * +from contextlib import contextmanager + +if TYPE_CHECKING: + from .tmodule import TModule + from .manager import TransactionManager + +__all__ = ["Transaction"] + + +class Transaction(TransactionBase): + """Transaction. + + A `Transaction` represents a task which needs to be regularly done. + Execution of a `Transaction` always lasts a single clock cycle. + A `Transaction` signals readiness for execution by setting the + `request` signal. If the conditions for its execution are met, it + can be granted by the `TransactionManager`. + + A `Transaction` can, as part of its execution, call a number of + `Method`\\s. A `Transaction` can be granted only if every `Method` + it runs is ready. + + A `Transaction` cannot execute concurrently with another, conflicting + `Transaction`. Conflicts between `Transaction`\\s are either explicit + or implicit. An explicit conflict is added using the `add_conflict` + method. Implicit conflicts arise between pairs of `Transaction`\\s + which use the same `Method`. + + A module which defines a `Transaction` should use `body` to + describe used methods and the transaction's effect on the module state. + The used methods should be called inside the `body`'s + `with` block. + + Attributes + ---------- + name: str + Name of this `Transaction`. + request: Signal, in + Signals that the transaction wants to run. If omitted, the transaction + is always ready. Defined in the constructor. + runnable: Signal, out + Signals that all used methods are ready. + grant: Signal, out + Signals that the transaction is granted by the `TransactionManager`, + and all used methods are called. + """ + + def __init__( + self, *, name: Optional[str] = None, manager: Optional["TransactionManager"] = None, src_loc: int | SrcLoc = 0 + ): + """ + Parameters + ---------- + name: str or None + Name hint for this `Transaction`. If `None` (default) the name is + inferred from the variable name this `Transaction` is assigned to. + If the `Transaction` was not assigned, the name is inferred from + the class name where the `Transaction` was constructed. + manager: TransactionManager + The `TransactionManager` controlling this `Transaction`. + If omitted, the manager is received from `TransactionContext`. + src_loc: int | SrcLoc + How many stack frames deep the source location is taken from. + Alternatively, the source location to use instead of the default. + """ + super().__init__(src_loc=get_src_loc(src_loc)) + self.owner, owner_name = get_caller_class_name(default="$transaction") + self.name = name or tracer.get_var_name(depth=2, default=owner_name) + if manager is None: + manager = DependencyContext.get().get_dependency(TransactionManagerKey()) + manager.add_transaction(self) + self.request = Signal(name=self.owned_name + "_request") + self.runnable = Signal(name=self.owned_name + "_runnable") + self.grant = Signal(name=self.owned_name + "_grant") + + @contextmanager + def body(self, m: "TModule", *, request: ValueLike = C(1)) -> Iterator["Transaction"]: + """Defines the `Transaction` body. + + This context manager allows to conveniently define the actions + performed by a `Transaction` when it's granted. Each assignment + added to a domain under `body` is guarded by the `grant` signal. + Combinational assignments which do not need to be guarded by + `grant` can be added to `m.d.top_comb` or `m.d.av_comb` instead of + `m.d.comb`. `Method` calls can be performed under `body`. + + Parameters + ---------- + m: TModule + The module where the `Transaction` is defined. + request: Signal + Indicates that the `Transaction` wants to be executed. By + default it is `Const(1)`, so it wants to be executed in + every clock cycle. + """ + if self.defined: + raise RuntimeError(f"Transaction '{self.name}' already defined") + self.def_order = next(TransactionBase.def_counter) + + m.d.av_comb += self.request.eq(request) + with self.context(m): + with m.AvoidedIf(self.grant): + yield self + + def __repr__(self) -> str: + return "(transaction {})".format(self.name) + + def debug_signals(self) -> SignalBundle: + return [self.request, self.runnable, self.grant] diff --git a/transactron/core/transaction_base.py b/transactron/core/transaction_base.py new file mode 100644 index 000000000..be4fe6f93 --- /dev/null +++ b/transactron/core/transaction_base.py @@ -0,0 +1,209 @@ +from collections import defaultdict +from contextlib import contextmanager +from enum import Enum, auto +from itertools import count +from typing import ( + ClassVar, + TypeAlias, + TypedDict, + Union, + TypeVar, + Protocol, + Self, + runtime_checkable, + TYPE_CHECKING, + Iterator, +) +from amaranth import * + +from .tmodule import TModule, CtrlPath +from transactron.graph import Owned +from transactron.utils import * + +if TYPE_CHECKING: + from .method import Method + from .transaction import Transaction + +__all__ = ["TransactionBase", "Priority"] + +TransactionOrMethod: TypeAlias = Union["Transaction", "Method"] +TransactionOrMethodBound = TypeVar("TransactionOrMethodBound", "Transaction", "Method") + + +class Priority(Enum): + #: Conflicting transactions/methods don't have a priority order. + UNDEFINED = auto() + #: Left transaction/method is prioritized over the right one. + LEFT = auto() + #: Right transaction/method is prioritized over the left one. + RIGHT = auto() + + +class RelationBase(TypedDict): + end: TransactionOrMethod + priority: Priority + conflict: bool + silence_warning: bool + + +class Relation(RelationBase): + start: TransactionOrMethod + + +@runtime_checkable +class TransactionBase(Owned, Protocol): + stack: ClassVar[list[Union["Transaction", "Method"]]] = [] + def_counter: ClassVar[count] = count() + def_order: int + defined: bool = False + name: str + src_loc: SrcLoc + method_uses: dict["Method", tuple[MethodStruct, Signal]] + method_calls: defaultdict["Method", list[tuple[CtrlPath, MethodStruct, ValueLike]]] + relations: list[RelationBase] + simultaneous_list: list[TransactionOrMethod] + independent_list: list[TransactionOrMethod] + ctrl_path: CtrlPath = CtrlPath(-1, []) + + def __init__(self, *, src_loc: int | SrcLoc): + self.src_loc = get_src_loc(src_loc) + self.method_uses = {} + self.method_calls = defaultdict(list) + self.relations = [] + self.simultaneous_list = [] + self.independent_list = [] + + def add_conflict(self, end: TransactionOrMethod, priority: Priority = Priority.UNDEFINED) -> None: + """Registers a conflict. + + Record that that the given `Transaction` or `Method` cannot execute + simultaneously with this `Method` or `Transaction`. Typical reason + is using a common resource (register write or memory port). + + Parameters + ---------- + end: Transaction or Method + The conflicting `Transaction` or `Method` + priority: Priority, optional + Is one of conflicting `Transaction`\\s or `Method`\\s prioritized? + Defaults to undefined priority relation. + """ + self.relations.append( + RelationBase(end=end, priority=priority, conflict=True, silence_warning=self.owner != end.owner) + ) + + def schedule_before(self, end: TransactionOrMethod) -> None: + """Adds a priority relation. + + Record that that the given `Transaction` or `Method` needs to be + scheduled before this `Method` or `Transaction`, without adding + a conflict. Typical reason is data forwarding. + + Parameters + ---------- + end: Transaction or Method + The other `Transaction` or `Method` + """ + self.relations.append( + RelationBase(end=end, priority=Priority.LEFT, conflict=False, silence_warning=self.owner != end.owner) + ) + + def simultaneous(self, *others: TransactionOrMethod) -> None: + """Adds simultaneity relations. + + The given `Transaction`\\s or `Method``\\s will execute simultaneously + (in the same clock cycle) with this `Transaction` or `Method`. + + Parameters + ---------- + *others: Transaction or Method + The `Transaction`\\s or `Method`\\s to be executed simultaneously. + """ + self.simultaneous_list += others + + def simultaneous_alternatives(self, *others: TransactionOrMethod) -> None: + """Adds exclusive simultaneity relations. + + Each of the given `Transaction`\\s or `Method``\\s will execute + simultaneously (in the same clock cycle) with this `Transaction` or + `Method`. However, each of the given `Transaction`\\s or `Method`\\s + will be separately considered for execution. + + Parameters + ---------- + *others: Transaction or Method + The `Transaction`\\s or `Method`\\s to be executed simultaneously, + but mutually exclusive, with this `Transaction` or `Method`. + """ + self.simultaneous(*others) + others[0]._independent(*others[1:]) + + def _independent(self, *others: TransactionOrMethod) -> None: + """Adds independence relations. + + This `Transaction` or `Method`, together with all the given + `Transaction`\\s or `Method`\\s, will never be considered (pairwise) + for simultaneous execution. + + Warning: this function is an implementation detail, do not use in + user code. + + Parameters + ---------- + *others: Transaction or Method + The `Transaction`\\s or `Method`\\s which, together with this + `Transaction` or `Method`, need to be independently considered + for execution. + """ + self.independent_list += others + + @contextmanager + def context(self: TransactionOrMethodBound, m: TModule) -> Iterator[TransactionOrMethodBound]: + self.ctrl_path = m.ctrl_path + + parent = TransactionBase.peek() + if parent is not None: + parent.schedule_before(self) + + TransactionBase.stack.append(self) + + try: + yield self + finally: + TransactionBase.stack.pop() + self.defined = True + + def _set_method_uses(self, m: ModuleLike): + for method, calls in self.method_calls.items(): + arg_rec, enable_sig = self.method_uses[method] + if len(calls) == 1: + m.d.comb += arg_rec.eq(calls[0][1]) + m.d.comb += enable_sig.eq(calls[0][2]) + else: + call_ens = Cat([en for _, _, en in calls]) + + for i in OneHotSwitchDynamic(m, call_ens): + m.d.comb += arg_rec.eq(calls[i][1]) + m.d.comb += enable_sig.eq(1) + + @classmethod + def get(cls) -> Self: + ret = cls.peek() + if ret is None: + raise RuntimeError("No current body") + return ret + + @classmethod + def peek(cls) -> Optional[Self]: + if not TransactionBase.stack: + return None + if not isinstance(TransactionBase.stack[-1], cls): + raise RuntimeError(f"Current body not a {cls.__name__}") + return TransactionBase.stack[-1] + + @property + def owned_name(self): + if self.owner is not None and self.owner.__class__.__name__ != self.name: + return f"{self.owner.__class__.__name__}_{self.name}" + else: + return self.name diff --git a/transactron/lib/adapters.py b/transactron/lib/adapters.py index ed7f2640f..4a7ea8460 100644 --- a/transactron/lib/adapters.py +++ b/transactron/lib/adapters.py @@ -2,8 +2,7 @@ from ..utils import SrcLoc, get_src_loc, MethodStruct from ..core import * -from ..core import SignalBundle -from ..utils._typing import type_self_kwargs_as +from ..utils._typing import type_self_kwargs_as, SignalBundle __all__ = [ "AdapterBase", diff --git a/transactron/lib/buttons.py b/transactron/lib/buttons.py index 59bf081b5..d275cd25d 100644 --- a/transactron/lib/buttons.py +++ b/transactron/lib/buttons.py @@ -2,7 +2,7 @@ from transactron.utils.transactron_helpers import from_method_layout from ..core import * -from ..utils import SrcLoc, get_src_loc +from ..utils import SrcLoc, get_src_loc, MethodLayout __all__ = ["ClickIn", "ClickOut"] diff --git a/transactron/lib/connectors.py b/transactron/lib/connectors.py index b9a6eb204..511cf6248 100644 --- a/transactron/lib/connectors.py +++ b/transactron/lib/connectors.py @@ -3,7 +3,7 @@ from transactron.utils.transactron_helpers import from_method_layout from ..core import * -from ..utils import SrcLoc, get_src_loc +from ..utils import SrcLoc, get_src_loc, MethodLayout __all__ = [ "FIFO", diff --git a/transactron/lib/reqres.py b/transactron/lib/reqres.py index 518d53443..f9aeb6e06 100644 --- a/transactron/lib/reqres.py +++ b/transactron/lib/reqres.py @@ -1,6 +1,6 @@ from amaranth import * from ..core import * -from ..utils import SrcLoc, get_src_loc +from ..utils import SrcLoc, get_src_loc, MethodLayout from .connectors import Forwarder, FIFO from transactron.lib import BasicFifo from amaranth.utils import * diff --git a/transactron/lib/transformers.py b/transactron/lib/transformers.py index a1445fcf5..f874cea2c 100644 --- a/transactron/lib/transformers.py +++ b/transactron/lib/transformers.py @@ -2,11 +2,19 @@ from transactron.utils.transactron_helpers import get_src_loc from ..core import * -from ..core import RecordDict from ..utils import SrcLoc from typing import Optional, Protocol from collections.abc import Callable -from transactron.utils import ValueLike, assign, AssignType, ModuleLike, MethodStruct, HasElaborate +from transactron.utils import ( + ValueLike, + assign, + AssignType, + ModuleLike, + MethodStruct, + HasElaborate, + MethodLayout, + RecordDict, +) from .connectors import Forwarder, ManyToOneConnectTrans, ConnectTrans from .simultaneous import condition diff --git a/transactron/profiler.py b/transactron/profiler.py index 0132b2ef7..28574731a 100644 --- a/transactron/profiler.py +++ b/transactron/profiler.py @@ -4,7 +4,8 @@ from dataclasses import dataclass, field from dataclasses_json import dataclass_json from transactron.utils import SrcLoc, IdGenerator -from transactron.core import MethodMap, TransactionManager +from transactron.core import TransactionManager +from transactron.core.manager import MethodMap __all__ = [ diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index a769bba13..13a95c053 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -17,7 +17,8 @@ from .gtkw_extension import write_vcd_ext from transactron import Method from transactron.lib import AdapterTrans -from transactron.core import TransactionManagerKey, TransactionModule +from transactron.core.keys import TransactionManagerKey +from transactron.core import TransactionModule from transactron.utils import ModuleConnector, HasElaborate, auto_debug_signals, HasDebugSignals T = TypeVar("T") diff --git a/transactron/testing/profiler.py b/transactron/testing/profiler.py index 18451112c..b70119c7d 100644 --- a/transactron/testing/profiler.py +++ b/transactron/testing/profiler.py @@ -1,5 +1,6 @@ from amaranth.sim import * -from transactron.core import MethodMap, TransactionManager +from transactron.core import TransactionManager +from transactron.core.manager import MethodMap from transactron.profiler import CycleProfile, MethodSamples, Profile, ProfileData, ProfileSamples, TransactionSamples from .functions import TestGen diff --git a/transactron/testing/testbenchio.py b/transactron/testing/testbenchio.py index 8f9cc253d..cbe4c446f 100644 --- a/transactron/testing/testbenchio.py +++ b/transactron/testing/testbenchio.py @@ -2,8 +2,7 @@ from amaranth.sim import Settle, Passive from typing import Optional, Callable from transactron.lib import AdapterBase -from transactron.core import ValueLike, SignalBundle -from transactron.utils import mock_def_helper +from transactron.utils import ValueLike, SignalBundle, mock_def_helper from transactron.utils._typing import RecordIntDictRet, RecordValueDict, RecordIntDict from .functions import set_inputs, get_outputs, TestGen diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index 5acd3ab70..32497c7d5 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -44,6 +44,7 @@ "RecordIntDict", "RecordIntDictRet", "RecordValueDict", + "RecordDict", "ROGraph", "Graph", "GraphCC", @@ -72,6 +73,7 @@ RecordIntDict: TypeAlias = Mapping[str, Union[int, "RecordIntDict"]] RecordIntDictRet: TypeAlias = Mapping[str, Any] # full typing hard to work with RecordValueDict: TypeAlias = Mapping[str, Union[ValueLike, "RecordValueDict"]] +RecordDict: TypeAlias = ValueLike | Mapping[str, "RecordDict"] T = TypeVar("T") U = TypeVar("U") diff --git a/transactron/utils/gen.py b/transactron/utils/gen.py index daf462ce7..68374198c 100644 --- a/transactron/utils/gen.py +++ b/transactron/utils/gen.py @@ -6,7 +6,9 @@ from amaranth.back import verilog from amaranth.hdl import Fragment -from transactron.core import TransactionManager, MethodMap, TransactionManagerKey +from transactron.core import TransactionManager +from transactron.core.keys import TransactionManagerKey +from transactron.core.manager import MethodMap from transactron.lib.metrics import HardwareMetricsManager from transactron.lib import logging from transactron.utils.dependencies import DependencyContext From 074ff6bef5a0c5b530ccdb918874e10d1022b9cd Mon Sep 17 00:00:00 2001 From: lekcyjna123 <34948061+lekcyjna123@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:57:39 +0100 Subject: [PATCH 2/6] Spring cleanup - coreblocks (#620) --- coreblocks/{fu => backend}/__init__.py | 0 .../backend.py => backend/annoucement.py} | 0 coreblocks/{stages => backend}/retirement.py | 8 +- coreblocks/cache/icache.py | 3 +- coreblocks/cache/refiller.py | 3 +- coreblocks/core.py | 30 ++-- .../__init__.py | 0 .../{structs_common => core_structs}/rat.py | 3 +- .../{structs_common => core_structs}/rf.py | 3 +- .../{structs_common => core_structs}/rob.py | 3 +- coreblocks/frontend/decoder/__init__.py | 2 + .../frontend/{ => decoder}/decode_stage.py | 6 +- .../frontend/{ => decoder}/instr_decoder.py | 2 + .../{ => decoder}/instr_description.py | 2 + coreblocks/frontend/decoder/isa.py | 154 +++++++++++++++++ .../{params => frontend/decoder}/optypes.py | 2 +- coreblocks/frontend/{ => decoder}/rvc.py | 1 + .../{lsu => frontend/fetch}/__init__.py | 0 coreblocks/frontend/{ => fetch}/fetch.py | 6 +- .../{stages => func_blocks}/__init__.py | 0 .../csr}/csr.py | 160 +----------------- .../fu}/__init__.py | 0 coreblocks/{ => func_blocks}/fu/alu.py | 9 +- .../fu/common}/fu_decoder.py | 5 +- .../fu/common}/rs.py | 4 +- .../fu/common}/rs_func_block.py | 6 +- coreblocks/{ => func_blocks}/fu/div_unit.py | 10 +- .../{ => func_blocks}/fu/division/common.py | 2 +- .../fu/division/long_division.py | 2 +- coreblocks/{ => func_blocks}/fu/exception.py | 11 +- coreblocks/{ => func_blocks}/fu/jumpbranch.py | 11 +- coreblocks/{ => func_blocks}/fu/mul_unit.py | 15 +- coreblocks/{ => func_blocks}/fu/priv.py | 20 ++- coreblocks/{ => func_blocks}/fu/shift_unit.py | 8 +- .../fu/unsigned_multiplication}/__init__.py | 0 .../fu/unsigned_multiplication/common.py | 3 +- .../unsigned_multiplication/fast_recursive.py | 2 +- .../fu/unsigned_multiplication/sequence.py | 2 +- .../fu/unsigned_multiplication/shift.py | 2 +- coreblocks/{ => func_blocks}/fu/zbc.py | 14 +- coreblocks/{ => func_blocks}/fu/zbs.py | 8 +- coreblocks/func_blocks/interface/__init__.py | 0 .../interface}/func_blocks_unifier.py | 0 .../interface/func_protocols.py} | 0 coreblocks/func_blocks/lsu/__init__.py | 0 coreblocks/{ => func_blocks}/lsu/dummyLsu.py | 7 +- coreblocks/{ => func_blocks}/lsu/pma.py | 0 coreblocks/{params => interface}/keys.py | 2 +- coreblocks/{params => interface}/layouts.py | 4 +- coreblocks/params/__init__.py | 5 +- coreblocks/params/configurations.py | 30 ++-- coreblocks/params/fu_params.py | 8 +- coreblocks/params/genparams.py | 2 +- coreblocks/params/instr.py | 3 +- coreblocks/params/{isa.py => isa_params.py} | 152 +---------------- coreblocks/priv/__init__.py | 0 coreblocks/priv/csr/__init__.py | 0 .../csr/csr_instances.py} | 2 +- coreblocks/priv/csr/csr_register.py | 150 ++++++++++++++++ coreblocks/priv/traps/__init__.py | 0 .../traps}/exception.py | 6 +- .../traps}/instr_counter.py | 2 +- .../traps}/interrupt_controller.py | 2 +- coreblocks/scheduler/scheduler.py | 8 +- coreblocks/scheduler/wakeup_select.py | 3 +- scripts/synthesize.py | 12 +- test/cache/test_icache.py | 3 +- test/frontend/test_decode_stage.py | 6 +- test/frontend/test_fetch.py | 3 +- test/frontend/test_instr_decoder.py | 3 +- test/frontend/test_rvc.py | 3 +- test/fu/functional_common.py | 8 +- test/fu/test_alu.py | 4 +- test/fu/test_div_unit.py | 4 +- test/fu/test_exception_unit.py | 8 +- test/fu/test_fu_decoder.py | 5 +- test/fu/test_jb_unit.py | 7 +- test/fu/test_mul_unit.py | 4 +- test/fu/test_shift_unit.py | 4 +- test/fu/test_unsigned_mul_unit.py | 8 +- test/fu/test_zbc.py | 4 +- test/fu/test_zbs.py | 5 +- test/lsu/test_dummylsu.py | 10 +- test/lsu/test_pma.py | 12 +- test/params/test_configurations.py | 2 +- test/params/test_isa.py | 2 +- test/scheduler/test_rs_selection.py | 4 +- test/scheduler/test_scheduler.py | 18 +- test/scheduler/test_wakeup_select.py | 5 +- test/stages/test_backend.py | 4 +- test/stages/test_retirement.py | 11 +- test/structs_common/test_csr.py | 10 +- test/structs_common/test_exception.py | 6 +- test/structs_common/test_rat.py | 2 +- test/structs_common/test_reorder_buffer.py | 2 +- test/structs_common/test_rs.py | 3 +- test/transactions/test_transaction_lib.py | 1 - 97 files changed, 578 insertions(+), 513 deletions(-) rename coreblocks/{fu => backend}/__init__.py (100%) rename coreblocks/{stages/backend.py => backend/annoucement.py} (100%) rename coreblocks/{stages => backend}/retirement.py (97%) rename coreblocks/{fu/unsigned_multiplication => core_structs}/__init__.py (100%) rename coreblocks/{structs_common => core_structs}/rat.py (94%) rename coreblocks/{structs_common => core_structs}/rf.py (96%) rename coreblocks/{structs_common => core_structs}/rob.py (96%) create mode 100644 coreblocks/frontend/decoder/__init__.py rename coreblocks/frontend/{ => decoder}/decode_stage.py (96%) rename coreblocks/frontend/{ => decoder}/instr_decoder.py (99%) rename coreblocks/frontend/{ => decoder}/instr_description.py (99%) create mode 100644 coreblocks/frontend/decoder/isa.py rename coreblocks/{params => frontend/decoder}/optypes.py (97%) rename coreblocks/frontend/{ => decoder}/rvc.py (99%) rename coreblocks/{lsu => frontend/fetch}/__init__.py (100%) rename coreblocks/frontend/{ => fetch}/fetch.py (97%) rename coreblocks/{stages => func_blocks}/__init__.py (100%) rename coreblocks/{structs_common => func_blocks/csr}/csr.py (64%) rename coreblocks/{structs_common => func_blocks/fu}/__init__.py (100%) rename coreblocks/{ => func_blocks}/fu/alu.py (96%) rename coreblocks/{fu => func_blocks/fu/common}/fu_decoder.py (94%) rename coreblocks/{structs_common => func_blocks/fu/common}/rs.py (96%) rename coreblocks/{stages => func_blocks/fu/common}/rs_func_block.py (94%) rename coreblocks/{ => func_blocks}/fu/div_unit.py (93%) rename coreblocks/{ => func_blocks}/fu/division/common.py (87%) rename coreblocks/{ => func_blocks}/fu/division/long_division.py (99%) rename coreblocks/{ => func_blocks}/fu/exception.py (90%) rename coreblocks/{ => func_blocks}/fu/jumpbranch.py (95%) rename coreblocks/{ => func_blocks}/fu/mul_unit.py (93%) rename coreblocks/{ => func_blocks}/fu/priv.py (86%) rename coreblocks/{ => func_blocks}/fu/shift_unit.py (92%) rename coreblocks/{utils => func_blocks/fu/unsigned_multiplication}/__init__.py (100%) rename coreblocks/{ => func_blocks}/fu/unsigned_multiplication/common.py (94%) rename coreblocks/{ => func_blocks}/fu/unsigned_multiplication/fast_recursive.py (97%) rename coreblocks/{ => func_blocks}/fu/unsigned_multiplication/sequence.py (98%) rename coreblocks/{ => func_blocks}/fu/unsigned_multiplication/shift.py (93%) rename coreblocks/{ => func_blocks}/fu/zbc.py (96%) rename coreblocks/{ => func_blocks}/fu/zbs.py (92%) create mode 100644 coreblocks/func_blocks/interface/__init__.py rename coreblocks/{stages => func_blocks/interface}/func_blocks_unifier.py (100%) rename coreblocks/{utils/protocols.py => func_blocks/interface/func_protocols.py} (100%) create mode 100644 coreblocks/func_blocks/lsu/__init__.py rename coreblocks/{ => func_blocks}/lsu/dummyLsu.py (97%) rename coreblocks/{ => func_blocks}/lsu/pma.py (100%) rename coreblocks/{params => interface}/keys.py (93%) rename coreblocks/{params => interface}/layouts.py (99%) rename coreblocks/params/{isa.py => isa_params.py} (63%) create mode 100644 coreblocks/priv/__init__.py create mode 100644 coreblocks/priv/csr/__init__.py rename coreblocks/{structs_common/csr_generic.py => priv/csr/csr_instances.py} (98%) create mode 100644 coreblocks/priv/csr/csr_register.py create mode 100644 coreblocks/priv/traps/__init__.py rename coreblocks/{structs_common => priv/traps}/exception.py (96%) rename coreblocks/{structs_common => priv/traps}/instr_counter.py (95%) rename coreblocks/{structs_common => priv/traps}/interrupt_controller.py (94%) diff --git a/coreblocks/fu/__init__.py b/coreblocks/backend/__init__.py similarity index 100% rename from coreblocks/fu/__init__.py rename to coreblocks/backend/__init__.py diff --git a/coreblocks/stages/backend.py b/coreblocks/backend/annoucement.py similarity index 100% rename from coreblocks/stages/backend.py rename to coreblocks/backend/annoucement.py diff --git a/coreblocks/stages/retirement.py b/coreblocks/backend/retirement.py similarity index 97% rename from coreblocks/stages/retirement.py rename to coreblocks/backend/retirement.py index 1225e7e57..cd1ac4696 100644 --- a/coreblocks/stages/retirement.py +++ b/coreblocks/backend/retirement.py @@ -1,5 +1,5 @@ from amaranth import * -from coreblocks.params.layouts import RetirementLayouts +from coreblocks.interface.layouts import RetirementLayouts from transactron.core import Method, Transaction, TModule, def_method from transactron.lib.simultaneous import condition @@ -7,9 +7,9 @@ from transactron.lib.metrics import * from coreblocks.params.genparams import GenParams -from coreblocks.params.isa import ExceptionCause -from coreblocks.params.keys import CoreStateKey, GenericCSRRegistersKey -from coreblocks.structs_common.csr_generic import CSRAddress, DoubleCounterCSR +from coreblocks.frontend.decoder.isa import ExceptionCause +from coreblocks.interface.keys import CoreStateKey, GenericCSRRegistersKey +from coreblocks.priv.csr.csr_instances import CSRAddress, DoubleCounterCSR class Retirement(Elaboratable): diff --git a/coreblocks/cache/icache.py b/coreblocks/cache/icache.py index 09899afb6..f94c6e07c 100644 --- a/coreblocks/cache/icache.py +++ b/coreblocks/cache/icache.py @@ -7,7 +7,8 @@ from transactron.core import def_method, Priority, TModule from transactron import Method, Transaction -from coreblocks.params import ICacheLayouts, ICacheParameters +from coreblocks.params import ICacheParameters +from coreblocks.interface.layouts import ICacheLayouts from transactron.utils import assign, OneHotSwitchDynamic from transactron.lib import * from coreblocks.peripherals.bus_adapter import BusMasterInterface diff --git a/coreblocks/cache/refiller.py b/coreblocks/cache/refiller.py index e8a261e26..311764852 100644 --- a/coreblocks/cache/refiller.py +++ b/coreblocks/cache/refiller.py @@ -1,6 +1,7 @@ from amaranth import * from coreblocks.cache.icache import CacheRefillerInterface -from coreblocks.params import ICacheLayouts, ICacheParameters +from coreblocks.params import ICacheParameters +from coreblocks.interface.layouts import ICacheLayouts from coreblocks.peripherals.bus_adapter import BusMasterInterface from transactron.core import Transaction from transactron.lib import Forwarder, Method, TModule, def_method diff --git a/coreblocks/core.py b/coreblocks/core.py index a91b2e827..145abf653 100644 --- a/coreblocks/core.py +++ b/coreblocks/core.py @@ -2,13 +2,13 @@ from amaranth.lib.wiring import flipped, connect from transactron.utils.dependencies import DependencyManager, DependencyContext -from coreblocks.stages.func_blocks_unifier import FuncBlocksUnifier -from coreblocks.structs_common.instr_counter import CoreInstructionCounter -from coreblocks.structs_common.interrupt_controller import InterruptController +from coreblocks.func_blocks.interface.func_blocks_unifier import FuncBlocksUnifier +from coreblocks.priv.traps.instr_counter import CoreInstructionCounter +from coreblocks.priv.traps.interrupt_controller import InterruptController from transactron.core import Transaction, TModule from transactron.lib import FIFO, ConnectTrans -from coreblocks.params.layouts import * -from coreblocks.params.keys import ( +from coreblocks.interface.layouts import * +from coreblocks.interface.keys import ( BranchVerifyKey, FetchResumeKey, GenericCSRRegistersKey, @@ -16,21 +16,21 @@ CommonBusDataKey, ) from coreblocks.params.genparams import GenParams -from coreblocks.params.isa import Extension -from coreblocks.frontend.decode_stage import DecodeStage -from coreblocks.structs_common.rat import FRAT, RRAT -from coreblocks.structs_common.rob import ReorderBuffer -from coreblocks.structs_common.rf import RegisterFile -from coreblocks.structs_common.csr_generic import GenericCSRRegisters -from coreblocks.structs_common.exception import ExceptionCauseRegister +from coreblocks.params.isa_params import Extension +from coreblocks.frontend.decoder.decode_stage import DecodeStage +from coreblocks.core_structs.rat import FRAT, RRAT +from coreblocks.core_structs.rob import ReorderBuffer +from coreblocks.core_structs.rf import RegisterFile +from coreblocks.priv.csr.csr_instances import GenericCSRRegisters +from coreblocks.priv.traps.exception import ExceptionCauseRegister from coreblocks.scheduler.scheduler import Scheduler -from coreblocks.stages.backend import ResultAnnouncement -from coreblocks.stages.retirement import Retirement +from coreblocks.backend.annoucement import ResultAnnouncement +from coreblocks.backend.retirement import Retirement from coreblocks.cache.icache import ICache, ICacheBypass from coreblocks.peripherals.bus_adapter import WishboneMasterAdapter from coreblocks.peripherals.wishbone import WishboneMaster, WishboneInterface from coreblocks.cache.refiller import SimpleCommonBusCacheRefiller -from coreblocks.frontend.fetch import Fetch, UnalignedFetch +from coreblocks.frontend.fetch.fetch import Fetch, UnalignedFetch from transactron.lib.transformers import MethodMap, MethodProduct from transactron.lib import BasicFifo from transactron.lib.metrics import HwMetricsEnabledKey diff --git a/coreblocks/fu/unsigned_multiplication/__init__.py b/coreblocks/core_structs/__init__.py similarity index 100% rename from coreblocks/fu/unsigned_multiplication/__init__.py rename to coreblocks/core_structs/__init__.py diff --git a/coreblocks/structs_common/rat.py b/coreblocks/core_structs/rat.py similarity index 94% rename from coreblocks/structs_common/rat.py rename to coreblocks/core_structs/rat.py index 9a823688c..10ee248af 100644 --- a/coreblocks/structs_common/rat.py +++ b/coreblocks/core_structs/rat.py @@ -1,6 +1,7 @@ from amaranth import * from transactron import Method, def_method, TModule -from coreblocks.params import RATLayouts, GenParams +from coreblocks.interface.layouts import RATLayouts +from coreblocks.params import GenParams __all__ = ["FRAT", "RRAT"] diff --git a/coreblocks/structs_common/rf.py b/coreblocks/core_structs/rf.py similarity index 96% rename from coreblocks/structs_common/rf.py rename to coreblocks/core_structs/rf.py index 899e99593..f7a9b8a7f 100644 --- a/coreblocks/structs_common/rf.py +++ b/coreblocks/core_structs/rf.py @@ -1,6 +1,7 @@ from amaranth import * from transactron import Method, def_method, TModule -from coreblocks.params import RFLayouts, GenParams +from coreblocks.interface.layouts import RFLayouts +from coreblocks.params import GenParams from transactron.utils.transactron_helpers import make_layout __all__ = ["RegisterFile"] diff --git a/coreblocks/structs_common/rob.py b/coreblocks/core_structs/rob.py similarity index 96% rename from coreblocks/structs_common/rob.py rename to coreblocks/core_structs/rob.py index b2b74f9ae..1f3806d46 100644 --- a/coreblocks/structs_common/rob.py +++ b/coreblocks/core_structs/rob.py @@ -1,7 +1,8 @@ from amaranth import * from transactron import Method, def_method, TModule from transactron.lib.metrics import * -from ..params import GenParams, ROBLayouts +from coreblocks.interface.layouts import ROBLayouts +from coreblocks.params import GenParams __all__ = ["ReorderBuffer"] diff --git a/coreblocks/frontend/decoder/__init__.py b/coreblocks/frontend/decoder/__init__.py new file mode 100644 index 000000000..0a4dc5f63 --- /dev/null +++ b/coreblocks/frontend/decoder/__init__.py @@ -0,0 +1,2 @@ +from .isa import * # noqa: F401 +from .optypes import * # noqa: F401 diff --git a/coreblocks/frontend/decode_stage.py b/coreblocks/frontend/decoder/decode_stage.py similarity index 96% rename from coreblocks/frontend/decode_stage.py rename to coreblocks/frontend/decoder/decode_stage.py index 6ba649db7..77807fb47 100644 --- a/coreblocks/frontend/decode_stage.py +++ b/coreblocks/frontend/decoder/decode_stage.py @@ -1,10 +1,10 @@ from amaranth import * -from coreblocks.params.isa import Funct3 -from coreblocks.params.optypes import OpType +from coreblocks.frontend.decoder.isa import Funct3 +from coreblocks.frontend.decoder.optypes import OpType from transactron.lib.metrics import * from transactron import Method, Transaction, TModule -from ..params import GenParams +from coreblocks.params import GenParams from .instr_decoder import InstrDecoder from coreblocks.params import * diff --git a/coreblocks/frontend/instr_decoder.py b/coreblocks/frontend/decoder/instr_decoder.py similarity index 99% rename from coreblocks/frontend/instr_decoder.py rename to coreblocks/frontend/decoder/instr_decoder.py index fff818b7a..fdb71a4c8 100644 --- a/coreblocks/frontend/instr_decoder.py +++ b/coreblocks/frontend/decoder/instr_decoder.py @@ -4,6 +4,8 @@ from amaranth import * from coreblocks.params import * +from coreblocks.frontend.decoder.optypes import * +from coreblocks.frontend.decoder.isa import * from .instr_description import instructions_by_optype, Encoding __all__ = ["InstrDecoder"] diff --git a/coreblocks/frontend/instr_description.py b/coreblocks/frontend/decoder/instr_description.py similarity index 99% rename from coreblocks/frontend/instr_description.py rename to coreblocks/frontend/decoder/instr_description.py index 632d436cc..d59309aff 100644 --- a/coreblocks/frontend/instr_description.py +++ b/coreblocks/frontend/decoder/instr_description.py @@ -2,6 +2,8 @@ from typing import Optional from coreblocks.params import * +from .isa import * +from .optypes import * @dataclass(frozen=True) diff --git a/coreblocks/frontend/decoder/isa.py b/coreblocks/frontend/decoder/isa.py new file mode 100644 index 000000000..229d65c9b --- /dev/null +++ b/coreblocks/frontend/decoder/isa.py @@ -0,0 +1,154 @@ +from amaranth.lib.enum import unique, Enum, IntEnum, IntFlag + +__all__ = [ + "InstrType", + "Opcode", + "Funct3", + "Funct7", + "Funct12", + "ExceptionCause", + "FenceTarget", + "FenceFm", + "Registers", +] + + +@unique +class InstrType(Enum): + R = 0 + I = 1 # noqa: E741 + S = 2 + B = 3 + U = 4 + J = 5 + + +@unique +class Opcode(IntEnum, shape=5): + LOAD = 0b00000 + LOAD_FP = 0b00001 + MISC_MEM = 0b00011 + OP_IMM = 0b00100 + AUIPC = 0b00101 + OP_IMM_32 = 0b00110 + STORE = 0b01000 + STORE_FP = 0b01001 + OP = 0b01100 + LUI = 0b01101 + OP32 = 0b01110 + BRANCH = 0b11000 + JALR = 0b11001 + JAL = 0b11011 + SYSTEM = 0b11100 + + +class Funct3(IntEnum, shape=3): + JALR = BEQ = B = ADD = SUB = FENCE = PRIV = MUL = MULW = _EINSTRACCESSFAULT = 0b000 + BNE = H = SLL = FENCEI = CSRRW = MULH = BCLR = BINV = BSET = CLZ = CPOP = CTZ = ROL \ + = SEXTB = SEXTH = CLMUL = _EILLEGALINSTR = 0b001 # fmt: skip + W = SLT = CSRRS = MULHSU = SH1ADD = CLMULR = _EBREAKPOINT = 0b010 + D = SLTU = CSRRC = MULHU = CLMULH = _EINSTRPAGEFAULT = 0b011 + BLT = BU = XOR = DIV = DIVW = SH2ADD = MIN = XNOR = ZEXTH = 0b100 + BGE = HU = SR = CSRRWI = DIVU = DIVUW = BEXT = ORCB = REV8 = ROR = MINU = 0b101 + BLTU = OR = CSRRSI = REM = REMW = SH3ADD = MAX = ORN = 0b110 + BGEU = AND = CSRRCI = REMU = REMUW = ANDN = MAXU = 0b111 + + +class Funct7(IntEnum, shape=7): + SL = SLT = ADD = XOR = OR = AND = 0b0000000 + SA = SUB = ANDN = ORN = XNOR = 0b0100000 + MULDIV = 0b0000001 + SH1ADD = SH2ADD = SH3ADD = 0b0010000 + BCLR = BEXT = 0b0100100 + BINV = REV8 = 0b0110100 + BSET = ORCB = 0b0010100 + MAX = MIN = CLMUL = 0b0000101 + ROL = ROR = SEXTB = SEXTH = CPOP = CLZ = CTZ = 0b0110000 + ZEXTH = 0b0000100 + SFENCEVMA = 0b0001001 + + +class Funct12(IntEnum, shape=12): + ECALL = 0b000000000000 + EBREAK = 0b000000000001 + SRET = 0b000100000010 + MRET = 0b001100000010 + WFI = 0b000100000101 + CPOP = 0b011000000010 + CLZ = 0b011000000000 + CTZ = 0b011000000001 + ORCB = 0b001010000111 + REV8_32 = 0b011010011000 + REV8_64 = 0b011010111000 + SEXTB = 0b011000000100 + SEXTH = 0b011000000101 + ZEXTH = 0b000010000000 + + +class Registers(IntEnum, shape=5): + X0 = ZERO = 0b00000 # hardwired zero + X1 = RA = 0b00001 # return address + X2 = SP = 0b00010 # stack pointer + X3 = GP = 0b00011 # global pointer + X4 = TP = 0b00100 # thread pointer + X5 = T0 = 0b00101 # temporary register 0 + X6 = T1 = 0b00110 # temporary register 1 + X7 = T2 = 0b00111 # temporary register 2 + X8 = S0 = FP = 0b01000 # saved register 0 / frame pointer + X9 = S1 = 0b01001 # saved register 1 + X10 = A0 = 0b01010 # function argument 0 / return value 0 + X11 = A1 = 0b01011 # function argument 1 / return value 1 + X12 = A2 = 0b01100 # function argument 2 + X13 = A3 = 0b01101 # function argument 3 + X14 = A4 = 0b01110 # function argument 4 + X15 = A5 = 0b01111 # function argument 5 + X16 = A6 = 0b10000 # function argument 6 + X17 = A7 = 0b10001 # function argument 7 + X18 = S2 = 0b10010 # saved register 2 + X19 = S3 = 0b10011 # saved register 3 + X20 = S4 = 0b10100 # saved register 4 + X21 = S5 = 0b10101 # saved register 5 + X22 = S6 = 0b10110 # saved register 6 + X23 = S7 = 0b10111 # saved register 7 + X24 = S8 = 0b11000 # saved register 8 + X25 = S9 = 0b11001 # saved register 9 + X26 = S10 = 0b11010 # saved register 10 + X27 = S11 = 0b11011 # saved register 11 + X28 = T3 = 0b11100 # temporary register 3 + X29 = T4 = 0b11101 # temporary register 4 + X30 = T5 = 0b11110 # temporary register 5 + X31 = T6 = 0b11111 # temporary register 6 + + +@unique +class FenceTarget(IntFlag, shape=4): + MEM_W = 0b0001 + MEM_R = 0b0010 + DEV_O = 0b0100 + DEV_I = 0b1000 + + +@unique +class FenceFm(IntEnum, shape=4): + NONE = 0b0000 + TSO = 0b1000 + + +@unique +class ExceptionCause(IntEnum, shape=5): + INSTRUCTION_ADDRESS_MISALIGNED = 0 + INSTRUCTION_ACCESS_FAULT = 1 + ILLEGAL_INSTRUCTION = 2 + BREAKPOINT = 3 + LOAD_ADDRESS_MISALIGNED = 4 + LOAD_ACCESS_FAULT = 5 + STORE_ADDRESS_MISALIGNED = 6 + STORE_ACCESS_FAULT = 7 + ENVIRONMENT_CALL_FROM_U = 8 + ENVIRONMENT_CALL_FROM_S = 9 + ENVIRONMENT_CALL_FROM_M = 11 + INSTRUCTION_PAGE_FAULT = 12 + LOAD_PAGE_FAULT = 13 + STORE_PAGE_FAULT = 15 + _COREBLOCKS_ASYNC_INTERRUPT = 16 + _COREBLOCKS_MISPREDICTION = 17 diff --git a/coreblocks/params/optypes.py b/coreblocks/frontend/decoder/optypes.py similarity index 97% rename from coreblocks/params/optypes.py rename to coreblocks/frontend/decoder/optypes.py index 60fd52c19..fe60c62e2 100644 --- a/coreblocks/params/optypes.py +++ b/coreblocks/frontend/decoder/optypes.py @@ -1,7 +1,7 @@ from enum import IntEnum, auto, unique from coreblocks.params import Extension -from coreblocks.params.isa import extension_implications, extension_only_implies +from coreblocks.params.isa_params import extension_implications, extension_only_implies @unique diff --git a/coreblocks/frontend/rvc.py b/coreblocks/frontend/decoder/rvc.py similarity index 99% rename from coreblocks/frontend/rvc.py rename to coreblocks/frontend/decoder/rvc.py index bd01255fd..4ff48c07d 100644 --- a/coreblocks/frontend/rvc.py +++ b/coreblocks/frontend/decoder/rvc.py @@ -2,6 +2,7 @@ from transactron import TModule from coreblocks.params import * +from coreblocks.frontend.decoder.isa import * from transactron.utils import ValueLike diff --git a/coreblocks/lsu/__init__.py b/coreblocks/frontend/fetch/__init__.py similarity index 100% rename from coreblocks/lsu/__init__.py rename to coreblocks/frontend/fetch/__init__.py diff --git a/coreblocks/frontend/fetch.py b/coreblocks/frontend/fetch/fetch.py similarity index 97% rename from coreblocks/frontend/fetch.py rename to coreblocks/frontend/fetch/fetch.py index 33a1a2129..add09c6c1 100644 --- a/coreblocks/frontend/fetch.py +++ b/coreblocks/frontend/fetch/fetch.py @@ -3,9 +3,11 @@ from transactron.lib import BasicFifo, Semaphore from transactron.lib.metrics import * from coreblocks.cache.iface import CacheInterface -from coreblocks.frontend.rvc import InstrDecompress, is_instr_compressed +from coreblocks.frontend.decoder.rvc import InstrDecompress, is_instr_compressed from transactron import def_method, Method, Transaction, TModule -from ..params import * +from coreblocks.params import * +from coreblocks.interface.layouts import * +from coreblocks.frontend.decoder.isa import * class Fetch(Elaboratable): diff --git a/coreblocks/stages/__init__.py b/coreblocks/func_blocks/__init__.py similarity index 100% rename from coreblocks/stages/__init__.py rename to coreblocks/func_blocks/__init__.py diff --git a/coreblocks/structs_common/csr.py b/coreblocks/func_blocks/csr/csr.py similarity index 64% rename from coreblocks/structs_common/csr.py rename to coreblocks/func_blocks/csr/csr.py index a01a028fa..43ddfe957 100644 --- a/coreblocks/structs_common/csr.py +++ b/coreblocks/func_blocks/csr/csr.py @@ -1,163 +1,21 @@ from amaranth import * from amaranth.lib.data import StructLayout -from amaranth.lib.enum import IntEnum -from dataclasses import dataclass from transactron import Method, def_method, Transaction, TModule -from transactron.utils import assign, bits_from_int +from transactron.utils import assign from coreblocks.params.genparams import GenParams -from transactron.utils.dependencies import DependencyManager, ListKey +from transactron.utils.dependencies import DependencyManager from coreblocks.params.fu_params import BlockComponentParams -from coreblocks.params.layouts import FetchLayouts, FuncUnitLayouts, CSRLayouts -from coreblocks.params.isa import Funct3, ExceptionCause -from coreblocks.params.keys import ( - AsyncInterruptInsertSignalKey, +from coreblocks.interface.layouts import FetchLayouts, FuncUnitLayouts, CSRLayouts +from coreblocks.frontend.decoder import Funct3, ExceptionCause, OpType +from coreblocks.func_blocks.interface.func_protocols import FuncBlock +from coreblocks.priv.csr.csr_register import * +from coreblocks.interface.keys import ( FetchResumeKey, - ExceptionReportKey, InstructionPrecommitKey, + ExceptionReportKey, + AsyncInterruptInsertSignalKey, ) -from coreblocks.params.optypes import OpType -from coreblocks.utils.protocols import FuncBlock -from transactron.utils.transactron_helpers import from_method_layout - - -class PrivilegeLevel(IntEnum, shape=2): - USER = 0b00 - SUPERVISOR = 0b01 - MACHINE = 0b11 - - -def csr_access_privilege(csr_addr: int) -> tuple[PrivilegeLevel, bool]: - read_only = bits_from_int(csr_addr, 10, 2) == 0b11 - - match bits_from_int(csr_addr, 8, 2): - case 0b00: - return (PrivilegeLevel.USER, read_only) - case 0b01: - return (PrivilegeLevel.SUPERVISOR, read_only) - case 0b10: # Hypervisior CSRs - accessible with VS mode (S with extension) - return (PrivilegeLevel.SUPERVISOR, read_only) - case _: - return (PrivilegeLevel.MACHINE, read_only) - - -@dataclass(frozen=True) -class CSRListKey(ListKey["CSRRegister"]): - """DependencyManager key collecting CSR registers globally as a list.""" - - # This key is defined here, because it is only used internally by CSRRegister and CSRUnit - pass - - -class CSRRegister(Elaboratable): - """CSR Register - Used to define a CSR register and specify its behaviour. - `CSRRegisters` are automatically assigned to `CSRListKey` dependency key, to be accessed from `CSRUnits`. - - Attributes - ---------- - read: Method - Reads register value and side effect status. - Side effect fields `read` and `written` are set if register was accessed by _fu_read or _fu_write - methods (by CSR instruction) in a current cycle; they can be used to trigger other actions. - Always ready. - write: Method - Updates register value. - Always ready. If _fu_write is called simultaneously, this call is ignored. - _fu_read: Method - Method connected automatically by `CSRUnit`. Reads register value. - _fu_write: Method - Method connected automatically by `CSRUnit`. Updates register value. - Always ready. Has priority over `write` method. - - Examples - -------- - .. highlight:: python - .. code-block:: python - - # Timer register that increments on each cycle and resets if read by CSR instruction - csr = CSRRegister(1, gen_params) - with Transaction.body(m): - csr_val = csr.read() - with m.If(csr_val.read): - csr.write(0) - with m.Else(): - csr.write(csr_val.data + 1) - """ - - def __init__(self, csr_number: int, gen_params: GenParams, *, ro_bits: int = 0): - """ - Parameters - ---------- - csr_number: int - Address of this CSR Register. - gen_params: GenParams - Core generation parameters. - ro_bits: int - Bit mask of read-only bits in register. - Writes from _fu_write (instructions) to those bits are ignored. - Note that this parameter is only required if there are some read-only - bits in read-write register. Writes to read-only registers specified - by upper 2 bits of CSR address set to `0b11` are discarded by `CSRUnit`. - """ - self.gen_params = gen_params - self.csr_number = csr_number - self.ro_bits = ro_bits - - csr_layouts = gen_params.get(CSRLayouts) - - self.read = Method(o=csr_layouts.read) - self.write = Method(i=csr_layouts.write) - - # Methods connected automatically by CSRUnit - self._fu_read = Method(o=csr_layouts._fu_read) - self._fu_write = Method(i=csr_layouts._fu_write) - - self.value = Signal(gen_params.isa.xlen) - self.side_effects = Signal(StructLayout({"read": 1, "write": 1})) - - # append to global CSR list - dm = gen_params.get(DependencyManager) - dm.add_dependency(CSRListKey(), self) - - def elaborate(self, platform): - m = TModule() - - internal_method_layout = from_method_layout([("data", self.gen_params.isa.xlen), ("active", 1)]) - write_internal = Signal(internal_method_layout) - fu_write_internal = Signal(internal_method_layout) - - m.d.sync += self.side_effects.eq(0) - - @def_method(m, self.write) - def _(data): - m.d.comb += write_internal.data.eq(data) - m.d.comb += write_internal.active.eq(1) - - @def_method(m, self._fu_write) - def _(data): - m.d.comb += fu_write_internal.data.eq(data) - m.d.comb += fu_write_internal.active.eq(1) - m.d.sync += self.side_effects.write.eq(1) - - @def_method(m, self.read) - def _(): - return {"data": self.value, "read": self.side_effects.read, "written": self.side_effects.write} - - @def_method(m, self._fu_read) - def _(): - m.d.sync += self.side_effects.read.eq(1) - return self.value - - # Writes from instructions have priority - with m.If(fu_write_internal.active & write_internal.active): - m.d.sync += self.value.eq((fu_write_internal.data & ~self.ro_bits) | (write_internal.data & self.ro_bits)) - with m.Elif(fu_write_internal.active): - m.d.sync += self.value.eq((fu_write_internal.data & ~self.ro_bits) | (self.value & self.ro_bits)) - with m.Elif(write_internal.active): - m.d.sync += self.value.eq(write_internal.data) - - return m class CSRUnit(FuncBlock, Elaboratable): diff --git a/coreblocks/structs_common/__init__.py b/coreblocks/func_blocks/fu/__init__.py similarity index 100% rename from coreblocks/structs_common/__init__.py rename to coreblocks/func_blocks/fu/__init__.py diff --git a/coreblocks/fu/alu.py b/coreblocks/func_blocks/fu/alu.py similarity index 96% rename from coreblocks/fu/alu.py rename to coreblocks/func_blocks/fu/alu.py index 114e367ce..adfcc6a3f 100644 --- a/coreblocks/fu/alu.py +++ b/coreblocks/func_blocks/fu/alu.py @@ -4,13 +4,16 @@ from transactron import * from transactron.lib import FIFO -from coreblocks.params import OpType, Funct3, Funct7, GenParams, FuncUnitLayouts, FunctionalComponentParams +from coreblocks.frontend.decoder.isa import Funct3, Funct7 +from coreblocks.frontend.decoder.optypes import OpType +from coreblocks.interface.layouts import FuncUnitLayouts +from coreblocks.params import GenParams, FunctionalComponentParams from transactron.utils import HasElaborate, OneHotSwitch -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager from enum import IntFlag, auto -from coreblocks.utils.protocols import FuncUnit +from coreblocks.func_blocks.interface.func_protocols import FuncUnit from transactron.utils import popcount, count_leading_zeros diff --git a/coreblocks/fu/fu_decoder.py b/coreblocks/func_blocks/fu/common/fu_decoder.py similarity index 94% rename from coreblocks/fu/fu_decoder.py rename to coreblocks/func_blocks/fu/common/fu_decoder.py index eeaae8bf1..2324b9f24 100644 --- a/coreblocks/fu/fu_decoder.py +++ b/coreblocks/func_blocks/fu/common/fu_decoder.py @@ -1,11 +1,12 @@ from typing import Sequence, Type from amaranth import * -from coreblocks.params import GenParams, CommonLayoutFields +from coreblocks.params import GenParams +from coreblocks.interface.layouts import CommonLayoutFields from enum import IntFlag -from coreblocks.params.optypes import OpType +from coreblocks.frontend.decoder.optypes import OpType class Decoder(Elaboratable): diff --git a/coreblocks/structs_common/rs.py b/coreblocks/func_blocks/fu/common/rs.py similarity index 96% rename from coreblocks/structs_common/rs.py rename to coreblocks/func_blocks/fu/common/rs.py index 6af0b5e2a..56287df27 100644 --- a/coreblocks/structs_common/rs.py +++ b/coreblocks/func_blocks/fu/common/rs.py @@ -3,7 +3,9 @@ from amaranth import * from amaranth.lib.coding import PriorityEncoder from transactron import Method, def_method, TModule -from coreblocks.params import RSLayouts, GenParams, OpType +from coreblocks.params import GenParams +from coreblocks.frontend.decoder import OpType +from coreblocks.interface.layouts import RSLayouts from transactron.utils import RecordDict from transactron.utils.transactron_helpers import make_layout diff --git a/coreblocks/stages/rs_func_block.py b/coreblocks/func_blocks/fu/common/rs_func_block.py similarity index 94% rename from coreblocks/stages/rs_func_block.py rename to coreblocks/func_blocks/fu/common/rs_func_block.py index 9b3a45c4b..66fed3d0e 100644 --- a/coreblocks/stages/rs_func_block.py +++ b/coreblocks/func_blocks/fu/common/rs_func_block.py @@ -2,11 +2,13 @@ from amaranth import * from dataclasses import dataclass from coreblocks.params import * -from coreblocks.structs_common.rs import RS +from .rs import RS from coreblocks.scheduler.wakeup_select import WakeupSelect from transactron import Method, TModule -from coreblocks.utils.protocols import FuncUnit, FuncBlock +from coreblocks.func_blocks.interface.func_protocols import FuncUnit, FuncBlock from transactron.lib import Collector +from coreblocks.frontend.decoder import OpType +from coreblocks.interface.layouts import RSLayouts, FuncUnitLayouts __all__ = ["RSFuncBlock", "RSBlockComponent"] diff --git a/coreblocks/fu/div_unit.py b/coreblocks/func_blocks/fu/div_unit.py similarity index 93% rename from coreblocks/fu/div_unit.py rename to coreblocks/func_blocks/fu/div_unit.py index 9e3f3dfc6..bc4938624 100644 --- a/coreblocks/fu/div_unit.py +++ b/coreblocks/func_blocks/fu/div_unit.py @@ -6,16 +6,18 @@ from amaranth.lib import data from coreblocks.params.fu_params import FunctionalComponentParams -from coreblocks.params import Funct3, GenParams, FuncUnitLayouts, OpType +from coreblocks.params import GenParams +from coreblocks.frontend.decoder import Funct3, OpType +from coreblocks.interface.layouts import FuncUnitLayouts from transactron import * from transactron.core import def_method from transactron.lib import * -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager from transactron.utils import OneHotSwitch -from coreblocks.utils.protocols import FuncUnit -from coreblocks.fu.division.long_division import LongDivider +from coreblocks.func_blocks.interface.func_protocols import FuncUnit +from coreblocks.func_blocks.fu.division.long_division import LongDivider class DivFn(DecoderManager): diff --git a/coreblocks/fu/division/common.py b/coreblocks/func_blocks/fu/division/common.py similarity index 87% rename from coreblocks/fu/division/common.py rename to coreblocks/func_blocks/fu/division/common.py index a48175086..c253090f1 100644 --- a/coreblocks/fu/division/common.py +++ b/coreblocks/func_blocks/fu/division/common.py @@ -1,6 +1,6 @@ from amaranth import * from coreblocks.params import GenParams -from coreblocks.params.layouts import DivUnitLayouts +from coreblocks.interface.layouts import DivUnitLayouts from transactron.core import Method diff --git a/coreblocks/fu/division/long_division.py b/coreblocks/func_blocks/fu/division/long_division.py similarity index 99% rename from coreblocks/fu/division/long_division.py rename to coreblocks/func_blocks/fu/division/long_division.py index c74958340..efa140430 100644 --- a/coreblocks/fu/division/long_division.py +++ b/coreblocks/func_blocks/fu/division/long_division.py @@ -3,7 +3,7 @@ from coreblocks.params import GenParams from transactron import * from transactron.core import def_method -from coreblocks.fu.division.common import DividerBase +from coreblocks.func_blocks.fu.division.common import DividerBase """ Algorithm - multi-cycle array divider diff --git a/coreblocks/fu/exception.py b/coreblocks/func_blocks/fu/exception.py similarity index 90% rename from coreblocks/fu/exception.py rename to coreblocks/func_blocks/fu/exception.py index 2927c0399..a944276fd 100644 --- a/coreblocks/fu/exception.py +++ b/coreblocks/func_blocks/fu/exception.py @@ -1,19 +1,20 @@ from typing import Sequence from amaranth import * from transactron.utils.dependencies import DependencyManager -from coreblocks.params.isa import Funct3, ExceptionCause from transactron import * from transactron.lib import FIFO -from coreblocks.params import OpType, GenParams, FuncUnitLayouts, FunctionalComponentParams +from coreblocks.params import GenParams, FunctionalComponentParams +from coreblocks.frontend.decoder import Funct3, OpType, ExceptionCause +from coreblocks.interface.layouts import FuncUnitLayouts from transactron.utils import OneHotSwitch -from coreblocks.params.keys import ExceptionReportKey +from coreblocks.interface.keys import ExceptionReportKey -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager from enum import IntFlag, auto -from coreblocks.utils.protocols import FuncUnit +from coreblocks.func_blocks.interface.func_protocols import FuncUnit __all__ = ["ExceptionFuncUnit", "ExceptionUnitComponent"] diff --git a/coreblocks/fu/jumpbranch.py b/coreblocks/func_blocks/fu/jumpbranch.py similarity index 95% rename from coreblocks/fu/jumpbranch.py rename to coreblocks/func_blocks/fu/jumpbranch.py index 8b4ba52c9..aeb6fed22 100644 --- a/coreblocks/fu/jumpbranch.py +++ b/coreblocks/func_blocks/fu/jumpbranch.py @@ -9,12 +9,13 @@ from transactron.lib import * from transactron.lib import logging from transactron.utils import DependencyManager -from coreblocks.params import * -from coreblocks.params.keys import AsyncInterruptInsertSignalKey, BranchVerifyKey +from coreblocks.params import GenParams, FunctionalComponentParams, Extension +from coreblocks.frontend.decoder import Funct3, OpType, ExceptionCause +from coreblocks.interface.layouts import FuncUnitLayouts, JumpBranchLayouts +from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, BranchVerifyKey, ExceptionReportKey from transactron.utils import OneHotSwitch -from coreblocks.utils.protocols import FuncUnit - -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.interface.func_protocols import FuncUnit +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager __all__ = ["JumpBranchFuncUnit", "JumpComponent"] diff --git a/coreblocks/fu/mul_unit.py b/coreblocks/func_blocks/fu/mul_unit.py similarity index 93% rename from coreblocks/fu/mul_unit.py rename to coreblocks/func_blocks/fu/mul_unit.py index 0deba543a..60b0b53a6 100644 --- a/coreblocks/fu/mul_unit.py +++ b/coreblocks/func_blocks/fu/mul_unit.py @@ -4,22 +4,23 @@ from amaranth import * -from coreblocks.fu.unsigned_multiplication.fast_recursive import RecursiveUnsignedMul -from coreblocks.fu.unsigned_multiplication.sequence import SequentialUnsignedMul -from coreblocks.fu.unsigned_multiplication.shift import ShiftUnsignedMul -from coreblocks.params.fu_params import FunctionalComponentParams -from coreblocks.params import Funct3, GenParams, FuncUnitLayouts, OpType +from coreblocks.func_blocks.fu.unsigned_multiplication.fast_recursive import RecursiveUnsignedMul +from coreblocks.func_blocks.fu.unsigned_multiplication.sequence import SequentialUnsignedMul +from coreblocks.func_blocks.fu.unsigned_multiplication.shift import ShiftUnsignedMul +from coreblocks.params import GenParams, FunctionalComponentParams +from coreblocks.frontend.decoder import Funct3, OpType +from coreblocks.interface.layouts import FuncUnitLayouts from transactron import * from transactron.core import def_method from transactron.lib import * -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager __all__ = ["MulUnit", "MulFn", "MulComponent", "MulType"] from transactron.utils import OneHotSwitch -from coreblocks.utils.protocols import FuncUnit +from coreblocks.func_blocks.interface.func_protocols import FuncUnit class MulFn(DecoderManager): diff --git a/coreblocks/fu/priv.py b/coreblocks/func_blocks/fu/priv.py similarity index 86% rename from coreblocks/fu/priv.py rename to coreblocks/func_blocks/fu/priv.py index 1e7d599d5..d153df540 100644 --- a/coreblocks/fu/priv.py +++ b/coreblocks/func_blocks/fu/priv.py @@ -1,6 +1,6 @@ from amaranth import * -from enum import IntFlag, auto +from enum import IntFlag, auto, unique from typing import Sequence @@ -9,10 +9,20 @@ from transactron.utils import DependencyManager from coreblocks.params import * -from coreblocks.params.keys import MretKey -from coreblocks.utils.protocols import FuncUnit - -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.params import GenParams, FunctionalComponentParams +from coreblocks.frontend.decoder import OpType, ExceptionCause +from coreblocks.interface.layouts import FuncUnitLayouts, RetirementLayouts, FetchLayouts +from coreblocks.interface.keys import ( + MretKey, + AsyncInterruptInsertSignalKey, + ExceptionReportKey, + GenericCSRRegistersKey, + InstructionPrecommitKey, + FetchResumeKey, +) +from coreblocks.func_blocks.interface.func_protocols import FuncUnit + +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager class PrivilegedFn(DecoderManager): diff --git a/coreblocks/fu/shift_unit.py b/coreblocks/func_blocks/fu/shift_unit.py similarity index 92% rename from coreblocks/fu/shift_unit.py rename to coreblocks/func_blocks/fu/shift_unit.py index 0df08b73c..1c27c78a3 100644 --- a/coreblocks/fu/shift_unit.py +++ b/coreblocks/func_blocks/fu/shift_unit.py @@ -4,13 +4,15 @@ from transactron import * from transactron.lib import FIFO -from coreblocks.params import OpType, Funct3, Funct7, GenParams, FuncUnitLayouts, FunctionalComponentParams +from coreblocks.params import GenParams, FunctionalComponentParams +from coreblocks.frontend.decoder import Funct3, OpType, Funct7 +from coreblocks.interface.layouts import FuncUnitLayouts from transactron.utils import OneHotSwitch -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager from enum import IntFlag, auto -from coreblocks.utils.protocols import FuncUnit +from coreblocks.func_blocks.interface.func_protocols import FuncUnit __all__ = ["ShiftFuncUnit", "ShiftUnitComponent"] diff --git a/coreblocks/utils/__init__.py b/coreblocks/func_blocks/fu/unsigned_multiplication/__init__.py similarity index 100% rename from coreblocks/utils/__init__.py rename to coreblocks/func_blocks/fu/unsigned_multiplication/__init__.py diff --git a/coreblocks/fu/unsigned_multiplication/common.py b/coreblocks/func_blocks/fu/unsigned_multiplication/common.py similarity index 94% rename from coreblocks/fu/unsigned_multiplication/common.py rename to coreblocks/func_blocks/fu/unsigned_multiplication/common.py index aa9d11924..28c2cf977 100644 --- a/coreblocks/fu/unsigned_multiplication/common.py +++ b/coreblocks/func_blocks/fu/unsigned_multiplication/common.py @@ -1,6 +1,7 @@ from amaranth import * -from coreblocks.params import GenParams, UnsignedMulUnitLayouts +from coreblocks.params import GenParams +from coreblocks.interface.layouts import UnsignedMulUnitLayouts from transactron import * from transactron.core import def_method diff --git a/coreblocks/fu/unsigned_multiplication/fast_recursive.py b/coreblocks/func_blocks/fu/unsigned_multiplication/fast_recursive.py similarity index 97% rename from coreblocks/fu/unsigned_multiplication/fast_recursive.py rename to coreblocks/func_blocks/fu/unsigned_multiplication/fast_recursive.py index b5b189b91..fdb294907 100644 --- a/coreblocks/fu/unsigned_multiplication/fast_recursive.py +++ b/coreblocks/func_blocks/fu/unsigned_multiplication/fast_recursive.py @@ -1,6 +1,6 @@ from amaranth import * -from coreblocks.fu.unsigned_multiplication.common import MulBaseUnsigned, DSPMulUnit +from coreblocks.func_blocks.fu.unsigned_multiplication.common import MulBaseUnsigned, DSPMulUnit from coreblocks.params import GenParams from transactron import * from transactron.core import def_method diff --git a/coreblocks/fu/unsigned_multiplication/sequence.py b/coreblocks/func_blocks/fu/unsigned_multiplication/sequence.py similarity index 98% rename from coreblocks/fu/unsigned_multiplication/sequence.py rename to coreblocks/func_blocks/fu/unsigned_multiplication/sequence.py index 549c125e0..41733027b 100644 --- a/coreblocks/fu/unsigned_multiplication/sequence.py +++ b/coreblocks/func_blocks/fu/unsigned_multiplication/sequence.py @@ -1,6 +1,6 @@ from amaranth import * -from coreblocks.fu.unsigned_multiplication.common import DSPMulUnit, MulBaseUnsigned +from coreblocks.func_blocks.fu.unsigned_multiplication.common import DSPMulUnit, MulBaseUnsigned from coreblocks.params import GenParams from transactron import * from transactron.core import def_method diff --git a/coreblocks/fu/unsigned_multiplication/shift.py b/coreblocks/func_blocks/fu/unsigned_multiplication/shift.py similarity index 93% rename from coreblocks/fu/unsigned_multiplication/shift.py rename to coreblocks/func_blocks/fu/unsigned_multiplication/shift.py index 9bdd8c336..77dbb19b2 100644 --- a/coreblocks/fu/unsigned_multiplication/shift.py +++ b/coreblocks/func_blocks/fu/unsigned_multiplication/shift.py @@ -1,6 +1,6 @@ from amaranth import * -from coreblocks.fu.unsigned_multiplication.common import MulBaseUnsigned +from coreblocks.func_blocks.fu.unsigned_multiplication.common import MulBaseUnsigned from coreblocks.params import GenParams from transactron.core import def_method, TModule diff --git a/coreblocks/fu/zbc.py b/coreblocks/func_blocks/fu/zbc.py similarity index 96% rename from coreblocks/fu/zbc.py rename to coreblocks/func_blocks/fu/zbc.py index 8834d40fe..6d7158011 100644 --- a/coreblocks/fu/zbc.py +++ b/coreblocks/func_blocks/fu/zbc.py @@ -4,18 +4,14 @@ from amaranth import * -from coreblocks.fu.fu_decoder import DecoderManager -from coreblocks.params import ( - Funct3, - OpType, - GenParams, - FuncUnitLayouts, - FunctionalComponentParams, -) +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager +from coreblocks.params import GenParams, FunctionalComponentParams +from coreblocks.frontend.decoder import Funct3, OpType +from coreblocks.interface.layouts import FuncUnitLayouts from transactron import Method, def_method, TModule from transactron.lib import FIFO from transactron.utils import OneHotSwitch -from coreblocks.utils.protocols import FuncUnit +from coreblocks.func_blocks.interface.func_protocols import FuncUnit class ZbcFn(DecoderManager): diff --git a/coreblocks/fu/zbs.py b/coreblocks/func_blocks/fu/zbs.py similarity index 92% rename from coreblocks/fu/zbs.py rename to coreblocks/func_blocks/fu/zbs.py index d135c5e75..7fca4253e 100644 --- a/coreblocks/fu/zbs.py +++ b/coreblocks/func_blocks/fu/zbs.py @@ -2,13 +2,15 @@ from typing import Sequence from amaranth import * -from coreblocks.params import Funct3, GenParams, FuncUnitLayouts, OpType, Funct7, FunctionalComponentParams +from coreblocks.params import GenParams, FunctionalComponentParams +from coreblocks.frontend.decoder import Funct3, OpType, Funct7 +from coreblocks.interface.layouts import FuncUnitLayouts from transactron import Method, TModule, def_method from transactron.lib import FIFO from transactron.utils import OneHotSwitch -from coreblocks.utils.protocols import FuncUnit +from coreblocks.func_blocks.interface.func_protocols import FuncUnit -from coreblocks.fu.fu_decoder import DecoderManager +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager class ZbsFunction(DecoderManager): diff --git a/coreblocks/func_blocks/interface/__init__.py b/coreblocks/func_blocks/interface/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coreblocks/stages/func_blocks_unifier.py b/coreblocks/func_blocks/interface/func_blocks_unifier.py similarity index 100% rename from coreblocks/stages/func_blocks_unifier.py rename to coreblocks/func_blocks/interface/func_blocks_unifier.py diff --git a/coreblocks/utils/protocols.py b/coreblocks/func_blocks/interface/func_protocols.py similarity index 100% rename from coreblocks/utils/protocols.py rename to coreblocks/func_blocks/interface/func_protocols.py diff --git a/coreblocks/func_blocks/lsu/__init__.py b/coreblocks/func_blocks/lsu/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coreblocks/lsu/dummyLsu.py b/coreblocks/func_blocks/lsu/dummyLsu.py similarity index 97% rename from coreblocks/lsu/dummyLsu.py rename to coreblocks/func_blocks/lsu/dummyLsu.py index 3b8edd4a4..ccda62e32 100644 --- a/coreblocks/lsu/dummyLsu.py +++ b/coreblocks/func_blocks/lsu/dummyLsu.py @@ -6,10 +6,13 @@ from coreblocks.peripherals.bus_adapter import BusMasterInterface from transactron.lib.connectors import Forwarder from transactron.utils import assign, ModuleLike, DependencyManager -from coreblocks.utils.protocols import FuncBlock +from coreblocks.func_blocks.interface.func_protocols import FuncBlock from transactron.lib.simultaneous import condition -from coreblocks.lsu.pma import PMAChecker +from coreblocks.frontend.decoder import * +from coreblocks.interface.layouts import LSULayouts, FuncUnitLayouts +from coreblocks.func_blocks.lsu.pma import PMAChecker +from coreblocks.interface.keys import ExceptionReportKey, CommonBusDataKey, InstructionPrecommitKey __all__ = ["LSUDummy", "LSUBlockComponent"] diff --git a/coreblocks/lsu/pma.py b/coreblocks/func_blocks/lsu/pma.py similarity index 100% rename from coreblocks/lsu/pma.py rename to coreblocks/func_blocks/lsu/pma.py diff --git a/coreblocks/params/keys.py b/coreblocks/interface/keys.py similarity index 93% rename from coreblocks/params/keys.py rename to coreblocks/interface/keys.py index eab1b3985..9f863c614 100644 --- a/coreblocks/params/keys.py +++ b/coreblocks/interface/keys.py @@ -8,7 +8,7 @@ from amaranth import Signal if TYPE_CHECKING: - from coreblocks.structs_common.csr_generic import GenericCSRRegisters # noqa: F401 + from coreblocks.priv.csr.csr_instances import GenericCSRRegisters # noqa: F401 __all__ = [ "CommonBusDataKey", diff --git a/coreblocks/params/layouts.py b/coreblocks/interface/layouts.py similarity index 99% rename from coreblocks/params/layouts.py rename to coreblocks/interface/layouts.py index 98f69344c..5db15302e 100644 --- a/coreblocks/params/layouts.py +++ b/coreblocks/interface/layouts.py @@ -1,6 +1,6 @@ from amaranth.lib.data import StructLayout -from coreblocks.params import GenParams, OpType, Funct7, Funct3 -from coreblocks.params.isa import ExceptionCause +from coreblocks.params import GenParams +from coreblocks.frontend.decoder import ExceptionCause, OpType, Funct7, Funct3 from transactron.utils import LayoutList, LayoutListField, layout_subset from transactron.utils.transactron_helpers import from_method_layout, make_layout diff --git a/coreblocks/params/__init__.py b/coreblocks/params/__init__.py index 8e5e6dfc1..60379a5ab 100644 --- a/coreblocks/params/__init__.py +++ b/coreblocks/params/__init__.py @@ -1,8 +1,5 @@ -from .isa import * # noqa: F401 -from .optypes import * # noqa: F401 +from .isa_params import * # noqa: F401 from .genparams import * # noqa: F401 -from .layouts import * # noqa: F401 from .fu_params import * # noqa: F401 -from .keys import * # noqa: F401 from .icache_params import * # noqa: F401 from .instr import * # noqa: F401 diff --git a/coreblocks/params/configurations.py b/coreblocks/params/configurations.py index 6d69cd8f9..a9dee4931 100644 --- a/coreblocks/params/configurations.py +++ b/coreblocks/params/configurations.py @@ -2,23 +2,23 @@ import dataclasses from dataclasses import dataclass, field -from coreblocks.lsu.pma import PMARegion +from coreblocks.func_blocks.lsu.pma import PMARegion -from coreblocks.params.isa import Extension +from coreblocks.params.isa_params import Extension from coreblocks.params.fu_params import BlockComponentParams -from coreblocks.stages.rs_func_block import RSBlockComponent - -from coreblocks.fu.alu import ALUComponent -from coreblocks.fu.shift_unit import ShiftUnitComponent -from coreblocks.fu.jumpbranch import JumpComponent -from coreblocks.fu.mul_unit import MulComponent, MulType -from coreblocks.fu.div_unit import DivComponent -from coreblocks.fu.zbc import ZbcComponent -from coreblocks.fu.zbs import ZbsComponent -from coreblocks.fu.exception import ExceptionUnitComponent -from coreblocks.fu.priv import PrivilegedUnitComponent -from coreblocks.lsu.dummyLsu import LSUBlockComponent -from coreblocks.structs_common.csr import CSRBlockComponent +from coreblocks.func_blocks.fu.common.rs_func_block import RSBlockComponent + +from coreblocks.func_blocks.fu.alu import ALUComponent +from coreblocks.func_blocks.fu.shift_unit import ShiftUnitComponent +from coreblocks.func_blocks.fu.jumpbranch import JumpComponent +from coreblocks.func_blocks.fu.mul_unit import MulComponent, MulType +from coreblocks.func_blocks.fu.div_unit import DivComponent +from coreblocks.func_blocks.fu.zbc import ZbcComponent +from coreblocks.func_blocks.fu.zbs import ZbsComponent +from coreblocks.func_blocks.fu.exception import ExceptionUnitComponent +from coreblocks.func_blocks.fu.priv import PrivilegedUnitComponent +from coreblocks.func_blocks.lsu.dummyLsu import LSUBlockComponent +from coreblocks.func_blocks.csr.csr import CSRBlockComponent __all__ = ["CoreConfiguration", "basic_core_config", "tiny_core_config", "full_core_config", "test_core_config"] diff --git a/coreblocks/params/fu_params.py b/coreblocks/params/fu_params.py index e5962a1a9..297e9e9fc 100644 --- a/coreblocks/params/fu_params.py +++ b/coreblocks/params/fu_params.py @@ -1,16 +1,16 @@ from abc import abstractmethod, ABC from collections.abc import Collection, Iterable -from coreblocks.utils.protocols import FuncBlock, FuncUnit -from coreblocks.params.isa import Extension, extension_implications -from coreblocks.params.optypes import optypes_required_by_extensions +from coreblocks.func_blocks.interface.func_protocols import FuncBlock, FuncUnit +from coreblocks.params.isa_params import Extension, extension_implications +from coreblocks.frontend.decoder import optypes_required_by_extensions from typing import TYPE_CHECKING if TYPE_CHECKING: from coreblocks.params.genparams import GenParams - from coreblocks.params.optypes import OpType + from coreblocks.frontend.decoder.optypes import OpType __all__ = [ diff --git a/coreblocks/params/genparams.py b/coreblocks/params/genparams.py index 3691d02ca..5b6fe0ce2 100644 --- a/coreblocks/params/genparams.py +++ b/coreblocks/params/genparams.py @@ -2,7 +2,7 @@ from amaranth.utils import exact_log2 -from .isa import ISA, gen_isa_string +from .isa_params import ISA, gen_isa_string from .icache_params import ICacheParameters from .fu_params import extensions_supported from ..peripherals.wishbone import WishboneParameters diff --git a/coreblocks/params/instr.py b/coreblocks/params/instr.py index efaab82cb..8f14d6b11 100644 --- a/coreblocks/params/instr.py +++ b/coreblocks/params/instr.py @@ -4,7 +4,8 @@ from amaranth import * from transactron.utils import ValueLike -from coreblocks.params.isa import * +from coreblocks.params.isa_params import * +from coreblocks.frontend.decoder.isa import * __all__ = [ diff --git a/coreblocks/params/isa.py b/coreblocks/params/isa_params.py similarity index 63% rename from coreblocks/params/isa.py rename to coreblocks/params/isa_params.py index dde829023..39ce1abfa 100644 --- a/coreblocks/params/isa.py +++ b/coreblocks/params/isa_params.py @@ -1,164 +1,14 @@ from itertools import takewhile -from amaranth.lib.enum import unique, Enum, IntEnum, IntFlag, auto +from amaranth.lib.enum import unique, auto import enum __all__ = [ - "InstrType", - "Opcode", - "Funct3", - "Funct7", - "Funct12", - "ExceptionCause", "Extension", - "FenceTarget", - "FenceFm", "ISA", - "Registers", ] -@unique -class InstrType(Enum): - R = 0 - I = 1 # noqa: E741 - S = 2 - B = 3 - U = 4 - J = 5 - - -@unique -class Opcode(IntEnum, shape=5): - LOAD = 0b00000 - LOAD_FP = 0b00001 - MISC_MEM = 0b00011 - OP_IMM = 0b00100 - AUIPC = 0b00101 - OP_IMM_32 = 0b00110 - STORE = 0b01000 - STORE_FP = 0b01001 - OP = 0b01100 - LUI = 0b01101 - OP32 = 0b01110 - BRANCH = 0b11000 - JALR = 0b11001 - JAL = 0b11011 - SYSTEM = 0b11100 - - -class Funct3(IntEnum, shape=3): - JALR = BEQ = B = ADD = SUB = FENCE = PRIV = MUL = MULW = _EINSTRACCESSFAULT = 0b000 - BNE = H = SLL = FENCEI = CSRRW = MULH = BCLR = BINV = BSET = CLZ = CPOP = CTZ = ROL \ - = SEXTB = SEXTH = CLMUL = _EILLEGALINSTR = 0b001 # fmt: skip - W = SLT = CSRRS = MULHSU = SH1ADD = CLMULR = _EBREAKPOINT = 0b010 - D = SLTU = CSRRC = MULHU = CLMULH = _EINSTRPAGEFAULT = 0b011 - BLT = BU = XOR = DIV = DIVW = SH2ADD = MIN = XNOR = ZEXTH = 0b100 - BGE = HU = SR = CSRRWI = DIVU = DIVUW = BEXT = ORCB = REV8 = ROR = MINU = 0b101 - BLTU = OR = CSRRSI = REM = REMW = SH3ADD = MAX = ORN = 0b110 - BGEU = AND = CSRRCI = REMU = REMUW = ANDN = MAXU = 0b111 - - -class Funct7(IntEnum, shape=7): - SL = SLT = ADD = XOR = OR = AND = 0b0000000 - SA = SUB = ANDN = ORN = XNOR = 0b0100000 - MULDIV = 0b0000001 - SH1ADD = SH2ADD = SH3ADD = 0b0010000 - BCLR = BEXT = 0b0100100 - BINV = REV8 = 0b0110100 - BSET = ORCB = 0b0010100 - MAX = MIN = CLMUL = 0b0000101 - ROL = ROR = SEXTB = SEXTH = CPOP = CLZ = CTZ = 0b0110000 - ZEXTH = 0b0000100 - SFENCEVMA = 0b0001001 - - -class Funct12(IntEnum, shape=12): - ECALL = 0b000000000000 - EBREAK = 0b000000000001 - SRET = 0b000100000010 - MRET = 0b001100000010 - WFI = 0b000100000101 - CPOP = 0b011000000010 - CLZ = 0b011000000000 - CTZ = 0b011000000001 - ORCB = 0b001010000111 - REV8_32 = 0b011010011000 - REV8_64 = 0b011010111000 - SEXTB = 0b011000000100 - SEXTH = 0b011000000101 - ZEXTH = 0b000010000000 - - -class Registers(IntEnum, shape=5): - X0 = ZERO = 0b00000 # hardwired zero - X1 = RA = 0b00001 # return address - X2 = SP = 0b00010 # stack pointer - X3 = GP = 0b00011 # global pointer - X4 = TP = 0b00100 # thread pointer - X5 = T0 = 0b00101 # temporary register 0 - X6 = T1 = 0b00110 # temporary register 1 - X7 = T2 = 0b00111 # temporary register 2 - X8 = S0 = FP = 0b01000 # saved register 0 / frame pointer - X9 = S1 = 0b01001 # saved register 1 - X10 = A0 = 0b01010 # function argument 0 / return value 0 - X11 = A1 = 0b01011 # function argument 1 / return value 1 - X12 = A2 = 0b01100 # function argument 2 - X13 = A3 = 0b01101 # function argument 3 - X14 = A4 = 0b01110 # function argument 4 - X15 = A5 = 0b01111 # function argument 5 - X16 = A6 = 0b10000 # function argument 6 - X17 = A7 = 0b10001 # function argument 7 - X18 = S2 = 0b10010 # saved register 2 - X19 = S3 = 0b10011 # saved register 3 - X20 = S4 = 0b10100 # saved register 4 - X21 = S5 = 0b10101 # saved register 5 - X22 = S6 = 0b10110 # saved register 6 - X23 = S7 = 0b10111 # saved register 7 - X24 = S8 = 0b11000 # saved register 8 - X25 = S9 = 0b11001 # saved register 9 - X26 = S10 = 0b11010 # saved register 10 - X27 = S11 = 0b11011 # saved register 11 - X28 = T3 = 0b11100 # temporary register 3 - X29 = T4 = 0b11101 # temporary register 4 - X30 = T5 = 0b11110 # temporary register 5 - X31 = T6 = 0b11111 # temporary register 6 - - -@unique -class FenceTarget(IntFlag, shape=4): - MEM_W = 0b0001 - MEM_R = 0b0010 - DEV_O = 0b0100 - DEV_I = 0b1000 - - -@unique -class FenceFm(IntEnum, shape=4): - NONE = 0b0000 - TSO = 0b1000 - - -@unique -class ExceptionCause(IntEnum, shape=5): - INSTRUCTION_ADDRESS_MISALIGNED = 0 - INSTRUCTION_ACCESS_FAULT = 1 - ILLEGAL_INSTRUCTION = 2 - BREAKPOINT = 3 - LOAD_ADDRESS_MISALIGNED = 4 - LOAD_ACCESS_FAULT = 5 - STORE_ADDRESS_MISALIGNED = 6 - STORE_ACCESS_FAULT = 7 - ENVIRONMENT_CALL_FROM_U = 8 - ENVIRONMENT_CALL_FROM_S = 9 - ENVIRONMENT_CALL_FROM_M = 11 - INSTRUCTION_PAGE_FAULT = 12 - LOAD_PAGE_FAULT = 13 - STORE_PAGE_FAULT = 15 - _COREBLOCKS_ASYNC_INTERRUPT = 16 - _COREBLOCKS_MISPREDICTION = 17 - - @unique class Extension(enum.IntFlag): """ diff --git a/coreblocks/priv/__init__.py b/coreblocks/priv/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coreblocks/priv/csr/__init__.py b/coreblocks/priv/csr/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coreblocks/structs_common/csr_generic.py b/coreblocks/priv/csr/csr_instances.py similarity index 98% rename from coreblocks/structs_common/csr_generic.py rename to coreblocks/priv/csr/csr_instances.py index 6f9150d81..58b033472 100644 --- a/coreblocks/structs_common/csr_generic.py +++ b/coreblocks/priv/csr/csr_instances.py @@ -4,7 +4,7 @@ from typing import Optional from coreblocks.params.genparams import GenParams -from coreblocks.structs_common.csr import CSRRegister +from coreblocks.priv.csr.csr_register import CSRRegister from transactron.core import Method, Transaction, def_method, TModule diff --git a/coreblocks/priv/csr/csr_register.py b/coreblocks/priv/csr/csr_register.py new file mode 100644 index 000000000..8018463fd --- /dev/null +++ b/coreblocks/priv/csr/csr_register.py @@ -0,0 +1,150 @@ +from amaranth import * +from amaranth.lib.data import StructLayout +from amaranth.lib.enum import IntEnum +from dataclasses import dataclass + +from transactron import Method, def_method, TModule +from transactron.utils import bits_from_int +from coreblocks.params.genparams import GenParams +from transactron.utils.dependencies import DependencyManager, ListKey +from coreblocks.interface.layouts import CSRLayouts +from transactron.utils.transactron_helpers import from_method_layout + + +class PrivilegeLevel(IntEnum, shape=2): + USER = 0b00 + SUPERVISOR = 0b01 + MACHINE = 0b11 + + +def csr_access_privilege(csr_addr: int) -> tuple[PrivilegeLevel, bool]: + read_only = bits_from_int(csr_addr, 10, 2) == 0b11 + + match bits_from_int(csr_addr, 8, 2): + case 0b00: + return (PrivilegeLevel.USER, read_only) + case 0b01: + return (PrivilegeLevel.SUPERVISOR, read_only) + case 0b10: # Hypervisior CSRs - accessible with VS mode (S with extension) + return (PrivilegeLevel.SUPERVISOR, read_only) + case _: + return (PrivilegeLevel.MACHINE, read_only) + + +@dataclass(frozen=True) +class CSRListKey(ListKey["CSRRegister"]): + """DependencyManager key collecting CSR registers globally as a list.""" + + # This key is defined here, because it is only used internally by CSRRegister and CSRUnit + pass + + +class CSRRegister(Elaboratable): + """CSR Register + Used to define a CSR register and specify its behaviour. + `CSRRegisters` are automatically assigned to `CSRListKey` dependency key, to be accessed from `CSRUnits`. + + Attributes + ---------- + read: Method + Reads register value and side effect status. + Side effect fields `read` and `written` are set if register was accessed by _fu_read or _fu_write + methods (by CSR instruction) in a current cycle; they can be used to trigger other actions. + Always ready. + write: Method + Updates register value. + Always ready. If _fu_write is called simultaneously, this call is ignored. + _fu_read: Method + Method connected automatically by `CSRUnit`. Reads register value. + _fu_write: Method + Method connected automatically by `CSRUnit`. Updates register value. + Always ready. Has priority over `write` method. + + Examples + -------- + .. highlight:: python + .. code-block:: python + + # Timer register that increments on each cycle and resets if read by CSR instruction + csr = CSRRegister(1, gen_params) + with Transaction.body(m): + csr_val = csr.read() + with m.If(csr_val.read): + csr.write(0) + with m.Else(): + csr.write(csr_val.data + 1) + """ + + def __init__(self, csr_number: int, gen_params: GenParams, *, ro_bits: int = 0): + """ + Parameters + ---------- + csr_number: int + Address of this CSR Register. + gen_params: GenParams + Core generation parameters. + ro_bits: int + Bit mask of read-only bits in register. + Writes from _fu_write (instructions) to those bits are ignored. + Note that this parameter is only required if there are some read-only + bits in read-write register. Writes to read-only registers specified + by upper 2 bits of CSR address set to `0b11` are discarded by `CSRUnit`. + """ + self.gen_params = gen_params + self.csr_number = csr_number + self.ro_bits = ro_bits + + csr_layouts = gen_params.get(CSRLayouts) + + self.read = Method(o=csr_layouts.read) + self.write = Method(i=csr_layouts.write) + + # Methods connected automatically by CSRUnit + self._fu_read = Method(o=csr_layouts._fu_read) + self._fu_write = Method(i=csr_layouts._fu_write) + + self.value = Signal(gen_params.isa.xlen) + self.side_effects = Signal(StructLayout({"read": 1, "write": 1})) + + # append to global CSR list + dm = gen_params.get(DependencyManager) + dm.add_dependency(CSRListKey(), self) + + def elaborate(self, platform): + m = TModule() + + internal_method_layout = from_method_layout([("data", self.gen_params.isa.xlen), ("active", 1)]) + write_internal = Signal(internal_method_layout) + fu_write_internal = Signal(internal_method_layout) + + m.d.sync += self.side_effects.eq(0) + + @def_method(m, self.write) + def _(data): + m.d.comb += write_internal.data.eq(data) + m.d.comb += write_internal.active.eq(1) + + @def_method(m, self._fu_write) + def _(data): + m.d.comb += fu_write_internal.data.eq(data) + m.d.comb += fu_write_internal.active.eq(1) + m.d.sync += self.side_effects.write.eq(1) + + @def_method(m, self.read) + def _(): + return {"data": self.value, "read": self.side_effects.read, "written": self.side_effects.write} + + @def_method(m, self._fu_read) + def _(): + m.d.sync += self.side_effects.read.eq(1) + return self.value + + # Writes from instructions have priority + with m.If(fu_write_internal.active & write_internal.active): + m.d.sync += self.value.eq((fu_write_internal.data & ~self.ro_bits) | (write_internal.data & self.ro_bits)) + with m.Elif(fu_write_internal.active): + m.d.sync += self.value.eq((fu_write_internal.data & ~self.ro_bits) | (self.value & self.ro_bits)) + with m.Elif(write_internal.active): + m.d.sync += self.value.eq(write_internal.data) + + return m diff --git a/coreblocks/priv/traps/__init__.py b/coreblocks/priv/traps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coreblocks/structs_common/exception.py b/coreblocks/priv/traps/exception.py similarity index 96% rename from coreblocks/structs_common/exception.py rename to coreblocks/priv/traps/exception.py index 4385b12f6..fea3cafaf 100644 --- a/coreblocks/structs_common/exception.py +++ b/coreblocks/priv/traps/exception.py @@ -2,9 +2,9 @@ from transactron.utils.dependencies import DependencyManager from coreblocks.params.genparams import GenParams -from coreblocks.params.isa import ExceptionCause -from coreblocks.params.layouts import ExceptionRegisterLayouts -from coreblocks.params.keys import ExceptionReportKey +from coreblocks.frontend.decoder.isa import ExceptionCause +from coreblocks.interface.layouts import ExceptionRegisterLayouts +from coreblocks.interface.keys import ExceptionReportKey from transactron.core import TModule, def_method, Method from transactron.lib.connectors import ConnectTrans from transactron.lib.fifo import BasicFifo diff --git a/coreblocks/structs_common/instr_counter.py b/coreblocks/priv/traps/instr_counter.py similarity index 95% rename from coreblocks/structs_common/instr_counter.py rename to coreblocks/priv/traps/instr_counter.py index 73b30eb7a..b30790aa9 100644 --- a/coreblocks/structs_common/instr_counter.py +++ b/coreblocks/priv/traps/instr_counter.py @@ -1,6 +1,6 @@ from amaranth import * from coreblocks.params.genparams import GenParams -from coreblocks.params.layouts import CoreInstructionCounterLayouts +from coreblocks.interface.layouts import CoreInstructionCounterLayouts from transactron.core import Method, TModule, def_method diff --git a/coreblocks/structs_common/interrupt_controller.py b/coreblocks/priv/traps/interrupt_controller.py similarity index 94% rename from coreblocks/structs_common/interrupt_controller.py rename to coreblocks/priv/traps/interrupt_controller.py index 3b98f8a53..d79ac18bd 100644 --- a/coreblocks/structs_common/interrupt_controller.py +++ b/coreblocks/priv/traps/interrupt_controller.py @@ -1,7 +1,7 @@ from amaranth import * from transactron.utils.dependencies import DependencyManager from coreblocks.params.genparams import GenParams -from coreblocks.params.keys import AsyncInterruptInsertSignalKey, MretKey +from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, MretKey from transactron.core import Method, TModule, def_method diff --git a/coreblocks/scheduler/scheduler.py b/coreblocks/scheduler/scheduler.py index 6e7e152bd..f9758fac7 100644 --- a/coreblocks/scheduler/scheduler.py +++ b/coreblocks/scheduler/scheduler.py @@ -4,11 +4,13 @@ from transactron import Method, Transaction, TModule from transactron.lib import FIFO, Forwarder -from coreblocks.params import SchedulerLayouts, GenParams, OpType +from coreblocks.interface.layouts import SchedulerLayouts +from coreblocks.params import GenParams +from coreblocks.frontend.decoder.optypes import OpType from transactron.utils import assign, AssignType from transactron.utils.dependencies import DependencyManager -from coreblocks.params.keys import CoreStateKey -from coreblocks.utils.protocols import FuncBlock +from coreblocks.interface.keys import CoreStateKey +from coreblocks.func_blocks.interface.func_protocols import FuncBlock __all__ = ["Scheduler"] diff --git a/coreblocks/scheduler/wakeup_select.py b/coreblocks/scheduler/wakeup_select.py index 724d6ffe7..a9eaaf302 100644 --- a/coreblocks/scheduler/wakeup_select.py +++ b/coreblocks/scheduler/wakeup_select.py @@ -1,6 +1,7 @@ from amaranth import * -from coreblocks.params import GenParams, FuncUnitLayouts +from coreblocks.params import GenParams +from coreblocks.interface.layouts import FuncUnitLayouts from transactron.utils import assign, AssignType from transactron.core import * diff --git a/scripts/synthesize.py b/scripts/synthesize.py index 6c5c2f7eb..71c3574c2 100755 --- a/scripts/synthesize.py +++ b/scripts/synthesize.py @@ -19,12 +19,12 @@ from coreblocks.params.genparams import GenParams from coreblocks.params.fu_params import FunctionalComponentParams from coreblocks.core import Core -from coreblocks.fu.alu import ALUComponent -from coreblocks.fu.div_unit import DivComponent -from coreblocks.fu.mul_unit import MulComponent, MulType -from coreblocks.fu.shift_unit import ShiftUnitComponent -from coreblocks.fu.zbc import ZbcComponent -from coreblocks.fu.zbs import ZbsComponent +from coreblocks.func_blocks.fu.alu import ALUComponent +from coreblocks.func_blocks.fu.div_unit import DivComponent +from coreblocks.func_blocks.fu.mul_unit import MulComponent, MulType +from coreblocks.func_blocks.fu.shift_unit import ShiftUnitComponent +from coreblocks.func_blocks.fu.zbc import ZbcComponent +from coreblocks.func_blocks.fu.zbs import ZbsComponent from transactron import TransactionModule from transactron.lib import AdapterBase, AdapterTrans from coreblocks.peripherals.wishbone import WishboneArbiter, WishboneInterface diff --git a/test/cache/test_icache.py b/test/cache/test_icache.py index 2afeff6db..3bd198c43 100644 --- a/test/cache/test_icache.py +++ b/test/cache/test_icache.py @@ -8,7 +8,8 @@ from transactron.lib import AdapterTrans, Adapter from coreblocks.cache.icache import ICache, ICacheBypass, CacheRefillerInterface -from coreblocks.params import GenParams, ICacheLayouts +from coreblocks.params import GenParams +from coreblocks.interface.layouts import ICacheLayouts from coreblocks.peripherals.wishbone import WishboneMaster, WishboneParameters from coreblocks.peripherals.bus_adapter import WishboneMasterAdapter from coreblocks.params.configurations import test_core_config diff --git a/test/frontend/test_decode_stage.py b/test/frontend/test_decode_stage.py index c9c80251a..c3bc6338e 100644 --- a/test/frontend/test_decode_stage.py +++ b/test/frontend/test_decode_stage.py @@ -2,8 +2,10 @@ from transactron.testing import TestCaseWithSimulator, TestbenchIO, SimpleTestCircuit, ModuleConnector -from coreblocks.frontend.decode_stage import DecodeStage -from coreblocks.params import GenParams, FetchLayouts, DecodeLayouts, OpType, Funct3, Funct7 +from coreblocks.frontend.decoder.decode_stage import DecodeStage +from coreblocks.params import GenParams +from coreblocks.frontend.decoder import OpType, Funct3, Funct7 +from coreblocks.interface.layouts import FetchLayouts, DecodeLayouts from coreblocks.params.configurations import test_core_config diff --git a/test/frontend/test_fetch.py b/test/frontend/test_fetch.py index e57392485..b9ff1388c 100644 --- a/test/frontend/test_fetch.py +++ b/test/frontend/test_fetch.py @@ -7,10 +7,11 @@ from transactron.core import Method from transactron.lib import AdapterTrans, FIFO, Adapter -from coreblocks.frontend.fetch import Fetch, UnalignedFetch +from coreblocks.frontend.fetch.fetch import Fetch, UnalignedFetch from coreblocks.cache.iface import CacheInterface from coreblocks.params import * from coreblocks.params.configurations import test_core_config +from coreblocks.interface.layouts import ICacheLayouts, FetchLayouts from transactron.utils import ModuleConnector from transactron.testing import TestCaseWithSimulator, TestbenchIO, def_method_mock, SimpleTestCircuit diff --git a/test/frontend/test_instr_decoder.py b/test/frontend/test_instr_decoder.py index 4c0a0b4b6..2fea2e77f 100644 --- a/test/frontend/test_instr_decoder.py +++ b/test/frontend/test_instr_decoder.py @@ -4,7 +4,8 @@ from coreblocks.params import * from coreblocks.params.configurations import test_core_config -from coreblocks.frontend.instr_decoder import InstrDecoder, Encoding, instructions_by_optype +from coreblocks.frontend.decoder.instr_decoder import InstrDecoder, Encoding, instructions_by_optype +from coreblocks.frontend.decoder import * from unittest import TestCase from typing import Optional diff --git a/test/frontend/test_rvc.py b/test/frontend/test_rvc.py index d31d21e63..0b099f751 100644 --- a/test/frontend/test_rvc.py +++ b/test/frontend/test_rvc.py @@ -3,7 +3,8 @@ from amaranth.sim import Settle from amaranth import * -from coreblocks.frontend.rvc import InstrDecompress +from coreblocks.frontend.decoder.rvc import InstrDecompress +from coreblocks.frontend.decoder import * from coreblocks.params import * from coreblocks.params.configurations import test_core_config from transactron.utils import ValueLike diff --git a/test/fu/functional_common.py b/test/fu/functional_common.py index 7d21682cb..51e8713ef 100644 --- a/test/fu/functional_common.py +++ b/test/fu/functional_common.py @@ -11,10 +11,10 @@ from coreblocks.params.configurations import test_core_config from transactron.utils.dependencies import DependencyManager from coreblocks.params.fu_params import FunctionalComponentParams -from coreblocks.params.isa import Funct3, Funct7 -from coreblocks.params.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey -from coreblocks.params.layouts import ExceptionRegisterLayouts -from coreblocks.params.optypes import OpType +from coreblocks.frontend.decoder.isa import Funct3, Funct7 +from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey +from coreblocks.interface.layouts import ExceptionRegisterLayouts +from coreblocks.frontend.decoder.optypes import OpType from transactron.lib import Adapter from transactron.testing import RecordIntDict, RecordIntDictRet, TestbenchIO, TestCaseWithSimulator, SimpleTestCircuit from transactron.utils import ModuleConnector diff --git a/test/fu/test_alu.py b/test/fu/test_alu.py index 7e973fc92..3fb67072d 100644 --- a/test/fu/test_alu.py +++ b/test/fu/test_alu.py @@ -1,5 +1,5 @@ -from coreblocks.params import Funct3, Funct7, OpType -from coreblocks.fu.alu import AluFn, ALUComponent +from coreblocks.frontend.decoder import Funct3, Funct7, OpType +from coreblocks.func_blocks.fu.alu import AluFn, ALUComponent from test.fu.functional_common import ExecFn, FunctionalUnitTestCase diff --git a/test/fu/test_div_unit.py b/test/fu/test_div_unit.py index c8c3e9b4c..0b792825c 100644 --- a/test/fu/test_div_unit.py +++ b/test/fu/test_div_unit.py @@ -1,7 +1,7 @@ from parameterized import parameterized_class -from coreblocks.params import Funct3, Funct7, OpType -from coreblocks.fu.div_unit import DivFn, DivComponent +from coreblocks.frontend.decoder import Funct3, Funct7, OpType +from coreblocks.func_blocks.fu.div_unit import DivFn, DivComponent from test.fu.functional_common import ExecFn, FunctionalUnitTestCase diff --git a/test/fu/test_exception_unit.py b/test/fu/test_exception_unit.py index a1484b074..35adb71b6 100644 --- a/test/fu/test_exception_unit.py +++ b/test/fu/test_exception_unit.py @@ -1,8 +1,6 @@ -from amaranth import * - -from coreblocks.params import * -from coreblocks.fu.exception import ExceptionUnitFn, ExceptionUnitComponent -from coreblocks.params.isa import ExceptionCause +from coreblocks.func_blocks.fu.exception import ExceptionUnitFn, ExceptionUnitComponent +from coreblocks.frontend.decoder.isa import ExceptionCause +from coreblocks.frontend.decoder import OpType, Funct3 from test.fu.functional_common import ExecFn, FunctionalUnitTestCase diff --git a/test/fu/test_fu_decoder.py b/test/fu/test_fu_decoder.py index 965e07e40..1674af001 100644 --- a/test/fu/test_fu_decoder.py +++ b/test/fu/test_fu_decoder.py @@ -5,8 +5,9 @@ from transactron.testing import SimpleTestCircuit, TestCaseWithSimulator -from coreblocks.fu.fu_decoder import DecoderManager, Decoder -from coreblocks.params import OpType, Funct3, Funct7, GenParams +from coreblocks.func_blocks.fu.common.fu_decoder import DecoderManager, Decoder +from coreblocks.frontend.decoder import OpType, Funct3, Funct7 +from coreblocks.params import GenParams from coreblocks.params.configurations import test_core_config from enum import IntFlag, auto diff --git a/test/fu/test_jb_unit.py b/test/fu/test_jb_unit.py index 559062989..5a6ba2ce7 100644 --- a/test/fu/test_jb_unit.py +++ b/test/fu/test_jb_unit.py @@ -3,10 +3,11 @@ from parameterized import parameterized_class from coreblocks.params import * -from coreblocks.fu.jumpbranch import JumpBranchFuncUnit, JumpBranchFn, JumpComponent +from coreblocks.func_blocks.fu.jumpbranch import JumpBranchFuncUnit, JumpBranchFn, JumpComponent from transactron import Method, def_method, TModule -from coreblocks.params.layouts import FuncUnitLayouts -from coreblocks.utils.protocols import FuncUnit +from coreblocks.interface.layouts import FuncUnitLayouts, JumpBranchLayouts +from coreblocks.func_blocks.interface.func_protocols import FuncUnit +from coreblocks.frontend.decoder import Funct3, OpType, ExceptionCause from transactron.utils import signed_to_int diff --git a/test/fu/test_mul_unit.py b/test/fu/test_mul_unit.py index 34f87e7a8..201ddd98b 100644 --- a/test/fu/test_mul_unit.py +++ b/test/fu/test_mul_unit.py @@ -1,7 +1,7 @@ from parameterized import parameterized_class -from coreblocks.params import * -from coreblocks.fu.mul_unit import MulFn, MulComponent, MulType +from coreblocks.frontend.decoder import Funct3, Funct7, OpType +from coreblocks.func_blocks.fu.mul_unit import MulFn, MulComponent, MulType from transactron.utils import signed_to_int, int_to_signed diff --git a/test/fu/test_shift_unit.py b/test/fu/test_shift_unit.py index 20eed6d55..f004bccac 100644 --- a/test/fu/test_shift_unit.py +++ b/test/fu/test_shift_unit.py @@ -1,5 +1,5 @@ -from coreblocks.params import Funct3, Funct7, OpType -from coreblocks.fu.shift_unit import ShiftUnitFn, ShiftUnitComponent +from coreblocks.frontend.decoder import Funct3, Funct7, OpType +from coreblocks.func_blocks.fu.shift_unit import ShiftUnitFn, ShiftUnitComponent from test.fu.functional_common import ExecFn, FunctionalUnitTestCase diff --git a/test/fu/test_unsigned_mul_unit.py b/test/fu/test_unsigned_mul_unit.py index 56b3657e6..42e5fc624 100644 --- a/test/fu/test_unsigned_mul_unit.py +++ b/test/fu/test_unsigned_mul_unit.py @@ -5,10 +5,10 @@ from amaranth.sim import Settle from parameterized import parameterized_class -from coreblocks.fu.unsigned_multiplication.common import MulBaseUnsigned -from coreblocks.fu.unsigned_multiplication.fast_recursive import RecursiveUnsignedMul -from coreblocks.fu.unsigned_multiplication.sequence import SequentialUnsignedMul -from coreblocks.fu.unsigned_multiplication.shift import ShiftUnsignedMul +from coreblocks.func_blocks.fu.unsigned_multiplication.common import MulBaseUnsigned +from coreblocks.func_blocks.fu.unsigned_multiplication.fast_recursive import RecursiveUnsignedMul +from coreblocks.func_blocks.fu.unsigned_multiplication.sequence import SequentialUnsignedMul +from coreblocks.func_blocks.fu.unsigned_multiplication.shift import ShiftUnsignedMul from transactron.testing import TestCaseWithSimulator, SimpleTestCircuit diff --git a/test/fu/test_zbc.py b/test/fu/test_zbc.py index c4c63041d..e26ae379b 100644 --- a/test/fu/test_zbc.py +++ b/test/fu/test_zbc.py @@ -1,7 +1,7 @@ from parameterized import parameterized_class -from coreblocks.fu.zbc import ZbcFn, ZbcComponent -from coreblocks.params import * +from coreblocks.func_blocks.fu.zbc import ZbcFn, ZbcComponent +from coreblocks.frontend.decoder import Funct3, Funct7, OpType from coreblocks.params.configurations import test_core_config from test.fu.functional_common import ExecFn, FunctionalUnitTestCase diff --git a/test/fu/test_zbs.py b/test/fu/test_zbs.py index a629dd3b5..790bc58a2 100644 --- a/test/fu/test_zbs.py +++ b/test/fu/test_zbs.py @@ -1,6 +1,5 @@ -from coreblocks.params import Funct3, Funct7 -from coreblocks.fu.zbs import ZbsFunction, ZbsComponent -from coreblocks.params.optypes import OpType +from coreblocks.frontend.decoder import Funct3, Funct7, OpType +from coreblocks.func_blocks.fu.zbs import ZbsFunction, ZbsComponent from test.fu.functional_common import ExecFn, FunctionalUnitTestCase diff --git a/test/lsu/test_dummylsu.py b/test/lsu/test_dummylsu.py index 61e9a3f29..4211720a6 100644 --- a/test/lsu/test_dummylsu.py +++ b/test/lsu/test_dummylsu.py @@ -6,13 +6,13 @@ from transactron.lib import Adapter from transactron.utils import int_to_signed, signed_to_int -from coreblocks.params import OpType, GenParams -from coreblocks.lsu.dummyLsu import LSUDummy +from coreblocks.params import GenParams +from coreblocks.func_blocks.lsu.dummyLsu import LSUDummy from coreblocks.params.configurations import test_core_config -from coreblocks.params.isa import * -from coreblocks.params.keys import ExceptionReportKey +from coreblocks.frontend.decoder import * +from coreblocks.interface.keys import ExceptionReportKey from transactron.utils.dependencies import DependencyManager -from coreblocks.params.layouts import ExceptionRegisterLayouts +from coreblocks.interface.layouts import ExceptionRegisterLayouts from coreblocks.peripherals.wishbone import * from transactron.testing import TestbenchIO, TestCaseWithSimulator, def_method_mock from coreblocks.peripherals.bus_adapter import WishboneMasterAdapter diff --git a/test/lsu/test_pma.py b/test/lsu/test_pma.py index 07c36652d..aa19b4005 100644 --- a/test/lsu/test_pma.py +++ b/test/lsu/test_pma.py @@ -1,14 +1,14 @@ from amaranth.sim import Settle -from coreblocks.lsu.pma import PMAChecker, PMARegion +from coreblocks.func_blocks.lsu.pma import PMAChecker, PMARegion from transactron.lib import Adapter -from coreblocks.params import OpType, GenParams -from coreblocks.lsu.dummyLsu import LSUDummy +from coreblocks.params import GenParams +from coreblocks.func_blocks.lsu.dummyLsu import LSUDummy from coreblocks.params.configurations import test_core_config -from coreblocks.params.isa import * -from coreblocks.params.keys import ExceptionReportKey +from coreblocks.frontend.decoder import * +from coreblocks.interface.keys import ExceptionReportKey from transactron.utils.dependencies import DependencyManager -from coreblocks.params.layouts import ExceptionRegisterLayouts +from coreblocks.interface.layouts import ExceptionRegisterLayouts from coreblocks.peripherals.wishbone import * from transactron.testing import TestbenchIO, TestCaseWithSimulator, def_method_mock from coreblocks.peripherals.bus_adapter import WishboneMasterAdapter diff --git a/test/params/test_configurations.py b/test/params/test_configurations.py index 786dbad93..f15171966 100644 --- a/test/params/test_configurations.py +++ b/test/params/test_configurations.py @@ -3,7 +3,7 @@ from coreblocks.params.genparams import GenParams from coreblocks.params.configurations import * -from coreblocks.params.isa import gen_isa_string +from coreblocks.params.isa_params import gen_isa_string from coreblocks.params.fu_params import extensions_supported diff --git a/test/params/test_isa.py b/test/params/test_isa.py index ef77c8c7a..5b3295f38 100644 --- a/test/params/test_isa.py +++ b/test/params/test_isa.py @@ -1,6 +1,6 @@ import unittest -from coreblocks.params.isa import Extension, ISA +from coreblocks.params.isa_params import Extension, ISA class TestISA(unittest.TestCase): diff --git a/test/scheduler/test_rs_selection.py b/test/scheduler/test_rs_selection.py index 322323fb2..58dea22c3 100644 --- a/test/scheduler/test_rs_selection.py +++ b/test/scheduler/test_rs_selection.py @@ -4,7 +4,9 @@ from amaranth import * from amaranth.sim import Settle, Passive -from coreblocks.params import GenParams, RSLayouts, SchedulerLayouts, OpType, Funct3, Funct7 +from coreblocks.params import GenParams +from coreblocks.interface.layouts import RSLayouts, SchedulerLayouts +from coreblocks.frontend.decoder import OpType, Funct3, Funct7 from coreblocks.params.configurations import test_core_config from coreblocks.scheduler.scheduler import RSSelection from transactron.lib import FIFO, Adapter, AdapterTrans diff --git a/test/scheduler/test_scheduler.py b/test/scheduler/test_scheduler.py index a25979c49..3c50efab6 100644 --- a/test/scheduler/test_scheduler.py +++ b/test/scheduler/test_scheduler.py @@ -5,20 +5,22 @@ from amaranth import * from amaranth.sim import Settle from parameterized import parameterized_class -from coreblocks.params.keys import CoreStateKey -from coreblocks.params.layouts import RetirementLayouts -from coreblocks.stages.rs_func_block import RSBlockComponent +from coreblocks.interface.keys import CoreStateKey +from coreblocks.interface.layouts import RetirementLayouts +from coreblocks.func_blocks.fu.common.rs_func_block import RSBlockComponent from transactron.core import Method from transactron.lib import FIFO, AdapterTrans, Adapter from transactron.utils.dependencies import DependencyManager from coreblocks.scheduler.scheduler import Scheduler -from coreblocks.structs_common.rf import RegisterFile -from coreblocks.structs_common.rat import FRAT -from coreblocks.params import RSLayouts, DecodeLayouts, SchedulerLayouts, GenParams, OpType, Funct3, Funct7 +from coreblocks.core_structs.rf import RegisterFile +from coreblocks.core_structs.rat import FRAT +from coreblocks.params import GenParams +from coreblocks.interface.layouts import RSLayouts, DecodeLayouts, SchedulerLayouts +from coreblocks.frontend.decoder import OpType, Funct3, Funct7 from coreblocks.params.configurations import test_core_config -from coreblocks.structs_common.rob import ReorderBuffer -from coreblocks.utils.protocols import FuncBlock +from coreblocks.core_structs.rob import ReorderBuffer +from coreblocks.func_blocks.interface.func_protocols import FuncBlock from transactron.testing import RecordIntDict, TestCaseWithSimulator, TestGen, TestbenchIO, def_method_mock diff --git a/test/scheduler/test_wakeup_select.py b/test/scheduler/test_wakeup_select.py index ec0cb158c..4ff298da9 100644 --- a/test/scheduler/test_wakeup_select.py +++ b/test/scheduler/test_wakeup_select.py @@ -8,9 +8,10 @@ from inspect import isclass import random -from coreblocks.params import GenParams, RSLayouts +from coreblocks.params import GenParams +from coreblocks.interface.layouts import RSLayouts from coreblocks.params.configurations import test_core_config -from coreblocks.stages.rs_func_block import RSBlockComponent +from coreblocks.func_blocks.fu.common.rs_func_block import RSBlockComponent from transactron import * from transactron.lib import Adapter from coreblocks.scheduler.wakeup_select import * diff --git a/test/stages/test_backend.py b/test/stages/test_backend.py index 2dc1695f8..780ff1d20 100644 --- a/test/stages/test_backend.py +++ b/test/stages/test_backend.py @@ -4,8 +4,8 @@ from amaranth import * from transactron.lib import FIFO, AdapterTrans, Adapter, ManyToOneConnectTrans -from coreblocks.stages.backend import ResultAnnouncement -from coreblocks.params.layouts import * +from coreblocks.backend.annoucement import ResultAnnouncement +from coreblocks.interface.layouts import * from coreblocks.params import GenParams from coreblocks.params.configurations import test_core_config from transactron.testing import TestCaseWithSimulator, TestbenchIO diff --git a/test/stages/test_retirement.py b/test/stages/test_retirement.py index 1502eb0b9..c544baa3c 100644 --- a/test/stages/test_retirement.py +++ b/test/stages/test_retirement.py @@ -1,10 +1,11 @@ -from coreblocks.params.layouts import CoreInstructionCounterLayouts, ExceptionRegisterLayouts, FetchLayouts -from coreblocks.stages.retirement import * -from coreblocks.structs_common.csr_generic import GenericCSRRegisters +from coreblocks.interface.layouts import CoreInstructionCounterLayouts, ExceptionRegisterLayouts, FetchLayouts +from coreblocks.backend.retirement import * +from coreblocks.priv.csr.csr_instances import GenericCSRRegisters from transactron.lib import FIFO, Adapter -from coreblocks.structs_common.rat import FRAT, RRAT -from coreblocks.params import ROBLayouts, RFLayouts, GenParams, LSULayouts, SchedulerLayouts +from coreblocks.core_structs.rat import FRAT, RRAT +from coreblocks.params import GenParams +from coreblocks.interface.layouts import ROBLayouts, RFLayouts, LSULayouts, SchedulerLayouts from coreblocks.params.configurations import test_core_config from transactron.testing import * diff --git a/test/structs_common/test_csr.py b/test/structs_common/test_csr.py index 4df317ba8..62ec75bbe 100644 --- a/test/structs_common/test_csr.py +++ b/test/structs_common/test_csr.py @@ -1,14 +1,14 @@ from amaranth import * from transactron.lib import Adapter -from coreblocks.structs_common.csr import CSRUnit, CSRRegister +from coreblocks.func_blocks.csr.csr import CSRUnit +from coreblocks.priv.csr.csr_register import CSRRegister from coreblocks.params import GenParams -from coreblocks.params.isa import Funct3, ExceptionCause +from coreblocks.frontend.decoder import Funct3, ExceptionCause, OpType from coreblocks.params.configurations import test_core_config -from coreblocks.params.layouts import ExceptionRegisterLayouts -from coreblocks.params.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey +from coreblocks.interface.layouts import ExceptionRegisterLayouts +from coreblocks.interface.keys import AsyncInterruptInsertSignalKey, ExceptionReportKey from transactron.utils.dependencies import DependencyManager -from coreblocks.params.optypes import OpType from transactron.testing import * diff --git a/test/structs_common/test_exception.py b/test/structs_common/test_exception.py index 1988f5ad3..4cd99cb48 100644 --- a/test/structs_common/test_exception.py +++ b/test/structs_common/test_exception.py @@ -1,9 +1,9 @@ from amaranth import * -from coreblocks.params.layouts import ROBLayouts +from coreblocks.interface.layouts import ROBLayouts -from coreblocks.structs_common.exception import ExceptionCauseRegister +from coreblocks.priv.traps.exception import ExceptionCauseRegister from coreblocks.params import GenParams -from coreblocks.params.isa import ExceptionCause +from coreblocks.frontend.decoder import ExceptionCause from coreblocks.params.configurations import test_core_config from transactron.lib import Adapter from transactron.utils import ModuleConnector diff --git a/test/structs_common/test_rat.py b/test/structs_common/test_rat.py index 6fb281761..56ccbe6c4 100644 --- a/test/structs_common/test_rat.py +++ b/test/structs_common/test_rat.py @@ -1,6 +1,6 @@ from transactron.testing import TestCaseWithSimulator, SimpleTestCircuit -from coreblocks.structs_common.rat import FRAT, RRAT +from coreblocks.core_structs.rat import FRAT, RRAT from coreblocks.params import GenParams from coreblocks.params.configurations import test_core_config diff --git a/test/structs_common/test_reorder_buffer.py b/test/structs_common/test_reorder_buffer.py index 26731e635..e29bcf385 100644 --- a/test/structs_common/test_reorder_buffer.py +++ b/test/structs_common/test_reorder_buffer.py @@ -2,7 +2,7 @@ from transactron.testing import TestCaseWithSimulator, SimpleTestCircuit -from coreblocks.structs_common.rob import ReorderBuffer +from coreblocks.core_structs.rob import ReorderBuffer from coreblocks.params import GenParams from coreblocks.params.configurations import test_core_config diff --git a/test/structs_common/test_rs.py b/test/structs_common/test_rs.py index d5b9b4741..4e86a46de 100644 --- a/test/structs_common/test_rs.py +++ b/test/structs_common/test_rs.py @@ -2,9 +2,10 @@ from transactron.testing import TestCaseWithSimulator, get_outputs, SimpleTestCircuit -from coreblocks.structs_common.rs import RS +from coreblocks.func_blocks.fu.common.rs import RS from coreblocks.params import * from coreblocks.params.configurations import test_core_config +from coreblocks.frontend.decoder import OpType def create_check_list(rs_entries_bits: int, insert_list: list[dict]) -> list[dict]: diff --git a/test/transactions/test_transaction_lib.py b/test/transactions/test_transaction_lib.py index dd3899964..c8e758ce7 100644 --- a/test/transactions/test_transaction_lib.py +++ b/test/transactions/test_transaction_lib.py @@ -11,7 +11,6 @@ from amaranth import * from transactron import * from transactron.lib import * -from coreblocks.utils import * from transactron.utils._typing import ModuleLike, MethodStruct, RecordDict from transactron.utils import ModuleConnector from transactron.testing import ( From b7df72a83350d118afa2f7087da686e9380a2bdb Mon Sep 17 00:00:00 2001 From: Marek Materzok Date: Thu, 21 Mar 2024 22:57:08 +0100 Subject: [PATCH 3/6] Update GH actions (#622) --- .github/workflows/benchmark.yml | 14 ++++---- .github/workflows/deploy_gh_pages.yml | 4 +-- .github/workflows/main.yml | 46 +++++++++++++-------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 1fc7fac1d..859df8ef1 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,7 +16,7 @@ jobs: timeout-minutes: 40 container: ghcr.io/kuznia-rdzeni/amaranth-synth:ecp5-2023.11.19_v steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set ownership (Github Actions workaround) run: | @@ -24,7 +24,7 @@ jobs: chown -R $(id -u):$(id -g) $PWD - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -66,14 +66,14 @@ jobs: container: ghcr.io/kuznia-rdzeni/riscv-toolchain:2024.03.12 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Build embench run: cd test/external/embench && make - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: "embench" path: | @@ -87,7 +87,7 @@ jobs: needs: build-perf-benchmarks steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set ownership (Github Actions workaround) run: | @@ -95,7 +95,7 @@ jobs: chown -R $(id -u):$(id -g) $PWD - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -111,7 +111,7 @@ jobs: . venv/bin/activate PYTHONHASHSEED=0 TRANSACTRON_VERBOSE=1 ./scripts/gen_verilog.py --verbose --config full - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: "embench" path: test/external/embench/build diff --git a/.github/workflows/deploy_gh_pages.yml b/.github/workflows/deploy_gh_pages.yml index 89fdeea7d..eaf35d90a 100644 --- a/.github/workflows/deploy_gh_pages.yml +++ b/.github/workflows/deploy_gh_pages.yml @@ -21,10 +21,10 @@ jobs: BUILD_DIR: "build" steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 06ceb129d..05200b7e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,10 +18,10 @@ jobs: timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -37,7 +37,7 @@ jobs: . venv/bin/activate PYTHONHASHSEED=0 TRANSACTRON_VERBOSE=1 ./scripts/gen_verilog.py --verbose --config full - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: "verilog-full-core" path: | @@ -60,7 +60,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get submodules HEAD hash working-directory: . @@ -72,7 +72,7 @@ jobs: - name: Cache compiled and reference riscv-arch-test id: cache-riscv-arch-test - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-riscv-arch-test with: @@ -93,7 +93,7 @@ jobs: - if: ${{ steps.cache-riscv-arch-test.outputs.cache-hit != 'true' }} name: Checkout with submodules - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive @@ -127,7 +127,7 @@ jobs: - if: ${{ steps.cache-riscv-arch-test.outputs.cache-hit != 'true' }} name: Upload compiled and reference tests artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "riscof-tests" path: | @@ -143,10 +143,10 @@ jobs: timeout-minutes: 30 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -157,7 +157,7 @@ jobs: python3 -m pip install --upgrade pip python3 -m pip install -r requirements-dev.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Download full verilog core with: name: "verilog-full-core" @@ -168,7 +168,7 @@ jobs: git config --global --add safe.directory /__w/coreblocks/coreblocks git submodule > .gitmodules-hash - - uses: actions/cache@v3 + - uses: actions/cache@v4 name: Download tests from cache env: cache-name: cache-riscv-arch-test @@ -204,7 +204,7 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get submodules HEAD hash run: | @@ -213,7 +213,7 @@ jobs: - name: Cache regression-tests id: cache-regression - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-regression-tests with: @@ -229,7 +229,7 @@ jobs: - if: ${{ steps.cache-regression.outputs.cache-hit != 'true' }} name: Checkout with submodules - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive @@ -238,7 +238,7 @@ jobs: - if: ${{ steps.cache-regression.outputs.cache-hit != 'true' }} name: Upload riscv-tests - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: test/external/riscv-tests @@ -250,10 +250,10 @@ jobs: needs: [ build-regression-tests, build-core ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' @@ -264,7 +264,7 @@ jobs: python3 -m pip install --upgrade pip python3 -m pip install -r requirements-dev.txt - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Download full verilog core with: name: "verilog-full-core" @@ -275,7 +275,7 @@ jobs: git config --global --add safe.directory /__w/coreblocks/coreblocks git submodule > .gitmodules-hash - - uses: actions/cache@v3 + - uses: actions/cache@v4 name: Download tests from cache env: cache-name: cache-regression-tests @@ -307,10 +307,10 @@ jobs: timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' @@ -339,10 +339,10 @@ jobs: timeout-minutes: 5 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' From 29e522aa0e280df39d56d893f097fc8e05f9d915 Mon Sep 17 00:00:00 2001 From: Marek Materzok Date: Fri, 22 Mar 2024 00:11:36 +0100 Subject: [PATCH 4/6] Allow parallel compilation of the verilated model (#623) --- test/regression/cocotb/benchmark.Makefile | 1 + test/regression/cocotb/signature.Makefile | 1 + test/regression/cocotb/test.Makefile | 1 + 3 files changed, 3 insertions(+) diff --git a/test/regression/cocotb/benchmark.Makefile b/test/regression/cocotb/benchmark.Makefile index 5c89d3785..9962315fb 100644 --- a/test/regression/cocotb/benchmark.Makefile +++ b/test/regression/cocotb/benchmark.Makefile @@ -15,6 +15,7 @@ SIM_BUILD = build/benchmark # Yosys/Amaranth borkedness workaround ifeq ($(SIM),verilator) EXTRA_ARGS += -Wno-CASEINCOMPLETE -Wno-CASEOVERLAP -Wno-WIDTHEXPAND -Wno-WIDTHTRUNC + BUILD_ARGS += -j`nproc` endif ifeq ($(TRACES),1) diff --git a/test/regression/cocotb/signature.Makefile b/test/regression/cocotb/signature.Makefile index 74b803083..b4f690635 100644 --- a/test/regression/cocotb/signature.Makefile +++ b/test/regression/cocotb/signature.Makefile @@ -15,6 +15,7 @@ SIM_BUILD = build/signature # Yosys/Amaranth borkedness workaround ifeq ($(SIM),verilator) EXTRA_ARGS += -Wno-CASEINCOMPLETE -Wno-CASEOVERLAP -Wno-WIDTHEXPAND -Wno-WIDTHTRUNC + BUILD_ARGS += -j`nproc` endif ifeq ($(TRACES),1) diff --git a/test/regression/cocotb/test.Makefile b/test/regression/cocotb/test.Makefile index bda120bc1..210618067 100644 --- a/test/regression/cocotb/test.Makefile +++ b/test/regression/cocotb/test.Makefile @@ -15,6 +15,7 @@ SIM_BUILD = build/test # Yosys/Amaranth borkedness workaround ifeq ($(SIM),verilator) EXTRA_ARGS += -Wno-CASEINCOMPLETE -Wno-CASEOVERLAP -Wno-WIDTHEXPAND -Wno-WIDTHTRUNC + BUILD_ARGS += -j`nproc` endif ifeq ($(TRACES),1) From 961888af63aa2b356a3bfdd924aeaf65fa379a29 Mon Sep 17 00:00:00 2001 From: lekcyjna123 <34948061+lekcyjna123@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:21:34 +0100 Subject: [PATCH 5/6] Remove riscvmodel dependency (#627) --- coreblocks/params/instr.py | 44 +++++++++++++++++++++++++++++++++++++- requirements-dev.txt | 1 - test/test_core.py | 10 ++++----- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/coreblocks/params/instr.py b/coreblocks/params/instr.py index 8f14d6b11..370d25b84 100644 --- a/coreblocks/params/instr.py +++ b/coreblocks/params/instr.py @@ -3,7 +3,7 @@ from amaranth.hdl import ValueCastable from amaranth import * -from transactron.utils import ValueLike +from transactron.utils import ValueLike, int_to_signed from coreblocks.params.isa_params import * from coreblocks.frontend.decoder.isa import * @@ -53,6 +53,10 @@ def __init__( def pack(self) -> Value: return Cat(C(0b11, 2), self.opcode, self.rd, self.funct3, self.rs1, self.rs2, self.funct7) + @staticmethod + def encode(opcode: int, rd: int, funct3: int, rs1: int, rs2: int, funct7: int): + return int(f"{funct7:07b}{rs2:05b}{rs1:05b}{funct3:03b}{rd:05b}{opcode:05b}11", 2) + class ITypeInstr(RISCVInstr): def __init__(self, opcode: ValueLike, rd: ValueLike, funct3: ValueLike, rs1: ValueLike, imm: ValueLike): @@ -65,6 +69,11 @@ def __init__(self, opcode: ValueLike, rd: ValueLike, funct3: ValueLike, rs1: Val def pack(self) -> Value: return Cat(C(0b11, 2), self.opcode, self.rd, self.funct3, self.rs1, self.imm) + @staticmethod + def encode(opcode: int, rd: int, funct3: int, rs1: int, imm: int): + imm = int_to_signed(imm, 12) + return int(f"{imm:012b}{rs1:05b}{funct3:03b}{rd:05b}{opcode:05b}11", 2) + class STypeInstr(RISCVInstr): def __init__(self, opcode: ValueLike, imm: ValueLike, funct3: ValueLike, rs1: ValueLike, rs2: ValueLike): @@ -77,6 +86,12 @@ def __init__(self, opcode: ValueLike, imm: ValueLike, funct3: ValueLike, rs1: Va def pack(self) -> Value: return Cat(C(0b11, 2), self.opcode, self.imm[0:5], self.funct3, self.rs1, self.rs2, self.imm[5:12]) + @staticmethod + def encode(opcode: int, imm: int, funct3: int, rs1: int, rs2: int): + imm = int_to_signed(imm, 12) + imm_str = f"{imm:012b}" + return int(f"{imm_str[5:12]:07b}{rs2:05b}{rs1:05b}{funct3:03b}{imm_str[0:5]:05b}{opcode:05b}11", 2) + class BTypeInstr(RISCVInstr): def __init__(self, opcode: ValueLike, imm: ValueLike, funct3: ValueLike, rs1: ValueLike, rs2: ValueLike): @@ -99,6 +114,16 @@ def pack(self) -> Value: self.imm[12], ) + @staticmethod + def encode(opcode: int, imm: int, funct3: int, rs1: int, rs2: int): + imm = int_to_signed(imm, 13) + imm_str = f"{imm:013b}" + return int( + f"{imm_str[12]:01b}{imm_str[5:11]:06b}{rs2:05b}{rs1:05b}{funct3:03b}{imm_str[1:5]:04b}" + + f"{imm_str[11]:01b}{opcode:05b}11", + 2, + ) + class UTypeInstr(RISCVInstr): def __init__(self, opcode: ValueLike, rd: ValueLike, imm: ValueLike): @@ -109,6 +134,11 @@ def __init__(self, opcode: ValueLike, rd: ValueLike, imm: ValueLike): def pack(self) -> Value: return Cat(C(0b11, 2), self.opcode, self.rd, self.imm[12:]) + @staticmethod + def encode(opcode: int, rd: int, imm: int): + imm = int_to_signed(imm, 20) + return int(f"{imm:020b}{rd:05b}{opcode:05b}11", 2) + class JTypeInstr(RISCVInstr): def __init__(self, opcode: ValueLike, rd: ValueLike, imm: ValueLike): @@ -119,6 +149,14 @@ def __init__(self, opcode: ValueLike, rd: ValueLike, imm: ValueLike): def pack(self) -> Value: return Cat(C(0b11, 2), self.opcode, self.rd, self.imm[12:20], self.imm[11], self.imm[1:11], self.imm[20]) + @staticmethod + def encode(opcode: int, rd: int, imm: int): + imm = int_to_signed(imm, 21) + imm_str = f"{imm:021b}" + return int( + f"{imm_str[20]:01b}{imm_str[1:11]:010b}{imm_str[11]:01b}{imm_str[12:20]:08b}{rd:05b}{opcode:05b}11", 2 + ) + class IllegalInstr(RISCVInstr): def __init__(self): @@ -127,6 +165,10 @@ def __init__(self): def pack(self) -> Value: return C(1).replicate(32) # Instructions with all bits set to 1 are reserved to be illegal. + @staticmethod + def encode(opcode: int, rd: int, imm: int): + return int("1" * 32, 2) + class EBreakInstr(ITypeInstr): def __init__(self): diff --git a/requirements-dev.txt b/requirements-dev.txt index 1d9530305..1f68b2f1f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,6 @@ black==23.3.0 docutils==0.15.2 flake8==6.0.0 pep8-naming==0.13.3 -git+https://github.com/kristopher38/riscv-python-model@b5d0737#riscv-model markupsafe==2.0.1 myst-parser==0.18.0 numpydoc==1.5.0 diff --git a/test/test_core.py b/test/test_core.py index a2cfd1d88..dbb8692f8 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7,7 +7,9 @@ from transactron.testing import TestCaseWithSimulator, TestbenchIO from coreblocks.core import Core +from coreblocks.frontend.decoder import Opcode, Funct3 from coreblocks.params import GenParams +from coreblocks.params.instr import * from coreblocks.params.configurations import CoreConfiguration, basic_core_config, full_core_config from coreblocks.peripherals.wishbone import WishboneSignature, WishboneMemorySlave @@ -16,10 +18,6 @@ import subprocess import tempfile from parameterized import parameterized_class -from riscvmodel.insn import ( - InstructionADDI, - InstructionLUI, -) class CoreTestElaboratable(Elaboratable): @@ -81,8 +79,8 @@ def push_register_load_imm(self, reg_id, val): if val & 0x800: lui_imm = (lui_imm + 1) & (0xFFFFF) - yield from self.push_instr(InstructionLUI(reg_id, lui_imm).encode()) - yield from self.push_instr(InstructionADDI(reg_id, reg_id, addi_imm).encode()) + yield from self.push_instr(UTypeInstr.encode(Opcode.LUI, reg_id, lui_imm)) + yield from self.push_instr(ITypeInstr.encode(Opcode.OP_IMM, reg_id, Funct3.ADD, reg_id, addi_imm)) class TestCoreAsmSourceBase(TestCoreBase): From 61285335cc79a3cae7595eae2c77a20c3e8ff180 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:05:33 +0100 Subject: [PATCH 6/6] Bump black from 23.3.0 to 24.3.0 (#625) --- requirements-dev.txt | 2 +- test/lsu/test_dummylsu.py | 6 +- transactron/lib/transformers.py | 3 +- transactron/utils/_typing.py | 81 +++++++------------ .../utils/amaranth_ext/elaboratables.py | 6 +- 5 files changed, 34 insertions(+), 64 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1f68b2f1f..fa39140f1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ -r requirements.txt -black==23.3.0 +black==24.3.0 docutils==0.15.2 flake8==6.0.0 pep8-naming==0.13.3 diff --git a/test/lsu/test_dummylsu.py b/test/lsu/test_dummylsu.py index 4211720a6..776f0e2cd 100644 --- a/test/lsu/test_dummylsu.py +++ b/test/lsu/test_dummylsu.py @@ -173,9 +173,9 @@ def generate_instr(self, max_reg_val, max_imm_val): self.exception_queue.append( { "rob_id": rob_id, - "cause": ExceptionCause.LOAD_ADDRESS_MISALIGNED - if misaligned - else ExceptionCause.LOAD_ACCESS_FAULT, + "cause": ( + ExceptionCause.LOAD_ADDRESS_MISALIGNED if misaligned else ExceptionCause.LOAD_ACCESS_FAULT + ), "pc": 0, } ) diff --git a/transactron/lib/transformers.py b/transactron/lib/transformers.py index f874cea2c..5e9b1a6b0 100644 --- a/transactron/lib/transformers.py +++ b/transactron/lib/transformers.py @@ -60,8 +60,7 @@ def use(self, m: ModuleLike): class Unifier(Transformer, Protocol): method: Method - def __init__(self, targets: list[Method]): - ... + def __init__(self, targets: list[Method]): ... class MethodMap(Elaboratable, Transformer): diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index 32497c7d5..e8e3152b9 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -86,17 +86,13 @@ # Protocols for Amaranth classes class _ModuleBuilderDomainsLike(Protocol): - def __getattr__(self, name: str) -> "_ModuleBuilderDomain": - ... + def __getattr__(self, name: str) -> "_ModuleBuilderDomain": ... - def __getitem__(self, name: str) -> "_ModuleBuilderDomain": - ... + def __getitem__(self, name: str) -> "_ModuleBuilderDomain": ... - def __setattr__(self, name: str, value: "_ModuleBuilderDomain") -> None: - ... + def __setattr__(self, name: str, value: "_ModuleBuilderDomain") -> None: ... - def __setitem__(self, name: str, value: "_ModuleBuilderDomain") -> None: - ... + def __setitem__(self, name: str, value: "_ModuleBuilderDomain") -> None: ... _T_ModuleBuilderDomains = TypeVar("_T_ModuleBuilderDomains", bound=_ModuleBuilderDomainsLike) @@ -127,80 +123,59 @@ def Default(self) -> AbstractContextManager[None]: # noqa: N802 def FSM( # noqa: N802 self, reset: Optional[str] = ..., domain: str = ..., name: str = ... - ) -> AbstractContextManager["amaranth.hdl._dsl.FSM"]: - ... + ) -> AbstractContextManager["amaranth.hdl._dsl.FSM"]: ... def State(self, name: str) -> AbstractContextManager[None]: # noqa: N802 ... @property - def next(self) -> NoReturn: - ... + def next(self) -> NoReturn: ... @next.setter - def next(self, name: str) -> None: - ... + def next(self, name: str) -> None: ... class AbstractSignatureMembers(Protocol): - def flip(self) -> "AbstractSignatureMembers": - ... + def flip(self) -> "AbstractSignatureMembers": ... - def __eq__(self, other) -> bool: - ... + def __eq__(self, other) -> bool: ... - def __contains__(self, name: str) -> bool: - ... + def __contains__(self, name: str) -> bool: ... - def __getitem__(self, name: str) -> Member: - ... + def __getitem__(self, name: str) -> Member: ... - def __setitem__(self, name: str, member: Member) -> NoReturn: - ... + def __setitem__(self, name: str, member: Member) -> NoReturn: ... - def __delitem__(self, name: str) -> NoReturn: - ... + def __delitem__(self, name: str) -> NoReturn: ... - def __iter__(self) -> Iterator[str]: - ... + def __iter__(self) -> Iterator[str]: ... - def __len__(self) -> int: - ... + def __len__(self) -> int: ... - def flatten(self, *, path: tuple[str | int, ...] = ...) -> Iterator[tuple[tuple[str | int, ...], Member]]: - ... + def flatten(self, *, path: tuple[str | int, ...] = ...) -> Iterator[tuple[tuple[str | int, ...], Member]]: ... - def create(self, *, path: tuple[str | int, ...] = ..., src_loc_at: int = ...) -> dict[str, Any]: - ... + def create(self, *, path: tuple[str | int, ...] = ..., src_loc_at: int = ...) -> dict[str, Any]: ... - def __repr__(self) -> str: - ... + def __repr__(self) -> str: ... class AbstractSignature(Protocol): - def flip(self) -> "AbstractSignature": - ... + def flip(self) -> "AbstractSignature": ... @property - def members(self) -> AbstractSignatureMembers: - ... + def members(self) -> AbstractSignatureMembers: ... - def __eq__(self, other) -> bool: - ... + def __eq__(self, other) -> bool: ... - def flatten(self, obj) -> Iterator[tuple[tuple[str | int, ...], Flow, ValueLike]]: - ... + def flatten(self, obj) -> Iterator[tuple[tuple[str | int, ...], Flow, ValueLike]]: ... - def is_compliant(self, obj, *, reasons: Optional[list[str]] = ..., path: tuple[str, ...] = ...) -> bool: - ... + def is_compliant(self, obj, *, reasons: Optional[list[str]] = ..., path: tuple[str, ...] = ...) -> bool: ... def create( self, *, path: tuple[str | int, ...] = ..., src_loc_at: int = ... - ) -> "AbstractInterface[AbstractSignature]": - ... + ) -> "AbstractInterface[AbstractSignature]": ... - def __repr__(self) -> str: - ... + def __repr__(self) -> str: ... _T_AbstractSignature = TypeVar("_T_AbstractSignature", bound=AbstractSignature) @@ -211,14 +186,12 @@ class AbstractInterface(Protocol, Generic[_T_AbstractSignature]): class HasElaborate(Protocol): - def elaborate(self, platform) -> "HasElaborate": - ... + def elaborate(self, platform) -> "HasElaborate": ... @runtime_checkable class HasDebugSignals(Protocol): - def debug_signals(self) -> SignalBundle: - ... + def debug_signals(self) -> SignalBundle: ... def type_self_kwargs_as(as_func: Callable[Concatenate[Any, P], Any]): diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 3af4ded98..b0ddbae35 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -59,13 +59,11 @@ def case(n: Optional[int] = None): @overload -def OneHotSwitchDynamic(m: ModuleLike, test: Value, *, default: Literal[True]) -> Iterable[Optional[int]]: - ... +def OneHotSwitchDynamic(m: ModuleLike, test: Value, *, default: Literal[True]) -> Iterable[Optional[int]]: ... @overload -def OneHotSwitchDynamic(m: ModuleLike, test: Value, *, default: Literal[False] = False) -> Iterable[int]: - ... +def OneHotSwitchDynamic(m: ModuleLike, test: Value, *, default: Literal[False] = False) -> Iterable[int]: ... def OneHotSwitchDynamic(m: ModuleLike, test: Value, *, default: bool = False) -> Iterable[Optional[int]]: