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

SRAM bus #29

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions amaranth_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 amaranth_soc/sram/bus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from amaranth 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, name=(name if name is not None else "sram"), 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 amaranth_soc/sram/wishbone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from amaranth import *
from amaranth.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, name=name, 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 amaranth_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 amaranth import *
from amaranth.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 amaranth_soc/test/test_sram_wishbone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# amaranth: UnusedElaboratable=no

import unittest
from amaranth import *
from amaranth.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()