Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster Wishbone #505

Merged
merged 10 commits into from
Nov 13, 2023
21 changes: 17 additions & 4 deletions coreblocks/frontend/icache.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,30 +382,43 @@ 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),
)

@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),
Expand Down
55 changes: 32 additions & 23 deletions coreblocks/peripherals/wishbone.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import operator

from transactron import Method, def_method, TModule
from transactron.core import Transaction
from transactron.lib import AdapterTrans
from coreblocks.utils.utils import OneHotSwitchDynamic, assign
from coreblocks.utils.fifo import BasicFifo
from transactron.lib.connectors import Forwarder


class WishboneParameters:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -140,49 +144,54 @@ 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"):
m.d.sync += self.wbMaster.rst.eq(1)
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)
m.next = "WBWaitACK"

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(1)
tr = Transaction()
with tr.body(m):
# will be always ready, as we checked that in Idle
tilk marked this conversation as resolved.
Show resolved Hide resolved
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)

tr.schedule_before(self.request)

return m


Expand Down
1 change: 1 addition & 0 deletions test/frontend/test_icache.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def wishbone_slave(self):
# Wishbone is addressing words, so we need to shit 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

Expand Down
68 changes: 48 additions & 20 deletions test/peripherals/test_wishbone.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -70,43 +70,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()
lekcyjna123 marked this conversation as resolved.
Show resolved Hide resolved

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
lekcyjna123 marked this conversation as resolved.
Show resolved Hide resolved
yield from wwb.slave_verify(3, 5, 1, 0)
yield from wwb.slave_respond(0)
yield
lekcyjna123 marked this conversation as resolved.
Show resolved Hide resolved

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):
Expand Down Expand Up @@ -325,6 +352,7 @@ def mem_op_process():
yield from self.m.request.call(addr=addr, data=data, we=write, sel=sel)
res = yield from self.m.result.call()
if write:
yield # workaround, memory state will change the next cycle!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this cause that we don't test if request in the first cycle after write works properly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior of the request itself is already tested in TestWishboneMaster.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the test to allow writes without delays, also added random latencies.

self.assertEqual((yield self.m.mem_slave.mem[addr]), mem_state[addr])
else:
self.assertEqual(res["data"], mem_state[addr])
Expand Down