hdl.mem: cast reset value for transparent read ports to integer.
[nmigen.git] / nmigen / hdl / mem.py
1 import operator
2 from collections import OrderedDict
3
4 from .. import tracer
5 from .ast import *
6 from .ir import Elaboratable, Instance
7
8
9 __all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"]
10
11
12 class Memory:
13 """A word addressable storage.
14
15 Parameters
16 ----------
17 width : int
18 Access granularity. Each storage element of this memory is ``width`` bits in size.
19 depth : int
20 Word count. This memory contains ``depth`` storage elements.
21 init : list of int
22 Initial values. At power on, each storage element in this memory is initialized to
23 the corresponding element of ``init``, if any, or to zero otherwise.
24 Uninitialized memories are not currently supported.
25 name : str
26 Name hint for this memory. If ``None`` (default) the name is inferred from the variable
27 name this ``Signal`` is assigned to.
28 attrs : dict
29 Dictionary of synthesis attributes.
30
31 Attributes
32 ----------
33 width : int
34 depth : int
35 init : list of int
36 attrs : dict
37 """
38 def __init__(self, *, width, depth, init=None, name=None, attrs=None, simulate=True):
39 if not isinstance(width, int) or width < 0:
40 raise TypeError("Memory width must be a non-negative integer, not {!r}"
41 .format(width))
42 if not isinstance(depth, int) or depth < 0:
43 raise TypeError("Memory depth must be a non-negative integer, not {!r}"
44 .format(depth))
45
46 self.name = name or tracer.get_var_name(depth=2, default="$memory")
47 self.src_loc = tracer.get_src_loc()
48
49 self.width = width
50 self.depth = depth
51 self.attrs = OrderedDict(() if attrs is None else attrs)
52
53 # Array of signals for simulation.
54 self._array = Array()
55 if simulate:
56 for addr in range(self.depth):
57 self._array.append(Signal(self.width, name="{}({})"
58 .format(name or "memory", addr)))
59
60 self.init = init
61
62 @property
63 def init(self):
64 return self._init
65
66 @init.setter
67 def init(self, new_init):
68 self._init = [] if new_init is None else list(new_init)
69 if len(self.init) > self.depth:
70 raise ValueError("Memory initialization value count exceed memory depth ({} > {})"
71 .format(len(self.init), self.depth))
72
73 try:
74 for addr in range(len(self._array)):
75 if addr < len(self._init):
76 self._array[addr].reset = operator.index(self._init[addr])
77 else:
78 self._array[addr].reset = 0
79 except TypeError as e:
80 raise TypeError("Memory initialization value at address {:x}: {}"
81 .format(addr, e)) from None
82
83 def read_port(self, *, src_loc_at=0, **kwargs):
84 return ReadPort(self, src_loc_at=1 + src_loc_at, **kwargs)
85
86 def write_port(self, *, src_loc_at=0, **kwargs):
87 return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs)
88
89 def __getitem__(self, index):
90 """Simulation only."""
91 return self._array[index]
92
93
94 class ReadPort(Elaboratable):
95 def __init__(self, memory, *, domain="sync", transparent=True, src_loc_at=0):
96 if domain == "comb" and not transparent:
97 raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent")
98
99 self.memory = memory
100 self.domain = domain
101 self.transparent = transparent
102
103 self.addr = Signal(range(memory.depth),
104 name="{}_r_addr".format(memory.name), src_loc_at=1 + src_loc_at)
105 self.data = Signal(memory.width,
106 name="{}_r_data".format(memory.name), src_loc_at=1 + src_loc_at)
107 if self.domain != "comb" and not transparent:
108 self.en = Signal(name="{}_r_en".format(memory.name), reset=1,
109 src_loc_at=1 + src_loc_at)
110 else:
111 self.en = Const(1)
112
113 def elaborate(self, platform):
114 f = Instance("$memrd",
115 p_MEMID=self.memory,
116 p_ABITS=self.addr.width,
117 p_WIDTH=self.data.width,
118 p_CLK_ENABLE=self.domain != "comb",
119 p_CLK_POLARITY=1,
120 p_TRANSPARENT=self.transparent,
121 i_CLK=ClockSignal(self.domain) if self.domain != "comb" else Const(0),
122 i_EN=self.en,
123 i_ADDR=self.addr,
124 o_DATA=self.data,
125 )
126 if self.domain == "comb":
127 # Asynchronous port
128 f.add_statements(self.data.eq(self.memory._array[self.addr]))
129 f.add_driver(self.data)
130 elif not self.transparent:
131 # Synchronous, read-before-write port
132 f.add_statements(
133 Switch(self.en, {
134 1: self.data.eq(self.memory._array[self.addr])
135 })
136 )
137 f.add_driver(self.data, self.domain)
138 else:
139 # Synchronous, write-through port
140 # This model is a bit unconventional. We model transparent ports as asynchronous ports
141 # that are latched when the clock is high. This isn't exactly correct, but it is very
142 # close to the correct behavior of a transparent port, and the difference should only
143 # be observable in pathological cases of clock gating. A register is injected to
144 # the address input to achieve the correct address-to-data latency. Also, the reset
145 # value of the data output is forcibly set to the 0th initial value, if any--note that
146 # many FPGAs do not guarantee this behavior!
147 if len(self.memory.init) > 0:
148 self.data.reset = operator.index(self.memory.init[0])
149 latch_addr = Signal.like(self.addr)
150 f.add_statements(
151 latch_addr.eq(self.addr),
152 Switch(ClockSignal(self.domain), {
153 0: self.data.eq(self.data),
154 1: self.data.eq(self.memory._array[latch_addr]),
155 }),
156 )
157 f.add_driver(latch_addr, self.domain)
158 f.add_driver(self.data)
159 return f
160
161
162 class WritePort(Elaboratable):
163 def __init__(self, memory, *, domain="sync", granularity=None, src_loc_at=0):
164 if granularity is None:
165 granularity = memory.width
166 if not isinstance(granularity, int) or granularity < 0:
167 raise TypeError("Write port granularity must be a non-negative integer, not {!r}"
168 .format(granularity))
169 if granularity > memory.width:
170 raise ValueError("Write port granularity must not be greater than memory width "
171 "({} > {})"
172 .format(granularity, memory.width))
173 if memory.width // granularity * granularity != memory.width:
174 raise ValueError("Write port granularity must divide memory width evenly")
175
176 self.memory = memory
177 self.domain = domain
178 self.granularity = granularity
179
180 self.addr = Signal(range(memory.depth),
181 name="{}_w_addr".format(memory.name), src_loc_at=1 + src_loc_at)
182 self.data = Signal(memory.width,
183 name="{}_w_data".format(memory.name), src_loc_at=1 + src_loc_at)
184 self.en = Signal(memory.width // granularity,
185 name="{}_w_en".format(memory.name), src_loc_at=1 + src_loc_at)
186
187 def elaborate(self, platform):
188 f = Instance("$memwr",
189 p_MEMID=self.memory,
190 p_ABITS=self.addr.width,
191 p_WIDTH=self.data.width,
192 p_CLK_ENABLE=1,
193 p_CLK_POLARITY=1,
194 p_PRIORITY=0,
195 i_CLK=ClockSignal(self.domain),
196 i_EN=Cat(Repl(en_bit, self.granularity) for en_bit in self.en),
197 i_ADDR=self.addr,
198 i_DATA=self.data,
199 )
200 if len(self.en) > 1:
201 for index, en_bit in enumerate(self.en):
202 offset = index * self.granularity
203 bits = slice(offset, offset + self.granularity)
204 write_data = self.memory._array[self.addr][bits].eq(self.data[bits])
205 f.add_statements(Switch(en_bit, { 1: write_data }))
206 else:
207 write_data = self.memory._array[self.addr].eq(self.data)
208 f.add_statements(Switch(self.en, { 1: write_data }))
209 for signal in self.memory._array:
210 f.add_driver(signal, self.domain)
211 return f
212
213
214 class DummyPort:
215 """Dummy memory port.
216
217 This port can be used in place of either a read or a write port for testing and verification.
218 It does not include any read/write port specific attributes, i.e. none besides ``"domain"``;
219 any such attributes may be set manually.
220 """
221 def __init__(self, *, data_width, addr_width, domain="sync", name=None, granularity=None):
222 self.domain = domain
223
224 if granularity is None:
225 granularity = data_width
226 if name is None:
227 name = tracer.get_var_name(depth=2, default="dummy")
228
229 self.addr = Signal(addr_width,
230 name="{}_addr".format(name), src_loc_at=1)
231 self.data = Signal(data_width,
232 name="{}_data".format(name), src_loc_at=1)
233 self.en = Signal(data_width // granularity,
234 name="{}_en".format(name), src_loc_at=1)