hdl.mem: document ReadPort and WritePort.
[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 """Get a read port.
85
86 See :class:`ReadPort` for details.
87
88 Arguments
89 ---------
90 domain : str
91 transparent : bool
92
93 Returns
94 -------
95 An instance of :class:`ReadPort` associated with this memory.
96 """
97 return ReadPort(self, src_loc_at=1 + src_loc_at, **kwargs)
98
99 def write_port(self, *, src_loc_at=0, **kwargs):
100 """Get a write port.
101
102 See :class:`WritePort` for details.
103
104 Arguments
105 ---------
106 domain : str
107 granularity : int
108
109 Returns
110 -------
111 An instance of :class:`WritePort` associated with this memory.
112 """
113 return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs)
114
115 def __getitem__(self, index):
116 """Simulation only."""
117 return self._array[index]
118
119
120 class ReadPort(Elaboratable):
121 """A memory read port.
122
123 Parameters
124 ----------
125 memory : :class:`Memory`
126 Memory associated with the port.
127 domain : str
128 Clock domain. Defaults to ``"sync"``. If set to ``"comb"``, the port is asynchronous.
129 Otherwise, the read data becomes available on the next clock cycle.
130 transparent : bool
131 Port transparency. If set (default), a read at an address that is also being written to in
132 the same clock cycle will output the new value. Otherwise, the old value will be output
133 first. This behavior only applies to ports in the same domain.
134
135 Attributes
136 ----------
137 memory : :class:`Memory`
138 domain : str
139 transparent : bool
140 addr : Signal(range(memory.depth)), in
141 Read address.
142 data : Signal(memory.width), out
143 Read data.
144 en : Signal or Const, in
145 Read enable. If asserted, ``data`` is updated with the word stored at ``addr``. Note that
146 transparent ports cannot assign ``en`` (which is hardwired to 1 instead), as doing so is
147 currently not supported by Yosys.
148
149 Exceptions
150 ----------
151 Raises :exn:`ValueError` if the read port is simultaneously asynchronous and non-transparent.
152 """
153 def __init__(self, memory, *, domain="sync", transparent=True, src_loc_at=0):
154 if domain == "comb" and not transparent:
155 raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent")
156
157 self.memory = memory
158 self.domain = domain
159 self.transparent = transparent
160
161 self.addr = Signal(range(memory.depth),
162 name="{}_r_addr".format(memory.name), src_loc_at=1 + src_loc_at)
163 self.data = Signal(memory.width,
164 name="{}_r_data".format(memory.name), src_loc_at=1 + src_loc_at)
165 if self.domain != "comb" and not transparent:
166 self.en = Signal(name="{}_r_en".format(memory.name), reset=1,
167 src_loc_at=1 + src_loc_at)
168 else:
169 self.en = Const(1)
170
171 def elaborate(self, platform):
172 f = Instance("$memrd",
173 p_MEMID=self.memory,
174 p_ABITS=self.addr.width,
175 p_WIDTH=self.data.width,
176 p_CLK_ENABLE=self.domain != "comb",
177 p_CLK_POLARITY=1,
178 p_TRANSPARENT=self.transparent,
179 i_CLK=ClockSignal(self.domain) if self.domain != "comb" else Const(0),
180 i_EN=self.en,
181 i_ADDR=self.addr,
182 o_DATA=self.data,
183 )
184 if self.domain == "comb":
185 # Asynchronous port
186 f.add_statements(self.data.eq(self.memory._array[self.addr]))
187 f.add_driver(self.data)
188 elif not self.transparent:
189 # Synchronous, read-before-write port
190 f.add_statements(
191 Switch(self.en, {
192 1: self.data.eq(self.memory._array[self.addr])
193 })
194 )
195 f.add_driver(self.data, self.domain)
196 else:
197 # Synchronous, write-through port
198 # This model is a bit unconventional. We model transparent ports as asynchronous ports
199 # that are latched when the clock is high. This isn't exactly correct, but it is very
200 # close to the correct behavior of a transparent port, and the difference should only
201 # be observable in pathological cases of clock gating. A register is injected to
202 # the address input to achieve the correct address-to-data latency. Also, the reset
203 # value of the data output is forcibly set to the 0th initial value, if any--note that
204 # many FPGAs do not guarantee this behavior!
205 if len(self.memory.init) > 0:
206 self.data.reset = operator.index(self.memory.init[0])
207 latch_addr = Signal.like(self.addr)
208 f.add_statements(
209 latch_addr.eq(self.addr),
210 Switch(ClockSignal(self.domain), {
211 0: self.data.eq(self.data),
212 1: self.data.eq(self.memory._array[latch_addr]),
213 }),
214 )
215 f.add_driver(latch_addr, self.domain)
216 f.add_driver(self.data)
217 return f
218
219
220 class WritePort(Elaboratable):
221 """A memory write port.
222
223 Parameters
224 ----------
225 memory : :class:`Memory`
226 Memory associated with the port.
227 domain : str
228 Clock domain. Defaults to ``"sync"``. Writes have a latency of 1 clock cycle.
229 granularity : int
230 Port granularity. Defaults to ``memory.width``. Write data is split evenly in
231 ``memory.width // granularity`` chunks, which can be updated independently.
232
233 Attributes
234 ----------
235 memory : :class:`Memory`
236 domain : str
237 granularity : int
238 addr : Signal(range(memory.depth)), in
239 Write address.
240 data : Signal(memory.width), in
241 Write data.
242 en : Signal(memory.width // granularity), in
243 Write enable. Each bit selects a non-overlapping chunk of ``granularity`` bits on the
244 ``data`` signal, which is written to memory at ``addr``. Unselected chunks are ignored.
245
246 Exceptions
247 ----------
248 Raises :exn:`ValueError` if the write port granularity is greater than memory width, or does not
249 divide memory width evenly.
250 """
251 def __init__(self, memory, *, domain="sync", granularity=None, src_loc_at=0):
252 if granularity is None:
253 granularity = memory.width
254 if not isinstance(granularity, int) or granularity < 0:
255 raise TypeError("Write port granularity must be a non-negative integer, not {!r}"
256 .format(granularity))
257 if granularity > memory.width:
258 raise ValueError("Write port granularity must not be greater than memory width "
259 "({} > {})"
260 .format(granularity, memory.width))
261 if memory.width // granularity * granularity != memory.width:
262 raise ValueError("Write port granularity must divide memory width evenly")
263
264 self.memory = memory
265 self.domain = domain
266 self.granularity = granularity
267
268 self.addr = Signal(range(memory.depth),
269 name="{}_w_addr".format(memory.name), src_loc_at=1 + src_loc_at)
270 self.data = Signal(memory.width,
271 name="{}_w_data".format(memory.name), src_loc_at=1 + src_loc_at)
272 self.en = Signal(memory.width // granularity,
273 name="{}_w_en".format(memory.name), src_loc_at=1 + src_loc_at)
274
275 def elaborate(self, platform):
276 f = Instance("$memwr",
277 p_MEMID=self.memory,
278 p_ABITS=self.addr.width,
279 p_WIDTH=self.data.width,
280 p_CLK_ENABLE=1,
281 p_CLK_POLARITY=1,
282 p_PRIORITY=0,
283 i_CLK=ClockSignal(self.domain),
284 i_EN=Cat(Repl(en_bit, self.granularity) for en_bit in self.en),
285 i_ADDR=self.addr,
286 i_DATA=self.data,
287 )
288 if len(self.en) > 1:
289 for index, en_bit in enumerate(self.en):
290 offset = index * self.granularity
291 bits = slice(offset, offset + self.granularity)
292 write_data = self.memory._array[self.addr][bits].eq(self.data[bits])
293 f.add_statements(Switch(en_bit, { 1: write_data }))
294 else:
295 write_data = self.memory._array[self.addr].eq(self.data)
296 f.add_statements(Switch(self.en, { 1: write_data }))
297 for signal in self.memory._array:
298 f.add_driver(signal, self.domain)
299 return f
300
301
302 class DummyPort:
303 """Dummy memory port.
304
305 This port can be used in place of either a read or a write port for testing and verification.
306 It does not include any read/write port specific attributes, i.e. none besides ``"domain"``;
307 any such attributes may be set manually.
308 """
309 def __init__(self, *, data_width, addr_width, domain="sync", name=None, granularity=None):
310 self.domain = domain
311
312 if granularity is None:
313 granularity = data_width
314 if name is None:
315 name = tracer.get_var_name(depth=2, default="dummy")
316
317 self.addr = Signal(addr_width,
318 name="{}_addr".format(name), src_loc_at=1)
319 self.data = Signal(data_width,
320 name="{}_data".format(name), src_loc_at=1)
321 self.en = Signal(data_width // granularity,
322 name="{}_en".format(name), src_loc_at=1)