interconnect/axi/axi_full: Switch to our own AXI Interconnect (Shared & Crossbar).

We were not able to simulate verilog_axi interconnect/crossbar correctly since to what
seems to be a simulation mismatch. The code also seems to requires fixing some synthesis
issues with Yosys. When tested with Vivado, the SoC was also miss-behaving (not booting
correctly).

The simulation mismatch issue is logged here: https://github.com/enjoy-digital/litex_verilog_axi_test/issues/1

Since we already had our own AXI-Lite interconnect, creating our AXI interconnect can be
largely based on it with only minor modifications, so switch to it. This also allow simplification
in the interconnect selection/instance.
This commit is contained in:
Florent Kermarrec 2022-06-20 19:50:49 +02:00
parent 573479b395
commit 1a90549fa3
3 changed files with 253 additions and 30 deletions

View File

@ -1125,19 +1125,11 @@ class SoC(Module):
"shared" : interconnect_shared_cls, "shared" : interconnect_shared_cls,
"crossbar": interconnect_crossbar_cls, "crossbar": interconnect_crossbar_cls,
}[self.bus.interconnect] }[self.bus.interconnect]
if interconnect_cls in [axi.AXIInterconnectShared, axi.AXICrossbar]: self.submodules.bus_interconnect = interconnect_cls(
# FIXME: WIP, try to have compatibility with other interconnects. masters = list(self.bus.masters.values()),
self.submodules.bus_interconnect = interconnect_cls( slaves = [(self.bus.regions[n].decoder(self.bus), s) for n, s in self.bus.slaves.items()],
platform = self.platform, register = True,
masters = list(self.bus.masters.values()), timeout_cycles = self.bus.timeout)
slaves = [(s, self.bus.regions[n].origin, self.bus.regions[n].size) for n, s in self.bus.slaves.items()]
)
else:
self.submodules.bus_interconnect = interconnect_cls(
masters = list(self.bus.masters.values()),
slaves = [(self.bus.regions[n].decoder(self.bus), s) for n, s in self.bus.slaves.items()],
register = True,
timeout_cycles = self.bus.timeout)
if hasattr(self, "ctrl") and self.bus.timeout is not None: if hasattr(self, "ctrl") and self.bus.timeout is not None:
if hasattr(self.ctrl, "bus_error") and hasattr(self.bus_interconnect, "timeout"): if hasattr(self.ctrl, "bus_error") and hasattr(self.bus_interconnect, "timeout"):
self.comb += self.ctrl.bus_error.eq(self.bus_interconnect.timeout.error) self.comb += self.ctrl.bus_error.eq(self.bus_interconnect.timeout.error)

View File

@ -264,34 +264,266 @@ class AXIConverter(Module):
else: else:
self.comb += master.connect(slave) self.comb += master.connect(slave)
# AXI Timeout --------------------------------------------------------------------------------------
class AXITimeout(Module):
"""Protect master against slave timeouts (master _has_ to respond correctly)"""
def __init__(self, master, cycles):
self.error = Signal()
wr_error = Signal()
rd_error = Signal()
# # #
self.comb += self.error.eq(wr_error | rd_error)
wr_timer = WaitTimer(int(cycles))
rd_timer = WaitTimer(int(cycles))
self.submodules += wr_timer, rd_timer
def channel_fsm(timer, wait_cond, error, response):
fsm = FSM(reset_state="WAIT")
fsm.act("WAIT",
timer.wait.eq(wait_cond),
# done is updated in `sync`, so we must make sure that `ready` has not been issued
# by slave during that single cycle, by checking `timer.wait`.
If(timer.done & timer.wait,
error.eq(1),
NextState("RESPOND")
)
)
fsm.act("RESPOND", *response)
return fsm
self.submodules.wr_fsm = channel_fsm(
timer = wr_timer,
wait_cond = (master.aw.valid & ~master.aw.ready) | (master.w.valid & ~master.w.ready),
error = wr_error,
response = [
master.aw.ready.eq(master.aw.valid),
master.w.ready.eq(master.w.valid),
master.b.valid.eq(~master.aw.valid & ~master.w.valid),
master.b.resp.eq(RESP_SLVERR),
If(master.b.valid & master.b.ready,
NextState("WAIT")
)
])
self.submodules.rd_fsm = channel_fsm(
timer = rd_timer,
wait_cond = master.ar.valid & ~master.ar.ready,
error = rd_error,
response = [
master.ar.ready.eq(master.ar.valid),
master.r.valid.eq(~master.ar.valid),
master.r.last.eq(1),
master.r.resp.eq(RESP_SLVERR),
master.r.data.eq(2**len(master.r.data) - 1),
If(master.r.valid & master.r.ready,
NextState("WAIT")
)
])
# AXI Interconnect Components ----------------------------------------------------------------------
class _AXIRequestCounter(Module):
def __init__(self, request, response, max_requests=256):
self.counter = counter = Signal(max=max_requests)
self.full = full = Signal()
self.empty = empty = Signal()
self.stall = stall = Signal()
self.ready = self.empty
self.comb += [
full.eq(counter == max_requests - 1),
empty.eq(counter == 0),
stall.eq(request & full),
]
self.sync += [
If(request & response,
counter.eq(counter)
).Elif(request & ~full,
counter.eq(counter + 1)
).Elif(response & ~empty,
counter.eq(counter - 1)
),
]
class AXIArbiter(Module):
"""AXI arbiter
Arbitrate between master interfaces and connect one to the target. New master will not be
selected until all requests have been responded to. Arbitration for write and read channels is
done separately.
"""
def __init__(self, masters, target):
self.submodules.rr_write = roundrobin.RoundRobin(len(masters), roundrobin.SP_CE)
self.submodules.rr_read = roundrobin.RoundRobin(len(masters), roundrobin.SP_CE)
def get_sig(interface, channel, name):
return getattr(getattr(interface, channel), name)
# Mux master->slave signals
for channel, name, direction in target.layout_flat():
rr = self.rr_write if channel in ["aw", "w", "b"] else self.rr_read
if direction == DIR_M_TO_S:
choices = Array(get_sig(m, channel, name) for m in masters)
self.comb += get_sig(target, channel, name).eq(choices[rr.grant])
# Connect slave->master signals
for channel, name, direction in target.layout_flat():
rr = self.rr_write if channel in ["aw", "w", "b"] else self.rr_read
if direction == DIR_S_TO_M:
source = get_sig(target, channel, name)
for i, m in enumerate(masters):
dest = get_sig(m, channel, name)
if name == "ready":
self.comb += dest.eq(source & (rr.grant == i))
else:
self.comb += dest.eq(source)
# Allow to change rr.grant only after all requests from a master have been responded to.
self.submodules.wr_lock = wr_lock = _AXIRequestCounter(
request = target.aw.valid & target.aw.ready,
response = target.b.valid & target.b.ready
)
self.submodules.rd_lock = rd_lock = _AXIRequestCounter(
request = target.ar.valid & target.ar.ready,
response = target.r.valid & target.r.ready & target.r.last
)
# Switch to next request only if there are no responses pending.
self.comb += [
self.rr_write.ce.eq(~(target.aw.valid | target.w.valid | target.b.valid) & wr_lock.ready),
self.rr_read.ce.eq(~(target.ar.valid | target.r.valid) & rd_lock.ready),
]
# Connect bus requests to round-robin selectors.
self.comb += [
self.rr_write.request.eq(Cat(*[m.aw.valid | m.w.valid | m.b.valid for m in masters])),
self.rr_read.request.eq(Cat(*[m.ar.valid | m.r.valid for m in masters])),
]
class AXIDecoder(Module):
"""AXI decoder
Decode master access to particular slave based on its decoder function.
slaves: [(decoder, slave), ...]
List of slaves with address decoders, where `decoder` is a function:
decoder(Signal(address_width - log2(data_width//8))) -> Signal(1)
that returns 1 when the slave is selected and 0 otherwise.
"""
def __init__(self, master, slaves, register=False):
# TODO: unused register argument
addr_shift = log2_int(master.data_width//8)
channels = {
"write": {"aw", "w", "b"},
"read": {"ar", "r"},
}
# Reverse mapping: directions[channel] -> "write"/"read".
directions = {ch: d for d, chs in channels.items() for ch in chs}
def new_slave_sel():
return {"write": Signal(len(slaves)), "read": Signal(len(slaves))}
slave_sel_dec = new_slave_sel()
slave_sel_reg = new_slave_sel()
slave_sel = new_slave_sel()
# We need to hold the slave selected until all responses come back.
# TODO: we could reuse arbiter counters
locks = {
"write": _AXIRequestCounter(
request = master.aw.valid & master.aw.ready,
response = master.b.valid & master.b.ready
),
"read": _AXIRequestCounter(
request = master.ar.valid & master.ar.ready,
response = master.r.valid & master.r.ready & master.r.last,
),
}
self.submodules += locks.values()
def get_sig(interface, channel, name):
return getattr(getattr(interface, channel), name)
# # #
# Decode slave addresses.
for i, (decoder, bus) in enumerate(slaves):
self.comb += [
slave_sel_dec["write"][i].eq(decoder(master.aw.addr[addr_shift:])),
slave_sel_dec["read"][i].eq(decoder(master.ar.addr[addr_shift:])),
]
# Change the current selection only when we've got all responses.
for channel in locks.keys():
self.sync += If(locks[channel].ready, slave_sel_reg[channel].eq(slave_sel_dec[channel]))
# We have to cut the delaying select.
for ch, final in slave_sel.items():
self.comb += [
If(locks[ch].ready,
final.eq(slave_sel_dec[ch])
).Else(
final.eq(slave_sel_reg[ch])
)
]
# Connect master->slaves signals except valid/ready.
for i, (_, slave) in enumerate(slaves):
for channel, name, direction in master.layout_flat():
if direction == DIR_M_TO_S:
src = get_sig(master, channel, name)
dst = get_sig(slave, channel, name)
# Mask master control signals depending on slave selection.
if name in ["valid", "ready"]:
src = src & slave_sel[directions[channel]][i]
self.comb += dst.eq(src)
# Connect slave->master signals masking not selected slaves.
for channel, name, direction in master.layout_flat():
if direction == DIR_S_TO_M:
dst = get_sig(master, channel, name)
masked = []
for i, (_, slave) in enumerate(slaves):
src = get_sig(slave, channel, name)
# Mask depending on channel.
mask = Replicate(slave_sel[directions[channel]][i], len(dst))
masked.append(src & mask)
self.comb += dst.eq(reduce(or_, masked))
# AXI Interconnect --------------------------------------------------------------------------------- # AXI Interconnect ---------------------------------------------------------------------------------
class AXIInterconnectPointToPoint(Module): class AXIInterconnectPointToPoint(Module):
"""AXI point to point interconnect"""
def __init__(self, master, slave): def __init__(self, master, slave):
self.comb += master.connect(slave) self.comb += master.connect(slave)
class AXIInterconnectShared(Module): class AXIInterconnectShared(Module):
"""AXI shared interconnect""" """AXI shared interconnect"""
def __init__(self, platform, masters, slaves): def __init__(self, masters, slaves, register=False, timeout_cycles=1e6):
# FIXME: WIP. shared = AXIInterface(data_width=masters[0].data_width)
from verilog_axi.axi.axi_interconnect import AXIInterconnect self.submodules.arbiter = AXIArbiter(masters, shared)
self.submodules.interconnect = AXIInterconnect(platform) self.submodules.decoder = AXIDecoder(shared, slaves)
for master in masters: if timeout_cycles is not None:
self.interconnect.add_slave(s_axi=master) self.submodules.timeout = AXITimeout(shared, timeout_cycles)
for slave, origin, size in slaves:
self.interconnect.add_master(m_axi=slave, origin=origin, size=size)
class AXICrossbar(Module): class AXICrossbar(Module):
"""AXI crossbar """AXI crossbar
MxN crossbar for M masters and N slaves. MxN crossbar for M masters and N slaves.
""" """
def __init__(self, platform, masters, slaves): def __init__(self, masters, slaves, register=False, timeout_cycles=1e6):
# FIXME: WIP. matches, busses = zip(*slaves)
from verilog_axi.axi.axi_crossbar import AXICrossbar access_m_s = [[AXIInterface() for j in slaves] for i in masters] # a[master][slave]
self.submodules.crossbar = AXICrossbar(platform) access_s_m = list(zip(*access_m_s)) # a[slave][master]
for master in masters: # Decode each master into its access row.
self.crossbar.add_slave(s_axi=master) for slaves, master in zip(access_m_s, masters):
for slave, origin, size in slaves: slaves = list(zip(matches, slaves))
self.crossbar.add_master(m_axi=slave, origin=origin, size=size) self.submodules += AXIDecoder(master, slaves, register)
# Arbitrate each access column onto its slave.
for masters, bus in zip(access_s_m, busses):
self.submodules += AXIArbiter(masters, bus)

View File

@ -93,7 +93,6 @@ git_repos = {
# LiteX Misc Cores. # LiteX Misc Cores.
# ----------------- # -----------------
"valentyusb": GitRepo(url="https://github.com/litex-hub/", branch="hw_cdc_eptri"), "valentyusb": GitRepo(url="https://github.com/litex-hub/", branch="hw_cdc_eptri"),
"litex_verilog_axi": GitRepo(url="https://github.com/enjoy-digital/", clone="recursive"),
# LiteX Boards. # LiteX Boards.
# ------------- # -------------