§ 3.1.6. Scalable Memory Container¶
MemoryContainer is a scalable native memory device, not a controller or a sixth storage technology. It uses the same pins and behavior as the selected FF, SRAM, DRAM, ROM, or NOR-flash device in 3.1.1–3.1.5. There is no request queue, response register, or implicit transaction clock.
| Component | Description | Structural | Behavioral |
|---|---|---|---|
MemoryContainer<Model,W> |
Capacity-configurable native memory, backed by the selected technology's tested behavioral implementation | No | Yes |
The corresponding expanded implementation is the native device documented in that technology's section. The catalog factory reuses that device's behavioral implementation directly: there is no hidden circuit or second timing model. A MemoryController can drive either representation.
Configuration¶
W fixes the data-pin width: 8, 16, or 32 bits. N=depth_words is selected at construction. Capacity is N×W/8 bytes and must fit the 32-bit byte-address range. K=words_per_row describes physical row organization, not host paging. FF does not use K; the other container models accept K=1/2/4/8/16 dividing N.
| Parameter | Default | Meaning |
|---|---|---|
depth_words |
4 in C++; 4096 in the Python factory | Number of addressed words |
paged |
1 | Allocate host pages only when contents differ from the initial fill |
max_resident_bytes |
67,108,864 | Content allocation budget; DRAM also checks row metadata |
record_history |
1 | Record memory-content history |
max_history_bytes |
16,777,216 | Fail-fast history budget; no silent eviction |
Geometry and native timing parameters follow the selected device. guard_ticks and refresh_interval_ticks belong to controller/assembly configuration; they do not add behavior to a standalone container. In particular, a DRAM container does not refresh itself.
Host pages cover 4096 simulated bytes and store value/known masks. Untouched capacity does not allocate content pages. DRAM keeps metadata only for visited rows, and ROM parses only the supplied image prefix. Budgets are not a process-RSS cap: event history, objects, temporary buffers, and allocator overhead also consume memory. Disable memory and simulator histories separately for long performance runs.
Python creates a 1 MiB direct FF device as follows:
memory = circuit_backend.create_memory_container(
"MEMORY", "ff", data_width=32, depth_words=262144,
parameters={"paged": "1", "record_history": "0"},
)
The C++ factory is createMemoryContainer<32, circuit::memory::FFModel>("MEMORY", config); it constructs the native implementation through Component::createWithContext. Runtime-width code uses createMemoryContainer(name, width, technology, config). Catalog IDs are memory.<technology>.scalable-device.<width>. The memory.scalable.* IDs name transaction endpoints built around this device (the form every RV32I core uses), not the device contract itself. Those endpoints default to their fused behavioral form; expanding one in the explorer shows its controller around this compact native device, without expanding the storage into cells.
3.1.6.1. MemoryContainer<Model,W>¶
The selected technology fixes the native pin schema. Changing capacity does not change the pins; changing technology may.
IO pins¶
The following table is the FF-model interface used by the embedded example and strict single-cycle core.
| Pin | Direction | Width | Description |
|---|---|---|---|
WORD_ADDR |
Input | 32 | Word index, not byte address |
WRITE_DATA |
Input | W | Positioned store bits |
WRITE_MASK |
Input | W/8 | Enabled byte lanes |
WE, READ_EN |
Input | 1 | Rising-edge write enable; asynchronous read enable |
CLK, RST |
Input | 1 | Write clock; active-high storage reset |
READ_DATA |
Output | W | Selected word, or zero when disabled/out of range |
FAULT |
Output | 1 | Invalid active access; X for unknown required controls |
Other models expose the complete interfaces in SRAM, DRAM, ROM, and NOR flash, including DRAM ROW_OPEN and flash WRITE_ENABLED. They do not expose REQ/RSP pins. A universal requester connects to a matching technology controller, not directly to these native pins.
Behavior¶
| Model | Access and storage rules | Direct single-cycle CPU suitability |
|---|---|---|
| FF | Asynchronous read; masked write on LOW→HIGH CLK; native reset clears all words | Yes, with sufficient combinational settling time |
| SRAM | Asynchronous CE/WE/OE; address/data changes restart timed access phases | Reads can settle within a long cycle; writes need deliberate clock-boundary integration |
| DRAM | ACT, column READ/WRITE, PRECHARGE, REFRESH; destructive sampling, restore, retention | Requires command sequencing; not an instantaneous RAM |
| ROM | Asynchronous read; construction image with zero-filled tail; no runtime write | Read-only access can settle within a cycle |
| NOR flash | WREN, timed PROGRAM/ERASE, READ; programming clears bits and erase restores ones | Requires command sequencing for modifications |
The strict single-cycle system currently uses the FF model for both instruction and data storage. It ties native memory RST low so resetting the CPU preserves its images. PC, registers, and data-memory writes use the same edge. Capacity affects host storage, not a mandatory number of CPU cycles.
Image provisioning through initializeMemoryBytes is allowed only before the first evaluation. It is distinct from runtime pins and from reset; reset never reopens provisioning. ROM/flash provisioning describes an initial image, not an instruction that changes read-only/flash programming rules. Native-device references remain authoritative for all technology-specific behavior.
C++ example¶
This four-state FF reference spells out the direct-access behavior. It reuses only value helpers and bounded MemoryStorage, not the simulator's device algorithm. Native SRAM/DRAM/ROM/flash use the complete behavioral examples in their respective sections; wrapping them as containers adds no protocol state.
#include "modules/memory/DigitalMemory.hpp"
namespace memory_native_example {
using namespace circuit::memory_device;
template<size_t W> class FF {
MemoryStorage storage;
LogicValue previous = X;
public:
struct Inputs {
Bits WORD_ADDR = bits(32,0), WRITE_DATA = Bits(W,L),
WRITE_MASK = Bits(W/8,L);
LogicValue CLK=L, RST=L, WE=L, READ_EN=L;
};
struct Outputs { Bits READ_DATA; LogicValue FAULT; };
explicit FF(size_t words)
: storage(words,W,X,true,{true,64*1024*1024,16*1024*1024}) {}
Outputs evaluate(size_t time,const Inputs& i) {
const bool address_known=known(i.WORD_ADDR);
const bool mask_known=known(i.WRITE_MASK);
const auto word=number(i.WORD_ADDR);
const bool selected=address_known && word<storage.capacityWords();
LogicValue fault=L;
if(i.WE!=L || i.READ_EN!=L) {
if(!binary(i.WE)||!binary(i.READ_EN)||!address_known||(i.WE==H&&!mask_known)) fault=X;
else if(!selected) fault=H;
}
if(i.RST==H) storage.fill(time,L);
else if(!binary(i.RST)) storage.fill(time,X);
else if(previous==L && i.CLK==H && i.WE==H && selected && mask_known && fault==L) {
for(size_t byte=0;byte<W/8;++byte) if(i.WRITE_MASK[byte]==H)
storage.writeLogic(time,word*(W/8)+byte,
Bits(i.WRITE_DATA.begin()+byte*8,i.WRITE_DATA.begin()+(byte+1)*8),true);
}
previous=i.CLK;
Bits result(W,L);
if(!binary(i.READ_EN)||(i.READ_EN==H&&!address_known)) result.assign(W,X);
else if(i.READ_EN==H&&selected) result=storage.readLogic(word*(W/8),W/8);
return {result,fault};
}
};
}
Unknown inputs and timing¶
Stored Z becomes X. The FF model reads without any clock edge and writes only on a known LOW→HIGH transition. Unknown reset makes FF contents unknown; unknown required access controls produce FAULT=X. Its output propagation takes simulator ticks, so “single-cycle” does not mean zero delay. Native SRAM phases and DRAM/flash deadlines remain active; they are not converted into CPU-clock waits or removed by selecting a large capacity.
Implementation¶
The factory selects the existing native behavioral device and labels the leaf MemoryContainer<model,width>. FF reuses the FF-array behavior; SRAM reuses the complete access-sequence model; DRAM/ROM/NOR reuse their native command/read models. All share host allocation/provisioning support, not one universal cell algorithm.
For a visible transaction wrapper, request memory.<technology>.scalable-endpoint.<width>: its structural controller drives a scalable device. The endpoint's fused behavioral alternative is BehavioralMemoryEndpoint, documented in 3.3.
Examples¶
The FF scenario covers all four words, asynchronous reads, edge-triggered writes, byte masks, invalid addresses, and storage reset. It does not need a request-accept or response-consume edge.
All 52 recorded rows of NativeMemoryContainerTest, in checkpoint order. Outputs are test expectations verified against the simulation. Time is the absolute settled checkpoint time in simulator units, not milliseconds or CPU cycles.
Default scenario profile: canonical-default-preview / behavioral (1aaae4b751470580). Changing fidelity or the circuit can change these times.
| Checkpointzero-based number · timeline label | Timesimulator units | CLKInput · 1 bit |
READ_ENInput · 1 bit |
RSTInput · 1 bit |
WEInput · 1 bit |
WORD_ADDRInput · 32 bit |
WRITE_DATAInput · 8 bit |
WRITE_MASKInput · 1 bit |
FAULTExpected output · 1 bit |
READ_DATAExpected output · 8 bit |
|---|---|---|---|---|---|---|---|---|---|---|
#0initial-disabled | 17 | 0 |
0 |
1 |
0 |
0x00000000 |
0x00 |
1 |
0 |
0x00 |
#1release-reset | 18 | 0 |
0 |
0 |
0 |
0x00000000 |
0x00 |
1 |
0 |
0x00 |
#2setup-0 | 19 | 0 |
0 |
0 |
0 |
0x00000000 |
0x81 |
1 |
0 |
0x00 |
#3write-enable-0 | 22 | 0 |
0 |
0 |
1 |
0x00000000 |
0x81 |
1 |
0 |
0x00 |
#4write-edge-0 | 40 | 1 |
0 |
0 |
1 |
0x00000000 |
0x81 |
1 |
0 |
0x00 |
#5finish-write-0 | 43 | 0 |
0 |
0 |
0 |
0x00000000 |
0x81 |
1 |
0 |
0x00 |
#6read-0 | 46 | 0 |
1 |
0 |
0 |
0x00000000 |
0x81 |
1 |
0 |
0x81 |
#7setup-1 | 62 | 0 |
0 |
0 |
0 |
0x00000001 |
0x80 |
1 |
0 |
0x00 |
#8write-enable-1 | 65 | 0 |
0 |
0 |
1 |
0x00000001 |
0x80 |
1 |
0 |
0x00 |
#9write-edge-1 | 83 | 1 |
0 |
0 |
1 |
0x00000001 |
0x80 |
1 |
0 |
0x00 |
#10finish-write-1 | 86 | 0 |
0 |
0 |
0 |
0x00000001 |
0x80 |
1 |
0 |
0x00 |
#11read-1 | 89 | 0 |
1 |
0 |
0 |
0x00000001 |
0x80 |
1 |
0 |
0x80 |
#12setup-2 | 105 | 0 |
0 |
0 |
0 |
0x00000002 |
0x83 |
1 |
0 |
0x00 |
#13write-enable-2 | 108 | 0 |
0 |
0 |
1 |
0x00000002 |
0x83 |
1 |
0 |
0x00 |
#14write-edge-2 | 126 | 1 |
0 |
0 |
1 |
0x00000002 |
0x83 |
1 |
0 |
0x00 |
#15finish-write-2 | 129 | 0 |
0 |
0 |
0 |
0x00000002 |
0x83 |
1 |
0 |
0x00 |
#16read-2 | 132 | 0 |
1 |
0 |
0 |
0x00000002 |
0x83 |
1 |
0 |
0x83 |
#17setup-3 | 148 | 0 |
0 |
0 |
0 |
0x00000003 |
0x82 |
1 |
0 |
0x00 |
#18write-enable-3 | 151 | 0 |
0 |
0 |
1 |
0x00000003 |
0x82 |
1 |
0 |
0x00 |
#19write-edge-3 | 169 | 1 |
0 |
0 |
1 |
0x00000003 |
0x82 |
1 |
0 |
0x00 |
#20finish-write-3 | 172 | 0 |
0 |
0 |
0 |
0x00000003 |
0x82 |
1 |
0 |
0x00 |
#21read-3 | 175 | 0 |
1 |
0 |
0 |
0x00000003 |
0x82 |
1 |
0 |
0x82 |
#22read-back-3 | 176 | 0 |
1 |
0 |
0 |
0x00000003 |
0x82 |
1 |
0 |
0x82 |
#23read-back-2 | 192 | 0 |
1 |
0 |
0 |
0x00000002 |
0x82 |
1 |
0 |
0x83 |
#24read-back-1 | 208 | 0 |
1 |
0 |
0 |
0x00000001 |
0x82 |
1 |
0 |
0x80 |
#25read-back-0 | 224 | 0 |
1 |
0 |
0 |
0x00000000 |
0x82 |
1 |
0 |
0x81 |
#26invalid-4-read | 240 | 0 |
1 |
0 |
0 |
0x00000004 |
0x82 |
1 |
1 |
0x00 |
#27invalid-4-disabled | 243 | 0 |
0 |
0 |
0 |
0x00000004 |
0x82 |
1 |
0 |
0x00 |
#28invalid-4-write-enable | 246 | 0 |
0 |
0 |
1 |
0x00000004 |
0x00 |
1 |
1 |
0x00 |
#29invalid-4-write-edge | 264 | 1 |
0 |
0 |
1 |
0x00000004 |
0x00 |
1 |
1 |
0x00 |
#30invalid-4-write-end | 267 | 0 |
0 |
0 |
0 |
0x00000004 |
0x00 |
1 |
0 |
0x00 |
#31invalid-2147483648-read | 283 | 0 |
1 |
0 |
0 |
0x80000000 |
0x00 |
1 |
1 |
0x00 |
#32invalid-2147483648-disabled | 286 | 0 |
0 |
0 |
0 |
0x80000000 |
0x00 |
1 |
0 |
0x00 |
#33invalid-2147483648-write-enable | 289 | 0 |
0 |
0 |
1 |
0x80000000 |
0x00 |
1 |
1 |
0x00 |
#34invalid-2147483648-write-edge | 307 | 1 |
0 |
0 |
1 |
0x80000000 |
0x00 |
1 |
1 |
0x00 |
#35invalid-2147483648-write-end | 310 | 0 |
0 |
0 |
0 |
0x80000000 |
0x00 |
1 |
0 |
0x00 |
#36invalid-4294967295-read | 326 | 0 |
1 |
0 |
0 |
0xFFFFFFFF |
0x00 |
1 |
1 |
0x00 |
#37invalid-4294967295-disabled | 329 | 0 |
0 |
0 |
0 |
0xFFFFFFFF |
0x00 |
1 |
0 |
0x00 |
#38invalid-4294967295-write-enable | 332 | 0 |
0 |
0 |
1 |
0xFFFFFFFF |
0x00 |
1 |
1 |
0x00 |
#39invalid-4294967295-write-edge | 350 | 1 |
0 |
0 |
1 |
0xFFFFFFFF |
0x00 |
1 |
1 |
0x00 |
#40invalid-4294967295-write-end | 353 | 0 |
0 |
0 |
0 |
0xFFFFFFFF |
0x00 |
1 |
0 |
0x00 |
#41after-invalid-writes-3 | 369 | 0 |
1 |
0 |
0 |
0x00000003 |
0x00 |
1 |
0 |
0x82 |
#42after-invalid-writes-2 | 385 | 0 |
1 |
0 |
0 |
0x00000002 |
0x00 |
1 |
0 |
0x83 |
#43after-invalid-writes-1 | 401 | 0 |
1 |
0 |
0 |
0x00000001 |
0x00 |
1 |
0 |
0x80 |
#44after-invalid-writes-0 | 417 | 0 |
1 |
0 |
0 |
0x00000000 |
0x00 |
1 |
0 |
0x81 |
#45disable-after-range-checks | 420 | 0 |
0 |
0 |
0 |
0x00000000 |
0x00 |
1 |
0 |
0x00 |
#46masked-setup | 421 | 0 |
0 |
0 |
0 |
0x00000000 |
0x78 |
1 |
0 |
0x00 |
#47masked-enable | 424 | 0 |
0 |
0 |
1 |
0x00000000 |
0x78 |
1 |
0 |
0x00 |
#48masked-edge | 442 | 1 |
0 |
0 |
1 |
0x00000000 |
0x78 |
1 |
0 |
0x00 |
#49masked-end | 445 | 0 |
0 |
0 |
0 |
0x00000000 |
0x78 |
1 |
0 |
0x00 |
#50masked-read | 448 | 0 |
1 |
0 |
0 |
0x00000000 |
0x78 |
1 |
0 |
0x78 |
#51reset-retention | 466 | 0 |
1 |
1 |
0 |
0x00000000 |
0x78 |
1 |
0 |
0x00 |
Scroll within the table for all rows and pins. Buses use hexadecimal; values containing X/Z use MSB-first binary. Inputs show the declared values in effect at each checkpoint, including retained inputs. Sequential tests also replay preparation changes between checkpoints, such as lowering the clock. Use the matching checkpoint label in the simulation below.
Embedded simulation¶
Source and tests¶
NativeMemoryContainerTest supplies every displayed row. MemoryEndpointIntegrationTest.py compiles the exact FF example and checks its outputs against those rows. NativeMemoryDeviceContractTest compares native containers against expanded devices, checks visible controllers with scalable storage for all five technologies, and exercises 256 MiB logical capacities under small resident budgets. Existing endpoint tests retain handshake/backpressure/reset coverage.
Sources: MemoryContainer.hpp/.cpp, MemoryDeviceStorage.hpp, the native device implementations, and src/tests/MemoryEndpointTests.cpp.