From 5add6267487ec2b54ddf6ac6b08dd78b2d7c64c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Urba=C5=84czyk?= Date: Tue, 2 Apr 2024 15:08:43 +0100 Subject: [PATCH 1/2] Add Pipe module (#639) --- test/transactions/test_transaction_lib.py | 11 +++- transactron/lib/connectors.py | 65 +++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/test/transactions/test_transaction_lib.py b/test/transactions/test_transaction_lib.py index 78119067f..5dabe051e 100644 --- a/test/transactions/test_transaction_lib.py +++ b/test/transactions/test_transaction_lib.py @@ -32,7 +32,7 @@ def elaborate(self, platform): return self.connect -FIFO_Like: TypeAlias = FIFO | Forwarder | Connect | RevConnect +FIFO_Like: TypeAlias = FIFO | Forwarder | Connect | RevConnect | Pipe class TestFifoBase(TestCaseWithSimulator): @@ -128,6 +128,15 @@ def process(): sim.add_sync_process(process) +class TestPipe(TestFifoBase): + @parameterized.expand([(0, 0), (2, 0), (0, 2), (1, 1)]) + def test_fifo(self, writer_rand, reader_rand): + self.do_test_fifo(Pipe, writer_rand=writer_rand, reader_rand=reader_rand) + + def test_pipelining(self): + self.do_test_fifo(Pipe, writer_rand=0, reader_rand=0) + + class TestMemoryBank(TestCaseWithSimulator): test_conf = [(9, 3, 3, 3, 14), (16, 1, 1, 3, 15), (16, 1, 1, 1, 16), (12, 3, 1, 1, 17)] diff --git a/transactron/lib/connectors.py b/transactron/lib/connectors.py index e35d969db..50f01a76b 100644 --- a/transactron/lib/connectors.py +++ b/transactron/lib/connectors.py @@ -12,6 +12,7 @@ "ConnectTrans", "ManyToOneConnectTrans", "StableSelectingNetwork", + "Pipe", ] @@ -148,6 +149,70 @@ def _(): return m +class Pipe(Elaboratable): + """ + This module implements a `Pipe`. It is a halfway between + `Forwarder` and `2-FIFO`. In the `Pipe` data is always + stored localy, so the critical path of the data is cut, but there is a + combinational path between the control signals of the `read` and + the `write` methods. For comparison: + - in `Forwarder` there is both a data and a control combinational path + - in `2-FIFO` there are no combinational paths + + The `read` method is scheduled before the `write`. + + Attributes + ---------- + read: Method + Reads from the pipe. Accepts an empty argument, returns a structure. + Ready only if the pipe is not empty. + write: Method + Writes to the pipe. Accepts a structure, returns empty result. + Ready only if the pipe is not full. + clean: Method + Cleans the pipe. Has priority over `read` and `write` methods. + """ + + def __init__(self, layout: MethodLayout): + """ + Parameters + ---------- + layout: record layout + The format of records forwarded. + """ + self.read = Method(o=layout) + self.write = Method(i=layout) + self.clean = Method() + self.head = Signal.like(self.read.data_out) + + self.clean.add_conflict(self.read, Priority.LEFT) + self.clean.add_conflict(self.write, Priority.LEFT) + + def elaborate(self, platform): + m = TModule() + + reg = Signal.like(self.read.data_out) + reg_valid = Signal() + + self.read.schedule_before(self.write) # to avoid combinational loops + + @def_method(m, self.read, ready=reg_valid) + def _(): + m.d.sync += reg_valid.eq(0) + return reg + + @def_method(m, self.write, ready=~reg_valid | self.read.run) + def _(arg): + m.d.sync += reg.eq(arg) + m.d.sync += reg_valid.eq(1) + + @def_method(m, self.clean) + def _(): + m.d.sync += reg_valid.eq(0) + + return m + + class Connect(Elaboratable): """Forwarding by transaction simultaneity From 4f25673dbe3fad00510239bcdb43bc7d0b4f54a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Urba=C5=84czyk?= Date: Tue, 2 Apr 2024 15:36:29 +0100 Subject: [PATCH 2/2] Fix yet another bug in the instruction cache (#638) --- coreblocks/cache/icache.py | 6 ++++-- test/cache/test_icache.py | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/coreblocks/cache/icache.py b/coreblocks/cache/icache.py index 08cd51784..d41559f5b 100644 --- a/coreblocks/cache/icache.py +++ b/coreblocks/cache/icache.py @@ -172,8 +172,6 @@ def elaborate(self, platform): with m.If(refill_finish): m.next = "LOOKUP" - accepting_requests = fsm.ongoing("LOOKUP") & ~needs_refill - # Replacement policy way_selector = Signal(self.params.num_of_ways, reset=1) with m.If(refill_finish): @@ -185,6 +183,9 @@ def elaborate(self, platform): m.d.comb += assign(mem_read_addr, prev_mem_read_addr) mem_read_output_valid = Signal() + forwarding_response_now = Signal() + accepting_requests = ~mem_read_output_valid | forwarding_response_now + with Transaction(name="MemRead").body( m, request=fsm.ongoing("LOOKUP") & (mem_read_output_valid | refill_error_saved) ): @@ -194,6 +195,7 @@ def elaborate(self, platform): tag_hit_any = reduce(operator.or_, tag_hit) with m.If(tag_hit_any | refill_error_saved): + m.d.comb += forwarding_response_now.eq(1) self.perf_hits.incr(m, cond=tag_hit_any) mem_out = Signal(self.params.fetch_block_bytes * 8) for i in OneHotSwitchDynamic(m, Cat(tag_hit)): diff --git a/test/cache/test_icache.py b/test/cache/test_icache.py index f53cff894..e8753fa65 100644 --- a/test/cache/test_icache.py +++ b/test/cache/test_icache.py @@ -330,6 +330,8 @@ def setUp(self) -> None: self.refill_requests = deque() self.issued_requests = deque() + self.accept_refill_request = True + self.refill_in_fly = False self.refill_word_cnt = 0 self.refill_addr = 0 @@ -347,7 +349,7 @@ def init_module(self, ways, sets) -> None: self.cp = self.gen_params.icache_params self.m = ICacheTestCircuit(self.gen_params) - @def_method_mock(lambda self: self.m.refiller.start_refill_mock) + @def_method_mock(lambda self: self.m.refiller.start_refill_mock, enable=lambda self: self.accept_refill_request) def start_refill_mock(self, addr): self.refill_requests.append(addr) self.refill_block_cnt = 0 @@ -631,11 +633,21 @@ def cache_process(): # Schedule two requests and then flush yield from self.send_req(0x00000000 + self.cp.line_size_bytes) yield from self.send_req(0x00010000) - yield from self.m.flush_cache.call() - self.mem[0x00010000] = random.randrange(2**self.gen_params.isa.ilen) - # And accept the results + yield from self.m.flush_cache.call_init() + yield + # We cannot flush until there are two pending requests + self.assertFalse((yield from self.m.flush_cache.done())) + yield + yield from self.m.flush_cache.disable() + yield + + # Accept the first response self.assert_resp((yield from self.m.accept_res.call())) + + yield from self.m.flush_cache.call() + + # And accept the second response ensuring that we got old data self.assert_resp((yield from self.m.accept_res.call())) self.expect_refill(0x00000000 + self.cp.line_size_bytes) @@ -753,6 +765,16 @@ def test_random(self): if random.random() < 0.05: self.add_bad_addr(i) + def refiller_ctrl(): + yield Passive() + + while True: + yield from self.random_wait_geom(0.4) + self.accept_refill_request = False + + yield from self.random_wait_geom(0.7) + self.accept_refill_request = True + def sender(): for _ in range(iterations): yield from self.send_req(random.randrange(0, max_addr, 4)) @@ -773,3 +795,4 @@ def receiver(): with self.run_simulation(self.m) as sim: sim.add_sync_process(sender) sim.add_sync_process(receiver) + sim.add_sync_process(refiller_ctrl)