2 from collections
import OrderedDict
6 from .ir
import Elaboratable
, Instance
9 __all__
= ["Memory", "ReadPort", "WritePort", "DummyPort"]
13 """A word addressable storage.
18 Access granularity. Each storage element of this memory is ``width`` bits in size.
20 Word count. This memory contains ``depth`` storage elements.
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.
26 Name hint for this memory. If ``None`` (default) the name is inferred from the variable
27 name this ``Signal`` is assigned to.
29 Dictionary of synthesis attributes.
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}"
42 if not isinstance(depth
, int) or depth
< 0:
43 raise TypeError("Memory depth must be a non-negative integer, not {!r}"
46 self
.name
= name
or tracer
.get_var_name(depth
=2, default
="$memory")
47 self
.src_loc
= tracer
.get_src_loc()
51 self
.attrs
= OrderedDict(() if attrs
is None else attrs
)
53 # Array of signals for simulation.
56 for addr
in range(self
.depth
):
57 self
._array
.append(Signal(self
.width
, name
="{}({})"
58 .format(name
or "memory", addr
)))
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
))
74 for addr
in range(len(self
._array
)):
75 if addr
< len(self
._init
):
76 self
._array
[addr
].reset
= operator
.index(self
._init
[addr
])
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
83 def read_port(self
, *, src_loc_at
=0, **kwargs
):
86 See :class:`ReadPort` for details.
95 An instance of :class:`ReadPort` associated with this memory.
97 return ReadPort(self
, src_loc_at
=1 + src_loc_at
, **kwargs
)
99 def write_port(self
, *, src_loc_at
=0, **kwargs
):
102 See :class:`WritePort` for details.
111 An instance of :class:`WritePort` associated with this memory.
113 return WritePort(self
, src_loc_at
=1 + src_loc_at
, **kwargs
)
115 def __getitem__(self
, index
):
116 """Simulation only."""
117 return self
._array
[index
]
120 class ReadPort(Elaboratable
):
121 """A memory read port.
125 memory : :class:`Memory`
126 Memory associated with the port.
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.
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.
137 memory : :class:`Memory`
140 addr : Signal(range(memory.depth)), in
142 data : Signal(memory.width), out
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.
151 Raises :exn:`ValueError` if the read port is simultaneously asynchronous and non-transparent.
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")
159 self
.transparent
= transparent
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
)
171 def elaborate(self
, platform
):
172 f
= Instance("$memrd",
174 p_ABITS
=self
.addr
.width
,
175 p_WIDTH
=self
.data
.width
,
176 p_CLK_ENABLE
=self
.domain
!= "comb",
178 p_TRANSPARENT
=self
.transparent
,
179 i_CLK
=ClockSignal(self
.domain
) if self
.domain
!= "comb" else Const(0),
184 if self
.domain
== "comb":
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
192 1: self
.data
.eq(self
.memory
._array
[self
.addr
])
195 f
.add_driver(self
.data
, self
.domain
)
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
)
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
]),
215 f
.add_driver(latch_addr
, self
.domain
)
216 f
.add_driver(self
.data
)
220 class WritePort(Elaboratable
):
221 """A memory write port.
225 memory : :class:`Memory`
226 Memory associated with the port.
228 Clock domain. Defaults to ``"sync"``. Writes have a latency of 1 clock cycle.
230 Port granularity. Defaults to ``memory.width``. Write data is split evenly in
231 ``memory.width // granularity`` chunks, which can be updated independently.
235 memory : :class:`Memory`
238 addr : Signal(range(memory.depth)), in
240 data : Signal(memory.width), in
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.
248 Raises :exn:`ValueError` if the write port granularity is greater than memory width, or does not
249 divide memory width evenly.
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 "
260 .format(granularity
, memory
.width
))
261 if memory
.width
// granularity
* granularity
!= memory
.width
:
262 raise ValueError("Write port granularity must divide memory width evenly")
266 self
.granularity
= granularity
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
)
275 def elaborate(self
, platform
):
276 f
= Instance("$memwr",
278 p_ABITS
=self
.addr
.width
,
279 p_WIDTH
=self
.data
.width
,
283 i_CLK
=ClockSignal(self
.domain
),
284 i_EN
=Cat(Repl(en_bit
, self
.granularity
) for en_bit
in self
.en
),
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
}))
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
)
303 """Dummy memory port.
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.
309 def __init__(self
, *, data_width
, addr_width
, domain
="sync", name
=None, granularity
=None):
312 if granularity
is None:
313 granularity
= data_width
315 name
= tracer
.get_var_name(depth
=2, default
="dummy")
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)