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:
parent
573479b395
commit
1a90549fa3
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
# -------------
|
# -------------
|
||||||
|
|
Loading…
Reference in New Issue