Skip to content

Commit 055ffd6

Browse files
authored
RFC #62: The MemoryData class
2 parents f182996 + 8eff396 commit 055ffd6

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed

text/0062-memory-data.md

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
- Start Date: 2024-03-25
2+
- RFC PR: [amaranth-lang/rfcs#62](https://github.com/amaranth-lang/rfcs/pull/62)
3+
- Amaranth Issue: [amaranth-lang/amaranth#1241](https://github.com/amaranth-lang/amaranth/issues/1241)
4+
5+
# The `MemoryData` class
6+
7+
## Summary
8+
[summary]: #summary
9+
10+
A new class, `amaranth.hdl.MemoryData`, is added to represent the data and identity of a memory. It is used to reference the memory in simulation.
11+
12+
## Motivation
13+
[motivation]: #motivation
14+
15+
It is commonly useful to access a memory in a simulation testbench without having to create a special port for it. This requires storing some kind of a reference to the memory on the elaboratables.
16+
17+
Currently, the object used for this is of a private `MemoryIdentity` class. It is implicitly created by `lib.memory.Memory` constructor, and passed to the private `_MemorySim{Read|Write}` objects when `__getitem__` is called. This has a few problems:
18+
19+
- the `Memory` needs to be instantiated in the constructor of the containing elaboratable; combined with its mutability and the occasional need to defer memory port creation to `elaborate`, this results in elaboratables that break when elaborated more than once
20+
- `amaranth.sim` currently requires a gross hack to recognize `Memory` objects in `traces`
21+
- occasionally, it is useful to have `Signal`s that are not included in the design proper, but are used to communicate between simulator processes; it could be likewise useful with memories, but it cannot work with the current code (since the `MemoryIdentity` that the simulator gets doesn't have enough information to actually create backing storage for the memory)
22+
- `MemoryIdentity` nor `MemoryInstance` don't contain information about the element shape, which would require further wrappers on `lib.Memory` to perform shape conversion in post-RFC 36 world
23+
24+
The proposed `MemoryData` class:
25+
26+
- replaces `MemoryIdentity` and serves as the reference point for the simulator
27+
- encapsulates the memory's shape, depth, and initial value (so the simulator can use it to create backing storage)
28+
- in the common scenario, is created by the user in elaboratable constructor, stored as an attribute on the elaboratable, then passed to `Memory` constructor in `elaborate`
29+
30+
## Guide-level explanation
31+
[guide-level-explanation]: #guide-level-explanation
32+
33+
If the memory is to be accessible in simulation, the code to create a memory changes from:
34+
35+
```py
36+
m.submodules.memory = memory = Memory(shape=..., depth=..., init=...)
37+
port = memory.read_port(...)
38+
```
39+
40+
to:
41+
42+
```py
43+
# in __init__
44+
self.mem_data = MemoryData(shape=..., depth=..., init=...)
45+
# in elaborate
46+
m.submodules.memory = memory = Memory(self.mem_data)
47+
port = memory.read_port(...)
48+
```
49+
50+
The `my_component.mem_data` object can then be used in simulation to read and write memory:
51+
52+
```py
53+
addr = 0x1234
54+
row = sim.get(mem_data[addr])
55+
row += 1
56+
sim.set(mem_data[addr], row)
57+
```
58+
59+
The old way of creating memories is still supported, though somewhat less flexible.
60+
61+
## Reference-level explanation
62+
[reference-level-explanation]: #reference-level-explanation
63+
64+
Two new classes are added:
65+
66+
- `amaranth.hdl.MemoryData(*, shape: ShapeLike, depth: int, init: Iterable[int | Any], name=None)`: represents a memory's data storage. `name`, if not specified, defaults to the variable name used to store the `MemoryData`, like it does for `Signal`.
67+
- `__getitem__(self, addr: int) -> MemoryData._Row | ValueCastable`: creates a `MemoryData._Row` object; if `self.shape` is a `ShapeCastable`, the `MemoryData._Row` object constructed is immediately wrapped via `ShapeCastable.__call__`
68+
- `amaranth.hdl.MemoryData._Row` (subclass of `Value`): represents a single row of `MemoryData`, has no public constructor nor operations (other than ones derived from `Value`), can only be used in simulator processes and testbenches
69+
70+
The `MemoryData` class allows access to its constructor arguments via read-only properties.
71+
72+
The `lib.memory.Memory.Init` class is moved to `amaranth.hdl.MemoryData.Init`. It is used for the `init` property of `MemoryData`.
73+
74+
The `Memory` constructor is changed to:
75+
76+
- `amaranth.lib.memory.Memory(data: MemoryData = None, *, shape=None, depth=None, init=None, name=None)`
77+
78+
- either `data`, or all three of `shape`, `depth`, `init` need to be provided, but not both
79+
- if `data` is provided, it is used directly, and stored
80+
- if `shape`, `depth`, `init` (and possibly `name`) are provided, they are used to create a `MemoryData`, which is then stored
81+
82+
The `MemoryData` object is accessible via a new read-only `data` property on `Memory`. The existing `shape`, `depth`, `init` properties become aliases for `data.shape`, `data.depth`, `data.init`.
83+
84+
`MemoryInstance` constructor is likewise changed to:
85+
86+
- `amaranth.hdl.MemoryInstance(data: MemoryData, *, attrs={})`
87+
88+
The `sim.memory_read` and `sim.memory_write` methods proposed by RFC 36 are removed. Instead, the new `Memory._Row` simulation-only value is introduced, which can be passed to `get`/`set`/`changed` like any other value. Masked writes can be implemented by `set` with a slice of `MemoryData._Row`, just like for signals.
89+
90+
`MemoryData` and `MemoryData._Row` instances (possibly wrapped in `ShapeCastable` for the latter) can be added to the `traces` argument when writing a VCD file with the simulator.
91+
92+
Using `MemoryData._Row` within an elaboratable results in an immediate error, even if the design is only to be used in simulation. The only place where `MemoryData._Row` is valid is within an argument to `sim.get`/`sim.set`/`sim.changed` and similar functions.
93+
94+
`sim.edge` remains restricted to plain `Signal` and single-bit slices thereof. `MemoryData._Row` is not supported.
95+
96+
## Drawbacks
97+
[drawbacks]: #drawbacks
98+
99+
None.
100+
101+
## Rationale and alternatives
102+
[rationale-and-alternatives]: #rationale-and-alternatives
103+
104+
`MemoryData` having `shape`, `depth`, and `init` is necessary to allow the simulator to create the underlying storage if the memory is not included in the design hierarchy, but is used to communicate between simulator processes.
105+
106+
## Prior art
107+
[prior-art]: #prior-art
108+
109+
`MemoryData` is conceptually equivalent to a 2D `Signal`, for simulation purposes. It thus follows similar rules.
110+
111+
## Unresolved questions
112+
[unresolved-questions]: #unresolved-questions
113+
114+
None.
115+
116+
## Future possibilities
117+
[future-possibilities]: #future-possibilities
118+
119+
A `MemoryData.Slice` class could be added, allowing a whole range of memory addresses to be `get`/`set` at once, monitored for changes with `changes`, or added to `traces`.
120+
121+
Support for `__getitem__(Value)` could be added (currently it would be blocked on CXXRTL capabilities).

0 commit comments

Comments
 (0)