Skip to content

Commit d99c8ee

Browse files
committed
wishbone: add Wishbone-attached SRAM.
Closes #89.
1 parent 058057f commit d99c8ee

File tree

2 files changed

+327
-0
lines changed

2 files changed

+327
-0
lines changed

amaranth_soc/wishbone/sram.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from amaranth import *
2+
from amaranth.lib import wiring
3+
from amaranth.lib.wiring import In
4+
from amaranth.lib.memory import MemoryData, Memory
5+
from amaranth.utils import exact_log2
6+
7+
from ..memory import MemoryMap
8+
from .bus import Signature
9+
10+
11+
__all__ = ["WishboneSRAM"]
12+
13+
14+
class WishboneSRAM(wiring.Component):
15+
"""Wishbone-attached SRAM.
16+
17+
Wishbone bus accesses have a latency of one clock cycle. Incremental and constant address
18+
bursts are supported.
19+
20+
Arguments
21+
---------
22+
size : :class:`int`, power of two
23+
SRAM size, in units of ``granularity`` bits.
24+
data_width : ``8``, ``16``, ``32`` or ``64``
25+
Wishbone bus data width.
26+
granularity : ``8``, ``16``, ``32`` or ``64``, optional
27+
Wishbone bus granularity. If unspecified, it defaults to ``data_width``.
28+
writable : bool
29+
Write capability. If disabled, writes are ignored. Enabled by default.
30+
init : iterable of initial values, optional
31+
Initial values for memory rows. There are ``(size * granularity) // data_width`` rows,
32+
and each row has a shape of ``unsigned(data_width)``.
33+
34+
Members
35+
-------
36+
wb_bus : ``In(wishbone.Signature(...))``
37+
Wishbone bus interface.
38+
39+
Raises
40+
------
41+
:exc:`ValueError`
42+
If ``size * granularity`` is lesser than ``data_width``.
43+
"""
44+
def __init__(self, *, size, data_width, granularity=None, writable=True, init=()):
45+
if granularity is None:
46+
granularity = data_width
47+
48+
if not isinstance(size, int) or size <= 0 or size & size-1:
49+
raise TypeError(f"Size must be an integer power of two, not {size!r}")
50+
if data_width not in (8, 16, 32, 64):
51+
raise TypeError(f"Data width must be 8, 16, 32 or 64, not {data_width!r}")
52+
if granularity not in (8, 16, 32, 64):
53+
raise TypeError(f"Granularity must be 8, 16, 32 or 64, not {granularity!r}")
54+
if size * granularity < data_width:
55+
raise ValueError(f"The product of size {size} and granularity {granularity} must be "
56+
f"greater than or equal to data width {data_width}, not "
57+
f"{size * granularity}")
58+
59+
self._size = size
60+
self._writable = bool(writable)
61+
self._mem_data = MemoryData(depth=(size * granularity) // data_width,
62+
shape=unsigned(data_width), init=init)
63+
self._mem = Memory(self._mem_data)
64+
65+
super().__init__({"wb_bus": In(Signature(addr_width=exact_log2(self._mem.depth),
66+
data_width=data_width, granularity=granularity,
67+
features=("cti", "bte")))})
68+
69+
self.wb_bus.memory_map = MemoryMap(addr_width=exact_log2(size), data_width=granularity)
70+
self.wb_bus.memory_map.add_resource(self._mem, name=("mem",), size=size)
71+
self.wb_bus.memory_map.freeze()
72+
73+
@property
74+
def size(self):
75+
return self._size
76+
77+
@property
78+
def writable(self):
79+
return self._writable
80+
81+
@property
82+
def init(self):
83+
return self._mem_data.init
84+
85+
@init.setter
86+
def init(self, init):
87+
self._mem_data.init = init
88+
89+
def elaborate(self, platform):
90+
m = Module()
91+
m.submodules.mem = self._mem
92+
93+
read_port = self._mem.read_port()
94+
m.d.comb += [
95+
read_port.addr.eq(self.wb_bus.adr),
96+
self.wb_bus.dat_r.eq(read_port.data),
97+
]
98+
99+
if self.writable:
100+
write_port = self._mem.write_port(granularity=self.wb_bus.granularity)
101+
m.d.comb += [
102+
write_port.addr.eq(self.wb_bus.adr),
103+
write_port.data.eq(self.wb_bus.dat_w),
104+
]
105+
106+
with m.If(self.wb_bus.cyc & self.wb_bus.stb):
107+
if self.writable:
108+
m.d.comb += write_port.en.eq(Mux(self.wb_bus.we, self.wb_bus.sel, 0))
109+
m.d.sync += self.wb_bus.ack.eq(1)
110+
with m.Else():
111+
m.d.sync += self.wb_bus.ack.eq(0)
112+
113+
return m

tests/test_wishbone_sram.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# amaranth: UnusedElaboratable=no
2+
3+
import unittest
4+
from amaranth import *
5+
from amaranth.sim import *
6+
7+
from amaranth_soc import wishbone
8+
from amaranth_soc.wishbone.sram import WishboneSRAM
9+
10+
11+
class WishboneSRAMTestCase(unittest.TestCase):
12+
def test_init(self):
13+
# default granularity, writable, no initial values
14+
dut_1 = WishboneSRAM(size=1024, data_width=32)
15+
self.assertEqual(dut_1.size, 1024)
16+
self.assertEqual(dut_1.writable, True)
17+
self.assertEqual(list(dut_1.init), [0 for _ in range(1024)])
18+
self.assertEqual(dut_1.wb_bus.addr_width, 10)
19+
self.assertEqual(dut_1.wb_bus.data_width, 32)
20+
self.assertEqual(dut_1.wb_bus.granularity, 32)
21+
self.assertEqual(dut_1.wb_bus.features,
22+
{wishbone.Feature.CTI, wishbone.Feature.BTE})
23+
self.assertEqual(dut_1.wb_bus.memory_map.addr_width, 10)
24+
self.assertEqual(dut_1.wb_bus.memory_map.data_width, 32)
25+
self.assertEqual(dut_1.wb_bus.memory_map.alignment, 0)
26+
self.assertEqual(list(dut_1.wb_bus.memory_map.resources()),
27+
[(dut_1._mem, ("mem",), (0, 1024))])
28+
# custom granularity, read-only, with initial values
29+
dut_2 = WishboneSRAM(size=4, data_width=16, granularity=8, writable=False,
30+
init=(0xbbaa, 0xddcc))
31+
self.assertEqual(dut_2.size, 4)
32+
self.assertEqual(dut_2.writable, False)
33+
self.assertEqual(list(dut_2.init), [0xbbaa, 0xddcc])
34+
self.assertEqual(dut_2.wb_bus.addr_width, 1)
35+
self.assertEqual(dut_2.wb_bus.data_width, 16)
36+
self.assertEqual(dut_2.wb_bus.granularity, 8)
37+
self.assertEqual(dut_2.wb_bus.features,
38+
{wishbone.Feature.CTI, wishbone.Feature.BTE})
39+
self.assertEqual(dut_2.wb_bus.memory_map.addr_width, 2)
40+
self.assertEqual(dut_2.wb_bus.memory_map.data_width, 8)
41+
self.assertEqual(dut_2.wb_bus.memory_map.alignment, 0)
42+
self.assertEqual(list(dut_2.wb_bus.memory_map.resources()),
43+
[(dut_2._mem, ("mem",), (0, 4))])
44+
45+
def test_memory_data_init_set(self):
46+
dut = WishboneSRAM(size=4, data_width=16, granularity=8)
47+
self.assertEqual(list(dut.init), [0x0000, 0x0000])
48+
dut.init = [0xbbaa, 0xddcc]
49+
self.assertEqual(list(dut._mem_data.init), [0xbbaa, 0xddcc])
50+
51+
def test_init_wrong_size(self):
52+
with self.assertRaisesRegex(TypeError, r"Size must be an integer power of two, not 1.0"):
53+
WishboneSRAM(size=1.0, data_width=32)
54+
with self.assertRaisesRegex(TypeError, r"Size must be an integer power of two, not 3"):
55+
WishboneSRAM(size=3, data_width=32)
56+
57+
def test_init_wrong_data_width(self):
58+
with self.assertRaisesRegex(TypeError, r"Data width must be 8, 16, 32 or 64, not 'foo'"):
59+
WishboneSRAM(size=1024, data_width="foo")
60+
with self.assertRaisesRegex(TypeError, r"Data width must be 8, 16, 32 or 64, not 128"):
61+
WishboneSRAM(size=1024, data_width=128)
62+
63+
def test_init_wrong_granularity(self):
64+
with self.assertRaisesRegex(TypeError, r"Granularity must be 8, 16, 32 or 64, not 'foo'"):
65+
WishboneSRAM(size=1024, data_width=32, granularity="foo")
66+
with self.assertRaisesRegex(TypeError, r"Granularity must be 8, 16, 32 or 64, not 128"):
67+
WishboneSRAM(size=1024, data_width=32, granularity=128)
68+
69+
def test_init_size_smaller_than_data_width(self):
70+
with self.assertRaisesRegex(ValueError,
71+
r"The product of size 2 and granularity 8 must be greater than or equal to data "
72+
r"width 32, not 16"):
73+
WishboneSRAM(size=2, data_width=32, granularity=8)
74+
75+
def test_sim_writable(self):
76+
dut = WishboneSRAM(size=128, data_width=32, granularity=8, writable=True, init=range(32))
77+
78+
async def wb_cycle(ctx, *, adr, sel, we, dat_w, cti, bte=0, assert_dat_r=None):
79+
ctx.set(dut.wb_bus.cyc, 1)
80+
ctx.set(dut.wb_bus.stb, 1)
81+
ctx.set(dut.wb_bus.adr, adr)
82+
ctx.set(dut.wb_bus.sel, sel)
83+
ctx.set(dut.wb_bus.we, we)
84+
ctx.set(dut.wb_bus.dat_w, dat_w)
85+
ctx.set(dut.wb_bus.cti, cti)
86+
ctx.set(dut.wb_bus.bte, bte)
87+
88+
await ctx.tick()
89+
self.assertEqual(ctx.get(dut.wb_bus.ack), 1)
90+
if assert_dat_r is not None:
91+
self.assertEqual(ctx.get(dut.wb_bus.dat_r), assert_dat_r)
92+
93+
ctx.set(dut.wb_bus.cyc, 0)
94+
ctx.set(dut.wb_bus.stb, 0)
95+
96+
async def testbench(ctx):
97+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
98+
for i in range(32):
99+
self.assertEqual(ctx.get(dut._mem_data[i]), i)
100+
101+
# cti = CLASSIC =======================================================================
102+
103+
# - left shift all values by 24 bits:
104+
for i in range(32):
105+
await wb_cycle(ctx, cti=wishbone.CycleType.CLASSIC,
106+
adr=i, sel=0b1001, we=1, dat_w=(i << 24) | 0x00ffff00,
107+
assert_dat_r=i)
108+
await ctx.tick()
109+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
110+
111+
for i in range(32):
112+
self.assertEqual(ctx.get(dut._mem_data[i]), i << 24)
113+
114+
# cti = INCR_BURST, bte = LINEAR ======================================================
115+
116+
# - right shift all values by 24 bits:
117+
for i in range(32):
118+
cti = wishbone.CycleType.END_OF_BURST if i == 31 else wishbone.CycleType.INCR_BURST
119+
await wb_cycle(ctx, cti=cti, bte=wishbone.BurstTypeExt.LINEAR,
120+
adr=i, sel=0b1001, we=1, dat_w=i | 0x00ffff00,
121+
assert_dat_r=i << 24)
122+
123+
await ctx.tick()
124+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
125+
for i in range(32):
126+
self.assertEqual(ctx.get(dut._mem_data[i]), i)
127+
128+
# cti = INCR_BURST, bte = WRAP_4 ======================================================
129+
130+
# - increment values at addresses 0..15:
131+
for i in (1,2,3,0, 5,6,7,4, 9,10,11,8, 13,14,15,12):
132+
cti = wishbone.CycleType.END_OF_BURST if i == 12 else wishbone.CycleType.INCR_BURST
133+
await wb_cycle(ctx, cti=cti, bte=wishbone.BurstTypeExt.WRAP_4,
134+
adr=i, sel=0b0001, we=1, dat_w=i + 1,
135+
assert_dat_r=i)
136+
137+
await ctx.tick()
138+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
139+
for i in range(16):
140+
self.assertEqual(ctx.get(dut._mem_data[i]), i + 1)
141+
142+
# cti = INCR_BURST, bte = WRAP_8 ======================================================
143+
144+
# - increment values at addresses 0..15:
145+
for i in (1,2,3,4,5,6,7,0, 9,10,11,12,13,14,15,8):
146+
cti = wishbone.CycleType.END_OF_BURST if i == 8 else wishbone.CycleType.INCR_BURST
147+
await wb_cycle(ctx, cti=cti, bte=wishbone.BurstTypeExt.WRAP_8,
148+
adr=i, sel=0b0001, we=1, dat_w=i + 2,
149+
assert_dat_r=i + 1)
150+
151+
await ctx.tick()
152+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
153+
for i in range(16):
154+
self.assertEqual(ctx.get(dut._mem_data[i]), i + 2)
155+
156+
# cti = INCR_BURST, bte = WRAP_16 =====================================================
157+
158+
# - increment values at addresses 0..15:
159+
for i in (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0):
160+
cti = wishbone.CycleType.END_OF_BURST if i == 0 else wishbone.CycleType.INCR_BURST
161+
await wb_cycle(ctx, cti=cti, bte=wishbone.BurstTypeExt.WRAP_16,
162+
adr=i, sel=0b0001, we=1, dat_w=i + 3,
163+
assert_dat_r=i + 2)
164+
165+
await ctx.tick()
166+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
167+
for i in range(16):
168+
self.assertEqual(ctx.get(dut._mem_data[i]), i + 3)
169+
170+
# cti = CONST_BURST ===================================================================
171+
172+
# - increment value at address 31, 16 times in a row:
173+
for i in range(16):
174+
cti = wishbone.CycleType.END_OF_BURST if i == 15 else wishbone.CycleType.CONST_BURST
175+
await wb_cycle(ctx, cti=cti, adr=31, sel=0b0001, we=1, dat_w=31 + i + 1,
176+
assert_dat_r=31 + i)
177+
178+
await ctx.tick()
179+
self.assertEqual(ctx.get(dut.wb_bus.ack), 0)
180+
self.assertEqual(ctx.get(dut._mem_data[31]), 31 + 16)
181+
182+
sim = Simulator(dut)
183+
sim.add_clock(1e-6)
184+
sim.add_testbench(testbench)
185+
with sim.write_vcd(vcd_file="test.vcd"):
186+
sim.run()
187+
188+
def test_sim_readonly(self):
189+
dut = WishboneSRAM(size=128, data_width=32, granularity=8, writable=False, init=range(32))
190+
191+
async def testbench(ctx):
192+
for i in range(32):
193+
self.assertEqual(ctx.get(dut._mem_data[i]), i)
194+
195+
for i in range(32):
196+
ctx.set(dut.wb_bus.cyc, 1)
197+
ctx.set(dut.wb_bus.stb, 1)
198+
ctx.set(dut.wb_bus.adr, i)
199+
ctx.set(dut.wb_bus.sel, 0xf)
200+
ctx.set(dut.wb_bus.we, 1)
201+
ctx.set(dut.wb_bus.dat_w, 0xffffffff)
202+
await ctx.tick().until(dut.wb_bus.ack)
203+
ctx.set(dut.wb_bus.cyc, 0)
204+
ctx.set(dut.wb_bus.stb, 0)
205+
await ctx.tick()
206+
207+
for i in range(32):
208+
self.assertEqual(ctx.get(dut._mem_data[i]), i)
209+
210+
sim = Simulator(dut)
211+
sim.add_clock(1e-6)
212+
sim.add_testbench(testbench)
213+
with sim.write_vcd(vcd_file="test.vcd"):
214+
sim.run()

0 commit comments

Comments
 (0)