hdl.mem: add simulation model for memory.

This commit is contained in:
whitequark 2018-12-21 11:00:42 +00:00
parent a40e2cac4b
commit e58d9ec74d
2 changed files with 157 additions and 5 deletions

View file

@ -22,16 +22,21 @@ class Memory:
name = tracer.get_var_name(depth=2) name = tracer.get_var_name(depth=2)
except tracer.NameNotFound: except tracer.NameNotFound:
name = "$memory" name = "$memory"
self.name = name self.name = name
self.width = width self.width = width
self.depth = depth self.depth = depth
self.init = None if init is None else list(init)
if self.init is not None and len(self.init) > self.depth: self.init = [] if init is None else list(init)
if len(self.init) > self.depth:
raise ValueError("Memory initialization value count exceed memory depth ({} > {})" raise ValueError("Memory initialization value count exceed memory depth ({} > {})"
.format(len(self.init), self.depth)) .format(len(self.init), self.depth))
# Array of signals for simulation.
self._array = Array()
for addr, data in enumerate(self.init + [0 for _ in range(self.depth - len(self.init))]):
self._array.append(Signal(self.width, reset=data, name="{}[{}]".format(name, addr)))
def read_port(self, domain="sync", synchronous=True, transparent=True): def read_port(self, domain="sync", synchronous=True, transparent=True):
if not synchronous and not transparent: if not synchronous and not transparent:
raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent") raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent")
@ -51,6 +56,10 @@ class Memory:
raise ValueError("Write port granularity must divide memory width evenly") raise ValueError("Write port granularity must divide memory width evenly")
return WritePort(self, domain, priority, granularity) return WritePort(self, domain, priority, granularity)
def __getitem__(self, index):
"""Simulation only."""
return self._array[index]
class ReadPort: class ReadPort:
def __init__(self, memory, domain, synchronous, transparent): def __init__(self, memory, domain, synchronous, transparent):
@ -67,7 +76,7 @@ class ReadPort:
self.en = Const(1) self.en = Const(1)
def get_fragment(self, platform): def get_fragment(self, platform):
return Instance("$memrd", f = Instance("$memrd",
p_MEMID=self.memory, p_MEMID=self.memory,
p_ABITS=self.addr.nbits, p_ABITS=self.addr.nbits,
p_WIDTH=self.data.nbits, p_WIDTH=self.data.nbits,
@ -79,6 +88,26 @@ class ReadPort:
i_ADDR=self.addr, i_ADDR=self.addr,
o_DATA=self.data, o_DATA=self.data,
) )
read_data = self.data.eq(self.memory._array[self.addr])
if self.synchronous and not self.transparent:
# Synchronous, read-before-write port
f.add_statements(Switch(self.en, { 1: read_data }))
f.add_driver(self.data, self.domain)
elif self.synchronous:
# Synchronous, write-through port
# This model is a bit unconventional. We model transparent ports as asynchronous ports
# that are latched when the clock is high. This isn't exactly correct, but it is very
# close to the correct behavior of a transparent port, and the difference should only
# be observable in pathological cases of clock gating.
f.add_statements(Switch(ClockSignal(self.domain),
{ 1: self.data.eq(self.data), 0: read_data }))
f.add_driver(self.data)
else:
# Asynchronous port
f.add_statements(read_data)
f.add_driver(self.data)
return f
class WritePort: class WritePort:
def __init__(self, memory, domain, priority, granularity): def __init__(self, memory, domain, priority, granularity):
@ -92,7 +121,7 @@ class WritePort:
self.en = Signal(memory.width // granularity) self.en = Signal(memory.width // granularity)
def get_fragment(self, platform): def get_fragment(self, platform):
return Instance("$memwr", f = Instance("$memwr",
p_MEMID=self.memory, p_MEMID=self.memory,
p_ABITS=self.addr.nbits, p_ABITS=self.addr.nbits,
p_WIDTH=self.data.nbits, p_WIDTH=self.data.nbits,
@ -104,3 +133,15 @@ class WritePort:
i_ADDR=self.addr, i_ADDR=self.addr,
i_DATA=self.data, i_DATA=self.data,
) )
if len(self.en) > 1:
for index, en_bit in enumerate(self.en):
offset = index * self.granularity
bits = slice(offset, offset + self.granularity)
write_data = self.memory._array[self.addr][bits].eq(self.data[bits])
f.add_statements(Switch(en_bit, { 1: write_data }))
else:
write_data = self.memory._array[self.addr].eq(self.data)
f.add_statements(Switch(self.en, { 1: write_data }))
for signal in self.memory._array:
f.add_driver(signal, self.domain)
return f

View file

@ -4,6 +4,7 @@ from .tools import *
from ..tools import flatten, union from ..tools import flatten, union
from ..hdl.ast import * from ..hdl.ast import *
from ..hdl.cd import * from ..hdl.cd import *
from ..hdl.mem import *
from ..hdl.dsl import * from ..hdl.dsl import *
from ..hdl.ir import * from ..hdl.ir import *
from ..back.pysim import * from ..back.pysim import *
@ -390,3 +391,113 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
yield 1 yield 1
yield Delay() yield Delay()
sim.add_process(process) sim.add_process(process)
def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None):
self.m = Module()
self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
self.m.submodules.rdport = self.rdport = \
self.memory.read_port(synchronous=rd_synchronous, transparent=rd_transparent)
self.m.submodules.wrport = self.wrport = \
self.memory.write_port(granularity=wr_granularity)
def test_memory_init(self):
self.setUp_memory()
with self.assertSimulation(self.m) as sim:
def process():
yield
self.assertEqual((yield self.rdport.data), 0xaa)
yield self.rdport.addr.eq(1)
yield
self.assertEqual((yield self.rdport.data), 0x55)
yield self.rdport.addr.eq(2)
yield
self.assertEqual((yield self.rdport.data), 0x00)
sim.add_clock(1e-6)
sim.add_sync_process(process)
def test_memory_write(self):
self.setUp_memory()
with self.assertSimulation(self.m) as sim:
def process():
yield self.wrport.addr.eq(4)
yield self.wrport.data.eq(0x33)
yield self.wrport.en.eq(1)
yield
yield self.wrport.en.eq(0)
yield self.rdport.addr.eq(4)
yield
self.assertEqual((yield self.rdport.data), 0x33)
sim.add_clock(1e-6)
sim.add_sync_process(process)
def test_memory_write_granularity(self):
self.setUp_memory(wr_granularity=4)
with self.assertSimulation(self.m) as sim:
def process():
yield self.wrport.data.eq(0x50)
yield self.wrport.en.eq(0b00)
yield
yield self.wrport.en.eq(0)
yield
self.assertEqual((yield self.rdport.data), 0xaa)
yield self.wrport.en.eq(0b10)
yield
yield self.wrport.en.eq(0)
yield
self.assertEqual((yield self.rdport.data), 0x5a)
yield self.wrport.data.eq(0x33)
yield self.wrport.en.eq(0b01)
yield
yield self.wrport.en.eq(0)
yield
self.assertEqual((yield self.rdport.data), 0x53)
sim.add_clock(1e-6)
sim.add_sync_process(process)
def test_memory_read_before_write(self):
self.setUp_memory(rd_transparent=False)
with self.assertSimulation(self.m) as sim:
def process():
yield self.wrport.data.eq(0x33)
yield self.wrport.en.eq(1)
yield self.rdport.en.eq(1)
yield
self.assertEqual((yield self.rdport.data), 0xaa)
yield Delay(1e-6) # let comb propagate
self.assertEqual((yield self.rdport.data), 0xaa)
sim.add_clock(1e-6)
sim.add_sync_process(process)
def test_memory_write_through(self):
self.setUp_memory(rd_transparent=True)
with self.assertSimulation(self.m) as sim:
def process():
yield self.wrport.data.eq(0x33)
yield self.wrport.en.eq(1)
yield
self.assertEqual((yield self.rdport.data), 0xaa)
yield Delay(1e-6) # let comb propagate
self.assertEqual((yield self.rdport.data), 0x33)
sim.add_clock(1e-6)
sim.add_sync_process(process)
def test_memory_async_read_write(self):
self.setUp_memory(rd_synchronous=False)
with self.assertSimulation(self.m) as sim:
def process():
yield self.rdport.addr.eq(0)
yield Delay()
self.assertEqual((yield self.rdport.data), 0xaa)
yield self.rdport.addr.eq(1)
yield Delay()
self.assertEqual((yield self.rdport.data), 0x55)
yield self.rdport.addr.eq(0)
yield self.wrport.addr.eq(0)
yield self.wrport.data.eq(0x33)
yield self.wrport.en.eq(1)
yield Tick("sync")
self.assertEqual((yield self.rdport.data), 0xaa)
yield Delay(1e-6) # let comb propagate
self.assertEqual((yield self.rdport.data), 0x33)
sim.add_clock(1e-6)
sim.add_process(process)