Skip to content

Commit b35150a

Browse files
FatsieFatsieFS
authored andcommitted
SRAM bus
A low level SRAM interface, code structure is modeled after CSR interface.
1 parent 217d4ea commit b35150a

File tree

5 files changed

+380
-0
lines changed

5 files changed

+380
-0
lines changed

amaranth_soc/sram/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .bus import *
2+
from .wishbone import WishboneSRAMBridge as WishboneBridge
3+

amaranth_soc/sram/bus.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from amaranth import *
2+
3+
from ..memory import MemoryMap
4+
5+
class Interface(Record):
6+
"""SRAM interface.
7+
8+
A low-level interface for accessing SRAM blocks.
9+
10+
Parameters
11+
----------
12+
addr_width : int
13+
Address width. At most ``(2 ** addr_width) * data_width`` bits of memory will be available.
14+
data_width : int
15+
Data width. Word size of the SRAM block.
16+
words : int
17+
The number of words of :arg:`data_width` bits in the SRAM block. This allows to have an
18+
SRAM block not covering the full address space provided by :arg:`addr_width`. Only values
19+
are allowed where MSB of the address is used given the condition of
20+
``2**(addr_width - 1) < words <= 2**addr_width``.
21+
If no value is specified the full ``2**addr_width`` is used.
22+
name : str
23+
Name of the underlying record.
24+
25+
Attributes
26+
----------
27+
memory_map : MemoryMap
28+
The memory map of the SRAM block; determined by the :arg:`words` and arg:`data_width`
29+
arguments.
30+
a : Signal(addr_width)
31+
Address for reads and writes
32+
d_r : Signal(data_width)
33+
Read data. The SRAM interface is defined asynchronous and no guarantees are made on
34+
timing for availability of the data.
35+
d_w : Signal(data_width)
36+
Write data. The SRAM interface is defined asynchronous and does not define timing
37+
requirement of d_w and we.
38+
we : Signal()
39+
Enable write. The SRAM interface is defined asynchronous and does not define timing
40+
requirement of d_w and we.
41+
ce : Signal()
42+
Enable SRAM block interface.
43+
"""
44+
45+
def __init__(self, *, addr_width, data_width, words=None, name=None):
46+
if not isinstance(addr_width, int) or addr_width <= 0:
47+
raise ValueError(
48+
"Address width must be a positive integer, not {!r}".format(addr_width)
49+
)
50+
if not isinstance(data_width, int) or data_width <= 0:
51+
raise ValueError(
52+
"Data width must be a positive integer, not {!r}".format(data_width)
53+
)
54+
if words is None:
55+
words = 2**addr_width
56+
if not isinstance(words, int) or not (2**(addr_width - 1) < words <= 2**addr_width):
57+
raise ValueError(
58+
"# words has to integer between 2**(addr_width-1) and 2**addr_width, not {!r}"
59+
.format(words),
60+
)
61+
self.addr_width = addr_width
62+
self.data_width = data_width
63+
self.words = words
64+
self._map = memmap = MemoryMap(addr_width=addr_width, data_width=data_width)
65+
66+
super().__init__([
67+
("a", addr_width),
68+
("d_r", data_width),
69+
("d_w", data_width),
70+
("we", 1),
71+
("ce", 1),
72+
], name=name, src_loc_at=1)
73+
74+
memmap.add_resource(self, name=(name if name is not None else "sram"), size=words)
75+
memmap.freeze()
76+
77+
@property
78+
def memory_map(self):
79+
return self._map
80+
81+
def __hash__(self):
82+
"""Each object represents a different SRAM block"""
83+
return id(self)

amaranth_soc/sram/wishbone.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from amaranth import *
2+
from amaranth.utils import log2_int
3+
4+
from .bus import Interface
5+
from .. import wishbone as wb, memory as mem
6+
7+
8+
__all__ = ["WishboneSRAMBridge"]
9+
10+
11+
class WishboneSRAMBridge(Elaboratable):
12+
"""Wishbone to SRAM bridge.
13+
14+
A bus bridge for accessing SRAM blocks from Wishbone. This bridge drives one or more
15+
SRAM blocks from the Wishbone interface. The data width of the individual blocks
16+
determines the granularity of the Wishbone interface and the number of interfaces the
17+
data_width. A dynamic configurable number wait states can be inserted in each
18+
Wishbone bus operation.
19+
20+
Parameters
21+
----------
22+
sram_buses : :class:`..sram.Interface` or iterable of :class:`..sram.Interface`
23+
SRAM buses driven by the bridge. All buses need to have same address and data
24+
widths.
25+
wait_states : :class:Signal or :class:Const
26+
The number of wait states to insert before acknowledging a cycle. This value is
27+
not latched at the beginning of a cycle so should normally be kept stable during
28+
a bus cycle.
29+
30+
Attributes
31+
----------
32+
wb_bus : :class:`..wishbone.Interface`
33+
Wishbone bus provided by the bridge.
34+
"""
35+
def __init__(self, sram_buses, *, wait_states=Const(0, 1), name="wb"):
36+
if isinstance(sram_buses, Interface):
37+
sram_buses = [sram_buses]
38+
else:
39+
try:
40+
for sram_bus in sram_buses:
41+
assert isinstance(sram_bus, Interface)
42+
except:
43+
raise ValueError(
44+
"SRAM buses has to be an iterable of sram.Interface, not {!r}".format(sram_buses)
45+
)
46+
# Support len(sram_buses) and make sram_buses hashable
47+
sram_buses = tuple(sram_buses)
48+
n_rams = len(sram_buses)
49+
addr_width = sram_buses[0].addr_width
50+
granularity = sram_buses[0].data_width
51+
words = sram_buses[0].words
52+
for sram_bus in sram_buses[1:]:
53+
if sram_bus.addr_width != addr_width:
54+
raise ValueError("All SRAMs have to have the same address width")
55+
if sram_bus.data_width != granularity:
56+
raise ValueError("All SRAMs have to have the same data width")
57+
if sram_bus.words != words:
58+
raise ValueError("All SRAMs have to have the same number of words")
59+
data_width = granularity*len(sram_buses)
60+
61+
self.sram_buses = sram_buses
62+
self.wait_states = wait_states
63+
self.wb_bus = wb_bus = wb.Interface(
64+
addr_width=addr_width,
65+
data_width=data_width,
66+
granularity=granularity,
67+
name=name,
68+
)
69+
if n_rams == 1:
70+
wb_bus.memory_map = sram_buses[0].memory_map
71+
else:
72+
size = words*len(sram_buses)
73+
map_addr_width = log2_int(size, need_pow2=False)
74+
memmap = mem.MemoryMap(addr_width=map_addr_width, data_width=granularity)
75+
memmap.add_resource(sram_buses, name=name, size=size)
76+
wb_bus.memory_map = memmap
77+
78+
def elaborate(self, platform):
79+
sram_buses = self.sram_buses
80+
wait_states = self.wait_states
81+
wb_bus = self.wb_bus
82+
83+
m = Module()
84+
85+
wb_cycle = Signal()
86+
m.d.comb += wb_cycle.eq(wb_bus.cyc & wb_bus.stb)
87+
88+
for i, sram_bus in enumerate(sram_buses):
89+
s = slice(i*wb_bus.granularity, (i+1)*wb_bus.granularity)
90+
m.d.comb += [
91+
sram_bus.a.eq(wb_bus.adr),
92+
sram_bus.ce.eq(wb_cycle & wb_bus.sel[i]),
93+
sram_bus.we.eq(wb_cycle & wb_bus.sel[i] & wb_bus.we),
94+
wb_bus.dat_r[s].eq(sram_bus.d_r),
95+
sram_bus.d_w.eq(wb_bus.dat_w[s]),
96+
]
97+
98+
waitcnt = Signal(len(wait_states))
99+
with m.If(wb_cycle):
100+
with m.If(waitcnt != wait_states):
101+
m.d.comb += wb_bus.ack.eq(0)
102+
m.d.sync += waitcnt.eq(waitcnt + 1)
103+
with m.Else():
104+
m.d.comb += wb_bus.ack.eq(1)
105+
m.d.sync += waitcnt.eq(0)
106+
with m.Else():
107+
m.d.comb += wb_bus.ack.eq(0)
108+
m.d.sync += waitcnt.eq(0)
109+
110+
return m

amaranth_soc/test/test_sram_bus.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# nmigen: UnusedElaboratable=no
2+
3+
import unittest
4+
from amaranth import *
5+
from amaranth.hdl.rec import Layout
6+
7+
from .. import sram
8+
from ..memory import MemoryMap
9+
10+
11+
class InterfaceTestCase(unittest.TestCase):
12+
def test_layout(self):
13+
iface = sram.Interface(addr_width=12, data_width=8)
14+
self.assertEqual(iface.addr_width, 12)
15+
self.assertEqual(iface.data_width, 8)
16+
self.assertEqual(iface.layout, Layout.cast([
17+
("a", 12),
18+
("d_r", 8),
19+
("d_w", 8),
20+
("we", 1),
21+
("ce", 1),
22+
]))
23+
24+
def test_wrong_addr_width(self):
25+
with self.assertRaisesRegex(
26+
ValueError,
27+
r"Address width must be a positive integer, not -1",
28+
):
29+
sram.Interface(addr_width=-1, data_width=8)
30+
31+
def test_wrong_data_width(self):
32+
with self.assertRaisesRegex(
33+
ValueError,
34+
r"Data width must be a positive integer, not -1",
35+
):
36+
sram.Interface(addr_width=16, data_width=-1)
37+
38+
def test_no_set_map(self):
39+
iface = sram.Interface(addr_width=16, data_width=8)
40+
with self.assertRaisesRegex(AttributeError, "can't set attribute"):
41+
iface.memory_map = MemoryMap(addr_width=16, data_width=8)
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# amaranth: UnusedElaboratable=no
2+
3+
import unittest
4+
from amaranth import *
5+
from amaranth.back.pysim import *
6+
7+
from .. import sram
8+
9+
10+
class WishboneBridgeTestCase(unittest.TestCase):
11+
def test_wrong_sram_buses(self):
12+
with self.assertRaisesRegex(
13+
ValueError,
14+
r"SRAM buses has to be an iterable of sram.Interface, not 'foo'",
15+
):
16+
sram.WishboneBridge("foo")
17+
with self.assertRaisesRegex(
18+
ValueError,
19+
r"SRAM buses has to be an iterable of sram.Interface, not 'foo'",
20+
):
21+
sram.WishboneBridge(sram_buses="foo")
22+
23+
def test_wbbus_single(self):
24+
iface = sram.Interface(addr_width=10, data_width=8)
25+
bridge = sram.WishboneBridge(iface)
26+
self.assertEqual(bridge.wb_bus.addr_width, 10)
27+
self.assertEqual(bridge.wb_bus.data_width, 8)
28+
self.assertEqual(bridge.wb_bus.granularity, 8)
29+
self.assertFalse(hasattr(bridge.wb_bus, "stall"))
30+
31+
def test_wbbus_multi(self):
32+
ifaces = [sram.Interface(addr_width=10, data_width=8) for _ in range(4)]
33+
bridge = sram.WishboneBridge(ifaces)
34+
self.assertEqual(bridge.wb_bus.addr_width, 10)
35+
self.assertEqual(bridge.wb_bus.data_width, 32)
36+
self.assertEqual(bridge.wb_bus.granularity, 8)
37+
self.assertFalse(hasattr(bridge.wb_bus, "stall"))
38+
39+
def test_readwrite_single_nowait(self):
40+
iface = sram.Interface(addr_width=10, data_width=8)
41+
dut = sram.WishboneBridge(iface)
42+
43+
def sim_test():
44+
yield dut.wb_bus.cyc.eq(1)
45+
yield dut.wb_bus.stb.eq(0)
46+
yield dut.wb_bus.sel.eq(0b1)
47+
yield
48+
self.assertFalse((yield iface.ce), 0)
49+
50+
yield dut.wb_bus.we.eq(1)
51+
yield
52+
# we is only asserted when in Wishbone cycle
53+
self.assertEqual((yield iface.we), 0)
54+
55+
yield dut.wb_bus.adr.eq(1)
56+
yield dut.wb_bus.dat_w.eq(0x55)
57+
yield dut.wb_bus.stb.eq(1)
58+
yield
59+
self.assertEqual((yield iface.we), 1)
60+
self.assertEqual((yield iface.a), 1)
61+
self.assertEqual((yield iface.d_w), 0x55)
62+
self.assertEqual((yield iface.ce), 1)
63+
self.assertEqual((yield dut.wb_bus.ack), 1)
64+
65+
yield dut.wb_bus.stb.eq(0)
66+
yield
67+
self.assertEqual((yield dut.wb_bus.ack), 0)
68+
self.assertEqual((yield iface.we), 0)
69+
self.assertEqual((yield iface.ce), 0)
70+
71+
yield dut.wb_bus.we.eq(0)
72+
yield dut.wb_bus.stb.eq(1)
73+
yield iface.d_r.eq(0x55)
74+
yield
75+
self.assertEqual((yield dut.wb_bus.ack), 1)
76+
self.assertEqual((yield dut.wb_bus.dat_r), 0x55)
77+
78+
sim = Simulator(dut)
79+
sim.add_clock(1e-6)
80+
sim.add_sync_process(sim_test)
81+
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
82+
sim.run()
83+
84+
def test_readwrite_multi_wait1(self):
85+
ifaces = [sram.Interface(addr_width=10, data_width=8) for _ in range(4)]
86+
a = ifaces[0].a
87+
ce = Cat(iface.ce for iface in ifaces)
88+
we = Cat(iface.we for iface in ifaces)
89+
d_w = Cat(iface.d_w for iface in ifaces)
90+
d_r = Cat(iface.d_r for iface in ifaces)
91+
92+
dut = sram.WishboneBridge(ifaces, wait_states=Const(1, 1))
93+
94+
def sim_test():
95+
yield dut.wb_bus.cyc.eq(1)
96+
yield dut.wb_bus.stb.eq(0)
97+
yield dut.wb_bus.sel.eq(0b1100)
98+
yield
99+
for iface in ifaces:
100+
self.assertFalse((yield iface.ce), 0)
101+
102+
yield dut.wb_bus.we.eq(1)
103+
yield
104+
# we is only asserted when in Wishbone cycle
105+
self.assertEqual((yield we), 0b0000)
106+
107+
yield dut.wb_bus.adr.eq(1)
108+
yield dut.wb_bus.dat_w.eq(0xAA55AA55)
109+
yield dut.wb_bus.stb.eq(1)
110+
yield
111+
self.assertEqual((yield we), 0b1100)
112+
self.assertEqual((yield a), 1)
113+
self.assertEqual((yield d_w), 0xAA55AA55)
114+
self.assertEqual((yield ce), 0b1100)
115+
self.assertEqual((yield dut.wb_bus.ack), 0)
116+
yield
117+
yield
118+
yield
119+
self.assertEqual((yield dut.wb_bus.ack), 1)
120+
121+
yield dut.wb_bus.stb.eq(0)
122+
yield
123+
self.assertEqual((yield dut.wb_bus.ack), 0)
124+
self.assertEqual((yield we), 0b0000)
125+
self.assertEqual((yield ce), 0b0000)
126+
127+
yield dut.wb_bus.we.eq(0)
128+
yield dut.wb_bus.stb.eq(1)
129+
yield d_r.eq(0xAA550000)
130+
yield
131+
self.assertEqual((yield dut.wb_bus.dat_r), 0xAA550000)
132+
self.assertEqual((yield dut.wb_bus.ack), 0)
133+
yield
134+
yield
135+
yield
136+
self.assertEqual((yield dut.wb_bus.dat_r), 0xAA550000)
137+
self.assertEqual((yield dut.wb_bus.ack), 1)
138+
139+
sim = Simulator(dut)
140+
sim.add_clock(1e-6)
141+
sim.add_sync_process(sim_test)
142+
with sim.write_vcd(vcd_file=open("test.vcd", "w")):
143+
sim.run()

0 commit comments

Comments
 (0)