Skip to content

§ 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-disabled17 0 0 1 0 0x00000000 0x00 1 0 0x00
#1release-reset18 0 0 0 0 0x00000000 0x00 1 0 0x00
#2setup-019 0 0 0 0 0x00000000 0x81 1 0 0x00
#3write-enable-022 0 0 0 1 0x00000000 0x81 1 0 0x00
#4write-edge-040 1 0 0 1 0x00000000 0x81 1 0 0x00
#5finish-write-043 0 0 0 0 0x00000000 0x81 1 0 0x00
#6read-046 0 1 0 0 0x00000000 0x81 1 0 0x81
#7setup-162 0 0 0 0 0x00000001 0x80 1 0 0x00
#8write-enable-165 0 0 0 1 0x00000001 0x80 1 0 0x00
#9write-edge-183 1 0 0 1 0x00000001 0x80 1 0 0x00
#10finish-write-186 0 0 0 0 0x00000001 0x80 1 0 0x00
#11read-189 0 1 0 0 0x00000001 0x80 1 0 0x80
#12setup-2105 0 0 0 0 0x00000002 0x83 1 0 0x00
#13write-enable-2108 0 0 0 1 0x00000002 0x83 1 0 0x00
#14write-edge-2126 1 0 0 1 0x00000002 0x83 1 0 0x00
#15finish-write-2129 0 0 0 0 0x00000002 0x83 1 0 0x00
#16read-2132 0 1 0 0 0x00000002 0x83 1 0 0x83
#17setup-3148 0 0 0 0 0x00000003 0x82 1 0 0x00
#18write-enable-3151 0 0 0 1 0x00000003 0x82 1 0 0x00
#19write-edge-3169 1 0 0 1 0x00000003 0x82 1 0 0x00
#20finish-write-3172 0 0 0 0 0x00000003 0x82 1 0 0x00
#21read-3175 0 1 0 0 0x00000003 0x82 1 0 0x82
#22read-back-3176 0 1 0 0 0x00000003 0x82 1 0 0x82
#23read-back-2192 0 1 0 0 0x00000002 0x82 1 0 0x83
#24read-back-1208 0 1 0 0 0x00000001 0x82 1 0 0x80
#25read-back-0224 0 1 0 0 0x00000000 0x82 1 0 0x81
#26invalid-4-read240 0 1 0 0 0x00000004 0x82 1 1 0x00
#27invalid-4-disabled243 0 0 0 0 0x00000004 0x82 1 0 0x00
#28invalid-4-write-enable246 0 0 0 1 0x00000004 0x00 1 1 0x00
#29invalid-4-write-edge264 1 0 0 1 0x00000004 0x00 1 1 0x00
#30invalid-4-write-end267 0 0 0 0 0x00000004 0x00 1 0 0x00
#31invalid-2147483648-read283 0 1 0 0 0x80000000 0x00 1 1 0x00
#32invalid-2147483648-disabled286 0 0 0 0 0x80000000 0x00 1 0 0x00
#33invalid-2147483648-write-enable289 0 0 0 1 0x80000000 0x00 1 1 0x00
#34invalid-2147483648-write-edge307 1 0 0 1 0x80000000 0x00 1 1 0x00
#35invalid-2147483648-write-end310 0 0 0 0 0x80000000 0x00 1 0 0x00
#36invalid-4294967295-read326 0 1 0 0 0xFFFFFFFF 0x00 1 1 0x00
#37invalid-4294967295-disabled329 0 0 0 0 0xFFFFFFFF 0x00 1 0 0x00
#38invalid-4294967295-write-enable332 0 0 0 1 0xFFFFFFFF 0x00 1 1 0x00
#39invalid-4294967295-write-edge350 1 0 0 1 0xFFFFFFFF 0x00 1 1 0x00
#40invalid-4294967295-write-end353 0 0 0 0 0xFFFFFFFF 0x00 1 0 0x00
#41after-invalid-writes-3369 0 1 0 0 0x00000003 0x00 1 0 0x82
#42after-invalid-writes-2385 0 1 0 0 0x00000002 0x00 1 0 0x83
#43after-invalid-writes-1401 0 1 0 0 0x00000001 0x00 1 0 0x80
#44after-invalid-writes-0417 0 1 0 0 0x00000000 0x00 1 0 0x81
#45disable-after-range-checks420 0 0 0 0 0x00000000 0x00 1 0 0x00
#46masked-setup421 0 0 0 0 0x00000000 0x78 1 0 0x00
#47masked-enable424 0 0 0 1 0x00000000 0x78 1 0 0x00
#48masked-edge442 1 0 0 1 0x00000000 0x78 1 0 0x00
#49masked-end445 0 0 0 0 0x00000000 0x78 1 0 0x00
#50masked-read448 0 1 0 0 0x00000000 0x78 1 0 0x78
#51reset-retention466 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

Open native memory container

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.