-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A low level SRAM interface, code structure is modeled after CSR interface.
- Loading branch information
Showing
5 changed files
with
380 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .bus import * | ||
from .wishbone import WishboneSRAMBridge as WishboneBridge | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |