Skip to content

Commit

Permalink
SRAM bus
Browse files Browse the repository at this point in the history
A low level SRAM interface, code structure is modeled after CSR interface.
  • Loading branch information
Fatsie committed Apr 20, 2021
1 parent ecfad4d commit 5c991cd
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 0 deletions.
3 changes: 3 additions & 0 deletions nmigen_soc/sram/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .bus import *
from .wishbone import WishboneSRAMBridge as WishboneBridge

83 changes: 83 additions & 0 deletions nmigen_soc/sram/bus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from nmigen import *

from ..memory import MemoryMap

class Interface(Record):
"""SRAM interface.
A low-level interface for accessing SRAM blocks.
Parameters
----------
addr_width : int
Address width. At most ``(2 ** addr_width) * data_width`` bits of memory will be available.
data_width : int
Data width. Word size of the SRAM block.
words : int
The number of words of :arg:`data_width` bits in the SRAM block. This allows to have an
SRAM block not covering the full address space provided by :arg:`addr_width`. Only values
are allowed where MSB of the address is used given the condition of
``2**(addr_width - 1) < words <= 2**addr_width``.
If no value is specified the full ``2**addr_width`` is used.
name : str
Name of the underlying record.
Attributes
----------
memory_map : MemoryMap
The memory map of the SRAM block; determined by the :arg:`words` and arg:`data_width`
arguments.
a : Signal(addr_width)
Address for reads and writes
d_r : Signal(data_width)
Read data. The SRAM interface is defined asynchronous and no guarantees are made on
timing for availability of the data.
d_w : Signal(data_width)
Write data. The SRAM interface is defined asynchronous and does not define timing
requirement of d_w and we.
we : Signal()
Enable write. The SRAM interface is defined asynchronous and does not define timing
requirement of d_w and we.
ce : Signal()
Enable SRAM block interface.
"""

def __init__(self, *, addr_width, data_width, words=None, name=None):
if not isinstance(addr_width, int) or addr_width <= 0:
raise ValueError(
"Address width must be a positive integer, not {!r}".format(addr_width)
)
if not isinstance(data_width, int) or data_width <= 0:
raise ValueError(
"Data width must be a positive integer, not {!r}".format(data_width)
)
if words is None:
words = 2**addr_width
if not isinstance(words, int) or not (2**(addr_width - 1) < words <= 2**addr_width):
raise ValueError(
"# words has to integer between 2**(addr_width-1) and 2**addr_width, not {!r}"
.format(words),
)
self.addr_width = addr_width
self.data_width = data_width
self.words = words
self._map = memmap = MemoryMap(addr_width=addr_width, data_width=data_width)

super().__init__([
("a", addr_width),
("d_r", data_width),
("d_w", data_width),
("we", 1),
("ce", 1),
], name=name, src_loc_at=1)

memmap.add_resource(self, size=words)
memmap.freeze()

@property
def memory_map(self):
return self._map

def __hash__(self):
"""Each object represents a different SRAM block"""
return id(self)
110 changes: 110 additions & 0 deletions nmigen_soc/sram/wishbone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from nmigen import *
from nmigen.utils import log2_int

from .bus import Interface
from .. import wishbone as wb, memory as mem


__all__ = ["WishboneSRAMBridge"]


class WishboneSRAMBridge(Elaboratable):
"""Wishbone to SRAM bridge.
A bus bridge for accessing SRAM blocks from Wishbone. This bridge drives one or more
SRAM blocks from the Wishbone interface. The data width of the individual blocks
determines the granularity of the Wishbone interface and the number of interfaces the
data_width. A dynamic configurable number wait states can be inserted in each
Wishbone bus operation.
Parameters
----------
sram_buses : :class:`..sram.Interface` or iterable of :class:`..sram.Interface`
SRAM buses driven by the bridge. All buses need to have same address and data
widths.
wait_states : :class:Signal or :class:Const
The number of wait states to insert before acknowledging a cycle. This value is
not latched at the beginning of a cycle so should normally be kept stable during
a bus cycle.
Attributes
----------
wb_bus : :class:`..wishbone.Interface`
Wishbone bus provided by the bridge.
"""
def __init__(self, sram_buses, *, wait_states=Const(0, 1), name="wb"):
if isinstance(sram_buses, Interface):
sram_buses = [sram_buses]
else:
try:
for sram_bus in sram_buses:
assert isinstance(sram_bus, Interface)
except:
raise ValueError(
"SRAM buses has to be an iterable of sram.Interface, not {!r}".format(sram_buses)
)
# Support len(sram_buses) and make sram_buses hashable
sram_buses = tuple(sram_buses)
n_rams = len(sram_buses)
addr_width = sram_buses[0].addr_width
granularity = sram_buses[0].data_width
words = sram_buses[0].words
for sram_bus in sram_buses[1:]:
if sram_bus.addr_width != addr_width:
raise ValueError("All SRAMs have to have the same address width")
if sram_bus.data_width != granularity:
raise ValueError("All SRAMs have to have the same data width")
if sram_bus.words != words:
raise ValueError("All SRAMs have to have the same number of words")
data_width = granularity*len(sram_buses)

self.sram_buses = sram_buses
self.wait_states = wait_states
self.wb_bus = wb_bus = wb.Interface(
addr_width=addr_width,
data_width=data_width,
granularity=granularity,
name=name,
)
if n_rams == 1:
wb_bus.memory_map = sram_buses[0].memory_map
else:
size = words*len(sram_buses)
map_addr_width = log2_int(size, need_pow2=False)
memmap = mem.MemoryMap(addr_width=map_addr_width, data_width=granularity)
memmap.add_resource(sram_buses, size=size)
wb_bus.memory_map = memmap

def elaborate(self, platform):
sram_buses = self.sram_buses
wait_states = self.wait_states
wb_bus = self.wb_bus

m = Module()

wb_cycle = Signal()
m.d.comb += wb_cycle.eq(wb_bus.cyc & wb_bus.stb)

for i, sram_bus in enumerate(sram_buses):
s = slice(i*wb_bus.granularity, (i+1)*wb_bus.granularity)
m.d.comb += [
sram_bus.a.eq(wb_bus.adr),
sram_bus.ce.eq(wb_cycle & wb_bus.sel[i]),
sram_bus.we.eq(wb_cycle & wb_bus.sel[i] & wb_bus.we),
wb_bus.dat_r[s].eq(sram_bus.d_r),
sram_bus.d_w.eq(wb_bus.dat_w[s]),
]

waitcnt = Signal(len(wait_states))
with m.If(wb_cycle):
with m.If(waitcnt != wait_states):
m.d.comb += wb_bus.ack.eq(0)
m.d.sync += waitcnt.eq(waitcnt + 1)
with m.Else():
m.d.comb += wb_bus.ack.eq(1)
m.d.sync += waitcnt.eq(0)
with m.Else():
m.d.comb += wb_bus.ack.eq(0)
m.d.sync += waitcnt.eq(0)

return m
41 changes: 41 additions & 0 deletions nmigen_soc/test/test_sram_bus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# nmigen: UnusedElaboratable=no

import unittest
from nmigen import *
from nmigen.hdl.rec import Layout

from .. import sram
from ..memory import MemoryMap


class InterfaceTestCase(unittest.TestCase):
def test_layout(self):
iface = sram.Interface(addr_width=12, data_width=8)
self.assertEqual(iface.addr_width, 12)
self.assertEqual(iface.data_width, 8)
self.assertEqual(iface.layout, Layout.cast([
("a", 12),
("d_r", 8),
("d_w", 8),
("we", 1),
("ce", 1),
]))

def test_wrong_addr_width(self):
with self.assertRaisesRegex(
ValueError,
r"Address width must be a positive integer, not -1",
):
sram.Interface(addr_width=-1, data_width=8)

def test_wrong_data_width(self):
with self.assertRaisesRegex(
ValueError,
r"Data width must be a positive integer, not -1",
):
sram.Interface(addr_width=16, data_width=-1)

def test_no_set_map(self):
iface = sram.Interface(addr_width=16, data_width=8)
with self.assertRaisesRegex(AttributeError, "can't set attribute"):
iface.memory_map = MemoryMap(addr_width=16, data_width=8)
143 changes: 143 additions & 0 deletions nmigen_soc/test/test_sram_wishbone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# nmigen: UnusedElaboratable=no

import unittest
from nmigen import *
from nmigen.back.pysim import *

from .. import sram


class WishboneBridgeTestCase(unittest.TestCase):
def test_wrong_sram_buses(self):
with self.assertRaisesRegex(
ValueError,
r"SRAM buses has to be an iterable of sram.Interface, not 'foo'",
):
sram.WishboneBridge("foo")
with self.assertRaisesRegex(
ValueError,
r"SRAM buses has to be an iterable of sram.Interface, not 'foo'",
):
sram.WishboneBridge(sram_buses="foo")

def test_wbbus_single(self):
iface = sram.Interface(addr_width=10, data_width=8)
bridge = sram.WishboneBridge(iface)
self.assertEqual(bridge.wb_bus.addr_width, 10)
self.assertEqual(bridge.wb_bus.data_width, 8)
self.assertEqual(bridge.wb_bus.granularity, 8)
self.assertFalse(hasattr(bridge.wb_bus, "stall"))

def test_wbbus_multi(self):
ifaces = [sram.Interface(addr_width=10, data_width=8) for _ in range(4)]
bridge = sram.WishboneBridge(ifaces)
self.assertEqual(bridge.wb_bus.addr_width, 10)
self.assertEqual(bridge.wb_bus.data_width, 32)
self.assertEqual(bridge.wb_bus.granularity, 8)
self.assertFalse(hasattr(bridge.wb_bus, "stall"))

def test_readwrite_single_nowait(self):
iface = sram.Interface(addr_width=10, data_width=8)
dut = sram.WishboneBridge(iface)

def sim_test():
yield dut.wb_bus.cyc.eq(1)
yield dut.wb_bus.stb.eq(0)
yield dut.wb_bus.sel.eq(0b1)
yield
self.assertFalse((yield iface.ce), 0)

yield dut.wb_bus.we.eq(1)
yield
# we is only asserted when in Wishbone cycle
self.assertEqual((yield iface.we), 0)

yield dut.wb_bus.adr.eq(1)
yield dut.wb_bus.dat_w.eq(0x55)
yield dut.wb_bus.stb.eq(1)
yield
self.assertEqual((yield iface.we), 1)
self.assertEqual((yield iface.a), 1)
self.assertEqual((yield iface.d_w), 0x55)
self.assertEqual((yield iface.ce), 1)
self.assertEqual((yield dut.wb_bus.ack), 1)

yield dut.wb_bus.stb.eq(0)
yield
self.assertEqual((yield dut.wb_bus.ack), 0)
self.assertEqual((yield iface.we), 0)
self.assertEqual((yield iface.ce), 0)

yield dut.wb_bus.we.eq(0)
yield dut.wb_bus.stb.eq(1)
yield iface.d_r.eq(0x55)
yield
self.assertEqual((yield dut.wb_bus.ack), 1)
self.assertEqual((yield dut.wb_bus.dat_r), 0x55)

sim = Simulator(dut)
sim.add_clock(1e-6)
sim.add_sync_process(sim_test)
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
sim.run()

def test_readwrite_multi_wait1(self):
ifaces = [sram.Interface(addr_width=10, data_width=8) for _ in range(4)]
a = ifaces[0].a
ce = Cat(iface.ce for iface in ifaces)
we = Cat(iface.we for iface in ifaces)
d_w = Cat(iface.d_w for iface in ifaces)
d_r = Cat(iface.d_r for iface in ifaces)

dut = sram.WishboneBridge(ifaces, wait_states=Const(1, 1))

def sim_test():
yield dut.wb_bus.cyc.eq(1)
yield dut.wb_bus.stb.eq(0)
yield dut.wb_bus.sel.eq(0b1100)
yield
for iface in ifaces:
self.assertFalse((yield iface.ce), 0)

yield dut.wb_bus.we.eq(1)
yield
# we is only asserted when in Wishbone cycle
self.assertEqual((yield we), 0b0000)

yield dut.wb_bus.adr.eq(1)
yield dut.wb_bus.dat_w.eq(0xAA55AA55)
yield dut.wb_bus.stb.eq(1)
yield
self.assertEqual((yield we), 0b1100)
self.assertEqual((yield a), 1)
self.assertEqual((yield d_w), 0xAA55AA55)
self.assertEqual((yield ce), 0b1100)
self.assertEqual((yield dut.wb_bus.ack), 0)
yield
yield
yield
self.assertEqual((yield dut.wb_bus.ack), 1)

yield dut.wb_bus.stb.eq(0)
yield
self.assertEqual((yield dut.wb_bus.ack), 0)
self.assertEqual((yield we), 0b0000)
self.assertEqual((yield ce), 0b0000)

yield dut.wb_bus.we.eq(0)
yield dut.wb_bus.stb.eq(1)
yield d_r.eq(0xAA550000)
yield
self.assertEqual((yield dut.wb_bus.dat_r), 0xAA550000)
self.assertEqual((yield dut.wb_bus.ack), 0)
yield
yield
yield
self.assertEqual((yield dut.wb_bus.dat_r), 0xAA550000)
self.assertEqual((yield dut.wb_bus.ack), 1)

sim = Simulator(dut)
sim.add_clock(1e-6)
sim.add_sync_process(sim_test)
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
sim.run()

0 comments on commit 5c991cd

Please sign in to comment.