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 transactron.utils.utils import OneHotSwitchDynamic, assign
from transactron.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(result.read.run)
with Transaction().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)

result.write.schedule_before(self.request)
result.read.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 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

Expand Down
137 changes: 98 additions & 39 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 All @@ -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):
Expand All @@ -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()
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 @@ -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)
Expand All @@ -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)
11 changes: 8 additions & 3 deletions transactron/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class RelationBase(TypedDict):
end: TransactionOrMethod
priority: Priority
conflict: bool
silence_warning: bool


class Relation(RelationBase):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down