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
):
84 return ReadPort(self
, src_loc_at
=1 + src_loc_at
, **kwargs
)
86 def write_port(self
, *, src_loc_at
=0, **kwargs
):
87 return WritePort(self
, src_loc_at
=1 + src_loc_at
, **kwargs
)
89 def __getitem__(self
, index
):
90 """Simulation only."""
91 return self
._array
[index
]
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")
101 self
.transparent
= transparent
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
)
113 def elaborate(self
, platform
):
114 f
= Instance("$memrd",
116 p_ABITS
=self
.addr
.width
,
117 p_WIDTH
=self
.data
.width
,
118 p_CLK_ENABLE
=self
.domain
!= "comb",
120 p_TRANSPARENT
=self
.transparent
,
121 i_CLK
=ClockSignal(self
.domain
) if self
.domain
!= "comb" else Const(0),
126 if self
.domain
== "comb":
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
134 1: self
.data
.eq(self
.memory
._array
[self
.addr
])
137 f
.add_driver(self
.data
, self
.domain
)
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
)
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
]),
157 f
.add_driver(latch_addr
, self
.domain
)
158 f
.add_driver(self
.data
)
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 "
172 .format(granularity
, memory
.width
))
173 if memory
.width
// granularity
* granularity
!= memory
.width
:
174 raise ValueError("Write port granularity must divide memory width evenly")
178 self
.granularity
= granularity
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
)
187 def elaborate(self
, platform
):
188 f
= Instance("$memwr",
190 p_ABITS
=self
.addr
.width
,
191 p_WIDTH
=self
.data
.width
,
195 i_CLK
=ClockSignal(self
.domain
),
196 i_EN
=Cat(Repl(en_bit
, self
.granularity
) for en_bit
in self
.en
),
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
}))
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
)
215 """Dummy memory port.
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.
221 def __init__(self
, *, data_width
, addr_width
, domain
="sync", name
=None, granularity
=None):
224 if granularity
is None:
225 granularity
= data_width
227 name
= tracer
.get_var_name(depth
=2, default
="dummy")
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)