diff --git a/coreblocks/frontend/icache.py b/coreblocks/frontend/icache.py index 6fd7d38d1..576ea1220 100644 --- a/coreblocks/frontend/icache.py +++ b/coreblocks/frontend/icache.py @@ -382,10 +382,15 @@ def elaborate(self, platform): refill_active = Signal() word_counter = Signal(range(self.params.words_in_block)) - with Transaction().body(m, request=refill_active): + m.submodules.address_fwd = address_fwd = Forwarder( + [("word_counter", word_counter.shape()), ("refill_address", refill_address.shape())] + ) + + with Transaction().body(m): + address = address_fwd.read(m) self.wb_master.request( m, - addr=Cat(word_counter, refill_address), + addr=Cat(address["word_counter"], address["refill_address"]), data=0, we=0, sel=Repl(1, self.wb_master.wb_params.data_width // self.wb_master.wb_params.granularity), @@ -393,19 +398,27 @@ def elaborate(self, platform): @def_method(m, self.start_refill, ready=~refill_active) def _(addr) -> None: - m.d.sync += refill_address.eq(addr[self.params.offset_bits :]) + address = addr[self.params.offset_bits :] + m.d.sync += refill_address.eq(address) m.d.sync += refill_active.eq(1) m.d.sync += word_counter.eq(0) + address_fwd.write(m, word_counter=0, refill_address=address) + @def_method(m, self.accept_refill, ready=refill_active) def _(): fetched = self.wb_master.result(m) last = (word_counter == (self.params.words_in_block - 1)) | fetched.err - m.d.sync += word_counter.eq(word_counter + 1) + next_word_counter = Signal.like(word_counter) + m.d.top_comb += next_word_counter.eq(word_counter + 1) + + m.d.sync += word_counter.eq(next_word_counter) with m.If(last): m.d.sync += refill_active.eq(0) + with m.Else(): + address_fwd.write(m, word_counter=next_word_counter, refill_address=refill_address) return { "addr": Cat(Repl(0, log2_int(self.params.word_width_bytes)), word_counter, refill_address), diff --git a/coreblocks/peripherals/wishbone.py b/coreblocks/peripherals/wishbone.py index 01f80338e..ae88e74ec 100644 --- a/coreblocks/peripherals/wishbone.py +++ b/coreblocks/peripherals/wishbone.py @@ -6,9 +6,11 @@ import operator from transactron import Method, def_method, TModule +from transactron.core import Transaction from transactron.lib import AdapterTrans from transactron.utils.utils import OneHotSwitchDynamic, assign from transactron.utils.fifo import BasicFifo +from transactron.lib.connectors import Forwarder class WishboneParameters: @@ -109,8 +111,6 @@ def __init__(self, wb_params: WishboneParameters): self.request = Method(i=self.requestLayout) self.result = Method(o=self.resultLayout) - self.ready = Signal() - self.res_ready = Signal() self.result_data = Record(self.resultLayout) # latched input signals @@ -132,6 +132,10 @@ def generate_layouts(self, wb_params: WishboneParameters): def elaborate(self, platform): m = TModule() + m.submodules.result = result = Forwarder(self.resultLayout) + + request_ready = Signal() + def FSMWBCycStart(request): # noqa: N802 # internal FSM function that starts Wishbone cycle m.d.sync += self.wbMaster.cyc.eq(1) @@ -140,12 +144,6 @@ def FSMWBCycStart(request): # noqa: N802 m.d.sync += self.wbMaster.dat_w.eq(Mux(request.we, request.data, 0)) m.d.sync += self.wbMaster.we.eq(request.we) m.d.sync += self.wbMaster.sel.eq(request.sel) - m.next = "WBWaitACK" - - @def_method(m, self.result, ready=self.res_ready) - def _(): - m.d.sync += self.res_ready.eq(0) - return self.result_data with m.FSM("Reset"): with m.State("Reset"): @@ -153,17 +151,12 @@ def _(): m.next = "Idle" with m.State("Idle"): # default values for important signals - m.d.sync += self.ready.eq(1) m.d.sync += self.wbMaster.rst.eq(0) m.d.sync += self.wbMaster.stb.eq(0) m.d.sync += self.wbMaster.cyc.eq(0) - - @def_method(m, self.request, ready=(self.ready & ~self.res_ready)) - def _(arg): - m.d.sync += self.ready.eq(0) - m.d.sync += assign(self.txn_req, arg) - # do WBCycStart state in the same clock cycle - FSMWBCycStart(arg) + m.d.comb += request_ready.eq(1) + with m.If(self.request.run): + m.next = "WBWaitACK" with m.State("WBCycStart"): FSMWBCycStart(self.txn_req) @@ -171,18 +164,34 @@ def _(arg): with m.State("WBWaitACK"): with m.If(self.wbMaster.ack | self.wbMaster.err): - m.d.sync += self.wbMaster.cyc.eq(0) - m.d.sync += self.wbMaster.stb.eq(0) - m.d.sync += self.ready.eq(1) - m.d.sync += self.res_ready.eq(1) - m.d.sync += self.result_data.data.eq(Mux(self.txn_req.we, 0, self.wbMaster.dat_r)) - m.d.sync += self.result_data.err.eq(self.wbMaster.err) - m.next = "Idle" + m.d.comb += request_ready.eq(result.read.run) + with Transaction().body(m): + # will be always ready, as we checked that in Idle + result.write(m, data=Mux(self.txn_req.we, 0, self.wbMaster.dat_r), err=self.wbMaster.err) + with m.If(self.request.run): + m.next = "WBWaitACK" + with m.Else(): + m.d.sync += self.wbMaster.cyc.eq(0) + m.d.sync += self.wbMaster.stb.eq(0) + m.next = "Idle" with m.If(self.wbMaster.rty): m.d.sync += self.wbMaster.cyc.eq(1) m.d.sync += self.wbMaster.stb.eq(0) m.next = "WBCycStart" + @def_method(m, self.result) + def _(): + return result.read(m) + + @def_method(m, self.request, ready=request_ready & result.write.ready) + def _(arg): + m.d.sync += assign(self.txn_req, arg) + # do WBCycStart state in the same clock cycle + FSMWBCycStart(arg) + + result.write.schedule_before(self.request) + result.read.schedule_before(self.request) + return m diff --git a/test/frontend/test_icache.py b/test/frontend/test_icache.py index c0ac4f800..35fa0a6d8 100644 --- a/test/frontend/test_icache.py +++ b/test/frontend/test_icache.py @@ -92,6 +92,7 @@ def wishbone_slave(self): # Wishbone is addressing words, so we need to shift it a bit to get the real address. addr = (yield self.test_module.wb_ctrl.wb.adr) << log2_int(self.cp.word_width_bytes) + yield while random.random() < 0.5: yield diff --git a/test/peripherals/test_wishbone.py b/test/peripherals/test_wishbone.py index b4a7d78f0..9db5b9430 100644 --- a/test/peripherals/test_wishbone.py +++ b/test/peripherals/test_wishbone.py @@ -36,8 +36,8 @@ def slave_verify(self, exp_addr, exp_data, exp_we, exp_sel=0): assert (yield self.wb.stb) and (yield self.wb.cyc) assert (yield self.wb.adr) == exp_addr - assert (yield self.wb.we == exp_we) - assert (yield self.wb.sel == exp_sel) + assert (yield self.wb.we) == exp_we + assert (yield self.wb.sel) == exp_sel if exp_we: assert (yield self.wb.dat_w) == exp_data @@ -53,6 +53,10 @@ def slave_respond(self, data, ack=1, err=0, rty=0): yield self.wb.err.eq(0) yield self.wb.rty.eq(0) + def wait_ack(self): + while not ((yield self.wb.stb) and (yield self.wb.cyc) and (yield self.wb.ack)): + yield + class TestWishboneMaster(TestCaseWithSimulator): class WishboneMasterTestModule(Elaboratable): @@ -70,43 +74,70 @@ def test_manual(self): twbm = TestWishboneMaster.WishboneMasterTestModule() def process(): - wbm = twbm.wbm - wwb = WishboneInterfaceWrapper(wbm.wbMaster) - # read request yield from twbm.requestAdapter.call(addr=2, data=0, we=0, sel=1) + + # read request after delay yield - assert not (yield wbm.request.ready) - yield from wwb.slave_verify(2, 0, 0, 1) - yield from wwb.slave_respond(8) - resp = yield from twbm.resultAdapter.call() - assert (resp["data"]) == 8 + yield + yield from twbm.requestAdapter.call(addr=1, data=0, we=0, sel=1) # write request yield from twbm.requestAdapter.call(addr=3, data=5, we=1, sel=0) - yield - yield from wwb.slave_verify(3, 5, 1, 0) - yield from wwb.slave_respond(0) - yield from twbm.resultAdapter.call() # RTY and ERR responese yield from twbm.requestAdapter.call(addr=2, data=0, we=0, sel=0) - yield + resp = yield from twbm.requestAdapter.call_try() + self.assertIsNone(resp) # verify cycle restart + + def result_process(): + resp = yield from twbm.resultAdapter.call() + self.assertEqual(resp["data"], 8) + self.assertFalse(resp["err"]) + + resp = yield from twbm.resultAdapter.call() + self.assertEqual(resp["data"], 3) + self.assertFalse(resp["err"]) + + resp = yield from twbm.resultAdapter.call() + self.assertFalse(resp["err"]) + + resp = yield from twbm.resultAdapter.call() + self.assertEqual(resp["data"], 1) + self.assertTrue(resp["err"]) + + def slave(): + wwb = WishboneInterfaceWrapper(twbm.wbm.wbMaster) + + yield from wwb.slave_wait() + yield from wwb.slave_verify(2, 0, 0, 1) + yield from wwb.slave_respond(8) + yield Settle() + yield from wwb.slave_wait() + yield from wwb.slave_verify(1, 0, 0, 1) + yield from wwb.slave_respond(3) + yield Settle() + + yield # consecutive request + yield from wwb.slave_verify(3, 5, 1, 0) + yield from wwb.slave_respond(0) + yield + + yield # consecutive request yield from wwb.slave_verify(2, 0, 0, 0) yield from wwb.slave_respond(1, ack=0, err=0, rty=1) - yield + yield Settle() assert not (yield wwb.wb.stb) - assert not (yield wbm.result.ready) # verify cycle restart + yield from wwb.slave_wait() yield from wwb.slave_verify(2, 0, 0, 0) yield from wwb.slave_respond(1, ack=1, err=1, rty=0) - resp = yield from twbm.resultAdapter.call() - assert resp["data"] == 1 - assert resp["err"] with self.run_simulation(twbm) as sim: sim.add_sync_process(process) + sim.add_sync_process(result_process) + sim.add_sync_process(slave) class TestWishboneMuxer(TestCaseWithSimulator): @@ -295,7 +326,7 @@ def elaborate(self, platform): class TestWishboneMemorySlave(TestCaseWithSimulator): def setUp(self): - self.memsize = 430 # test some weird depth + self.memsize = 43 # test some weird depth self.iters = 300 self.addr_width = (self.memsize - 1).bit_length() # nearest log2 >= log2(memsize) @@ -307,27 +338,55 @@ def setUp(self): random.seed(42) def test_randomized(self): - def mem_op_process(): - mem_state = [0] * self.memsize + req_queue = deque() + wr_queue = deque() + + mem_state = [0] * self.memsize + def request_process(): for _ in range(self.iters): - addr = random.randint(0, self.memsize - 1) - data = random.randint(0, 2**self.wb_params.data_width - 1) - write = random.randint(0, 1) - sel = random.randint(0, 2**self.sel_width - 1) - if write: + req = { + "addr": random.randint(0, self.memsize - 1), + "data": random.randint(0, 2**self.wb_params.data_width - 1), + "we": random.randint(0, 1), + "sel": random.randint(0, 2**self.sel_width - 1), + } + req_queue.appendleft(req) + wr_queue.appendleft(req) + + while random.random() < 0.2: + yield + yield from self.m.request.call(req) + + def result_process(): + for _ in range(self.iters): + while random.random() < 0.2: + yield + res = yield from self.m.result.call() + req = req_queue.pop() + + if not req["we"]: + self.assertEqual(res["data"], mem_state[req["addr"]]) + + def write_process(): + wwb = WishboneInterfaceWrapper(self.m.mem_master.wbMaster) + for _ in range(self.iters): + yield from wwb.wait_ack() + req = wr_queue.pop() + + if req["we"]: for i in range(self.sel_width): - if sel & (1 << i): + if req["sel"] & (1 << i): granularity_mask = (2**self.wb_params.granularity - 1) << (i * self.wb_params.granularity) - mem_state[addr] &= ~granularity_mask - mem_state[addr] |= data & granularity_mask + mem_state[req["addr"]] &= ~granularity_mask + mem_state[req["addr"]] |= req["data"] & granularity_mask - yield from self.m.request.call(addr=addr, data=data, we=write, sel=sel) - res = yield from self.m.result.call() - if write: - self.assertEqual((yield self.m.mem_slave.mem[addr]), mem_state[addr]) - else: - self.assertEqual(res["data"], mem_state[addr]) + yield + + if req["we"]: + self.assertEqual((yield self.m.mem_slave.mem[req["addr"]]), mem_state[req["addr"]]) - with self.run_simulation(self.m, max_cycles=1500) as sim: - sim.add_sync_process(mem_op_process) + with self.run_simulation(self.m, max_cycles=3000) as sim: + sim.add_sync_process(request_process) + sim.add_sync_process(result_process) + sim.add_sync_process(write_process) diff --git a/transactron/core.py b/transactron/core.py index 363a59f06..a4b3f3b52 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -64,6 +64,7 @@ class RelationBase(TypedDict): end: TransactionOrMethod priority: Priority conflict: bool + silence_warning: bool class Relation(RelationBase): @@ -271,7 +272,7 @@ def add_edge(begin: Transaction, end: Transaction, priority: Priority, conflict: start = relation["start"] end = relation["end"] if not relation["conflict"]: # relation added with schedule_before - if end.def_order < start.def_order: + if end.def_order < start.def_order and not relation["silence_warning"]: raise RuntimeError(f"{start.name!r} scheduled before {end.name!r}, but defined afterwards") for trans_start in method_map.transactions_for(start): @@ -714,7 +715,9 @@ def add_conflict(self, end: TransactionOrMethod, priority: Priority = Priority.U Is one of conflicting `Transaction`\\s or `Method`\\s prioritized? Defaults to undefined priority relation. """ - self.relations.append(RelationBase(end=end, priority=priority, conflict=True)) + self.relations.append( + RelationBase(end=end, priority=priority, conflict=True, silence_warning=self.owner != end.owner) + ) def schedule_before(self, end: TransactionOrMethod) -> None: """Adds a priority relation. @@ -728,7 +731,9 @@ def schedule_before(self, end: TransactionOrMethod) -> None: end: Transaction or Method The other `Transaction` or `Method` """ - self.relations.append(RelationBase(end=end, priority=Priority.LEFT, conflict=False)) + self.relations.append( + RelationBase(end=end, priority=Priority.LEFT, conflict=False, silence_warning=self.owner != end.owner) + ) def use_method(self, method: "Method", arg: ValueLike, enable: ValueLike): if method in self.method_uses: