interconnect: add bus/bank components from Migen
This commit is contained in:
parent
ecdc4101b4
commit
f69674e89c
|
@ -0,0 +1,147 @@
|
|||
from migen.util.misc import xdir
|
||||
from migen.fhdl.std import *
|
||||
from migen.fhdl.tracer import get_obj_var_name
|
||||
|
||||
|
||||
class _CSRBase(HUID):
|
||||
def __init__(self, size, name):
|
||||
HUID.__init__(self)
|
||||
self.name = get_obj_var_name(name)
|
||||
if self.name is None:
|
||||
raise ValueError("Cannot extract CSR name from code, need to specify.")
|
||||
self.size = size
|
||||
|
||||
|
||||
class CSR(_CSRBase):
|
||||
def __init__(self, size=1, name=None):
|
||||
_CSRBase.__init__(self, size, name)
|
||||
self.re = Signal(name=self.name + "_re")
|
||||
self.r = Signal(self.size, name=self.name + "_r")
|
||||
self.w = Signal(self.size, name=self.name + "_w")
|
||||
|
||||
|
||||
class _CompoundCSR(_CSRBase, Module):
|
||||
def __init__(self, size, name):
|
||||
_CSRBase.__init__(self, size, name)
|
||||
self.simple_csrs = []
|
||||
|
||||
def get_simple_csrs(self):
|
||||
if not self.finalized:
|
||||
raise FinalizeError
|
||||
return self.simple_csrs
|
||||
|
||||
def do_finalize(self, busword):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CSRStatus(_CompoundCSR):
|
||||
def __init__(self, size=1, reset=0, name=None):
|
||||
_CompoundCSR.__init__(self, size, name)
|
||||
self.status = Signal(self.size, reset=reset)
|
||||
|
||||
def do_finalize(self, busword):
|
||||
nwords = (self.size + busword - 1)//busword
|
||||
for i in reversed(range(nwords)):
|
||||
nbits = min(self.size - i*busword, busword)
|
||||
sc = CSR(nbits, self.name + str(i) if nwords > 1 else self.name)
|
||||
self.comb += sc.w.eq(self.status[i*busword:i*busword+nbits])
|
||||
self.simple_csrs.append(sc)
|
||||
|
||||
|
||||
class CSRStorage(_CompoundCSR):
|
||||
def __init__(self, size=1, reset=0, atomic_write=False, write_from_dev=False, alignment_bits=0, name=None):
|
||||
_CompoundCSR.__init__(self, size, name)
|
||||
self.alignment_bits = alignment_bits
|
||||
self.storage_full = Signal(self.size, reset=reset)
|
||||
self.storage = Signal(self.size - self.alignment_bits, reset=reset >> alignment_bits)
|
||||
self.comb += self.storage.eq(self.storage_full[self.alignment_bits:])
|
||||
self.atomic_write = atomic_write
|
||||
self.re = Signal()
|
||||
if write_from_dev:
|
||||
self.we = Signal()
|
||||
self.dat_w = Signal(self.size - self.alignment_bits)
|
||||
self.sync += If(self.we, self.storage_full.eq(self.dat_w << self.alignment_bits))
|
||||
|
||||
def do_finalize(self, busword):
|
||||
nwords = (self.size + busword - 1)//busword
|
||||
if nwords > 1 and self.atomic_write:
|
||||
backstore = Signal(self.size - busword, name=self.name + "_backstore")
|
||||
for i in reversed(range(nwords)):
|
||||
nbits = min(self.size - i*busword, busword)
|
||||
sc = CSR(nbits, self.name + str(i) if nwords else self.name)
|
||||
self.simple_csrs.append(sc)
|
||||
lo = i*busword
|
||||
hi = lo+nbits
|
||||
# read
|
||||
if lo >= self.alignment_bits:
|
||||
self.comb += sc.w.eq(self.storage_full[lo:hi])
|
||||
elif hi > self.alignment_bits:
|
||||
self.comb += sc.w.eq(Cat(Replicate(0, hi - self.alignment_bits),
|
||||
self.storage_full[self.alignment_bits:hi]))
|
||||
else:
|
||||
self.comb += sc.w.eq(0)
|
||||
# write
|
||||
if nwords > 1 and self.atomic_write:
|
||||
if i:
|
||||
self.sync += If(sc.re, backstore[lo-busword:hi-busword].eq(sc.r))
|
||||
else:
|
||||
self.sync += If(sc.re, self.storage_full.eq(Cat(sc.r, backstore)))
|
||||
else:
|
||||
self.sync += If(sc.re, self.storage_full[lo:hi].eq(sc.r))
|
||||
self.sync += self.re.eq(sc.re)
|
||||
|
||||
|
||||
def csrprefix(prefix, csrs, done):
|
||||
for csr in csrs:
|
||||
if csr.huid not in done:
|
||||
csr.name = prefix + csr.name
|
||||
done.add(csr.huid)
|
||||
|
||||
|
||||
def memprefix(prefix, memories, done):
|
||||
for memory in memories:
|
||||
if memory.huid not in done:
|
||||
memory.name_override = prefix + memory.name_override
|
||||
done.add(memory.huid)
|
||||
|
||||
|
||||
def _make_gatherer(method, cls, prefix_cb):
|
||||
def gatherer(self):
|
||||
try:
|
||||
exclude = self.autocsr_exclude
|
||||
except AttributeError:
|
||||
exclude = {}
|
||||
try:
|
||||
prefixed = self.__prefixed
|
||||
except AttributeError:
|
||||
prefixed = self.__prefixed = set()
|
||||
r = []
|
||||
for k, v in xdir(self, True):
|
||||
if k not in exclude:
|
||||
if isinstance(v, cls):
|
||||
r.append(v)
|
||||
elif hasattr(v, method) and callable(getattr(v, method)):
|
||||
items = getattr(v, method)()
|
||||
prefix_cb(k + "_", items, prefixed)
|
||||
r += items
|
||||
return sorted(r, key=lambda x: x.huid)
|
||||
return gatherer
|
||||
|
||||
|
||||
class AutoCSR:
|
||||
get_memories = _make_gatherer("get_memories", Memory, memprefix)
|
||||
get_csrs = _make_gatherer("get_csrs", _CSRBase, csrprefix)
|
||||
|
||||
|
||||
class GenericBank(Module):
|
||||
def __init__(self, description, busword):
|
||||
# Turn description into simple CSRs and claim ownership of compound CSR modules
|
||||
self.simple_csrs = []
|
||||
for c in description:
|
||||
if isinstance(c, CSR):
|
||||
self.simple_csrs.append(c)
|
||||
else:
|
||||
c.finalize(busword)
|
||||
self.simple_csrs += c.get_simple_csrs()
|
||||
self.submodules += c
|
||||
self.decode_bits = bits_for(len(self.simple_csrs)-1)
|
|
@ -0,0 +1,215 @@
|
|||
from migen.fhdl.std import *
|
||||
from migen.bus.transactions import *
|
||||
from migen.bank.description import CSRStorage
|
||||
from migen.genlib.record import *
|
||||
from migen.genlib.misc import chooser
|
||||
|
||||
from misoc.interconnect import csr
|
||||
|
||||
|
||||
_layout = [
|
||||
("adr", "address_width", DIR_M_TO_S),
|
||||
("we", 1, DIR_M_TO_S),
|
||||
("dat_w", "data_width", DIR_M_TO_S),
|
||||
("dat_r", "data_width", DIR_S_TO_M)
|
||||
]
|
||||
|
||||
|
||||
class Interface(Record):
|
||||
def __init__(self, data_width=8, address_width=14):
|
||||
Record.__init__(self, set_layout_parameters(_layout,
|
||||
data_width=data_width, address_width=address_width))
|
||||
|
||||
|
||||
class Interconnect(Module):
|
||||
def __init__(self, master, slaves):
|
||||
self.comb += master.connect(*slaves)
|
||||
|
||||
|
||||
class Initiator(Module):
|
||||
def __init__(self, generator, bus=None):
|
||||
self.generator = generator
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
self.transaction = None
|
||||
self.read_data_ready = False
|
||||
self.done = False
|
||||
|
||||
def do_simulation(self, selfp):
|
||||
if not self.done:
|
||||
if self.transaction is not None:
|
||||
if isinstance(self.transaction, TRead):
|
||||
if self.read_data_ready:
|
||||
self.transaction.data = selfp.bus.dat_r
|
||||
self.transaction = None
|
||||
self.read_data_ready = False
|
||||
else:
|
||||
self.read_data_ready = True
|
||||
else:
|
||||
selfp.bus.we = 0
|
||||
self.transaction = None
|
||||
if self.transaction is None:
|
||||
try:
|
||||
self.transaction = next(self.generator)
|
||||
except StopIteration:
|
||||
self.transaction = None
|
||||
raise StopSimulation
|
||||
if self.transaction is not None:
|
||||
selfp.bus.adr = self.transaction.address
|
||||
if isinstance(self.transaction, TWrite):
|
||||
selfp.bus.we = 1
|
||||
selfp.bus.dat_w = self.transaction.data
|
||||
|
||||
|
||||
class SRAM(Module):
|
||||
def __init__(self, mem_or_size, address, read_only=None, init=None, bus=None):
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
data_width = flen(self.bus.dat_w)
|
||||
if isinstance(mem_or_size, Memory):
|
||||
mem = mem_or_size
|
||||
else:
|
||||
mem = Memory(data_width, mem_or_size//(data_width//8), init=init)
|
||||
csrw_per_memw = (mem.width + data_width - 1)//data_width
|
||||
word_bits = log2_int(csrw_per_memw)
|
||||
page_bits = log2_int((mem.depth*csrw_per_memw + 511)//512, False)
|
||||
if page_bits:
|
||||
self._page = CSRStorage(page_bits, name=mem.name_override + "_page")
|
||||
else:
|
||||
self._page = None
|
||||
if read_only is None:
|
||||
if hasattr(mem, "bus_read_only"):
|
||||
read_only = mem.bus_read_only
|
||||
else:
|
||||
read_only = False
|
||||
|
||||
###
|
||||
|
||||
port = mem.get_port(write_capable=not read_only)
|
||||
self.specials += mem, port
|
||||
|
||||
sel = Signal()
|
||||
sel_r = Signal()
|
||||
self.sync += sel_r.eq(sel)
|
||||
self.comb += sel.eq(self.bus.adr[9:] == address)
|
||||
|
||||
if word_bits:
|
||||
word_index = Signal(word_bits)
|
||||
word_expanded = Signal(csrw_per_memw*data_width)
|
||||
self.sync += word_index.eq(self.bus.adr[:word_bits])
|
||||
self.comb += [
|
||||
word_expanded.eq(port.dat_r),
|
||||
If(sel_r,
|
||||
chooser(word_expanded, word_index, self.bus.dat_r, n=csrw_per_memw, reverse=True)
|
||||
)
|
||||
]
|
||||
if not read_only:
|
||||
wregs = []
|
||||
for i in range(csrw_per_memw-1):
|
||||
wreg = Signal(data_width)
|
||||
self.sync += If(sel & self.bus.we & (self.bus.adr[:word_bits] == i), wreg.eq(self.bus.dat_w))
|
||||
wregs.append(wreg)
|
||||
memword_chunks = [self.bus.dat_w] + list(reversed(wregs))
|
||||
self.comb += [
|
||||
port.we.eq(sel & self.bus.we & (self.bus.adr[:word_bits] == csrw_per_memw - 1)),
|
||||
port.dat_w.eq(Cat(*memword_chunks))
|
||||
]
|
||||
else:
|
||||
self.comb += If(sel_r, self.bus.dat_r.eq(port.dat_r))
|
||||
if not read_only:
|
||||
self.comb += [
|
||||
port.we.eq(sel & self.bus.we),
|
||||
port.dat_w.eq(self.bus.dat_w)
|
||||
]
|
||||
|
||||
if self._page is None:
|
||||
self.comb += port.adr.eq(self.bus.adr[word_bits:word_bits+flen(port.adr)])
|
||||
else:
|
||||
pv = self._page.storage
|
||||
self.comb += port.adr.eq(Cat(self.bus.adr[word_bits:word_bits+flen(port.adr)-flen(pv)], pv))
|
||||
|
||||
def get_csrs(self):
|
||||
if self._page is None:
|
||||
return []
|
||||
else:
|
||||
return [self._page]
|
||||
|
||||
|
||||
class CSRBank(csr.GenericBank):
|
||||
def __init__(self, description, address=0, bus=None):
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
|
||||
###
|
||||
|
||||
GenericBank.__init__(self, description, flen(self.bus.dat_w))
|
||||
|
||||
sel = Signal()
|
||||
self.comb += sel.eq(self.bus.adr[9:] == address)
|
||||
|
||||
for i, c in enumerate(self.simple_csrs):
|
||||
self.comb += [
|
||||
c.r.eq(self.bus.dat_w[:c.size]),
|
||||
c.re.eq(sel & \
|
||||
self.bus.we & \
|
||||
(self.bus.adr[:self.decode_bits] == i))
|
||||
]
|
||||
|
||||
brcases = dict((i, self.bus.dat_r.eq(c.w)) for i, c in enumerate(self.simple_csrs))
|
||||
self.sync += [
|
||||
self.bus.dat_r.eq(0),
|
||||
If(sel, Case(self.bus.adr[:self.decode_bits], brcases))
|
||||
]
|
||||
|
||||
|
||||
# address_map(name, memory) returns the CSR offset at which to map
|
||||
# the CSR object (register bank or memory).
|
||||
# If memory=None, the object is the register bank of object source.name.
|
||||
# Otherwise, it is a memory object belonging to source.name.
|
||||
# address_map is called exactly once for each object at each call to
|
||||
# scan(), so it can have side effects.
|
||||
class CSRBankArray(Module):
|
||||
def __init__(self, source, address_map, *ifargs, **ifkwargs):
|
||||
self.source = source
|
||||
self.address_map = address_map
|
||||
self.scan(ifargs, ifkwargs)
|
||||
|
||||
def scan(self, ifargs, ifkwargs):
|
||||
self.banks = []
|
||||
self.srams = []
|
||||
for name, obj in xdir(self.source, True):
|
||||
if hasattr(obj, "get_csrs"):
|
||||
csrs = obj.get_csrs()
|
||||
else:
|
||||
csrs = []
|
||||
if hasattr(obj, "get_memories"):
|
||||
memories = obj.get_memories()
|
||||
for memory in memories:
|
||||
mapaddr = self.address_map(name, memory)
|
||||
if mapaddr is None:
|
||||
continue
|
||||
sram_bus = csr.Interface(*ifargs, **ifkwargs)
|
||||
mmap = csr.SRAM(memory, mapaddr, bus=sram_bus)
|
||||
self.submodules += mmap
|
||||
csrs += mmap.get_csrs()
|
||||
self.srams.append((name, memory, mapaddr, mmap))
|
||||
if csrs:
|
||||
mapaddr = self.address_map(name, None)
|
||||
if mapaddr is None:
|
||||
continue
|
||||
bank_bus = csr.Interface(*ifargs, **ifkwargs)
|
||||
rmap = Bank(csrs, mapaddr, bus=bank_bus)
|
||||
self.submodules += rmap
|
||||
self.banks.append((name, csrs, mapaddr, rmap))
|
||||
|
||||
def get_rmaps(self):
|
||||
return [rmap for name, csrs, mapaddr, rmap in self.banks]
|
||||
|
||||
def get_mmaps(self):
|
||||
return [mmap for name, memory, mapaddr, mmap in self.srams]
|
||||
|
||||
def get_buses(self):
|
||||
return [i.bus for i in self.get_rmaps() + self.get_mmaps()]
|
|
@ -0,0 +1,83 @@
|
|||
from migen.util.misc import xdir
|
||||
from migen.fhdl.std import *
|
||||
from migen.bank.description import *
|
||||
from migen.genlib.misc import optree
|
||||
|
||||
|
||||
class _EventSource(HUID):
|
||||
def __init__(self):
|
||||
HUID.__init__(self)
|
||||
self.status = Signal() # value in the status register
|
||||
self.pending = Signal() # value in the pending register + assert irq if unmasked
|
||||
self.trigger = Signal() # trigger signal interface to the user design
|
||||
self.clear = Signal() # clearing attempt by W1C to pending register, ignored by some event sources
|
||||
|
||||
|
||||
# set on a positive trigger pulse
|
||||
class EventSourcePulse(Module, _EventSource):
|
||||
def __init__(self):
|
||||
_EventSource.__init__(self)
|
||||
self.comb += self.status.eq(0)
|
||||
self.sync += [
|
||||
If(self.clear, self.pending.eq(0)),
|
||||
If(self.trigger, self.pending.eq(1))
|
||||
]
|
||||
|
||||
|
||||
# set on the falling edge of the trigger, status = trigger
|
||||
class EventSourceProcess(Module, _EventSource):
|
||||
def __init__(self):
|
||||
_EventSource.__init__(self)
|
||||
self.comb += self.status.eq(self.trigger)
|
||||
old_trigger = Signal()
|
||||
self.sync += [
|
||||
If(self.clear, self.pending.eq(0)),
|
||||
old_trigger.eq(self.trigger),
|
||||
If(~self.trigger & old_trigger, self.pending.eq(1))
|
||||
]
|
||||
|
||||
|
||||
# all status set by external trigger
|
||||
class EventSourceLevel(Module, _EventSource):
|
||||
def __init__(self):
|
||||
_EventSource.__init__(self)
|
||||
self.comb += [
|
||||
self.status.eq(self.trigger),
|
||||
self.pending.eq(self.trigger)
|
||||
]
|
||||
|
||||
|
||||
class EventManager(Module, AutoCSR):
|
||||
def __init__(self):
|
||||
self.irq = Signal()
|
||||
|
||||
def do_finalize(self):
|
||||
sources_u = [v for k, v in xdir(self, True) if isinstance(v, _EventSource)]
|
||||
sources = sorted(sources_u, key=lambda x: x.huid)
|
||||
n = len(sources)
|
||||
self.status = CSR(n)
|
||||
self.pending = CSR(n)
|
||||
self.enable = CSRStorage(n)
|
||||
|
||||
for i, source in enumerate(sources):
|
||||
self.comb += [
|
||||
self.status.w[i].eq(source.status),
|
||||
If(self.pending.re & self.pending.r[i], source.clear.eq(1)),
|
||||
self.pending.w[i].eq(source.pending)
|
||||
]
|
||||
|
||||
irqs = [self.pending.w[i] & self.enable.storage[i] for i in range(n)]
|
||||
self.comb += self.irq.eq(optree("|", irqs))
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
object.__setattr__(self, name, value)
|
||||
if isinstance(value, _EventSource):
|
||||
if self.finalized:
|
||||
raise FinalizeError
|
||||
self.submodules += value
|
||||
|
||||
|
||||
class SharedIRQ(Module):
|
||||
def __init__(self, *event_managers):
|
||||
self.irq = Signal()
|
||||
self.comb += self.irq.eq(optree("|", [ev.irq for ev in event_managers]))
|
|
@ -0,0 +1,718 @@
|
|||
from migen.fhdl.std import *
|
||||
from migen.genlib import roundrobin
|
||||
from migen.genlib.record import *
|
||||
from migen.genlib.misc import split, displacer, optree, chooser
|
||||
from migen.genlib.misc import FlipFlop, Counter
|
||||
from migen.genlib.fsm import FSM, NextState
|
||||
from migen.bus.transactions import *
|
||||
|
||||
from misoc.interconnect import csr
|
||||
|
||||
|
||||
_layout = [
|
||||
("adr", 30, DIR_M_TO_S),
|
||||
("dat_w", "data_width", DIR_M_TO_S),
|
||||
("dat_r", "data_width", DIR_S_TO_M),
|
||||
("sel", "sel_width", DIR_M_TO_S),
|
||||
("cyc", 1, DIR_M_TO_S),
|
||||
("stb", 1, DIR_M_TO_S),
|
||||
("ack", 1, DIR_S_TO_M),
|
||||
("we", 1, DIR_M_TO_S),
|
||||
("cti", 3, DIR_M_TO_S),
|
||||
("bte", 2, DIR_M_TO_S),
|
||||
("err", 1, DIR_S_TO_M)
|
||||
]
|
||||
|
||||
|
||||
class Interface(Record):
|
||||
def __init__(self, data_width=32):
|
||||
Record.__init__(self, set_layout_parameters(_layout,
|
||||
data_width=data_width,
|
||||
sel_width=data_width//8))
|
||||
|
||||
|
||||
class InterconnectPointToPoint(Module):
|
||||
def __init__(self, master, slave):
|
||||
self.comb += master.connect(slave)
|
||||
|
||||
|
||||
class Arbiter(Module):
|
||||
def __init__(self, masters, target):
|
||||
self.submodules.rr = roundrobin.RoundRobin(len(masters))
|
||||
|
||||
# mux master->slave signals
|
||||
for name, size, direction in _layout:
|
||||
if direction == DIR_M_TO_S:
|
||||
choices = Array(getattr(m, name) for m in masters)
|
||||
self.comb += getattr(target, name).eq(choices[self.rr.grant])
|
||||
|
||||
# connect slave->master signals
|
||||
for name, size, direction in _layout:
|
||||
if direction == DIR_S_TO_M:
|
||||
source = getattr(target, name)
|
||||
for i, m in enumerate(masters):
|
||||
dest = getattr(m, name)
|
||||
if name == "ack" or name == "err":
|
||||
self.comb += dest.eq(source & (self.rr.grant == i))
|
||||
else:
|
||||
self.comb += dest.eq(source)
|
||||
|
||||
# connect bus requests to round-robin selector
|
||||
reqs = [m.cyc for m in masters]
|
||||
self.comb += self.rr.request.eq(Cat(*reqs))
|
||||
|
||||
|
||||
class Decoder(Module):
|
||||
# slaves is a list of pairs:
|
||||
# 0) function that takes the address signal and returns a FHDL expression
|
||||
# that evaluates to 1 when the slave is selected and 0 otherwise.
|
||||
# 1) wishbone.Slave reference.
|
||||
# register adds flip-flops after the address comparators. Improves timing,
|
||||
# but breaks Wishbone combinatorial feedback.
|
||||
def __init__(self, master, slaves, register=False):
|
||||
ns = len(slaves)
|
||||
slave_sel = Signal(ns)
|
||||
slave_sel_r = Signal(ns)
|
||||
|
||||
# decode slave addresses
|
||||
self.comb += [slave_sel[i].eq(fun(master.adr))
|
||||
for i, (fun, bus) in enumerate(slaves)]
|
||||
if register:
|
||||
self.sync += slave_sel_r.eq(slave_sel)
|
||||
else:
|
||||
self.comb += slave_sel_r.eq(slave_sel)
|
||||
|
||||
# connect master->slaves signals except cyc
|
||||
for slave in slaves:
|
||||
for name, size, direction in _layout:
|
||||
if direction == DIR_M_TO_S and name != "cyc":
|
||||
self.comb += getattr(slave[1], name).eq(getattr(master, name))
|
||||
|
||||
# combine cyc with slave selection signals
|
||||
self.comb += [slave[1].cyc.eq(master.cyc & slave_sel[i])
|
||||
for i, slave in enumerate(slaves)]
|
||||
|
||||
# generate master ack (resp. err) by ORing all slave acks (resp. errs)
|
||||
self.comb += [
|
||||
master.ack.eq(optree("|", [slave[1].ack for slave in slaves])),
|
||||
master.err.eq(optree("|", [slave[1].err for slave in slaves]))
|
||||
]
|
||||
|
||||
# mux (1-hot) slave data return
|
||||
masked = [Replicate(slave_sel_r[i], flen(master.dat_r)) & slaves[i][1].dat_r for i in range(ns)]
|
||||
self.comb += master.dat_r.eq(optree("|", masked))
|
||||
|
||||
|
||||
class InterconnectShared(Module):
|
||||
def __init__(self, masters, slaves, register=False):
|
||||
shared = Interface()
|
||||
self.submodules += Arbiter(masters, shared)
|
||||
self.submodules += Decoder(shared, slaves, register)
|
||||
|
||||
|
||||
class Crossbar(Module):
|
||||
def __init__(self, masters, slaves, register=False):
|
||||
matches, busses = zip(*slaves)
|
||||
access = [[Interface() for j in slaves] for i in masters]
|
||||
# decode each master into its access row
|
||||
for row, master in zip(access, masters):
|
||||
row = list(zip(matches, row))
|
||||
self.submodules += Decoder(master, row, register)
|
||||
# arbitrate each access column onto its slave
|
||||
for column, bus in zip(zip(*access), busses):
|
||||
self.submodules += Arbiter(column, bus)
|
||||
|
||||
|
||||
class DownConverter(Module):
|
||||
"""DownConverter
|
||||
|
||||
This module splits Wishbone accesses from a master interface to a smaller
|
||||
slave interface.
|
||||
|
||||
Writes:
|
||||
Writes from master are splitted N writes to the slave. Access is acked when the last
|
||||
access is acked by the slave.
|
||||
|
||||
Reads:
|
||||
Read from master are splitted in N reads to the the slave. Read datas from
|
||||
the slave are cached before being presented concatenated on the last access.
|
||||
|
||||
TODO:
|
||||
Manage err signal? (Not implemented since we generally don't use it on Migen/MiSoC modules)
|
||||
"""
|
||||
def __init__(self, master, slave):
|
||||
dw_from = flen(master.dat_r)
|
||||
dw_to = flen(slave.dat_w)
|
||||
ratio = dw_from//dw_to
|
||||
|
||||
# # #
|
||||
|
||||
read = Signal()
|
||||
write = Signal()
|
||||
|
||||
counter = Counter(max=ratio)
|
||||
self.submodules += counter
|
||||
counter_done = Signal()
|
||||
self.comb += counter_done.eq(counter.value == ratio-1)
|
||||
|
||||
# Main FSM
|
||||
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
|
||||
fsm.act("IDLE",
|
||||
counter.reset.eq(1),
|
||||
If(master.stb & master.cyc,
|
||||
If(master.we,
|
||||
NextState("WRITE")
|
||||
).Else(
|
||||
NextState("READ")
|
||||
)
|
||||
)
|
||||
)
|
||||
fsm.act("WRITE",
|
||||
write.eq(1),
|
||||
slave.we.eq(1),
|
||||
slave.cyc.eq(1),
|
||||
If(master.stb & master.cyc,
|
||||
slave.stb.eq(1),
|
||||
If(slave.ack,
|
||||
counter.ce.eq(1),
|
||||
If(counter_done,
|
||||
master.ack.eq(1),
|
||||
NextState("IDLE")
|
||||
)
|
||||
)
|
||||
).Elif(~master.cyc,
|
||||
NextState("IDLE")
|
||||
)
|
||||
)
|
||||
fsm.act("READ",
|
||||
read.eq(1),
|
||||
slave.cyc.eq(1),
|
||||
If(master.stb & master.cyc,
|
||||
slave.stb.eq(1),
|
||||
If(slave.ack,
|
||||
counter.ce.eq(1),
|
||||
If(counter_done,
|
||||
master.ack.eq(1),
|
||||
NextState("IDLE")
|
||||
)
|
||||
)
|
||||
).Elif(~master.cyc,
|
||||
NextState("IDLE")
|
||||
)
|
||||
)
|
||||
|
||||
# Address
|
||||
self.comb += [
|
||||
If(counter_done,
|
||||
slave.cti.eq(7) # indicate end of burst
|
||||
).Else(
|
||||
slave.cti.eq(2)
|
||||
),
|
||||
slave.adr.eq(Cat(counter.value, master.adr))
|
||||
]
|
||||
|
||||
# Datapath
|
||||
cases = {}
|
||||
for i in range(ratio):
|
||||
cases[i] = [
|
||||
slave.sel.eq(master.sel[i*dw_to//8:(i+1)*dw_to]),
|
||||
slave.dat_w.eq(master.dat_w[i*dw_to:(i+1)*dw_to])
|
||||
]
|
||||
self.comb += Case(counter.value, cases)
|
||||
|
||||
|
||||
cached_data = Signal(dw_from)
|
||||
self.comb += master.dat_r.eq(Cat(cached_data[dw_to:], slave.dat_r))
|
||||
self.sync += \
|
||||
If(read & counter.ce,
|
||||
cached_data.eq(master.dat_r)
|
||||
)
|
||||
|
||||
|
||||
class UpConverter(Module):
|
||||
"""UpConverter
|
||||
|
||||
This module up-converts wishbone accesses and bursts from a master interface
|
||||
to a wider slave interface. This allows efficient use wishbone bursts.
|
||||
|
||||
Writes:
|
||||
Wishbone writes are cached before being written to the slave. Access to
|
||||
the slave is done at the end of a burst or when address reach end of burst
|
||||
addressing.
|
||||
|
||||
Reads:
|
||||
Cache is refilled only at the beginning of each burst, the subsequent
|
||||
reads of a burst use the cached data.
|
||||
|
||||
TODO:
|
||||
Manage err signal? (Not implemented since we generally don't use it on Migen/MiSoC modules)
|
||||
"""
|
||||
def __init__(self, master, slave):
|
||||
dw_from = flen(master.dat_r)
|
||||
dw_to = flen(slave.dat_w)
|
||||
ratio = dw_to//dw_from
|
||||
ratiobits = log2_int(ratio)
|
||||
|
||||
# # #
|
||||
|
||||
write = Signal()
|
||||
evict = Signal()
|
||||
refill = Signal()
|
||||
read = Signal()
|
||||
|
||||
address = FlipFlop(30)
|
||||
self.submodules += address
|
||||
self.comb += address.d.eq(master.adr)
|
||||
|
||||
counter = Counter(max=ratio)
|
||||
self.submodules += counter
|
||||
counter_offset = Signal(max=ratio)
|
||||
counter_done = Signal()
|
||||
self.comb += [
|
||||
counter_offset.eq(address.q),
|
||||
counter_done.eq((counter.value + counter_offset) == ratio-1)
|
||||
]
|
||||
|
||||
cached_data = Signal(dw_to)
|
||||
cached_sel = Signal(dw_to//8)
|
||||
|
||||
end_of_burst = Signal()
|
||||
self.comb += end_of_burst.eq(~master.cyc |
|
||||
(master.stb & master.cyc & master.ack & ((master.cti == 7) | counter_done)))
|
||||
|
||||
|
||||
need_refill = FlipFlop(reset=1)
|
||||
self.submodules += need_refill
|
||||
self.comb += [
|
||||
need_refill.reset.eq(end_of_burst),
|
||||
need_refill.d.eq(0)
|
||||
]
|
||||
|
||||
# Main FSM
|
||||
self.submodules.fsm = fsm = FSM()
|
||||
fsm.act("IDLE",
|
||||
counter.reset.eq(1),
|
||||
If(master.stb & master.cyc,
|
||||
address.ce.eq(1),
|
||||
If(master.we,
|
||||
NextState("WRITE")
|
||||
).Else(
|
||||
If(need_refill.q,
|
||||
NextState("REFILL")
|
||||
).Else(
|
||||
NextState("READ")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
fsm.act("WRITE",
|
||||
If(master.stb & master.cyc,
|
||||
write.eq(1),
|
||||
counter.ce.eq(1),
|
||||
master.ack.eq(1),
|
||||
If(counter_done,
|
||||
NextState("EVICT")
|
||||
)
|
||||
).Elif(~master.cyc,
|
||||
NextState("EVICT")
|
||||
)
|
||||
)
|
||||
fsm.act("EVICT",
|
||||
evict.eq(1),
|
||||
slave.stb.eq(1),
|
||||
slave.we.eq(1),
|
||||
slave.cyc.eq(1),
|
||||
slave.dat_w.eq(cached_data),
|
||||
slave.sel.eq(cached_sel),
|
||||
If(slave.ack,
|
||||
NextState("IDLE")
|
||||
)
|
||||
)
|
||||
fsm.act("REFILL",
|
||||
refill.eq(1),
|
||||
slave.stb.eq(1),
|
||||
slave.cyc.eq(1),
|
||||
If(slave.ack,
|
||||
need_refill.ce.eq(1),
|
||||
NextState("READ")
|
||||
)
|
||||
)
|
||||
fsm.act("READ",
|
||||
read.eq(1),
|
||||
If(master.stb & master.cyc,
|
||||
master.ack.eq(1)
|
||||
),
|
||||
NextState("IDLE")
|
||||
)
|
||||
|
||||
# Address
|
||||
self.comb += [
|
||||
slave.cti.eq(7), # we are not able to generate bursts since up-converting
|
||||
slave.adr.eq(address.q[ratiobits:])
|
||||
]
|
||||
|
||||
# Datapath
|
||||
cached_datas = [FlipFlop(dw_from) for i in range(ratio)]
|
||||
cached_sels = [FlipFlop(dw_from//8) for i in range(ratio)]
|
||||
self.submodules += cached_datas, cached_sels
|
||||
|
||||
cases = {}
|
||||
for i in range(ratio):
|
||||
write_sel = Signal()
|
||||
cases[i] = write_sel.eq(1)
|
||||
self.comb += [
|
||||
cached_sels[i].reset.eq(counter.reset),
|
||||
If(write,
|
||||
cached_datas[i].d.eq(master.dat_w),
|
||||
).Else(
|
||||
cached_datas[i].d.eq(slave.dat_r[dw_from*i:dw_from*(i+1)])
|
||||
),
|
||||
cached_sels[i].d.eq(master.sel),
|
||||
If((write & write_sel) | refill,
|
||||
cached_datas[i].ce.eq(1),
|
||||
cached_sels[i].ce.eq(1)
|
||||
)
|
||||
]
|
||||
self.comb += Case(counter.value + counter_offset, cases)
|
||||
|
||||
cases = {}
|
||||
for i in range(ratio):
|
||||
cases[i] = master.dat_r.eq(cached_datas[i].q)
|
||||
self.comb += Case(address.q[:ratiobits], cases)
|
||||
|
||||
self.comb += [
|
||||
cached_data.eq(Cat([cached_data.q for cached_data in cached_datas])),
|
||||
cached_sel.eq(Cat([cached_sel.q for cached_sel in cached_sels]))
|
||||
]
|
||||
|
||||
|
||||
class Converter(Module):
|
||||
"""Converter
|
||||
|
||||
This module is a wrapper for DownConverter and UpConverter.
|
||||
It should preferably be used rather than direct instantiations
|
||||
of specific converters.
|
||||
"""
|
||||
def __init__(self, master, slave):
|
||||
self.master = master
|
||||
self.slave = slave
|
||||
|
||||
# # #
|
||||
|
||||
dw_from = flen(master.dat_r)
|
||||
dw_to = flen(slave.dat_r)
|
||||
if dw_from > dw_to:
|
||||
downconverter = DownConverter(master, slave)
|
||||
self.submodules += downconverter
|
||||
elif dw_from < dw_to:
|
||||
upconverter = UpConverter(master, slave)
|
||||
self.submodules += upconverter
|
||||
else:
|
||||
Record.connect(master, slave)
|
||||
|
||||
|
||||
class Cache(Module):
|
||||
"""Cache
|
||||
|
||||
This module is a write-back wishbone cache that can be used as a L2 cache.
|
||||
Cachesize (in 32-bit words) is the size of the data store and must be a power of 2
|
||||
"""
|
||||
def __init__(self, cachesize, master, slave):
|
||||
self.master = master
|
||||
self.slave = slave
|
||||
|
||||
###
|
||||
|
||||
dw_from = flen(master.dat_r)
|
||||
dw_to = flen(slave.dat_r)
|
||||
if dw_to > dw_from and (dw_to % dw_from) != 0:
|
||||
raise ValueError("Slave data width must be a multiple of {dw}".format(dw=dw_from))
|
||||
if dw_to < dw_from and (dw_from % dw_to) != 0:
|
||||
raise ValueError("Master data width must be a multiple of {dw}".format(dw=dw_to))
|
||||
|
||||
# Split address:
|
||||
# TAG | LINE NUMBER | LINE OFFSET
|
||||
offsetbits = log2_int(max(dw_to//dw_from, 1))
|
||||
addressbits = flen(slave.adr) + offsetbits
|
||||
linebits = log2_int(cachesize) - offsetbits
|
||||
tagbits = addressbits - linebits
|
||||
wordbits = log2_int(max(dw_from//dw_to, 1))
|
||||
adr_offset, adr_line, adr_tag = split(master.adr, offsetbits, linebits, tagbits)
|
||||
word = Signal(wordbits) if wordbits else None
|
||||
|
||||
# Data memory
|
||||
data_mem = Memory(dw_to*2**wordbits, 2**linebits)
|
||||
data_port = data_mem.get_port(write_capable=True, we_granularity=8)
|
||||
self.specials += data_mem, data_port
|
||||
|
||||
write_from_slave = Signal()
|
||||
if adr_offset is None:
|
||||
adr_offset_r = None
|
||||
else:
|
||||
adr_offset_r = Signal(offsetbits)
|
||||
self.sync += adr_offset_r.eq(adr_offset)
|
||||
|
||||
self.comb += [
|
||||
data_port.adr.eq(adr_line),
|
||||
If(write_from_slave,
|
||||
displacer(slave.dat_r, word, data_port.dat_w),
|
||||
displacer(Replicate(1, dw_to//8), word, data_port.we)
|
||||
).Else(
|
||||
data_port.dat_w.eq(Replicate(master.dat_w, max(dw_to//dw_from, 1))),
|
||||
If(master.cyc & master.stb & master.we & master.ack,
|
||||
displacer(master.sel, adr_offset, data_port.we, 2**offsetbits, reverse=True)
|
||||
)
|
||||
),
|
||||
chooser(data_port.dat_r, word, slave.dat_w),
|
||||
slave.sel.eq(2**(dw_to//8)-1),
|
||||
chooser(data_port.dat_r, adr_offset_r, master.dat_r, reverse=True)
|
||||
]
|
||||
|
||||
|
||||
# Tag memory
|
||||
tag_layout = [("tag", tagbits), ("dirty", 1)]
|
||||
tag_mem = Memory(layout_len(tag_layout), 2**linebits)
|
||||
tag_port = tag_mem.get_port(write_capable=True)
|
||||
self.specials += tag_mem, tag_port
|
||||
tag_do = Record(tag_layout)
|
||||
tag_di = Record(tag_layout)
|
||||
self.comb += [
|
||||
tag_do.raw_bits().eq(tag_port.dat_r),
|
||||
tag_port.dat_w.eq(tag_di.raw_bits())
|
||||
]
|
||||
|
||||
self.comb += [
|
||||
tag_port.adr.eq(adr_line),
|
||||
tag_di.tag.eq(adr_tag)
|
||||
]
|
||||
if word is not None:
|
||||
self.comb += slave.adr.eq(Cat(word, adr_line, tag_do.tag))
|
||||
else:
|
||||
self.comb += slave.adr.eq(Cat(adr_line, tag_do.tag))
|
||||
|
||||
# slave word computation, word_clr and word_inc will be simplified
|
||||
# at synthesis when wordbits=0
|
||||
word_clr = Signal()
|
||||
word_inc = Signal()
|
||||
if word is not None:
|
||||
self.sync += \
|
||||
If(word_clr,
|
||||
word.eq(0),
|
||||
).Elif(word_inc,
|
||||
word.eq(word+1)
|
||||
)
|
||||
|
||||
def word_is_last(word):
|
||||
if word is not None:
|
||||
return word == 2**wordbits-1
|
||||
else:
|
||||
return 1
|
||||
|
||||
# Control FSM
|
||||
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
|
||||
fsm.act("IDLE",
|
||||
If(master.cyc & master.stb,
|
||||
NextState("TEST_HIT")
|
||||
)
|
||||
)
|
||||
fsm.act("TEST_HIT",
|
||||
word_clr.eq(1),
|
||||
If(tag_do.tag == adr_tag,
|
||||
master.ack.eq(1),
|
||||
If(master.we,
|
||||
tag_di.dirty.eq(1),
|
||||
tag_port.we.eq(1)
|
||||
),
|
||||
NextState("IDLE")
|
||||
).Else(
|
||||
If(tag_do.dirty,
|
||||
NextState("EVICT")
|
||||
).Else(
|
||||
NextState("REFILL_WRTAG")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
fsm.act("EVICT",
|
||||
slave.stb.eq(1),
|
||||
slave.cyc.eq(1),
|
||||
slave.we.eq(1),
|
||||
If(slave.ack,
|
||||
word_inc.eq(1),
|
||||
If(word_is_last(word),
|
||||
NextState("REFILL_WRTAG")
|
||||
)
|
||||
)
|
||||
)
|
||||
fsm.act("REFILL_WRTAG",
|
||||
# Write the tag first to set the slave address
|
||||
tag_port.we.eq(1),
|
||||
word_clr.eq(1),
|
||||
NextState("REFILL")
|
||||
)
|
||||
fsm.act("REFILL",
|
||||
slave.stb.eq(1),
|
||||
slave.cyc.eq(1),
|
||||
slave.we.eq(0),
|
||||
If(slave.ack,
|
||||
write_from_slave.eq(1),
|
||||
word_inc.eq(1),
|
||||
If(word_is_last(word),
|
||||
NextState("TEST_HIT"),
|
||||
).Else(
|
||||
NextState("REFILL")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Tap(Module):
|
||||
def __init__(self, bus, handler=print):
|
||||
self.bus = bus
|
||||
self.handler = handler
|
||||
|
||||
def do_simulation(self, selfp):
|
||||
if selfp.bus.ack:
|
||||
assert(selfp.bus.cyc and selfp.bus.stb)
|
||||
if selfp.bus.we:
|
||||
transaction = TWrite(selfp.bus.adr,
|
||||
selfp.bus.dat_w,
|
||||
selfp.bus.sel)
|
||||
else:
|
||||
transaction = TRead(selfp.bus.adr,
|
||||
selfp.bus.dat_r)
|
||||
self.handler(transaction)
|
||||
do_simulation.passive = True
|
||||
|
||||
|
||||
class Initiator(Module):
|
||||
def __init__(self, generator, bus=None):
|
||||
self.generator = generator
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
self.transaction_start = 0
|
||||
self.transaction = None
|
||||
|
||||
def do_simulation(self, selfp):
|
||||
if self.transaction is None or selfp.bus.ack:
|
||||
if self.transaction is not None:
|
||||
self.transaction.latency = selfp.simulator.cycle_counter - self.transaction_start - 1
|
||||
if isinstance(self.transaction, TRead):
|
||||
self.transaction.data = selfp.bus.dat_r
|
||||
try:
|
||||
self.transaction = next(self.generator)
|
||||
except StopIteration:
|
||||
selfp.bus.cyc = 0
|
||||
selfp.bus.stb = 0
|
||||
raise StopSimulation
|
||||
if self.transaction is not None:
|
||||
self.transaction_start = selfp.simulator.cycle_counter
|
||||
selfp.bus.cyc = 1
|
||||
selfp.bus.stb = 1
|
||||
selfp.bus.adr = self.transaction.address
|
||||
if isinstance(self.transaction, TWrite):
|
||||
selfp.bus.we = 1
|
||||
selfp.bus.sel = self.transaction.sel
|
||||
selfp.bus.dat_w = self.transaction.data
|
||||
else:
|
||||
selfp.bus.we = 0
|
||||
else:
|
||||
selfp.bus.cyc = 0
|
||||
selfp.bus.stb = 0
|
||||
|
||||
|
||||
class TargetModel:
|
||||
def read(self, address):
|
||||
return 0
|
||||
|
||||
def write(self, address, data, sel):
|
||||
pass
|
||||
|
||||
def can_ack(self, bus):
|
||||
return True
|
||||
|
||||
|
||||
class Target(Module):
|
||||
def __init__(self, model, bus=None):
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
self.model = model
|
||||
|
||||
def do_simulation(self, selfp):
|
||||
bus = selfp.bus
|
||||
if not bus.ack:
|
||||
if self.model.can_ack(bus) and bus.cyc and bus.stb:
|
||||
if bus.we:
|
||||
self.model.write(bus.adr, bus.dat_w, bus.sel)
|
||||
else:
|
||||
bus.dat_r = self.model.read(bus.adr)
|
||||
bus.ack = 1
|
||||
else:
|
||||
bus.ack = 0
|
||||
do_simulation.passive = True
|
||||
|
||||
|
||||
class SRAM(Module):
|
||||
def __init__(self, mem_or_size, read_only=None, init=None, bus=None):
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
bus_data_width = flen(self.bus.dat_r)
|
||||
if isinstance(mem_or_size, Memory):
|
||||
assert(mem_or_size.width <= bus_data_width)
|
||||
self.mem = mem_or_size
|
||||
else:
|
||||
self.mem = Memory(bus_data_width, mem_or_size//(bus_data_width//8), init=init)
|
||||
if read_only is None:
|
||||
if hasattr(self.mem, "bus_read_only"):
|
||||
read_only = self.mem.bus_read_only
|
||||
else:
|
||||
read_only = False
|
||||
|
||||
###
|
||||
|
||||
# memory
|
||||
port = self.mem.get_port(write_capable=not read_only, we_granularity=8)
|
||||
self.specials += self.mem, port
|
||||
# generate write enable signal
|
||||
if not read_only:
|
||||
self.comb += [port.we[i].eq(self.bus.cyc & self.bus.stb & self.bus.we & self.bus.sel[i])
|
||||
for i in range(4)]
|
||||
# address and data
|
||||
self.comb += [
|
||||
port.adr.eq(self.bus.adr[:flen(port.adr)]),
|
||||
self.bus.dat_r.eq(port.dat_r)
|
||||
]
|
||||
if not read_only:
|
||||
self.comb += port.dat_w.eq(self.bus.dat_w),
|
||||
# generate ack
|
||||
self.sync += [
|
||||
self.bus.ack.eq(0),
|
||||
If(self.bus.cyc & self.bus.stb & ~self.bus.ack, self.bus.ack.eq(1))
|
||||
]
|
||||
|
||||
|
||||
class CSRBank(csr.GenericBank):
|
||||
def __init__(self, description, bus=None):
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
|
||||
###
|
||||
|
||||
GenericBank.__init__(self, description, flen(self.bus.dat_w))
|
||||
|
||||
for i, c in enumerate(self.simple_csrs):
|
||||
self.comb += [
|
||||
c.r.eq(self.bus.dat_w[:c.size]),
|
||||
c.re.eq(self.bus.cyc & self.bus.stb & ~self.bus.ack & self.bus.we & \
|
||||
(self.bus.adr[:self.decode_bits] == i))
|
||||
]
|
||||
|
||||
brcases = dict((i, self.bus.dat_r.eq(c.w)) for i, c in enumerate(self.simple_csrs))
|
||||
self.sync += [
|
||||
Case(self.bus.adr[:self.decode_bits], brcases),
|
||||
If(bus.ack, bus.ack.eq(0)).Elif(bus.cyc & bus.stb, bus.ack.eq(1))
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
from migen.fhdl.std import *
|
||||
from migen.bus import wishbone
|
||||
from migen.bus import csr
|
||||
from migen.genlib.misc import timeline
|
||||
|
||||
|
||||
class WB2CSR(Module):
|
||||
def __init__(self, bus_wishbone=None, bus_csr=None):
|
||||
if bus_wishbone is None:
|
||||
bus_wishbone = wishbone.Interface()
|
||||
self.wishbone = bus_wishbone
|
||||
if bus_csr is None:
|
||||
bus_csr = csr.Interface()
|
||||
self.csr = bus_csr
|
||||
|
||||
###
|
||||
|
||||
self.sync += [
|
||||
self.csr.we.eq(0),
|
||||
self.csr.dat_w.eq(self.wishbone.dat_w),
|
||||
self.csr.adr.eq(self.wishbone.adr),
|
||||
self.wishbone.dat_r.eq(self.csr.dat_r)
|
||||
]
|
||||
self.sync += timeline(self.wishbone.cyc & self.wishbone.stb, [
|
||||
(1, [self.csr.we.eq(self.wishbone.we)]),
|
||||
(2, [self.wishbone.ack.eq(1)]),
|
||||
(3, [self.wishbone.ack.eq(0)])
|
||||
])
|
Loading…
Reference in New Issue