From 07f969cf193b97ce1e4cbcdaa815884e3243011b Mon Sep 17 00:00:00 2001 From: Kuba Nowak Date: Mon, 29 Jan 2024 09:02:10 +0100 Subject: [PATCH 01/29] Port ContentAddressableMemory. --- test/common/__init__.py | 1 + transactron/lib/storage.py | 79 ++++++++++++++++++- transactron/utils/_typing.py | 3 + .../utils/amaranth_ext/elaboratables.py | 61 ++++++++++++++ transactron/utils/data_repr.py | 4 +- 5 files changed, 143 insertions(+), 5 deletions(-) diff --git a/test/common/__init__.py b/test/common/__init__.py index 46eb4b5a1..dea6ec6e0 100644 --- a/test/common/__init__.py +++ b/test/common/__init__.py @@ -1,3 +1,4 @@ +from .input_generation import * # noqa: F401 from .functions import * # noqa: F401 from .infrastructure import * # noqa: F401 from .sugar import * # noqa: F401 diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 4b33f8eb1..faf80e30a 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -1,12 +1,12 @@ from amaranth import * from amaranth.utils import * from ..core import * -from ..utils import SrcLoc, get_src_loc +from ..utils import SrcLoc, get_src_loc, MultiPriorityEncoder from typing import Optional -from transactron.utils import assign, AssignType +from transactron.utils import assign, AssignType, LayoutLike from .reqres import ArgumentsToResultsZipper -__all__ = ["MemoryBank"] +__all__ = ["MemoryBank", "ContentAddressableMemory"] class MemoryBank(Elaboratable): @@ -133,3 +133,76 @@ def _(arg): m.d.comb += assign(write_args, arg, fields=AssignType.ALL) return m + + +class ContentAddressableMemory(Elaboratable): + """Content addresable memory + + This module implements a transactorn interface for the content addressable memory. + + .. warning:: + Current implementation has critical path O(entries_number). If needed we can + optimise it in future to have O(log(entries_number)). + + + Attributes + ---------- + pop : Method + Looks for the data in memory and, if found, returns it and removes it. + push : Method + Inserts new data. + """ + + def __init__(self, address_layout: LayoutLike, data_layout: LayoutLike, entries_number: int): + """ + Parameters + ---------- + address_layout : LayoutLike + The layout of the address records. + data_layout : LayoutLike + The layout of the data. + entries_number : int + The number of slots to create in memory. + """ + self.address_layout = address_layout + self.data_layout = data_layout + self.entries_number = entries_number + + self.pop = Method(i=[("addr", self.address_layout)], o=[("data", self.data_layout), ("not_found", 1)]) + self.push = Method(i=[("addr", self.address_layout), ("data", self.data_layout)]) + + def elaborate(self, platform) -> TModule: + m = TModule() + + address_array = Array([Record(self.address_layout) for _ in range(self.entries_number)]) + data_array = Array([Record(self.data_layout) for _ in range(self.entries_number)]) + valids = Signal(self.entries_number, name="valids") + + m.submodules.encoder_addr = encoder_addr = MultiPriorityEncoder(self.entries_number, 1) + m.submodules.encoder_valids = encoder_valids = MultiPriorityEncoder(self.entries_number, 1) + m.d.comb += encoder_valids.input.eq(~valids) + + @def_method(m, self.push, ready=~valids.all()) + def _(addr, data): + id = Signal(range(self.entries_number), name="id_push") + m.d.comb += id.eq(encoder_valids.outputs[0]) + m.d.sync += address_array[id].eq(addr) + m.d.sync += data_array[id].eq(data) + m.d.sync += valids.bit_select(id, 1).eq(1) + + if_addr = Signal(self.entries_number, name="if_addr") + data_to_send = Record(self.data_layout) + + @def_method(m, self.pop) + def _(addr): + m.d.top_comb += if_addr.eq(Cat([addr == stored_addr for stored_addr in address_array]) & valids) + id = encoder_addr.outputs[0] + with m.If(if_addr.any()): + m.d.comb += data_to_send.eq(data_array[id]) + m.d.sync += valids.bit_select(id, 1).eq(0) + + return {"data": data_to_send, "not_found": ~if_addr.any()} + + m.d.comb += encoder_addr.input.eq(if_addr) + + return m diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index 49e33db1b..519431f4a 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -1,4 +1,5 @@ from typing import ( + Tuple, Callable, Concatenate, Generic, @@ -27,6 +28,7 @@ "ValueLike", "StatementLike", "LayoutLike", + "SimpleLayout", "SwitchKey", "MethodLayout", "SrcLoc", @@ -61,6 +63,7 @@ SignalBundle: TypeAlias = Signal | Record | View | Iterable["SignalBundle"] | Mapping[str, "SignalBundle"] LayoutListField: TypeAlias = tuple[str, "ShapeLike | LayoutList"] LayoutList: TypeAlias = list[LayoutListField] +SimpleLayout = list[Tuple[str, Union[int, "SimpleLayout"]]] RecordIntDict: TypeAlias = Mapping[str, Union[int, "RecordIntDict"]] RecordIntDictRet: TypeAlias = Mapping[str, Any] # full typing hard to work with diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index b60232e43..390622c01 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -11,6 +11,7 @@ "ModuleConnector", "Scheduler", "RoundRobin", + "MultiPriorityEncoder", ] @@ -239,3 +240,63 @@ def elaborate(self, platform): m.d.sync += self.valid.eq(self.requests.any()) return m + + +class MultiPriorityEncoder(Elaboratable): + """Priority encoder with more outputs + + This is an extension of the `PriorityEncoder` from amaranth, that supports + generating more than one output from an input signal. In other words + it decodes multi-hot encoded signal to lists of signals in binary + format, each with index of a different high bit in input. + + Attributes + ---------- + input_width : int + Width of the input signal + outputs_count : int + Number of outputs to generate at once. + input : Signal, in + Signal with 1 on `i`-th bit if `i` can be selected by encoder + outputs : list[Signal], out + Signals with selected indicies, they are sorted in ascending order, + if the number of ready signals is less than `outputs_count`, + then valid signals are at the beginning of the list. + valids : list[Signals], out + One bit for each output signal, indicating whether the output is valid or not. + """ + + def __init__(self, input_width: int, outputs_count: int): + self.input_width = input_width + self.outputs_count = outputs_count + + self.input = Signal(self.input_width) + self.outputs = [Signal(range(self.input_width), name="output") for _ in range(self.outputs_count)] + self.valids = [Signal(name="valid") for _ in range(self.outputs_count)] + + def elaborate(self, platform): + m = Module() + + current_outputs = [Signal(range(self.input_width)) for _ in range(self.outputs_count)] + current_valids = [Signal() for _ in range(self.outputs_count)] + for j in reversed(range(self.input_width)): + new_current_outputs = [Signal(range(self.input_width)) for _ in range(self.outputs_count)] + new_current_valids = [Signal() for _ in range(self.outputs_count)] + with m.If(self.input[j]): + m.d.comb += new_current_outputs[0].eq(j) + m.d.comb += new_current_valids[0].eq(1) + for k in range(self.outputs_count - 1): + m.d.comb += new_current_outputs[k + 1].eq(current_outputs[k]) + m.d.comb += new_current_valids[k + 1].eq(current_valids[k]) + with m.Else(): + for k in range(self.outputs_count): + m.d.comb += new_current_outputs[k].eq(current_outputs[k]) + m.d.comb += new_current_valids[k].eq(current_valids[k]) + current_outputs = new_current_outputs + current_valids = new_current_valids + + for k in range(self.outputs_count): + m.d.comb += self.outputs[k].eq(current_outputs[k]) + m.d.comb += self.valids[k].eq(current_valids[k]) + + return m diff --git a/transactron/utils/data_repr.py b/transactron/utils/data_repr.py index d5d4029db..c18952d17 100644 --- a/transactron/utils/data_repr.py +++ b/transactron/utils/data_repr.py @@ -1,5 +1,5 @@ from collections.abc import Iterable, Mapping -from ._typing import LayoutList, ShapeLike, LayoutLike +from ._typing import LayoutList, SimpleLayout from typing import Any, Sized from statistics import fmean @@ -77,7 +77,7 @@ def bits_from_int(num: int, lower: int, length: int): return (num >> lower) & ((1 << (length)) - 1) -def data_layout(val: ShapeLike) -> LayoutLike: +def data_layout(val: int) -> SimpleLayout: return [("data", val)] From 9fee2ec3eecbbaa29f83d1093c1647addfb6a7cf Mon Sep 17 00:00:00 2001 From: Kuba Nowak Date: Mon, 29 Jan 2024 09:02:26 +0100 Subject: [PATCH 02/29] Port ContentAddressableMemory - tests --- test/common/input_generation.py | 17 +++++++ .../test_transactron_lib_storage.py | 50 +++++++++++++++++++ test/utils/test_amaranth_ext.py | 40 +++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 test/common/input_generation.py create mode 100644 test/transactions/test_transactron_lib_storage.py create mode 100644 test/utils/test_amaranth_ext.py diff --git a/test/common/input_generation.py b/test/common/input_generation.py new file mode 100644 index 000000000..6885235e2 --- /dev/null +++ b/test/common/input_generation.py @@ -0,0 +1,17 @@ +import random +from typing import Optional +from transactron.utils import SimpleLayout + + +def generate_based_on_layout(layout: SimpleLayout, *, max_bits: Optional[int] = None): + d = {} + for elem in layout: + if isinstance(elem[1], int): + if max_bits is None: + max_val = 2 ** elem[1] + else: + max_val = 2 ** min(max_bits, elem[1]) + d[elem[0]] = random.randrange(max_val) + else: + d[elem[0]] = generate_based_on_layout(elem[1]) + return d diff --git a/test/transactions/test_transactron_lib_storage.py b/test/transactions/test_transactron_lib_storage.py new file mode 100644 index 000000000..82bd3f4cc --- /dev/null +++ b/test/transactions/test_transactron_lib_storage.py @@ -0,0 +1,50 @@ +from test.common import * +import random +from transactron.lib.storage import ContentAddressableMemory + + +class TestContentAddressableMemory(TestCaseWithSimulator): + def setUp(self): + random.seed(14) + self.test_number = 50 + self.addr_width = 4 + self.content_width = 5 + self.entries_count = 8 + self.addr_layout = data_layout(self.addr_width) + self.content_layout = data_layout(self.content_width) + + self.circ = SimpleTestCircuit( + ContentAddressableMemory(self.addr_layout, self.content_layout, self.entries_count) + ) + + self.memory = {} + + def input_process(self): + for _ in range(self.test_number): + while True: + addr = generate_based_on_layout(self.addr_layout) + frozen_addr = frozenset(addr.items()) + if frozen_addr not in self.memory: + break + content = generate_based_on_layout(self.content_layout) + yield from self.circ.push.call(addr=addr, data=content) + yield Settle() + self.memory[frozen_addr] = content + + def output_process(self): + yield Passive() + while True: + addr = generate_based_on_layout(self.addr_layout) + res = yield from self.circ.pop.call(addr=addr) + frozen_addr = frozenset(addr.items()) + if frozen_addr in self.memory: + self.assertEqual(res["not_found"], 0) + self.assertEqual(res["data"], self.memory[frozen_addr]) + self.memory.pop(frozen_addr) + else: + self.assertEqual(res["not_found"], 1) + + def test_random(self): + with self.run_simulation(self.circ) as sim: + sim.add_sync_process(self.input_process) + sim.add_sync_process(self.output_process) diff --git a/test/utils/test_amaranth_ext.py b/test/utils/test_amaranth_ext.py new file mode 100644 index 000000000..789c6369d --- /dev/null +++ b/test/utils/test_amaranth_ext.py @@ -0,0 +1,40 @@ +from test.common import * +import random +from transactron.utils.amaranth_ext import MultiPriorityEncoder + + +class TestMultiPriorityEncoder(TestCaseWithSimulator): + def setUp(self): + random.seed(14) + self.test_number = 50 + self.input_width = 16 + self.output_count = 4 + + self.circ = MultiPriorityEncoder(self.input_width, self.output_count) + + def get_expected(self, input): + places = [] + for i in range(self.input_width): + if input % 2: + places.append(i) + input //= 2 + places += [None] * self.output_count + return places + + def process(self): + for _ in range(self.test_number): + input = random.randrange(2**self.input_width) + yield self.circ.input.eq(input) + yield Settle() + expected_output = self.get_expected(input) + for ex, real, valid in zip(expected_output, self.circ.outputs, self.circ.valids): + if ex is None: + self.assertEqual((yield valid), 0) + else: + self.assertEqual((yield valid), 1) + self.assertEqual((yield real), ex) + yield Delay(1e-7) + + def test_random(self): + with self.run_simulation(self.circ) as sim: + sim.add_process(self.process) From 3e58cc2bbf5add706dc981b17f32b23a0650c435 Mon Sep 17 00:00:00 2001 From: Kuba Nowak Date: Mon, 29 Jan 2024 09:05:12 +0100 Subject: [PATCH 03/29] Typos --- transactron/utils/amaranth_ext/elaboratables.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 390622c01..004344dea 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -245,10 +245,10 @@ def elaborate(self, platform): class MultiPriorityEncoder(Elaboratable): """Priority encoder with more outputs - This is an extension of the `PriorityEncoder` from amaranth, that supports - generating more than one output from an input signal. In other words - it decodes multi-hot encoded signal to lists of signals in binary - format, each with index of a different high bit in input. + This is an extension of the `PriorityEncoder` from amaranth that supports + more than one output from an input signal. In other words + it decodes multi-hot encoded signal into lists of signals in binary + format, each with the index of a different high bit in the input. Attributes ---------- @@ -259,8 +259,8 @@ class MultiPriorityEncoder(Elaboratable): input : Signal, in Signal with 1 on `i`-th bit if `i` can be selected by encoder outputs : list[Signal], out - Signals with selected indicies, they are sorted in ascending order, - if the number of ready signals is less than `outputs_count`, + Signals with selected indicies, sorted in ascending order, + if the number of ready signals is less than `outputs_count` then valid signals are at the beginning of the list. valids : list[Signals], out One bit for each output signal, indicating whether the output is valid or not. From 5bf3b5860dfd406690d390824634b7117dd406b4 Mon Sep 17 00:00:00 2001 From: lekcyjna123 <34948061+lekcyjna123@users.noreply.github.com> Date: Sun, 17 Mar 2024 12:00:35 +0100 Subject: [PATCH 04/29] Update transactron/utils/amaranth_ext/elaboratables.py Co-authored-by: Marek Materzok --- transactron/utils/amaranth_ext/elaboratables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 004344dea..22aa9aad4 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -262,7 +262,7 @@ class MultiPriorityEncoder(Elaboratable): Signals with selected indicies, sorted in ascending order, if the number of ready signals is less than `outputs_count` then valid signals are at the beginning of the list. - valids : list[Signals], out + valids : list[Signal], out One bit for each output signal, indicating whether the output is valid or not. """ From 7a1480355a971190466a93040ee8ccc5a505aaab Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 17 Mar 2024 12:24:07 +0100 Subject: [PATCH 05/29] Start updating --- transactron/lib/storage.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index faf80e30a..c3f8b00d7 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -138,7 +138,14 @@ def _(arg): class ContentAddressableMemory(Elaboratable): """Content addresable memory - This module implements a transactorn interface for the content addressable memory. + This module implements a content-addressable memory (in short CAM) with Transactron interface. + CAM is a type of memory where instead of predefined indexes there are used values feed in runtime + as keys (smimlar as in python dictionary). To insert new entry a pair `(key, value)` has to be + provided. Such pair takes an free slot which depends on internal implementation. To read value + a `key` has to be provided. It is compared with every valid key stored in CAM. If there is a hit, + a value is read. There can be many instances of the same key in CAM. In such case it is undefined + which value will be read. + .. warning:: Current implementation has critical path O(entries_number). If needed we can @@ -174,8 +181,8 @@ def __init__(self, address_layout: LayoutLike, data_layout: LayoutLike, entries_ def elaborate(self, platform) -> TModule: m = TModule() - address_array = Array([Record(self.address_layout) for _ in range(self.entries_number)]) - data_array = Array([Record(self.data_layout) for _ in range(self.entries_number)]) + address_array = Array([Signal(self.address_layout) for _ in range(self.entries_number)]) + data_array = Array([Signal(self.data_layout) for _ in range(self.entries_number)]) valids = Signal(self.entries_number, name="valids") m.submodules.encoder_addr = encoder_addr = MultiPriorityEncoder(self.entries_number, 1) From f560816999f8eda26fc9ddd9e24c953a20b76481 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 17 Mar 2024 16:11:22 +0100 Subject: [PATCH 06/29] Fix some typing. Introduce hypothesis to generate values according to layout. --- requirements-dev.txt | 1 + transactron/lib/storage.py | 6 ++--- transactron/testing/input_generation.py | 32 +++++++++++++++++-------- transactron/utils/_typing.py | 2 -- transactron/utils/data_repr.py | 2 +- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1d9530305..addb96214 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,3 +20,4 @@ pytest-xdist==3.5.0 pyelftools==0.29 tabulate==0.9.0 filelock==3.13.1 +hypothesis==6.99.6 diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 4cdb080eb..5f9da1692 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -163,7 +163,7 @@ class ContentAddressableMemory(Elaboratable): Inserts new data. """ - def __init__(self, address_layout: LayoutList, data_layout: LayoutList, entries_number: int): + def __init__(self, address_layout: MethodLayout, data_layout: LayoutList, entries_number: int): """ Parameters ---------- @@ -174,8 +174,8 @@ def __init__(self, address_layout: LayoutList, data_layout: LayoutList, entries_ entries_number : int The number of slots to create in memory. """ - self.address_layout = address_layout - self.data_layout = data_layout + self.address_layout = from_method_layout(address_layout) + self.data_layout = from_method_layout(data_layout) self.entries_number = entries_number self.pop = Method(i=[("addr", self.address_layout)], o=[("data", self.data_layout), ("not_found", 1)]) diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index 6885235e2..ae3f9fc35 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -1,17 +1,29 @@ +from amaranth import * import random from typing import Optional -from transactron.utils import SimpleLayout +from hypothesis.strategies import composite, DrawFn, integers +from transactron.utils import LayoutList - -def generate_based_on_layout(layout: SimpleLayout, *, max_bits: Optional[int] = None): +@composite +def generate_based_on_layout(draw : DrawFn, layout: LayoutList): d = {} - for elem in layout: - if isinstance(elem[1], int): - if max_bits is None: - max_val = 2 ** elem[1] + for name, sublayout in layout: + if isinstance(sublayout, list): + elem = draw(generate_based_on_layout(sublayout)) + elif isinstance(sublayout, int): + elem = draw(integers(min_value=0, max_value=sublayout)) + elif isinstance(sublayout, range): + elem = draw(integers(min_value=sublayout.start, max_value=sublayout.stop-1)) + elif isinstance(sublayout, Shape): + if sublayout.signed: + min_value = -2**(sublayout.width-1) + max_value = 2**(sublayout.width-1)-1 else: - max_val = 2 ** min(max_bits, elem[1]) - d[elem[0]] = random.randrange(max_val) + min_value = 0 + max_value = 2**sublayout.width + elem = draw(integers(min_value = min_value, max_value = max_value)) else: - d[elem[0]] = generate_based_on_layout(elem[1]) + # Currently type[Enum] and ShapeCastable + raise NotImplementedError("Passed LayoutList with syntax yet unsuported in automatic value generation.") + d[name] = elem return d diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index 555c41736..b03859db8 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -33,7 +33,6 @@ "ValueLike", "ShapeLike", "StatementLike", - "SimpleLayout", "SwitchKey", "SrcLoc", "MethodLayout", @@ -67,7 +66,6 @@ SignalBundle: TypeAlias = Signal | Record | View | Iterable["SignalBundle"] | Mapping[str, "SignalBundle"] LayoutListField: TypeAlias = tuple[str, "ShapeLike | LayoutList"] LayoutList: TypeAlias = list[LayoutListField] -SimpleLayout = list[Tuple[str, Union[int, "SimpleLayout"]]] LayoutIterable: TypeAlias = Iterable[LayoutListField] MethodLayout: TypeAlias = StructLayout | LayoutIterable MethodStruct: TypeAlias = "View[StructLayout]" diff --git a/transactron/utils/data_repr.py b/transactron/utils/data_repr.py index 0974ba4f0..95e6d4d4e 100644 --- a/transactron/utils/data_repr.py +++ b/transactron/utils/data_repr.py @@ -1,5 +1,5 @@ from collections.abc import Iterable, Mapping -from ._typing import LayoutList, SimpleLayout, ShapeLike +from ._typing import LayoutList, ShapeLike from typing import Any, Sized from statistics import fmean from amaranth.lib.data import StructLayout From a33e930ef05331d4d2551a705ba8a8b5a165e843 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 17 Mar 2024 16:24:12 +0100 Subject: [PATCH 07/29] Type fixes. --- test/transactions/test_transactron_lib_storage.py | 2 +- test/utils/test_amaranth_ext.py | 2 +- transactron/lib/storage.py | 2 +- transactron/testing/input_generation.py | 7 +++++-- transactron/utils/data_repr.py | 4 ++-- transactron/utils/transactron_helpers.py | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/transactions/test_transactron_lib_storage.py b/test/transactions/test_transactron_lib_storage.py index 82bd3f4cc..cccd99ac3 100644 --- a/test/transactions/test_transactron_lib_storage.py +++ b/test/transactions/test_transactron_lib_storage.py @@ -1,4 +1,4 @@ -from test.common import * +from transactron.testing import * import random from transactron.lib.storage import ContentAddressableMemory diff --git a/test/utils/test_amaranth_ext.py b/test/utils/test_amaranth_ext.py index 789c6369d..d1189574a 100644 --- a/test/utils/test_amaranth_ext.py +++ b/test/utils/test_amaranth_ext.py @@ -1,4 +1,4 @@ -from test.common import * +from transactron.testing import * import random from transactron.utils.amaranth_ext import MultiPriorityEncoder diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 5f9da1692..f68d7efb1 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -163,7 +163,7 @@ class ContentAddressableMemory(Elaboratable): Inserts new data. """ - def __init__(self, address_layout: MethodLayout, data_layout: LayoutList, entries_number: int): + def __init__(self, address_layout: MethodLayout, data_layout: MethodLayout, entries_number: int): """ Parameters ---------- diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index ae3f9fc35..bbfe6d602 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -1,11 +1,14 @@ from amaranth import * +from amaranth.lib.data import StructLayout import random from typing import Optional from hypothesis.strategies import composite, DrawFn, integers -from transactron.utils import LayoutList +from transactron.utils import MethodLayout @composite -def generate_based_on_layout(draw : DrawFn, layout: LayoutList): +def generate_based_on_layout(draw : DrawFn, layout: MethodLayout): + if isinstance(layout, StructLayout): + raise NotImplementedError("StructLayout is not supported in automatic value generation.") d = {} for name, sublayout in layout: if isinstance(sublayout, list): diff --git a/transactron/utils/data_repr.py b/transactron/utils/data_repr.py index 95e6d4d4e..acd7c7505 100644 --- a/transactron/utils/data_repr.py +++ b/transactron/utils/data_repr.py @@ -1,5 +1,5 @@ from collections.abc import Iterable, Mapping -from ._typing import LayoutList, ShapeLike +from ._typing import ShapeLike, MethodLayout from typing import Any, Sized from statistics import fmean from amaranth.lib.data import StructLayout @@ -78,7 +78,7 @@ def bits_from_int(num: int, lower: int, length: int): return (num >> lower) & ((1 << (length)) - 1) -def data_layout(val: ShapeLike) -> SimpleLayout: +def data_layout(val: ShapeLike) -> MethodLayout: return [("data", val)] diff --git a/transactron/utils/transactron_helpers.py b/transactron/utils/transactron_helpers.py index 048a2bb61..0bb55f311 100644 --- a/transactron/utils/transactron_helpers.py +++ b/transactron/utils/transactron_helpers.py @@ -133,7 +133,7 @@ def from_layout_field(shape: ShapeLike | LayoutList) -> ShapeLike: return shape -def make_layout(*fields: LayoutListField): +def make_layout(*fields: LayoutListField) -> StructLayout: return from_method_layout(fields) From b7c097fda322e972e3bd681ed3b72df7a21cb9f4 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Mon, 1 Apr 2024 17:35:25 +0200 Subject: [PATCH 08/29] Very WIP hypothesis tests which works. --- .gitignore | 1 + .../test_transactron_lib_storage.py | 99 ++++++++++++------- transactron/lib/storage.py | 2 +- transactron/testing/infrastructure.py | 56 ++++++++--- transactron/testing/input_generation.py | 4 +- 5 files changed, 111 insertions(+), 51 deletions(-) diff --git a/.gitignore b/.gitignore index c40fe28de..bb507a765 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ test/__profiles__/*.json pytestdebug.log _coreblocks_regression.lock _coreblocks_regression.counter +.hypothesis # cocotb build /test/regression/cocotb/build diff --git a/test/transactions/test_transactron_lib_storage.py b/test/transactions/test_transactron_lib_storage.py index cccd99ac3..d69a28b30 100644 --- a/test/transactions/test_transactron_lib_storage.py +++ b/test/transactions/test_transactron_lib_storage.py @@ -1,17 +1,18 @@ +from hypothesis import given, strategies as st, settings +from hypothesis.stateful import RuleBasedStateMachine, rule, initialize from transactron.testing import * -import random from transactron.lib.storage import ContentAddressableMemory +class TestContentAddressableMemory(TestCaseWithSimulator, TransactronHypothesis): + addr_width = 4 + content_width = 5 + addr_layout = data_layout(addr_width) + content_layout = data_layout(content_width) -class TestContentAddressableMemory(TestCaseWithSimulator): def setUp(self): - random.seed(14) + print("setup") self.test_number = 50 - self.addr_width = 4 - self.content_width = 5 self.entries_count = 8 - self.addr_layout = data_layout(self.addr_width) - self.content_layout = data_layout(self.content_width) self.circ = SimpleTestCircuit( ContentAddressableMemory(self.addr_layout, self.content_layout, self.entries_count) @@ -19,32 +20,62 @@ def setUp(self): self.memory = {} - def input_process(self): - for _ in range(self.test_number): + def input_process(self, hp_data): + def f(): + yield Settle() + for _ in self.shrinkable_loop(self.test_number, hp_data): + addr = hp_data.draw(generate_based_on_layout(self.addr_layout).filter(lambda x: frozenset(x.items()) not in self.memory)) + content = hp_data.draw(generate_based_on_layout(self.content_layout)) + yield from self.circ.push.call(addr=addr, data=content) + yield Settle() + self.memory[frozenset(addr.items())] = content + print(_) + print("SRODEK") + print("I_END") + return f + + def output_process(self, hp_data): + def f(): + print("OUT_PROC") + yield Passive() while True: - addr = generate_based_on_layout(self.addr_layout) + addr = hp_data.draw(generate_based_on_layout(self.addr_layout)) + res = yield from self.circ.pop.call(addr=addr) frozen_addr = frozenset(addr.items()) - if frozen_addr not in self.memory: - break - content = generate_based_on_layout(self.content_layout) - yield from self.circ.push.call(addr=addr, data=content) - yield Settle() - self.memory[frozen_addr] = content - - def output_process(self): - yield Passive() - while True: - addr = generate_based_on_layout(self.addr_layout) - res = yield from self.circ.pop.call(addr=addr) - frozen_addr = frozenset(addr.items()) - if frozen_addr in self.memory: - self.assertEqual(res["not_found"], 0) - self.assertEqual(res["data"], self.memory[frozen_addr]) - self.memory.pop(frozen_addr) - else: - self.assertEqual(res["not_found"], 1) - - def test_random(self): - with self.run_simulation(self.circ) as sim: - sim.add_sync_process(self.input_process) - sim.add_sync_process(self.output_process) + if frozen_addr in self.memory: + assert res["not_found"]== 0 + assert res["data"]== self.memory[frozen_addr] + self.memory.pop(frozen_addr) + else: + assert res["not_found"]== 1 + return f + +# @rule() +# def smok(self): +# pass + @given(st.data()) + def test_random(self, hp_data): + self.manual_setup_method() + self.setUp() + print("SMOK",flush=True) + with DependencyContext(self.dependency_manager): + with self.run_simulation(self.circ) as sim: + sim.add_sync_process(self.input_process( hp_data)) + sim.add_sync_process(self.output_process(hp_data)) + +# def teardown(self): +# print("teardownm") +# TestCaseWithSimulator.dependency_manager=DependencyManager() + +#settings.register_profile("ci", max_examples=30, stateful_step_count=1) +#settings.load_profile("ci") +#TestContentAddressableMemory.TestCase.settings=settings(max_examples=10, stateful_step_count=1) +#TestTT = TestContentAddressableMemory.TestCase +class TestClass: + def test_one(self): + x = "this" + assert "h" in x + + def test_two(self): + x = "hello" + assert hasattr(x, "check") diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index f68d7efb1..ede6d7c8b 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -201,7 +201,7 @@ def _(addr, data): m.d.sync += valids.bit_select(id, 1).eq(1) if_addr = Signal(self.entries_number, name="if_addr") - data_to_send = Record(self.data_layout) + data_to_send = Signal(self.data_layout) @def_method(m, self.pop) def _(addr): diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index a769bba13..c34ee68d7 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -3,6 +3,8 @@ import random import unittest import functools +import hypothesis as hp +import hypothesis.strategies as hpst from contextlib import contextmanager, nullcontext from typing import TypeVar, Generic, Type, TypeGuard, Any, Union, Callable, cast, TypeAlias from abc import ABC @@ -201,14 +203,13 @@ def run(self) -> bool: return not self.advance() -class TestCaseWithSimulator(unittest.TestCase): +class TestCaseWithSimulator(): dependency_manager: DependencyManager - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - + def manual_setup_method(self): self.dependency_manager = DependencyManager() + print("init") def wrap(f: Callable[[], None]): @functools.wraps(f) def wrapper(): @@ -241,8 +242,9 @@ def add_all_mocks(self, sim: PysimSimulator, frame_locals: dict) -> None: @contextmanager def run_simulation(self, module: HasElaborate, max_cycles: float = 10e4, add_transaction_module=True): traces_file = None - if "__TRANSACTRON_DUMP_TRACES" in os.environ: - traces_file = unittest.TestCase.id(self) +# TODO: fix +# if "__TRANSACTRON_DUMP_TRACES" in os.environ: +# traces_file = unittest.TestCase.id(self) clk_period = 1e-6 sim = PysimSimulator( @@ -264,21 +266,22 @@ def run_simulation(self, module: HasElaborate, max_cycles: float = 10e4, add_tra ) def on_error(): - self.assertTrue(False, "Simulation finished due to an error") + assert False, "Simulation finished due to an error" log_level = parse_logging_level(os.environ["__TRANSACTRON_LOG_LEVEL"]) log_filter = os.environ["__TRANSACTRON_LOG_FILTER"] sim.add_sync_process(make_logging_process(log_level, log_filter, on_error)) res = sim.run() + print("res", res) +# TODO fix +# if profile is not None: +# profile_dir = "test/__profiles__" +# profile_file = unittest.TestCase.id(self) +# os.makedirs(profile_dir, exist_ok=True) +# profile.encode(f"{profile_dir}/{profile_file}.json") - if profile is not None: - profile_dir = "test/__profiles__" - profile_file = unittest.TestCase.id(self) - os.makedirs(profile_dir, exist_ok=True) - profile.encode(f"{profile_dir}/{profile_file}.json") - - self.assertTrue(res, "Simulation time limit exceeded") + assert res, "Simulation time limit exceeded" def tick(self, cycle_cnt: int = 1): """ @@ -300,3 +303,28 @@ def random_wait_geom(self, prob: float = 0.5): """ while random.random() > prob: yield + +class TransactronHypothesis(): + def init_transactron_hypothesis(self, hp_data : hpst.DataObject): + self.hp_data = hp_data + + def shrinkable_loop(self, iter_count, hp_data): + """ + Trick based on https://github.com/HypothesisWorks/hypothesis/blob/6867da71beae0e4ed004b54b92ef7c74d0722815/hypothesis-python/src/hypothesis/stateful.py#L143 + """ + if iter_count == 0: + return + i = 0 + force_val = None + while True: + b = hp_data.draw(hpst.booleans()) + #b = self.hp_data.conjecture_data.draw_boolean(p=2**-16, forced = force_val) + print("shr loop", b) + if not b: + break + yield i + i += 1 + if i == iter_count: + force_val = False + + diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index bbfe6d602..4cda53bee 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -3,10 +3,10 @@ import random from typing import Optional from hypothesis.strategies import composite, DrawFn, integers -from transactron.utils import MethodLayout +from transactron.utils import MethodLayout, RecordIntDict @composite -def generate_based_on_layout(draw : DrawFn, layout: MethodLayout): +def generate_based_on_layout(draw : DrawFn, layout: MethodLayout) -> RecordIntDict: if isinstance(layout, StructLayout): raise NotImplementedError("StructLayout is not supported in automatic value generation.") d = {} From 80df321039c4b93ed6035962a64b80a0760d596b Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Mon, 1 Apr 2024 18:29:58 +0200 Subject: [PATCH 09/29] A little bit better solution. --- .../test_transactron_lib_storage.py | 47 ++++++------------- transactron/testing/infrastructure.py | 2 - transactron/testing/input_generation.py | 23 +++++++++ 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/test/transactions/test_transactron_lib_storage.py b/test/transactions/test_transactron_lib_storage.py index d69a28b30..0d2aed518 100644 --- a/test/transactions/test_transactron_lib_storage.py +++ b/test/transactions/test_transactron_lib_storage.py @@ -6,12 +6,11 @@ class TestContentAddressableMemory(TestCaseWithSimulator, TransactronHypothesis): addr_width = 4 content_width = 5 + test_number = 50 addr_layout = data_layout(addr_width) content_layout = data_layout(content_width) def setUp(self): - print("setup") - self.test_number = 50 self.entries_count = 8 self.circ = SimpleTestCircuit( @@ -20,26 +19,22 @@ def setUp(self): self.memory = {} - def input_process(self, hp_data): + def input_process(self, in_push): def f(): yield Settle() - for _ in self.shrinkable_loop(self.test_number, hp_data): - addr = hp_data.draw(generate_based_on_layout(self.addr_layout).filter(lambda x: frozenset(x.items()) not in self.memory)) - content = hp_data.draw(generate_based_on_layout(self.content_layout)) + for addr, content in in_push: + if frozenset(addr.items()) in self.memory: + continue yield from self.circ.push.call(addr=addr, data=content) yield Settle() self.memory[frozenset(addr.items())] = content - print(_) - print("SRODEK") - print("I_END") return f - def output_process(self, hp_data): + def output_process(self, in_pop): def f(): - print("OUT_PROC") yield Passive() - while True: - addr = hp_data.draw(generate_based_on_layout(self.addr_layout)) + while in_pop: + addr = in_pop.pop() res = yield from self.circ.pop.call(addr=addr) frozen_addr = frozenset(addr.items()) if frozen_addr in self.memory: @@ -50,32 +45,18 @@ def f(): assert res["not_found"]== 1 return f -# @rule() -# def smok(self): -# pass - @given(st.data()) - def test_random(self, hp_data): + @settings(max_examples=10) + @given(generate_shrinkable_list(test_number, st.tuples(generate_based_on_layout(addr_layout), generate_based_on_layout(content_layout))), + generate_shrinkable_list(test_number, generate_based_on_layout(addr_layout))) + def test_random(self, in_push, in_pop): self.manual_setup_method() self.setUp() - print("SMOK",flush=True) with DependencyContext(self.dependency_manager): with self.run_simulation(self.circ) as sim: - sim.add_sync_process(self.input_process( hp_data)) - sim.add_sync_process(self.output_process(hp_data)) - -# def teardown(self): -# print("teardownm") -# TestCaseWithSimulator.dependency_manager=DependencyManager() + sim.add_sync_process(self.input_process(in_push)) + sim.add_sync_process(self.output_process(in_pop)) #settings.register_profile("ci", max_examples=30, stateful_step_count=1) #settings.load_profile("ci") #TestContentAddressableMemory.TestCase.settings=settings(max_examples=10, stateful_step_count=1) #TestTT = TestContentAddressableMemory.TestCase -class TestClass: - def test_one(self): - x = "this" - assert "h" in x - - def test_two(self): - x = "hello" - assert hasattr(x, "check") diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index c34ee68d7..e051a65c6 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -209,7 +209,6 @@ class TestCaseWithSimulator(): def manual_setup_method(self): self.dependency_manager = DependencyManager() - print("init") def wrap(f: Callable[[], None]): @functools.wraps(f) def wrapper(): @@ -273,7 +272,6 @@ def on_error(): sim.add_sync_process(make_logging_process(log_level, log_filter, on_error)) res = sim.run() - print("res", res) # TODO fix # if profile is not None: # profile_dir = "test/__profiles__" diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index 4cda53bee..ab6953f5b 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -2,9 +2,32 @@ from amaranth.lib.data import StructLayout import random from typing import Optional +import hypothesis.strategies as st from hypothesis.strategies import composite, DrawFn, integers from transactron.utils import MethodLayout, RecordIntDict +@composite +def generate_shrinkable_list(draw : DrawFn, length : int, generator) -> list: + """ + Trick based on https://github.com/HypothesisWorks/hypothesis/blob/6867da71beae0e4ed004b54b92ef7c74d0722815/hypothesis-python/src/hypothesis/stateful.py#L143 + """ + hp_data = draw(st.data()) + lst=[] + if length == 0: + return lst + i = 0 + force_val = None + while True: + b = hp_data.conjecture_data.draw_boolean(p=2**-16, forced = force_val) + if b: + break + lst.append(draw(generator)) + i += 1 + if i == length: + force_val = True + return lst + + @composite def generate_based_on_layout(draw : DrawFn, layout: MethodLayout) -> RecordIntDict: if isinstance(layout, StructLayout): From 785657b47e7655f34160357eab610a95446ff6e9 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 28 Apr 2024 12:49:10 +0200 Subject: [PATCH 10/29] Prepare cleaner hypothesis integration. It work, but other tests not yet. --- .../test_transactron_lib_storage.py | 12 ++---- transactron/lib/storage.py | 4 +- transactron/testing/infrastructure.py | 42 ++++++++++++------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index 0d2aed518..b5c82eeae 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -3,7 +3,7 @@ from transactron.testing import * from transactron.lib.storage import ContentAddressableMemory -class TestContentAddressableMemory(TestCaseWithSimulator, TransactronHypothesis): +class TestContentAddressableMemory(TestCaseWithSimulator): addr_width = 4 content_width = 5 test_number = 50 @@ -49,14 +49,8 @@ def f(): @given(generate_shrinkable_list(test_number, st.tuples(generate_based_on_layout(addr_layout), generate_based_on_layout(content_layout))), generate_shrinkable_list(test_number, generate_based_on_layout(addr_layout))) def test_random(self, in_push, in_pop): - self.manual_setup_method() - self.setUp() - with DependencyContext(self.dependency_manager): + with self.reinitialize_fixtures(): + self.setUp() with self.run_simulation(self.circ) as sim: sim.add_sync_process(self.input_process(in_push)) sim.add_sync_process(self.output_process(in_pop)) - -#settings.register_profile("ci", max_examples=30, stateful_step_count=1) -#settings.load_profile("ci") -#TestContentAddressableMemory.TestCase.settings=settings(max_examples=10, stateful_step_count=1) -#TestTT = TestContentAddressableMemory.TestCase diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 03d8d7835..862fa6481 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -5,7 +5,7 @@ from ..core import * from ..utils import SrcLoc, get_src_loc, MultiPriorityEncoder from typing import Optional -from transactron.utils import assign, AssignType, LayoutList +from transactron.utils import assign, AssignType, LayoutList, MethodLayout from .reqres import ArgumentsToResultsZipper __all__ = ["MemoryBank", "ContentAddressableMemory", "AsyncMemoryBank"] @@ -163,7 +163,7 @@ class ContentAddressableMemory(Elaboratable): Inserts new data. """ - def __init__(self, address_layout: LayoutList, data_layout: LayoutList, entries_number: int): + def __init__(self, address_layout: MethodLayout, data_layout: MethodLayout, entries_number: int): """ Parameters ---------- diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index c1bbf97e1..bd6e57ee9 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -8,6 +8,7 @@ from abc import ABC from amaranth import * from amaranth.sim import * +import copy from transactron.utils.dependencies import DependencyContext, DependencyManager from .testbenchio import TestbenchIO @@ -205,8 +206,8 @@ def run(self) -> bool: class TestCaseWithSimulator: dependency_manager: DependencyManager - @pytest.fixture(autouse=True) - def configure_dependency_context(self, request): + @contextmanager + def configure_dependency_context(self): self.dependency_manager = DependencyManager() with DependencyContext(self.dependency_manager): yield @@ -226,20 +227,14 @@ def add_all_mocks(self, sim: PysimSimulator, frame_locals: dict) -> None: self.add_class_mocks(sim) self.add_local_mocks(sim, frame_locals) - @pytest.fixture(autouse=True) - def configure_traces(self, request): + def configure_traces(self): traces_file = None if "__TRANSACTRON_DUMP_TRACES" in os.environ: - traces_file = ".".join(request.node.nodeid.split("/")) + traces_file = self._transactron_current_output_file_name self._transactron_infrastructure_traces_file = traces_file - @pytest.fixture(autouse=True) - def fixture_sim_processes_to_add(self): - # By default return empty lists, it will be updated by other fixtures based on needs - self._transactron_sim_processes_to_add: list[Callable[[], Optional[Callable]]] = [] - - @pytest.fixture(autouse=True) - def configure_profiles(self, request, fixture_sim_processes_to_add, configure_dependency_context): + @contextmanager + def configure_profiles(self): profile = None if "__TRANSACTRON_PROFILE" in os.environ: @@ -259,12 +254,11 @@ def f(): if profile is not None: profile_dir = "test/__profiles__" - profile_file = ".".join(request.node.nodeid.split("/")) + profile_file = self._transactron_current_output_file_name os.makedirs(profile_dir, exist_ok=True) profile.encode(f"{profile_dir}/{profile_file}.json") - @pytest.fixture(autouse=True) - def configure_logging(self, fixture_sim_processes_to_add): + def configure_logging(self): def on_error(): assert False, "Simulation finished due to an error" @@ -272,6 +266,24 @@ def on_error(): log_filter = os.environ["__TRANSACTRON_LOG_FILTER"] self._transactron_sim_processes_to_add.append(lambda: make_logging_process(log_level, log_filter, on_error)) + @contextmanager + def reinitialize_fixtures(self): + self._transactron_current_output_file_name = self._transactron_base_output_file_name+"_"+str(self._transactron_hypothesis_iter_couter) + self._transactron_sim_processes_to_add: list[Callable[[], Optional[Callable]]] = [] + with self.configure_dependency_context(): + self.configure_traces() + with self.configure_profiles(): + self.configure_logging() + yield + + @pytest.fixture(autouse = True) + def fixture_initialize_testing_env(self, request): + self._transactron_hypothesis_iter_couter = 0 + self._transactron_base_output_file_name = ".".join(request.node.nodeid.split("/")) + self._transactron_current_output_file_name = self._transactron_base_output_file_name + with self.reinitialize_fixtures(): + yield + @contextmanager def run_simulation(self, module: HasElaborate, max_cycles: float = 10e4, add_transaction_module=True): clk_period = 1e-6 From a993985fd3a4da07d32ae4a0e7998bca4e3ad0ac Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 28 Apr 2024 13:05:26 +0200 Subject: [PATCH 11/29] Fix other tests --- test/frontend/test_decode_stage.py | 2 +- test/frontend/test_fetch.py | 2 +- test/func_blocks/fu/functional_common.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/frontend/test_decode_stage.py b/test/frontend/test_decode_stage.py index 38b9168b3..13c2e20bf 100644 --- a/test/frontend/test_decode_stage.py +++ b/test/frontend/test_decode_stage.py @@ -12,7 +12,7 @@ class TestDecode(TestCaseWithSimulator): @pytest.fixture(autouse=True) - def setup(self, configure_dependency_context): + def setup(self, fixture_initialize_testing_env): self.gen_params = GenParams(test_core_config.replace(start_pc=24)) fifo_in = FIFO(self.gen_params.get(FetchLayouts).raw_instr, depth=2) diff --git a/test/frontend/test_fetch.py b/test/frontend/test_fetch.py index fb1b240c2..17e0274aa 100644 --- a/test/frontend/test_fetch.py +++ b/test/frontend/test_fetch.py @@ -55,7 +55,7 @@ class TestFetchUnit(TestCaseWithSimulator): with_rvc: bool @pytest.fixture(autouse=True) - def setup(self, configure_dependency_context): + def setup(self, fixture_initialize_testing_env): self.pc = 0 self.gen_params = GenParams( test_core_config.replace( diff --git a/test/func_blocks/fu/functional_common.py b/test/func_blocks/fu/functional_common.py index 9b509df34..825a8e033 100644 --- a/test/func_blocks/fu/functional_common.py +++ b/test/func_blocks/fu/functional_common.py @@ -95,7 +95,7 @@ def compute_result(i1: int, i2: int, i_imm: int, pc: int, fn: _T, xlen: int) -> raise NotImplementedError @pytest.fixture(autouse=True) - def setup(self, configure_dependency_context): + def setup(self, fixture_initialize_testing_env): self.gen_params = GenParams(test_core_config) self.report_mock = TestbenchIO(Adapter(i=self.gen_params.get(ExceptionRegisterLayouts).report)) From 1fccc59e9e3ef2bcfb19df8c7e4687037f765c10 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 28 Apr 2024 13:07:56 +0200 Subject: [PATCH 12/29] Fix Multipriority encoder test. --- test/transactron/utils/test_amaranth_ext.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/transactron/utils/test_amaranth_ext.py b/test/transactron/utils/test_amaranth_ext.py index d1189574a..2d8f0d203 100644 --- a/test/transactron/utils/test_amaranth_ext.py +++ b/test/transactron/utils/test_amaranth_ext.py @@ -4,7 +4,7 @@ class TestMultiPriorityEncoder(TestCaseWithSimulator): - def setUp(self): + def setup_method(self): random.seed(14) self.test_number = 50 self.input_width = 16 @@ -29,10 +29,10 @@ def process(self): expected_output = self.get_expected(input) for ex, real, valid in zip(expected_output, self.circ.outputs, self.circ.valids): if ex is None: - self.assertEqual((yield valid), 0) + assert (yield valid) == 0 else: - self.assertEqual((yield valid), 1) - self.assertEqual((yield real), ex) + assert (yield valid) == 1 + assert (yield real) == ex yield Delay(1e-7) def test_random(self): From a1da5f6531ddf1af2b0c309b1770a8b135e8a1d3 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 28 Apr 2024 15:01:29 +0200 Subject: [PATCH 13/29] Make MuliPriorityEncoder logarithmic --- .../test_transactron_lib_storage.py | 3 + test/transactron/utils/test_amaranth_ext.py | 18 +++--- .../utils/amaranth_ext/elaboratables.py | 58 +++++++++++-------- 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index b5c82eeae..7124767fe 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -21,6 +21,9 @@ def setUp(self): def input_process(self, in_push): def f(): + a=C(0) + b=C(1) + print((yield (Cat([a,b])))) yield Settle() for addr, content in in_push: if frozenset(addr.items()) in self.memory: diff --git a/test/transactron/utils/test_amaranth_ext.py b/test/transactron/utils/test_amaranth_ext.py index 2d8f0d203..14bbccea4 100644 --- a/test/transactron/utils/test_amaranth_ext.py +++ b/test/transactron/utils/test_amaranth_ext.py @@ -4,14 +4,6 @@ class TestMultiPriorityEncoder(TestCaseWithSimulator): - def setup_method(self): - random.seed(14) - self.test_number = 50 - self.input_width = 16 - self.output_count = 4 - - self.circ = MultiPriorityEncoder(self.input_width, self.output_count) - def get_expected(self, input): places = [] for i in range(self.input_width): @@ -35,6 +27,14 @@ def process(self): assert (yield real) == ex yield Delay(1e-7) - def test_random(self): + @pytest.mark.parametrize("input_width", [1,5,16,23,24]) + @pytest.mark.parametrize("output_count", [1,3,4]) + def test_random(self, input_width, output_count): + random.seed(14) + self.test_number = 50 + self.input_width = input_width + self.output_count = output_count + self.circ = MultiPriorityEncoder(self.input_width, self.output_count) + with self.run_simulation(self.circ) as sim: sim.add_process(self.process) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 1837993a4..f20d9c084 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -3,7 +3,7 @@ from typing import Literal, Optional, overload from collections.abc import Iterable from amaranth import * -from transactron.utils._typing import HasElaborate, ModuleLike +from transactron.utils._typing import HasElaborate, ModuleLike, ValueLike __all__ = [ "OneHotSwitchDynamic", @@ -14,7 +14,6 @@ "MultiPriorityEncoder", ] - @contextmanager def OneHotSwitch(m: ModuleLike, test: Value): """One-hot switch. @@ -269,32 +268,45 @@ def __init__(self, input_width: int, outputs_count: int): self.outputs_count = outputs_count self.input = Signal(self.input_width) - self.outputs = [Signal(range(self.input_width), name="output") for _ in range(self.outputs_count)] - self.valids = [Signal(name="valid") for _ in range(self.outputs_count)] + self.outputs = [Signal(range(self.input_width), name=f"output_{i}") for i in range(self.outputs_count)] + self.valids = [Signal(name=f"valid_{i}") for i in range(self.outputs_count)] + + def build_tree(self, m : Module, in_sig : Signal, start_idx : int): + assert len(in_sig) > 0 + level_outputs = [Signal(range(self.input_width), name=f"_lvl_out_idx{start_idx}_{i}") for i in range(self.outputs_count)] + level_valids = [Signal(name=f"_lvl_val_idx{start_idx}_{i}") for i in range(self.outputs_count)] + if len(in_sig) == 1: + with m.If(in_sig): + m.d.comb += level_outputs[0].eq(start_idx) + m.d.comb += level_valids[0].eq(1) + else: + middle = len(in_sig)//2 + r_in = Signal(middle, name=f"_r_in_idx{start_idx}") + l_in = Signal(len(in_sig) - middle, name=f"_l_in_idx{start_idx}") + m.d.comb += r_in.eq(in_sig[0:middle]) + m.d.comb += l_in.eq(in_sig[middle:]) + r_out, r_val = self.build_tree(m, r_in, start_idx) + l_out, l_val = self.build_tree(m, l_in, start_idx+middle) + + with m.Switch(Cat(r_val)): + for i in range(self.outputs_count+1): + with m.Case((1< Date: Sun, 28 Apr 2024 15:39:08 +0200 Subject: [PATCH 14/29] Extend CAM --- transactron/lib/storage.py | 50 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 862fa6481..2eaa9d131 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -157,8 +157,12 @@ class ContentAddressableMemory(Elaboratable): Attributes ---------- - pop : Method - Looks for the data in memory and, if found, returns it and removes it. + read : Method + Nondestructive read + write : Method + If index present - do update + remove : Method + Remove push : Method Inserts new data. """ @@ -178,8 +182,10 @@ def __init__(self, address_layout: MethodLayout, data_layout: MethodLayout, entr self.data_layout = from_method_layout(data_layout) self.entries_number = entries_number - self.pop = Method(i=[("addr", self.address_layout)], o=[("data", self.data_layout), ("not_found", 1)]) + self.read = Method(i=[("addr", self.address_layout)], o=[("data", self.data_layout), ("not_found", 1)]) + self.remove=Method(i=[("addr", self.address_layout)]) self.push = Method(i=[("addr", self.address_layout), ("data", self.data_layout)]) + self.write = Method(i=[("addr", self.address_layout), ("data", self.data_layout)]) def elaborate(self, platform) -> TModule: m = TModule() @@ -189,32 +195,42 @@ def elaborate(self, platform) -> TModule: data_array = Array([Signal(self.data_layout) for _ in range(self.entries_number)]) valids = Signal(self.entries_number, name="valids") - m.submodules.encoder_addr = encoder_addr = MultiPriorityEncoder(self.entries_number, 1) - m.submodules.encoder_valids = encoder_valids = MultiPriorityEncoder(self.entries_number, 1) - m.d.comb += encoder_valids.input.eq(~valids) + m.submodules.encoder_read = encoder_read = MultiPriorityEncoder(self.entries_number, 1) + m.submodules.encoder_write = encoder_write = MultiPriorityEncoder(self.entries_number, 1) + m.submodules.encoder_push = encoder_push = MultiPriorityEncoder(self.entries_number, 1) + m.submodules.encoder_remove = encoder_remove = MultiPriorityEncoder(self.entries_number, 1) + m.d.comb += encoder_push.input.eq(~valids) @def_method(m, self.push, ready=~valids.all()) def _(addr, data): id = Signal(range(self.entries_number), name="id_push") - m.d.comb += id.eq(encoder_valids.outputs[0]) + m.d.top_comb += id.eq(encoder_push.outputs[0]) m.d.sync += address_array[id].eq(addr) m.d.sync += data_array[id].eq(data) m.d.sync += valids.bit_select(id, 1).eq(1) - if_addr = Signal(self.entries_number, name="if_addr") - data_to_send = Signal(self.data_layout) + @def_method(m, self.write) + def _(addr, data): + write_mask = Signal(self.entries_number, name="write_mask") + m.d.top_comb += write_mask.eq(Cat([addr == stored_addr for stored_addr in address_array]) & valids) + m.d.top_comb += encoder_write.input.eq(write_mask) + m.d.sync += data_array[encoder_write.outputs[0]].eq(data) - @def_method(m, self.pop) + @def_method(m, self.read) def _(addr): - m.d.top_comb += if_addr.eq(Cat([addr == stored_addr for stored_addr in address_array]) & valids) - id = encoder_addr.outputs[0] - with m.If(if_addr.any()): - m.d.comb += data_to_send.eq(data_array[id]) - m.d.sync += valids.bit_select(id, 1).eq(0) + read_mask = Signal(self.entries_number, name="read_mask") + m.d.top_comb += read_mask.eq(Cat([addr == stored_addr for stored_addr in address_array]) & valids) + m.d.top_comb += encoder_read.input.eq(read_mask) + return {"data": data_array[encoder_read.outputs[0]], "not_found": ~read_mask.any()} - return {"data": data_to_send, "not_found": ~if_addr.any()} + @def_method(m, self.remove) + def _(addr): + rm_mask = Signal(self.entries_number, name="rm_mask") + m.d.top_comb += rm_mask.eq(Cat([addr == stored_addr for stored_addr in address_array]) & valids) + m.d.top_comb += encoder_remove.input.eq(rm_mask) + with m.If(rm_mask.any()): + m.d.sync += valids.bit_select(encoder_remove.outputs[0], 1).eq(0) - m.d.comb += encoder_addr.input.eq(if_addr) return m From 54dcb16a76b8b630c26191653b8c672e4a8c675b Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 28 Apr 2024 17:29:50 +0200 Subject: [PATCH 15/29] Extend CAM test --- .../test_transactron_lib_storage.py | 87 ++++++++++++------- transactron/lib/storage.py | 6 +- transactron/testing/infrastructure.py | 4 + transactron/testing/input_generation.py | 41 ++++++++- 4 files changed, 102 insertions(+), 36 deletions(-) diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index 7124767fe..2b2e7e303 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -19,41 +19,70 @@ def setUp(self): self.memory = {} - def input_process(self, in_push): + def generic_process(self, method, input_lst, behaviour_check = None, state_change = None, input_verification = None, settle_count = 0): def f(): - a=C(0) - b=C(1) - print((yield (Cat([a,b])))) - yield Settle() - for addr, content in in_push: - if frozenset(addr.items()) in self.memory: + while input_lst: + elem = input_lst.pop() + if elem == OpNOP(): + yield continue - yield from self.circ.push.call(addr=addr, data=content) - yield Settle() - self.memory[frozenset(addr.items())] = content + if input_verification is not None and not input_verification(elem): + continue + response = yield from method.call(**elem) + yield from self.multi_settle(settle_count) + if behaviour_check is not None: + # Here accesses to circuit are allowed + yield from behaviour_check(elem, response) + yield Tick("sync_neg") + if state_change is not None: + # It is standard python function by purpose to don't allow accessing circuit + yield from self.multi_settle(settle_count) + state_change(elem, response) return f - def output_process(self, in_pop): - def f(): - yield Passive() - while in_pop: - addr = in_pop.pop() - res = yield from self.circ.pop.call(addr=addr) - frozen_addr = frozenset(addr.items()) - if frozen_addr in self.memory: - assert res["not_found"]== 0 - assert res["data"]== self.memory[frozen_addr] - self.memory.pop(frozen_addr) - else: - assert res["not_found"]== 1 - return f + def push_process(self, in_push): + def verify_in(elem): + return not (frozenset(elem["addr"].items()) in self.memory) + def modify_state(elem, response): + self.memory[frozenset(elem["addr"].items())] = elem["data"] + return self.generic_process(self.circ.push, in_push, state_change = modify_state, input_verification = verify_in, settle_count = 1) + + def read_process(self, in_read): + def check(elem, response): + addr = elem["addr"] + frozen_addr = frozenset(addr.items()) + if frozen_addr in self.memory: + assert response["not_found"]== 0 + assert response["data"]== self.memory[frozen_addr] + else: + assert response["not_found"]== 1 + return self.generic_process(self.circ.read, in_read, behaviour_check = check) + + def remove_process(self, in_remove): + def modify_state(elem, response): + if frozenset(elem["addr"].items()) in self.memory: + del self.memory[frozenset(elem["addr"].items())] + return self.generic_process(self.circ.push, in_remove, state_change = modify_state, settle_count = 2) + + def write_process(self, in_write): + def verify_in(elem): + return frozenset(elem["addr"].items()) in self.memory + def modify_state(elem, response): + self.memory[frozenset(elem["addr"].items())] = elem["data"] + return self.generic_process(self.circ.push, in_write, state_change = modify_state, input_verification = verify_in, settle_count = 1) @settings(max_examples=10) - @given(generate_shrinkable_list(test_number, st.tuples(generate_based_on_layout(addr_layout), generate_based_on_layout(content_layout))), - generate_shrinkable_list(test_number, generate_based_on_layout(addr_layout))) - def test_random(self, in_push, in_pop): + @given( + generate_process_input(test_number, 3, [("addr", addr_layout), ("data", content_layout)]), + generate_process_input(test_number, 3, [("addr", addr_layout), ("data", content_layout)]), + generate_process_input(test_number, 3, [("addr", addr_layout)]), + generate_process_input(test_number, 3, [("addr", addr_layout)]), + ) + def test_random(self, in_push, in_write, in_read, in_remove): with self.reinitialize_fixtures(): self.setUp() with self.run_simulation(self.circ) as sim: - sim.add_sync_process(self.input_process(in_push)) - sim.add_sync_process(self.output_process(in_pop)) + sim.add_sync_process(self.push_process(in_push)) + sim.add_sync_process(self.read_process(in_read)) + sim.add_sync_process(self.write_process(in_read)) + sim.add_sync_process(self.remove_process(in_read)) diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 2eaa9d131..12cc385f9 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -151,9 +151,7 @@ class ContentAddressableMemory(Elaboratable): .. warning:: - Current implementation has critical path O(entries_number). If needed we can - optimise it in future to have O(log(entries_number)). - + Pushing the value with index already present in CAM is an undefined behaviour. Attributes ---------- @@ -199,7 +197,7 @@ def elaborate(self, platform) -> TModule: m.submodules.encoder_write = encoder_write = MultiPriorityEncoder(self.entries_number, 1) m.submodules.encoder_push = encoder_push = MultiPriorityEncoder(self.entries_number, 1) m.submodules.encoder_remove = encoder_remove = MultiPriorityEncoder(self.entries_number, 1) - m.d.comb += encoder_push.input.eq(~valids) + m.d.top_comb += encoder_push.input.eq(~valids) @def_method(m, self.push, ready=~valids.all()) def _(addr, data): diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index bd6e57ee9..1cb407d06 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -326,3 +326,7 @@ def random_wait_geom(self, prob: float = 0.5): """ while random.random() > prob: yield + + def multi_settle(self, settle_count : int = 1): + for _ in range(settle_count): + yield Settle() diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index ab6953f5b..8971e3a91 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -1,13 +1,18 @@ from amaranth import * from amaranth.lib.data import StructLayout import random -from typing import Optional +from typing import Optional, TypeVar import hypothesis.strategies as st -from hypothesis.strategies import composite, DrawFn, integers +from hypothesis.strategies import composite, DrawFn, integers, SearchStrategy from transactron.utils import MethodLayout, RecordIntDict +class OpNOP(): + pass + +T = TypeVar("T") + @composite -def generate_shrinkable_list(draw : DrawFn, length : int, generator) -> list: +def generate_shrinkable_list(draw : DrawFn, length : int, generator : SearchStrategy[T]) -> list[T]: """ Trick based on https://github.com/HypothesisWorks/hypothesis/blob/6867da71beae0e4ed004b54b92ef7c74d0722815/hypothesis-python/src/hypothesis/stateful.py#L143 """ @@ -53,3 +58,33 @@ def generate_based_on_layout(draw : DrawFn, layout: MethodLayout) -> RecordIntDi raise NotImplementedError("Passed LayoutList with syntax yet unsuported in automatic value generation.") d[name] = elem return d + +def insert_nops(draw : DrawFn, max_nops : int, lst : list): + nops_nr = draw(integers(min_value=0, max_value=max_nops)) + for i in range(nops_nr): + lst.append(OpNOP()) + return lst + +@composite +def generate_nops_in_list(draw : DrawFn, max_nops : int, generate_list : SearchStrategy[list[T]]) -> list[T|OpNOP]: + lst = draw(generate_list) + out_lst = [] + out_lst = insert_nops(draw, max_nops, out_lst) + for i in lst: + out_lst.append(i) + out_lst = insert_nops(draw, max_nops, out_lst) + return out_lst + +@composite +def generate_method_input(draw : DrawFn, args : list[tuple[str, MethodLayout]]) -> dict[str, RecordIntDict]: + out = [] + for name, layout in args: + out.append((name, draw(generate_based_on_layout(layout)))) + return dict(out) + +@composite +def generate_process_input(draw : DrawFn, elem_count : int, max_nops : int, layouts : list[tuple[str, MethodLayout]]) -> list[dict[str, RecordIntDict] | OpNOP]: + return draw( + generate_nops_in_list(max_nops, generate_shrinkable_list(elem_count, generate_method_input(layouts))) + ) + From 0ba17ecf9c5e7f2bcbb7227e64194bcb59cc8fb5 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 28 Apr 2024 18:06:40 +0200 Subject: [PATCH 16/29] Some fixes to test. --- test/transactron/test_transactron_lib_storage.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index 2b2e7e303..ce169fd5a 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -6,7 +6,7 @@ class TestContentAddressableMemory(TestCaseWithSimulator): addr_width = 4 content_width = 5 - test_number = 50 + test_number = 5 addr_layout = data_layout(addr_width) content_layout = data_layout(content_width) @@ -23,7 +23,7 @@ def generic_process(self, method, input_lst, behaviour_check = None, state_chang def f(): while input_lst: elem = input_lst.pop() - if elem == OpNOP(): + if isinstance(elem, OpNOP): yield continue if input_verification is not None and not input_verification(elem): @@ -32,7 +32,9 @@ def f(): yield from self.multi_settle(settle_count) if behaviour_check is not None: # Here accesses to circuit are allowed - yield from behaviour_check(elem, response) + ret = behaviour_check(elem, response) + if isinstance(ret, Generator): + yield from ret yield Tick("sync_neg") if state_change is not None: # It is standard python function by purpose to don't allow accessing circuit From 283e9e725177909cc818af50a7aacb42e82eb9ee Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Fri, 3 May 2024 15:35:03 +0200 Subject: [PATCH 17/29] Fix test. --- .../test_transactron_lib_storage.py | 46 +++++++++++-------- transactron/lib/storage.py | 10 ++-- transactron/testing/infrastructure.py | 5 +- transactron/testing/input_generation.py | 3 +- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index ce169fd5a..f0857ff2e 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -1,4 +1,5 @@ -from hypothesis import given, strategies as st, settings +from datetime import timedelta +from hypothesis import given, strategies as st, settings, Phase, example from hypothesis.stateful import RuleBasedStateMachine, rule, initialize from transactron.testing import * from transactron.lib.storage import ContentAddressableMemory @@ -6,7 +7,8 @@ class TestContentAddressableMemory(TestCaseWithSimulator): addr_width = 4 content_width = 5 - test_number = 5 + test_number = 30 + nop_number = 3 addr_layout = data_layout(addr_width) content_layout = data_layout(content_width) @@ -19,14 +21,17 @@ def setUp(self): self.memory = {} - def generic_process(self, method, input_lst, behaviour_check = None, state_change = None, input_verification = None, settle_count = 0): + def generic_process(self, method, input_lst, behaviour_check = None, state_change = None, input_verification = None, settle_count = 0, name=""): def f(): while input_lst: + # wait till all processes will end the previous cycle + yield from self.multi_settle(4) elem = input_lst.pop() if isinstance(elem, OpNOP): yield continue if input_verification is not None and not input_verification(elem): + yield continue response = yield from method.call(**elem) yield from self.multi_settle(settle_count) @@ -35,11 +40,10 @@ def f(): ret = behaviour_check(elem, response) if isinstance(ret, Generator): yield from ret - yield Tick("sync_neg") if state_change is not None: # It is standard python function by purpose to don't allow accessing circuit - yield from self.multi_settle(settle_count) state_change(elem, response) + yield return f def push_process(self, in_push): @@ -47,7 +51,7 @@ def verify_in(elem): return not (frozenset(elem["addr"].items()) in self.memory) def modify_state(elem, response): self.memory[frozenset(elem["addr"].items())] = elem["data"] - return self.generic_process(self.circ.push, in_push, state_change = modify_state, input_verification = verify_in, settle_count = 1) + return self.generic_process(self.circ.push, in_push, state_change = modify_state, input_verification = verify_in, settle_count = 3, name="push") def read_process(self, in_read): def check(elem, response): @@ -58,33 +62,37 @@ def check(elem, response): assert response["data"]== self.memory[frozen_addr] else: assert response["not_found"]== 1 - return self.generic_process(self.circ.read, in_read, behaviour_check = check) + return self.generic_process(self.circ.read, in_read, behaviour_check = check, settle_count = 0, name="read") def remove_process(self, in_remove): def modify_state(elem, response): if frozenset(elem["addr"].items()) in self.memory: del self.memory[frozenset(elem["addr"].items())] - return self.generic_process(self.circ.push, in_remove, state_change = modify_state, settle_count = 2) + return self.generic_process(self.circ.remove, in_remove, state_change = modify_state, settle_count = 2, name="remv") def write_process(self, in_write): def verify_in(elem): - return frozenset(elem["addr"].items()) in self.memory + ret= frozenset(elem["addr"].items()) in self.memory + return ret + def check(elem, response): + assert response["not_found"]== int(frozenset(elem["addr"].items()) not in self.memory) def modify_state(elem, response): - self.memory[frozenset(elem["addr"].items())] = elem["data"] - return self.generic_process(self.circ.push, in_write, state_change = modify_state, input_verification = verify_in, settle_count = 1) + if frozenset(elem["addr"].items()) in self.memory: + self.memory[frozenset(elem["addr"].items())] = elem["data"] + return self.generic_process(self.circ.write, in_write, behaviour_check=check, state_change = modify_state, input_verification = None, settle_count = 1, name = "writ") - @settings(max_examples=10) + @settings(max_examples=10, phases=(Phase.explicit, Phase.reuse, Phase.generate, Phase.shrink), derandomize = True, deadline=timedelta(milliseconds=500)) @given( - generate_process_input(test_number, 3, [("addr", addr_layout), ("data", content_layout)]), - generate_process_input(test_number, 3, [("addr", addr_layout), ("data", content_layout)]), - generate_process_input(test_number, 3, [("addr", addr_layout)]), - generate_process_input(test_number, 3, [("addr", addr_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout), ("data", content_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout), ("data", content_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout)]), ) def test_random(self, in_push, in_write, in_read, in_remove): with self.reinitialize_fixtures(): self.setUp() - with self.run_simulation(self.circ) as sim: + with self.run_simulation(self.circ, max_cycles=500) as sim: sim.add_sync_process(self.push_process(in_push)) sim.add_sync_process(self.read_process(in_read)) - sim.add_sync_process(self.write_process(in_read)) - sim.add_sync_process(self.remove_process(in_read)) + sim.add_sync_process(self.write_process(in_write)) + sim.add_sync_process(self.remove_process(in_remove)) diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 12cc385f9..2e2604f68 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -183,14 +183,14 @@ def __init__(self, address_layout: MethodLayout, data_layout: MethodLayout, entr self.read = Method(i=[("addr", self.address_layout)], o=[("data", self.data_layout), ("not_found", 1)]) self.remove=Method(i=[("addr", self.address_layout)]) self.push = Method(i=[("addr", self.address_layout), ("data", self.data_layout)]) - self.write = Method(i=[("addr", self.address_layout), ("data", self.data_layout)]) + self.write = Method(i=[("addr", self.address_layout), ("data", self.data_layout)], o=[("not_found",1)]) def elaborate(self, platform) -> TModule: m = TModule() - address_array = Array([Signal(self.address_layout) for _ in range(self.entries_number)]) - data_array = Array([Signal(self.data_layout) for _ in range(self.entries_number)]) + address_array = Array([Signal(self.address_layout, name=f"address_array_{i}") for i in range(self.entries_number)]) + data_array = Array([Signal(self.data_layout, name=f"data_array_{i}") for i in range(self.entries_number)]) valids = Signal(self.entries_number, name="valids") m.submodules.encoder_read = encoder_read = MultiPriorityEncoder(self.entries_number, 1) @@ -212,7 +212,9 @@ def _(addr, data): write_mask = Signal(self.entries_number, name="write_mask") m.d.top_comb += write_mask.eq(Cat([addr == stored_addr for stored_addr in address_array]) & valids) m.d.top_comb += encoder_write.input.eq(write_mask) - m.d.sync += data_array[encoder_write.outputs[0]].eq(data) + with m.If(write_mask.any()): + m.d.sync += data_array[encoder_write.outputs[0]].eq(data) + return {"not_found" : ~write_mask.any()} @def_method(m, self.read) def _(addr): diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index 1cb407d06..9f40c3595 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -268,17 +268,18 @@ def on_error(): @contextmanager def reinitialize_fixtures(self): - self._transactron_current_output_file_name = self._transactron_base_output_file_name+"_"+str(self._transactron_hypothesis_iter_couter) + self._transactron_current_output_file_name = self._transactron_base_output_file_name+"_"+str(self._transactron_hypothesis_iter_counter) self._transactron_sim_processes_to_add: list[Callable[[], Optional[Callable]]] = [] with self.configure_dependency_context(): self.configure_traces() with self.configure_profiles(): self.configure_logging() yield + self._transactron_hypothesis_iter_counter += 1 @pytest.fixture(autouse = True) def fixture_initialize_testing_env(self, request): - self._transactron_hypothesis_iter_couter = 0 + self._transactron_hypothesis_iter_counter = 0 self._transactron_base_output_file_name = ".".join(request.node.nodeid.split("/")) self._transactron_current_output_file_name = self._transactron_base_output_file_name with self.reinitialize_fixtures(): diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index 8971e3a91..819fe757b 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -7,7 +7,8 @@ from transactron.utils import MethodLayout, RecordIntDict class OpNOP(): - pass + def __repr__(self): + return "OpNOP()" T = TypeVar("T") From 6a76949801daa70d23824985f153843b9dbe3aa7 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Fri, 3 May 2024 15:41:49 +0200 Subject: [PATCH 18/29] Some formating --- .../test_transactron_lib_storage.py | 70 ++++++++++++++----- test/transactron/utils/test_amaranth_ext.py | 4 +- transactron/lib/storage.py | 15 ++-- transactron/testing/infrastructure.py | 8 ++- transactron/testing/input_generation.py | 46 ++++++------ transactron/utils/_typing.py | 1 - .../utils/amaranth_ext/elaboratables.py | 22 +++--- 7 files changed, 107 insertions(+), 59 deletions(-) diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index f0857ff2e..d7236206e 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -4,6 +4,7 @@ from transactron.testing import * from transactron.lib.storage import ContentAddressableMemory + class TestContentAddressableMemory(TestCaseWithSimulator): addr_width = 4 content_width = 5 @@ -21,7 +22,16 @@ def setUp(self): self.memory = {} - def generic_process(self, method, input_lst, behaviour_check = None, state_change = None, input_verification = None, settle_count = 0, name=""): + def generic_process( + self, + method, + input_lst, + behaviour_check=None, + state_change=None, + input_verification=None, + settle_count=0, + name="", + ): def f(): while input_lst: # wait till all processes will end the previous cycle @@ -44,50 +54,78 @@ def f(): # It is standard python function by purpose to don't allow accessing circuit state_change(elem, response) yield + return f def push_process(self, in_push): def verify_in(elem): return not (frozenset(elem["addr"].items()) in self.memory) + def modify_state(elem, response): self.memory[frozenset(elem["addr"].items())] = elem["data"] - return self.generic_process(self.circ.push, in_push, state_change = modify_state, input_verification = verify_in, settle_count = 3, name="push") + + return self.generic_process( + self.circ.push, + in_push, + state_change=modify_state, + input_verification=verify_in, + settle_count=3, + name="push", + ) def read_process(self, in_read): def check(elem, response): addr = elem["addr"] frozen_addr = frozenset(addr.items()) if frozen_addr in self.memory: - assert response["not_found"]== 0 - assert response["data"]== self.memory[frozen_addr] + assert response["not_found"] == 0 + assert response["data"] == self.memory[frozen_addr] else: - assert response["not_found"]== 1 - return self.generic_process(self.circ.read, in_read, behaviour_check = check, settle_count = 0, name="read") + assert response["not_found"] == 1 + + return self.generic_process(self.circ.read, in_read, behaviour_check=check, settle_count=0, name="read") def remove_process(self, in_remove): def modify_state(elem, response): if frozenset(elem["addr"].items()) in self.memory: del self.memory[frozenset(elem["addr"].items())] - return self.generic_process(self.circ.remove, in_remove, state_change = modify_state, settle_count = 2, name="remv") + + return self.generic_process(self.circ.remove, in_remove, state_change=modify_state, settle_count=2, name="remv") def write_process(self, in_write): def verify_in(elem): - ret= frozenset(elem["addr"].items()) in self.memory + ret = frozenset(elem["addr"].items()) in self.memory return ret + def check(elem, response): - assert response["not_found"]== int(frozenset(elem["addr"].items()) not in self.memory) + assert response["not_found"] == int(frozenset(elem["addr"].items()) not in self.memory) + def modify_state(elem, response): if frozenset(elem["addr"].items()) in self.memory: self.memory[frozenset(elem["addr"].items())] = elem["data"] - return self.generic_process(self.circ.write, in_write, behaviour_check=check, state_change = modify_state, input_verification = None, settle_count = 1, name = "writ") - @settings(max_examples=10, phases=(Phase.explicit, Phase.reuse, Phase.generate, Phase.shrink), derandomize = True, deadline=timedelta(milliseconds=500)) + return self.generic_process( + self.circ.write, + in_write, + behaviour_check=check, + state_change=modify_state, + input_verification=None, + settle_count=1, + name="writ", + ) + + @settings( + max_examples=10, + phases=(Phase.explicit, Phase.reuse, Phase.generate, Phase.shrink), + derandomize=True, + deadline=timedelta(milliseconds=500), + ) @given( - generate_process_input(test_number, nop_number, [("addr", addr_layout), ("data", content_layout)]), - generate_process_input(test_number, nop_number, [("addr", addr_layout), ("data", content_layout)]), - generate_process_input(test_number, nop_number, [("addr", addr_layout)]), - generate_process_input(test_number, nop_number, [("addr", addr_layout)]), - ) + generate_process_input(test_number, nop_number, [("addr", addr_layout), ("data", content_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout), ("data", content_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout)]), + generate_process_input(test_number, nop_number, [("addr", addr_layout)]), + ) def test_random(self, in_push, in_write, in_read, in_remove): with self.reinitialize_fixtures(): self.setUp() diff --git a/test/transactron/utils/test_amaranth_ext.py b/test/transactron/utils/test_amaranth_ext.py index 14bbccea4..b41a6c497 100644 --- a/test/transactron/utils/test_amaranth_ext.py +++ b/test/transactron/utils/test_amaranth_ext.py @@ -27,8 +27,8 @@ def process(self): assert (yield real) == ex yield Delay(1e-7) - @pytest.mark.parametrize("input_width", [1,5,16,23,24]) - @pytest.mark.parametrize("output_count", [1,3,4]) + @pytest.mark.parametrize("input_width", [1, 5, 16, 23, 24]) + @pytest.mark.parametrize("output_count", [1, 3, 4]) def test_random(self, input_width, output_count): random.seed(14) self.test_number = 50 diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index 2e2604f68..ce97c6e77 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -37,7 +37,7 @@ def __init__( elem_count: int, granularity: Optional[int] = None, safe_writes: bool = True, - src_loc: int | SrcLoc = 0 + src_loc: int | SrcLoc = 0, ): """ Parameters @@ -141,7 +141,7 @@ def _(arg): class ContentAddressableMemory(Elaboratable): """Content addresable memory - This module implements a content-addressable memory (in short CAM) with Transactron interface. + This module implements a content-addressable memory (in short CAM) with Transactron interface. CAM is a type of memory where instead of predefined indexes there are used values feed in runtime as keys (smimlar as in python dictionary). To insert new entry a pair `(key, value)` has to be provided. Such pair takes an free slot which depends on internal implementation. To read value @@ -181,15 +181,16 @@ def __init__(self, address_layout: MethodLayout, data_layout: MethodLayout, entr self.entries_number = entries_number self.read = Method(i=[("addr", self.address_layout)], o=[("data", self.data_layout), ("not_found", 1)]) - self.remove=Method(i=[("addr", self.address_layout)]) + self.remove = Method(i=[("addr", self.address_layout)]) self.push = Method(i=[("addr", self.address_layout), ("data", self.data_layout)]) - self.write = Method(i=[("addr", self.address_layout), ("data", self.data_layout)], o=[("not_found",1)]) + self.write = Method(i=[("addr", self.address_layout), ("data", self.data_layout)], o=[("not_found", 1)]) def elaborate(self, platform) -> TModule: m = TModule() - - address_array = Array([Signal(self.address_layout, name=f"address_array_{i}") for i in range(self.entries_number)]) + address_array = Array( + [Signal(self.address_layout, name=f"address_array_{i}") for i in range(self.entries_number)] + ) data_array = Array([Signal(self.data_layout, name=f"data_array_{i}") for i in range(self.entries_number)]) valids = Signal(self.entries_number, name="valids") @@ -214,7 +215,7 @@ def _(addr, data): m.d.top_comb += encoder_write.input.eq(write_mask) with m.If(write_mask.any()): m.d.sync += data_array[encoder_write.outputs[0]].eq(data) - return {"not_found" : ~write_mask.any()} + return {"not_found": ~write_mask.any()} @def_method(m, self.read) def _(addr): diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index 9f40c3595..509945740 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -268,7 +268,9 @@ def on_error(): @contextmanager def reinitialize_fixtures(self): - self._transactron_current_output_file_name = self._transactron_base_output_file_name+"_"+str(self._transactron_hypothesis_iter_counter) + self._transactron_current_output_file_name = ( + self._transactron_base_output_file_name + "_" + str(self._transactron_hypothesis_iter_counter) + ) self._transactron_sim_processes_to_add: list[Callable[[], Optional[Callable]]] = [] with self.configure_dependency_context(): self.configure_traces() @@ -277,7 +279,7 @@ def reinitialize_fixtures(self): yield self._transactron_hypothesis_iter_counter += 1 - @pytest.fixture(autouse = True) + @pytest.fixture(autouse=True) def fixture_initialize_testing_env(self, request): self._transactron_hypothesis_iter_counter = 0 self._transactron_base_output_file_name = ".".join(request.node.nodeid.split("/")) @@ -328,6 +330,6 @@ def random_wait_geom(self, prob: float = 0.5): while random.random() > prob: yield - def multi_settle(self, settle_count : int = 1): + def multi_settle(self, settle_count: int = 1): for _ in range(settle_count): yield Settle() diff --git a/transactron/testing/input_generation.py b/transactron/testing/input_generation.py index 819fe757b..909da7a43 100644 --- a/transactron/testing/input_generation.py +++ b/transactron/testing/input_generation.py @@ -1,30 +1,33 @@ from amaranth import * from amaranth.lib.data import StructLayout -import random -from typing import Optional, TypeVar +from typing import TypeVar import hypothesis.strategies as st from hypothesis.strategies import composite, DrawFn, integers, SearchStrategy from transactron.utils import MethodLayout, RecordIntDict -class OpNOP(): + +class OpNOP: def __repr__(self): return "OpNOP()" + T = TypeVar("T") + @composite -def generate_shrinkable_list(draw : DrawFn, length : int, generator : SearchStrategy[T]) -> list[T]: +def generate_shrinkable_list(draw: DrawFn, length: int, generator: SearchStrategy[T]) -> list[T]: """ - Trick based on https://github.com/HypothesisWorks/hypothesis/blob/6867da71beae0e4ed004b54b92ef7c74d0722815/hypothesis-python/src/hypothesis/stateful.py#L143 + Trick based on https://github.com/HypothesisWorks/hypothesis/blob/ + 6867da71beae0e4ed004b54b92ef7c74d0722815/hypothesis-python/src/hypothesis/stateful.py#L143 """ hp_data = draw(st.data()) - lst=[] + lst = [] if length == 0: return lst i = 0 force_val = None while True: - b = hp_data.conjecture_data.draw_boolean(p=2**-16, forced = force_val) + b = hp_data.conjecture_data.draw_boolean(p=2**-16, forced=force_val) if b: break lst.append(draw(generator)) @@ -35,7 +38,7 @@ def generate_shrinkable_list(draw : DrawFn, length : int, generator : SearchStra @composite -def generate_based_on_layout(draw : DrawFn, layout: MethodLayout) -> RecordIntDict: +def generate_based_on_layout(draw: DrawFn, layout: MethodLayout) -> RecordIntDict: if isinstance(layout, StructLayout): raise NotImplementedError("StructLayout is not supported in automatic value generation.") d = {} @@ -45,29 +48,31 @@ def generate_based_on_layout(draw : DrawFn, layout: MethodLayout) -> RecordIntDi elif isinstance(sublayout, int): elem = draw(integers(min_value=0, max_value=sublayout)) elif isinstance(sublayout, range): - elem = draw(integers(min_value=sublayout.start, max_value=sublayout.stop-1)) + elem = draw(integers(min_value=sublayout.start, max_value=sublayout.stop - 1)) elif isinstance(sublayout, Shape): if sublayout.signed: - min_value = -2**(sublayout.width-1) - max_value = 2**(sublayout.width-1)-1 + min_value = -(2 ** (sublayout.width - 1)) + max_value = 2 ** (sublayout.width - 1) - 1 else: min_value = 0 max_value = 2**sublayout.width - elem = draw(integers(min_value = min_value, max_value = max_value)) + elem = draw(integers(min_value=min_value, max_value=max_value)) else: # Currently type[Enum] and ShapeCastable raise NotImplementedError("Passed LayoutList with syntax yet unsuported in automatic value generation.") d[name] = elem return d -def insert_nops(draw : DrawFn, max_nops : int, lst : list): + +def insert_nops(draw: DrawFn, max_nops: int, lst: list): nops_nr = draw(integers(min_value=0, max_value=max_nops)) for i in range(nops_nr): lst.append(OpNOP()) return lst + @composite -def generate_nops_in_list(draw : DrawFn, max_nops : int, generate_list : SearchStrategy[list[T]]) -> list[T|OpNOP]: +def generate_nops_in_list(draw: DrawFn, max_nops: int, generate_list: SearchStrategy[list[T]]) -> list[T | OpNOP]: lst = draw(generate_list) out_lst = [] out_lst = insert_nops(draw, max_nops, out_lst) @@ -76,16 +81,17 @@ def generate_nops_in_list(draw : DrawFn, max_nops : int, generate_list : SearchS out_lst = insert_nops(draw, max_nops, out_lst) return out_lst + @composite -def generate_method_input(draw : DrawFn, args : list[tuple[str, MethodLayout]]) -> dict[str, RecordIntDict]: +def generate_method_input(draw: DrawFn, args: list[tuple[str, MethodLayout]]) -> dict[str, RecordIntDict]: out = [] for name, layout in args: out.append((name, draw(generate_based_on_layout(layout)))) return dict(out) -@composite -def generate_process_input(draw : DrawFn, elem_count : int, max_nops : int, layouts : list[tuple[str, MethodLayout]]) -> list[dict[str, RecordIntDict] | OpNOP]: - return draw( - generate_nops_in_list(max_nops, generate_shrinkable_list(elem_count, generate_method_input(layouts))) - ) +@composite +def generate_process_input( + draw: DrawFn, elem_count: int, max_nops: int, layouts: list[tuple[str, MethodLayout]] +) -> list[dict[str, RecordIntDict] | OpNOP]: + return draw(generate_nops_in_list(max_nops, generate_shrinkable_list(elem_count, generate_method_input(layouts)))) diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index b65d0d803..e8e3152b9 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -1,5 +1,4 @@ from typing import ( - Tuple, Callable, Concatenate, Generic, diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index f20d9c084..255407ba5 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -3,7 +3,7 @@ from typing import Literal, Optional, overload from collections.abc import Iterable from amaranth import * -from transactron.utils._typing import HasElaborate, ModuleLike, ValueLike +from transactron.utils._typing import HasElaborate, ModuleLike __all__ = [ "OneHotSwitchDynamic", @@ -14,6 +14,7 @@ "MultiPriorityEncoder", ] + @contextmanager def OneHotSwitch(m: ModuleLike, test: Value): """One-hot switch. @@ -271,35 +272,36 @@ def __init__(self, input_width: int, outputs_count: int): self.outputs = [Signal(range(self.input_width), name=f"output_{i}") for i in range(self.outputs_count)] self.valids = [Signal(name=f"valid_{i}") for i in range(self.outputs_count)] - def build_tree(self, m : Module, in_sig : Signal, start_idx : int): + def build_tree(self, m: Module, in_sig: Signal, start_idx: int): assert len(in_sig) > 0 - level_outputs = [Signal(range(self.input_width), name=f"_lvl_out_idx{start_idx}_{i}") for i in range(self.outputs_count)] + level_outputs = [ + Signal(range(self.input_width), name=f"_lvl_out_idx{start_idx}_{i}") for i in range(self.outputs_count) + ] level_valids = [Signal(name=f"_lvl_val_idx{start_idx}_{i}") for i in range(self.outputs_count)] if len(in_sig) == 1: with m.If(in_sig): m.d.comb += level_outputs[0].eq(start_idx) m.d.comb += level_valids[0].eq(1) else: - middle = len(in_sig)//2 + middle = len(in_sig) // 2 r_in = Signal(middle, name=f"_r_in_idx{start_idx}") l_in = Signal(len(in_sig) - middle, name=f"_l_in_idx{start_idx}") m.d.comb += r_in.eq(in_sig[0:middle]) m.d.comb += l_in.eq(in_sig[middle:]) r_out, r_val = self.build_tree(m, r_in, start_idx) - l_out, l_val = self.build_tree(m, l_in, start_idx+middle) + l_out, l_val = self.build_tree(m, l_in, start_idx + middle) with m.Switch(Cat(r_val)): - for i in range(self.outputs_count+1): - with m.Case((1< Date: Fri, 3 May 2024 15:55:19 +0200 Subject: [PATCH 19/29] Fix formatting --- coreblocks/params/genparams.py | 2 +- requirements-dev.txt | 4 ++-- test/func_blocks/lsu/test_dummylsu.py | 4 ++-- test/transactron/test_transactron_lib_storage.py | 3 +-- transactron/testing/infrastructure.py | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/coreblocks/params/genparams.py b/coreblocks/params/genparams.py index aa656f66e..cddeb392b 100644 --- a/coreblocks/params/genparams.py +++ b/coreblocks/params/genparams.py @@ -25,7 +25,7 @@ def __init__(self, cfg: CoreConfiguration): ext_partial, ext_full = extensions_supported(self.func_units_config, cfg.embedded, cfg.compressed) extensions = ext_partial if cfg.allow_partial_extensions else ext_full if not cfg.allow_partial_extensions and ext_partial != ext_full: - raise RuntimeError(f"Extensions {ext_partial&~ext_full!r} are only partially supported") + raise RuntimeError(f"Extensions {ext_partial & ~ext_full!r} are only partially supported") extensions |= cfg._implied_extensions self.isa_str = gen_isa_string(extensions, cfg.xlen) diff --git a/requirements-dev.txt b/requirements-dev.txt index b728c1392..2e693f78b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,7 @@ -r requirements.txt -black==24.3.0 +black==24.4.2 docutils==0.15.2 -flake8==6.0.0 +flake8==7.0.0 pep8-naming==0.13.3 markupsafe==2.0.1 myst-parser==0.18.0 diff --git a/test/func_blocks/lsu/test_dummylsu.py b/test/func_blocks/lsu/test_dummylsu.py index dce0cbca6..8326b22cc 100644 --- a/test/func_blocks/lsu/test_dummylsu.py +++ b/test/func_blocks/lsu/test_dummylsu.py @@ -151,7 +151,7 @@ def generate_instr(self, max_reg_val, max_imm_val): "addr": word_addr, "mask": mask, "sign": signess, - "rnd_bytes": bytes.fromhex(f"{random.randint(0,2**32-1):08x}"), + "rnd_bytes": bytes.fromhex(f"{random.randint(0, 2**32-1):08x}"), "misaligned": misaligned, "err": bus_err, } @@ -262,7 +262,7 @@ def generate_instr(self, max_reg_val, max_imm_val): wish_data = { "addr": (s1_val + imm) >> 2, "mask": 0xF, - "rnd_bytes": bytes.fromhex(f"{random.randint(0,2**32-1):08x}"), + "rnd_bytes": bytes.fromhex(f"{random.randint(0, 2**32-1):08x}"), } return instr, wish_data diff --git a/test/transactron/test_transactron_lib_storage.py b/test/transactron/test_transactron_lib_storage.py index d7236206e..1f14922eb 100644 --- a/test/transactron/test_transactron_lib_storage.py +++ b/test/transactron/test_transactron_lib_storage.py @@ -1,6 +1,5 @@ from datetime import timedelta -from hypothesis import given, strategies as st, settings, Phase, example -from hypothesis.stateful import RuleBasedStateMachine, rule, initialize +from hypothesis import given, settings, Phase from transactron.testing import * from transactron.lib.storage import ContentAddressableMemory diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index 509945740..f238c87ce 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -8,7 +8,6 @@ from abc import ABC from amaranth import * from amaranth.sim import * -import copy from transactron.utils.dependencies import DependencyContext, DependencyManager from .testbenchio import TestbenchIO From 6312e1c86709d239ed204acd923de40f88d82a09 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Fri, 3 May 2024 16:06:50 +0200 Subject: [PATCH 20/29] Fix after merge --- test/frontend/test_fetch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/frontend/test_fetch.py b/test/frontend/test_fetch.py index bda517d2e..c6d83992f 100644 --- a/test/frontend/test_fetch.py +++ b/test/frontend/test_fetch.py @@ -416,7 +416,7 @@ class TestPredictionChecker(TestCaseWithSimulator): with_rvc: bool @pytest.fixture(autouse=True) - def setup(self, configure_dependency_context): + def setup(self, fixture_initialize_testing_env): self.gen_params = GenParams( test_core_config.replace(compressed=self.with_rvc, fetch_block_bytes_log=self.fetch_block_log) ) From 16b55c870af482f0ab86e94c56154e66c1c591f4 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 5 May 2024 09:28:01 +0200 Subject: [PATCH 21/29] Add create_priority_encoder --- .../utils/amaranth_ext/elaboratables.py | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 255407ba5..2801b1395 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -3,7 +3,7 @@ from typing import Literal, Optional, overload from collections.abc import Iterable from amaranth import * -from transactron.utils._typing import HasElaborate, ModuleLike +from transactron.utils._typing import HasElaborate, ModuleLike, ValueLike __all__ = [ "OneHotSwitchDynamic", @@ -272,6 +272,59 @@ def __init__(self, input_width: int, outputs_count: int): self.outputs = [Signal(range(self.input_width), name=f"output_{i}") for i in range(self.outputs_count)] self.valids = [Signal(name=f"valid_{i}") for i in range(self.outputs_count)] + @staticmethod + def create_priority_encoder(m : Module, input_width : int, input : ValueLike, outputs_count : int = 1, name : Optional[str]=None) -> list[tuple[Signal, Signal]]: + """ Syntax sugar for creation of MultiPriorityEncoder + + This staticmethod allow to use MultiPriorityEncoder in more functional + way. Instead of creating the instance manually, connecting all signals and + adding a submodule you can call this function to do it automaticaly. + + This function is equivalent of: + + .. highlight:: python + .. code-block:: python + + m.submodules += prio_encoder = PriorityEncoder(cnt) + m.d.top_comb += prio_encoder.input.eq(one_hot_singal) + idx = prio_encoder.outputs + valid = prio.encoder.valids + + Parameters + ---------- + m: TModule + Module to which MultiPriorityEncoder should be added. + input_width : int + Width of the one hot signal. + input : ValueLike + One hot signal to decode. + outputs_count : int + Count of different decoder outputs to be generated at once. Default: 1. + name : Optional[str[ + Name which should be used while adding MultiPriorityEncoder to the submodules. + If None it will be added as anonymous submodule. Provided name can not be + used in already added submodule. Default: None. + + Returns + ------- + return : list[tuple[Signal, Signal]] + Return list with len equal to outputs_count. Each tuple contain + a pair of decoded index on the first place and valid signal + on the second place. + """ + prio_encoder = MultiPriorityEncoder(input_width, outputs_count) + if name is None: + m.submodules += prio_encoder + else: + try: + getattr(m.submodules, name) + raise ValueError(f"Name: {name} is already in use, so MultiPriorityEncoder can not be added with it.") + except AttributeError: + setattr(m.submodules, name, prio_encoder) + m.d.top_comb += prio_encoder.input.eq(input) + return list(zip(prio_encoder.outputs, prio_encoder.valids)) + + def build_tree(self, m: Module, in_sig: Signal, start_idx: int): assert len(in_sig) > 0 level_outputs = [ From 0dd252ca7312c6b3415eac047e564d2aff3e2f5c Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 5 May 2024 09:43:41 +0200 Subject: [PATCH 22/29] Add test --- test/transactron/utils/test_amaranth_ext.py | 28 ++++++++++++++++++- .../utils/amaranth_ext/elaboratables.py | 4 +-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/test/transactron/utils/test_amaranth_ext.py b/test/transactron/utils/test_amaranth_ext.py index b41a6c497..b799861d2 100644 --- a/test/transactron/utils/test_amaranth_ext.py +++ b/test/transactron/utils/test_amaranth_ext.py @@ -1,3 +1,4 @@ +from transactron import TModule from transactron.testing import * import random from transactron.utils.amaranth_ext import MultiPriorityEncoder @@ -30,7 +31,7 @@ def process(self): @pytest.mark.parametrize("input_width", [1, 5, 16, 23, 24]) @pytest.mark.parametrize("output_count", [1, 3, 4]) def test_random(self, input_width, output_count): - random.seed(14) + random.seed(input_width+output_count) self.test_number = 50 self.input_width = input_width self.output_count = output_count @@ -38,3 +39,28 @@ def test_random(self, input_width, output_count): with self.run_simulation(self.circ) as sim: sim.add_process(self.process) + + @pytest.mark.parametrize("name", ["prio_encoder", None]) + def test_static_create(self, name): + random.seed(14) + self.test_number = 50 + self.input_width = 7 + self.output_count = 2 + + class DUT(Elaboratable): + def __init__(self, input_width, output_count, name): + self.input = Signal(input_width) + self.output_count = output_count + self.input_width = input_width + self.name = name + + def elaborate(self, platform): + m = Module() + out = MultiPriorityEncoder.create_priority_encoder(m, self.input_width, self.input, self.output_count, name=self.name) + self.outputs, self.valids = list(zip(*out)) + return m + + self.circ = DUT(self.input_width, self.output_count, name) + + with self.run_simulation(self.circ) as sim: + sim.add_process(self.process) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 2801b1395..5ef8d1bb5 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -292,7 +292,7 @@ def create_priority_encoder(m : Module, input_width : int, input : ValueLike, ou Parameters ---------- - m: TModule + m: Module Module to which MultiPriorityEncoder should be added. input_width : int Width of the one hot signal. @@ -321,7 +321,7 @@ def create_priority_encoder(m : Module, input_width : int, input : ValueLike, ou raise ValueError(f"Name: {name} is already in use, so MultiPriorityEncoder can not be added with it.") except AttributeError: setattr(m.submodules, name, prio_encoder) - m.d.top_comb += prio_encoder.input.eq(input) + m.d.comb += prio_encoder.input.eq(input) return list(zip(prio_encoder.outputs, prio_encoder.valids)) From b716644c30284a1616f339e05780a697ca353150 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 5 May 2024 10:13:33 +0200 Subject: [PATCH 23/29] Doc string changes. --- .../utils/amaranth_ext/elaboratables.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 5ef8d1bb5..1694f123a 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -274,13 +274,13 @@ def __init__(self, input_width: int, outputs_count: int): @staticmethod def create_priority_encoder(m : Module, input_width : int, input : ValueLike, outputs_count : int = 1, name : Optional[str]=None) -> list[tuple[Signal, Signal]]: - """ Syntax sugar for creation of MultiPriorityEncoder + """ Syntax sugar for creating MultiPriorityEncoder - This staticmethod allow to use MultiPriorityEncoder in more functional - way. Instead of creating the instance manually, connecting all signals and - adding a submodule you can call this function to do it automaticaly. + This staticmethod allows to use MultiPriorityEncoder in a more functional + way. Instead of creating the instance manually, connecting all the signals and + adding a submodule, you can call this function to do it automatically. - This function is equivalent of: + This function is equivalent to: .. highlight:: python .. code-block:: python @@ -293,24 +293,24 @@ def create_priority_encoder(m : Module, input_width : int, input : ValueLike, ou Parameters ---------- m: Module - Module to which MultiPriorityEncoder should be added. + Module to add the MultiPriorityEncoder to. input_width : int Width of the one hot signal. input : ValueLike - One hot signal to decode. + The one hot signal to decode. outputs_count : int - Count of different decoder outputs to be generated at once. Default: 1. + Number of different decoder outputs to generate at once. Default: 1. name : Optional[str[ - Name which should be used while adding MultiPriorityEncoder to the submodules. - If None it will be added as anonymous submodule. Provided name can not be - used in already added submodule. Default: None. + Name to use when adding MultiPriorityEncoder to submodules. + If None, it will be added as an anonymous submodule. The given name + can not be used in a submodule that has already been added. Default: None. Returns ------- return : list[tuple[Signal, Signal]] - Return list with len equal to outputs_count. Each tuple contain - a pair of decoded index on the first place and valid signal - on the second place. + Returns a list with len equal to outputs_count. Each tuple contains + a pair of decoded index on the first position and a valid signal + on the second position. """ prio_encoder = MultiPriorityEncoder(input_width, outputs_count) if name is None: From 42f810454333d7e5ea4b73b75691372f596c2fba Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 5 May 2024 10:14:24 +0200 Subject: [PATCH 24/29] Lint. --- test/transactron/utils/test_amaranth_ext.py | 7 ++++--- transactron/utils/amaranth_ext/elaboratables.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/transactron/utils/test_amaranth_ext.py b/test/transactron/utils/test_amaranth_ext.py index b799861d2..0b3b2e687 100644 --- a/test/transactron/utils/test_amaranth_ext.py +++ b/test/transactron/utils/test_amaranth_ext.py @@ -1,4 +1,3 @@ -from transactron import TModule from transactron.testing import * import random from transactron.utils.amaranth_ext import MultiPriorityEncoder @@ -31,7 +30,7 @@ def process(self): @pytest.mark.parametrize("input_width", [1, 5, 16, 23, 24]) @pytest.mark.parametrize("output_count", [1, 3, 4]) def test_random(self, input_width, output_count): - random.seed(input_width+output_count) + random.seed(input_width + output_count) self.test_number = 50 self.input_width = input_width self.output_count = output_count @@ -56,7 +55,9 @@ def __init__(self, input_width, output_count, name): def elaborate(self, platform): m = Module() - out = MultiPriorityEncoder.create_priority_encoder(m, self.input_width, self.input, self.output_count, name=self.name) + out = MultiPriorityEncoder.create_priority_encoder( + m, self.input_width, self.input, self.output_count, name=self.name + ) self.outputs, self.valids = list(zip(*out)) return m diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 1694f123a..7b47111c0 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -273,8 +273,10 @@ def __init__(self, input_width: int, outputs_count: int): self.valids = [Signal(name=f"valid_{i}") for i in range(self.outputs_count)] @staticmethod - def create_priority_encoder(m : Module, input_width : int, input : ValueLike, outputs_count : int = 1, name : Optional[str]=None) -> list[tuple[Signal, Signal]]: - """ Syntax sugar for creating MultiPriorityEncoder + def create_priority_encoder( + m: Module, input_width: int, input: ValueLike, outputs_count: int = 1, name: Optional[str] = None + ) -> list[tuple[Signal, Signal]]: + """Syntax sugar for creating MultiPriorityEncoder This staticmethod allows to use MultiPriorityEncoder in a more functional way. Instead of creating the instance manually, connecting all the signals and @@ -324,7 +326,6 @@ def create_priority_encoder(m : Module, input_width : int, input : ValueLike, ou m.d.comb += prio_encoder.input.eq(input) return list(zip(prio_encoder.outputs, prio_encoder.valids)) - def build_tree(self, m: Module, in_sig: Signal, start_idx: int): assert len(in_sig) > 0 level_outputs = [ From 89330408e43bbc55328e5869eb504bf692a820f4 Mon Sep 17 00:00:00 2001 From: lekcyjna123 <34948061+lekcyjna123@users.noreply.github.com> Date: Sun, 5 May 2024 13:18:52 +0200 Subject: [PATCH 25/29] Update transactron/utils/amaranth_ext/elaboratables.py Co-authored-by: Marek Materzok --- transactron/utils/amaranth_ext/elaboratables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 7b47111c0..a087c86be 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -302,7 +302,7 @@ def create_priority_encoder( The one hot signal to decode. outputs_count : int Number of different decoder outputs to generate at once. Default: 1. - name : Optional[str[ + name : Optional[str] Name to use when adding MultiPriorityEncoder to submodules. If None, it will be added as an anonymous submodule. The given name can not be used in a submodule that has already been added. Default: None. From 060a9047a07ec668b5dee44fad353924c5f66fba Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 5 May 2024 13:25:15 +0200 Subject: [PATCH 26/29] Added create_simple variant --- test/transactron/utils/test_amaranth_ext.py | 31 +++++++++++++++++-- .../utils/amaranth_ext/elaboratables.py | 13 +++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/test/transactron/utils/test_amaranth_ext.py b/test/transactron/utils/test_amaranth_ext.py index 0b3b2e687..7b7bd46be 100644 --- a/test/transactron/utils/test_amaranth_ext.py +++ b/test/transactron/utils/test_amaranth_ext.py @@ -39,6 +39,33 @@ def test_random(self, input_width, output_count): with self.run_simulation(self.circ) as sim: sim.add_process(self.process) + @pytest.mark.parametrize("name", ["prio_encoder", None]) + def test_static_create_simple(self, name): + random.seed(14) + self.test_number = 50 + self.input_width = 7 + self.output_count = 1 + + class DUT(Elaboratable): + def __init__(self, input_width, output_count, name): + self.input = Signal(input_width) + self.output_count = output_count + self.input_width = input_width + self.name = name + + def elaborate(self, platform): + m = Module() + out, val = MultiPriorityEncoder.create_simple(m, self.input_width, self.input, name=self.name) + # Save as a list to use common interface in testing + self.outputs = [out] + self.valids = [val] + return m + + self.circ = DUT(self.input_width, self.output_count, name) + + with self.run_simulation(self.circ) as sim: + sim.add_process(self.process) + @pytest.mark.parametrize("name", ["prio_encoder", None]) def test_static_create(self, name): random.seed(14) @@ -55,9 +82,7 @@ def __init__(self, input_width, output_count, name): def elaborate(self, platform): m = Module() - out = MultiPriorityEncoder.create_priority_encoder( - m, self.input_width, self.input, self.output_count, name=self.name - ) + out = MultiPriorityEncoder.create(m, self.input_width, self.input, self.output_count, name=self.name) self.outputs, self.valids = list(zip(*out)) return m diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index a087c86be..92dfbe688 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -273,7 +273,7 @@ def __init__(self, input_width: int, outputs_count: int): self.valids = [Signal(name=f"valid_{i}") for i in range(self.outputs_count)] @staticmethod - def create_priority_encoder( + def create( m: Module, input_width: int, input: ValueLike, outputs_count: int = 1, name: Optional[str] = None ) -> list[tuple[Signal, Signal]]: """Syntax sugar for creating MultiPriorityEncoder @@ -326,6 +326,17 @@ def create_priority_encoder( m.d.comb += prio_encoder.input.eq(input) return list(zip(prio_encoder.outputs, prio_encoder.valids)) + @staticmethod + def create_simple( + m: Module, input_width: int, input: ValueLike, name: Optional[str] = None + ) -> tuple[Signal, Signal]: + """Syntax sugar for creating MultiPriorityEncoder + + This is the same as `create` function, but with `outputs_count` hardcoded to 1. + """ + lst = MultiPriorityEncoder.create(m, input_width, input, outputs_count=1, name=name) + return lst[0] + def build_tree(self, m: Module, in_sig: Signal, start_idx: int): assert len(in_sig) > 0 level_outputs = [ From 384fc09067e7e8e2dab94a57257b7168c5f05ab3 Mon Sep 17 00:00:00 2001 From: lekcyjna123 <34948061+lekcyjna123@users.noreply.github.com> Date: Sun, 5 May 2024 13:26:33 +0200 Subject: [PATCH 27/29] Update transactron/utils/amaranth_ext/elaboratables.py Co-authored-by: Marek Materzok --- transactron/utils/amaranth_ext/elaboratables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index 92dfbe688..6048bc7a8 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -278,7 +278,7 @@ def create( ) -> list[tuple[Signal, Signal]]: """Syntax sugar for creating MultiPriorityEncoder - This staticmethod allows to use MultiPriorityEncoder in a more functional + This static method allows to use MultiPriorityEncoder in a more functional way. Instead of creating the instance manually, connecting all the signals and adding a submodule, you can call this function to do it automatically. From cfc4cca33eb124eec7dcf1d57ff67a485603b445 Mon Sep 17 00:00:00 2001 From: Lekcyjna <309016@uwr.edu.pl> Date: Sun, 5 May 2024 13:40:53 +0200 Subject: [PATCH 28/29] Add comments about output file. --- transactron/testing/infrastructure.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/transactron/testing/infrastructure.py b/transactron/testing/infrastructure.py index f238c87ce..33d39287c 100644 --- a/transactron/testing/infrastructure.py +++ b/transactron/testing/infrastructure.py @@ -267,6 +267,10 @@ def on_error(): @contextmanager def reinitialize_fixtures(self): + # File name to be used in the current test run (either standard or hypothesis iteration) + # for standard tests it will always have the suffix "_0". For hypothesis tests, it will be suffixed + # with the current hypothesis iteration number, so that each hypothesis run is saved to a + # the different file. self._transactron_current_output_file_name = ( self._transactron_base_output_file_name + "_" + str(self._transactron_hypothesis_iter_counter) ) @@ -280,9 +284,15 @@ def reinitialize_fixtures(self): @pytest.fixture(autouse=True) def fixture_initialize_testing_env(self, request): + # Hypothesis creates a single instance of a test class, which is later reused multiple times. + # This means that pytest fixtures are only run once. We can take advantage of this behaviour and + # initialise hypothesis related variables. + + # The counter for distinguishing between successive hypothesis iterations, it is incremented + # by `reinitialize_fixtures` which should be started at the beginning of each hypothesis run self._transactron_hypothesis_iter_counter = 0 + # Base name which will be used later to create file names for particular outputs self._transactron_base_output_file_name = ".".join(request.node.nodeid.split("/")) - self._transactron_current_output_file_name = self._transactron_base_output_file_name with self.reinitialize_fixtures(): yield From bbd1d7a4c4600414faa21b6ced7f0c4ed5818559 Mon Sep 17 00:00:00 2001 From: Marek Materzok Date: Tue, 14 May 2024 09:21:59 +0200 Subject: [PATCH 29/29] Fix typos --- transactron/lib/storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transactron/lib/storage.py b/transactron/lib/storage.py index ce97c6e77..835406b66 100644 --- a/transactron/lib/storage.py +++ b/transactron/lib/storage.py @@ -142,8 +142,8 @@ class ContentAddressableMemory(Elaboratable): """Content addresable memory This module implements a content-addressable memory (in short CAM) with Transactron interface. - CAM is a type of memory where instead of predefined indexes there are used values feed in runtime - as keys (smimlar as in python dictionary). To insert new entry a pair `(key, value)` has to be + CAM is a type of memory where instead of predefined indexes there are used values fed in runtime + as keys (similar as in python dictionary). To insert new entry a pair `(key, value)` has to be provided. Such pair takes an free slot which depends on internal implementation. To read value a `key` has to be provided. It is compared with every valid key stored in CAM. If there is a hit, a value is read. There can be many instances of the same key in CAM. In such case it is undefined