Support non-initialisable memories #824
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
In order to use memory types that don't support initialisation (e.g. iCE40 SPRAM), we need to be able to create memories without initialising them.
In a1e2c75, we check for a truthy
memory.init
, as the default value (None
) is coalesced to[]
in the setter:amaranth/amaranth/hdl/mem.py
Lines 66 to 68 in b1cce87
We could equally check if
len(memory.init) > 0
.Potential for breaking behaviour?
This does have the potential for wider-reaching impact on behaviour, for memory types that do support initialisation. Previously, a
None
or empty init would explicitly zero-initialise the entire memory, and indeed, in simulation this will still be the case, but now no$meminit
cell would be emitted and the exact behaviour is defined by the backend/target. To explicitly force zero-init as currently happens, one would need to supply at minimumMemory(init=[0])
.In practice, after Yosys is done, e.g. for
SB_RAM40_4K
, theINIT_0
–INIT_F
parameters to the target memory cell becomexxxx...
. By the time nextpnr is done with it, the corresponding.ram_data
in the bitstream have all zeroes.Changed meaning of
init
valuesI've included a second commit 1021e34, which is even more of a suggestion than the first, which updates the docstring/tests and modifies the behaviour of the attribute slightly. This moves the boundary of behaviour change; we now distinguish between:
init=None
(no explicit initialisation), andinit=[]
(zero-init).The type of
Memory.init
now also admitsNone
, which might bite users whose code relied on reading back theirmemory.init
assuming it would always be alist[int]
.Assuming it's OK to rely on the assumption "initialisable RAM cells that aren't explicitly initialised will get zero-initted anyway later on in the process by the backend" like above, I think the reasonable choice is to keep the default
init=None
. The API is conceptually simple: noinit
specified means no specified initialisation.Note on possible futures
There could be surprising consequences if that assumption stops holding. Continuing with the
SB_RAM40_4K
example, even though currently initial data is always included in the bitstream output by the Yosys/nextpnr flow, Memory Usage Guide for iCE40 Devices (p6) suggests this is optional. If this optionality were realised down the track, in sum these changes would mean EBR that was previously reliably zero-initialised would cease to be so.Thus, depending on our tolerance for possibly breaking changes, we may prefer a default of
init=[]
, which will zero-initialise all storage elements like the current default. Uninitialised memory would be opt-in only. There would still be the potential to break existing Amaranth designs (i.e. under the above scenario where the backend behaviour changes), as users may be explicitly passingNone
expecting zero-init.Alternative solutions
init
parameter (e.g.mem.UNINITIALIZED
) and retain all existing behaviour.Memory
to seem.Instance
and are not usable with Amaranth memory primitives.