Skip to content

§ 3.2. Memory Transactions

A shared, versioned transaction interface above the native devices in 3.1. It is a single-outstanding, aligned-word protocol, not AXI, a DDR interface, or a coherent interconnect. Width W is 8, 16, or 32; capacity is a construction parameter.

The transaction components are:

Component Description Structural Behavioral
MemoryTransactionPort<W> Track one delayed request; pass through or hold its response Yes No
MemoryResponseBuffer<W> One-entry response buffer with a combinational bypass Yes No
ClockedStateBit Resettable always-clocked bit for handshake state; a storage component documented in 4.3.1 Yes Yes

Protocol rules

Request and response transfers occur on a known rising CLK edge when the corresponding VALID and READY are both 1. The sender holds VALID and its payload until transfer; it must not wait for READY before asserting VALID. At most one request is outstanding. The response is held unchanged until consumed. A new request may be accepted on the response-consume edge. A zero-wait endpoint may present its response during the request cycle and transfer both on the same edge. This is protocol version 2; old version-1 recordings may have different timing.

Addresses are byte addresses aligned to W/8. Reads return a full word. Writes/programs use positioned byte lanes; mask bit b enables byte b. ISA load sizes, sign extension, unaligned splitting, bursts, atomics, arbitration and caching are outside this port.

REQ_OP Operation Meaning
0 READ Read one aligned word
1 WRITE Replace enabled bytes in writable FF/SRAM/DRAM
2 PROGRAM NOR flash only: clear enabled bits, never restore 0→1
3 ERASE_BLOCK NOR flash only: restore one whole block to ones; address must be block-aligned
4–7 Reserved Unsupported-operation response
RSP_STATUS Meaning
0 Success
1 Misaligned word/block address
2 Address outside configured capacity
3 Attempt to modify ROM
4 Unsupported operation
5 Native device failure
6 Unknown required request fields
7 Reserved

Error and non-read responses have zero data. Unknown stored bits remain X even with a successful read. A zero write mask does not change contents. Read requests ignore write data/mask; erase ignores both. Validation order is unknown opcode/address → reserved opcode → required mask → word alignment → capacity → technology permission/block alignment.

RST resets the transaction interface, not memory contents. It suppresses VALID/READY and discards response delivery. An already launched native sequence drains before reuse; its writes/programs/erases are not rolled back. Native FF storage reset is deliberately not tied to this interface reset.

3.2.1. MemoryTransactionPort<W>

Shared structural frontend. The backend captures the live request on an edge with START=1 and holds DONE/result until an edge with FINISH=1. It owns no memory contents and does not itself validate addresses or operations.

Here <W> denotes the pin width. The actual C++ MemoryTransactionPort constructor takes W as a runtime argument; the simplified reference below uses a template to keep its payload widths together.

IO pins

Pin Direction Width Description
CLK, RST Input 1 Clock and active-high interface reset
REQ_VALID Input 1 Request available
REQ_OP Input 3 Operation code
REQ_ADDR Input 32 Aligned byte address
REQ_WRITE_DATA Input W Positioned write/program data
REQ_WRITE_MASK Input W/8 Byte enables
REQ_READY Output 1 Can accept a request
RSP_VALID Output 1 Response available
RSP_READY Input 1 Requester accepts the response
RSP_READ_DATA Output W Held read result
RSP_STATUS Output 3 Held status
BACK_READY, DONE Input 1 Backend available / operation complete
RESULT Input W Backend read result
STATUS Input 3 Backend status
START, FINISH Output 1 Accept live request / release completed backend result
OP Output 3 Live operation
ADDR Output 32 Live byte address
DATA Output W Live data
MASK Output W/8 Live byte mask

Behavior

There is one pending bit, not a SETUP/WAIT/RESPONSE phase pipeline. START accepts a request only when the backend and response capacity are available. Completion passes directly through the response buffer. If the receiver cannot take it, the buffer captures result/status and FINISH releases the backend. Backend maintenance can then continue while that response remains held. The general FF controller uses the same response buffer directly, without this delayed-request frontend; its always-ready configuration instead returns a zero-wait response without a buffer.

C++ example

This reference shows the response storage and pending state. No request register is duplicated in the frontend: the native sequencer captures it on START. Control flags are binary; payloads use the project's four-state Bits and Request types. The example assumes an initial reset and compares settled behavior, not individual gate delays. It is not an additional registered behavioral implementation.

#include "MemoryEndpointEngine.hpp" // Only value/configuration helpers.

namespace memory_example {
using namespace circuit::memory;

template<std::size_t W>
class ResponseBuffer {
    bool full_ = false;
    Bits data_ = Bits(W, L), status_ = bits(3, 0);
public:
    struct Inputs {
        bool reset = false, edge = false, valid = false, ready = false;
        Bits data = Bits(W, L), status = bits(3, 0);
    };
    struct Outputs { bool ready, valid; Bits data, status; };
    Outputs outputs(const Inputs& i) const {
        return {!i.reset && (!full_ || i.ready),
                !i.reset && (full_ || i.valid),
                full_ ? data_ : i.data, full_ ? status_ : i.status};
    }
    void step(const Inputs& i) {
        if (i.reset) { full_ = false; data_.assign(W, L); status_ = bits(3, 0); }
        else if (i.edge) {
            const bool capture = i.valid && (!full_ || i.ready) && (full_ || !i.ready);
            const bool hold = full_ && !i.ready;
            if (capture) { data_ = i.data; status_ = i.status; }
            full_ = capture || hold;
        }
    }
};

template<std::size_t W>
class Port {
    static_assert(W == 8 || W == 16 || W == 32);
    bool pending_ = false;
    ResponseBuffer<W> response_;
public:
    struct Inputs {
        bool rising_edge = false, reset = false;
        bool request_valid = false, response_ready = false;
        bool backend_ready = false, done = false;
        Request request{bits(3, 0), bits(32, 0), Bits(W, L), Bits(W / 8, L)};
        Bits result = Bits(W, L), status = bits(3, 0);
    };
    struct Outputs {
        bool request_ready, response_valid, start, finish;
        Request request;
        Bits read_data, status;
    };
private:
    auto response_input(const Inputs& i) const {
        return typename ResponseBuffer<W>::Inputs{
            i.reset, i.rising_edge, pending_ && i.done,
            i.response_ready, i.result, i.status};
    }
public:
    Outputs outputs(const Inputs& i) const {
        auto r = response_.outputs(response_input(i));
        const bool complete = pending_ && i.done;
        const bool ready = !i.reset && (!pending_ || (complete && i.response_ready))
                           && i.backend_ready && r.ready;
        return {ready, r.valid, ready && i.request_valid, complete && r.ready,
                i.request, r.data, r.status};
    }
    Outputs step(const Inputs& i) {
        const auto before = outputs(i); // All FFs sample the old state together.
        response_.step(response_input(i));
        if (i.reset) pending_ = false;
        else if (i.rising_edge)
            pending_ = before.start || (pending_ && !before.finish);
        return outputs(i);
    }
};
} // namespace memory_example

An empty response buffer bypasses its registers. On a blocked transfer it captures the payload; when consuming an old response and accepting a new one together, the old response is delivered and the new one is retained. The frontend samples START/FINISH using pre-edge values. Changing request inputs after acceptance changes its live backend pins, but does not change the sequencer's captured request. Reset clears pending/held state, not memory.

request, result, and status must have the pin widths listed above. The excerpt deliberately does not validate addresses or opcodes; a frontend must forward those fields to the controller, including unknown payload bits. Its internal header requires the include and src/modules/memory include paths and SimulatorLib. MemoryEndpointIntegrationTest compiles and executes the exact excerpt at W=8/16/32, including no-edge bypass, backend stalls, reset, response holding and simultaneous consume/accept.

Unknown inputs and timing

CLK, reset, VALID and READY must be binary and meet structural setup/hold requirements. Unknown handshake controls are protocol violations, not alternate transactions. Payload data may contain X; the controller rejects unknown opcode/address or a required mask before issuing native commands. These registers and gates have propagation delays. Examples wait for settled outputs; simulator ticks are not measured chip nanoseconds.

Implementation

Structural implementation

PENDING is one ClockedStateBit. RESPONSE is a MemoryResponseBuffer<W> with an occupancy state bit, data/status registers, and bypass multiplexers; its reused register component is defined in 4.3.1. Live request fields cross the scope boundary through zero-delay rewires. The layout separates request forwarding, handshake control and response retention.

Examples

All 10 recorded rows of MemoryTransactionPortTest, 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 / structural (d57a5ed1eeab12fc). Changing fidelity or the circuit can change these times.

Checkpointzero-based number · timeline label Timesimulator units BACK_READYInput · 1 bit CLKInput · 1 bit DONEInput · 1 bit REQ_ADDRInput · 32 bit REQ_OPInput · 3 bit REQ_VALIDInput · 1 bit REQ_WRITE_DATAInput · 8 bit REQ_WRITE_MASKInput · 1 bit RESULTInput · 8 bit RSP_READYInput · 1 bit RSTInput · 1 bit STATUSInput · 3 bit ADDRExpected output · 32 bit DATAExpected output · 8 bit FINISHExpected output · 1 bit MASKExpected output · 1 bit OPExpected output · 3 bit REQ_READYExpected output · 1 bit RSP_READ_DATAExpected output · 8 bit RSP_STATUSExpected output · 3 bit RSP_VALIDExpected output · 1 bit STARTExpected output · 1 bit
#0reset9 1 0 0 0x00000000 0x0 0 0x00 0 0xA5 0 1 0x0 not checked not checked not checked not checked not checked 0 not checked not checked 0 not checked
#1release-reset15 1 0 0 0x00000000 0x0 0 0x00 0 0xA5 0 0 0x0 not checked not checked not checked not checked not checked 1 not checked not checked not checked not checked
#2request23 1 0 0 0x0000000C 0x0 1 0x00 0 0xA5 0 0 0x0 not checked not checked not checked not checked not checked not checked not checked not checked not checked not checked
#3accept33 1 1 0 0x0000000C 0x0 1 0x00 0 0xA5 0 0 0x0 0x0000000C not checked not checked not checked not checked 0 not checked not checked not checked not checked
#4backend-owns-payload38 1 0 0 0x00000000 0x0 0 0x00 0 0xA5 0 0 0x0 0x00000000 not checked not checked not checked not checked not checked not checked not checked not checked not checked
#5pending43 1 1 0 0x00000000 0x0 0 0x00 0 0xA5 0 0 0x0 not checked not checked not checked not checked not checked not checked not checked not checked 0 0
#6done56 1 0 1 0x00000000 0x0 0 0x00 0 0xA5 0 0 0x0 not checked not checked not checked not checked not checked not checked not checked not checked not checked not checked
#7capture-response69 1 1 1 0x00000000 0x0 0 0x00 0 0xA5 0 0 0x0 not checked not checked not checked not checked not checked not checked 0xA5 not checked 1 not checked
#8hold74 1 0 1 0x00000000 0x0 0 0x00 0 0x00 0 0 0x0 not checked not checked not checked not checked not checked not checked 0xA5 not checked not checked not checked
#9reset-response84 1 0 1 0x00000000 0x0 0 0x00 0 0x00 0 1 0x0 not checked not checked not checked not checked not checked 0 not checked not checked 0 not checked

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.

Accepts address 12, demonstrates that backend pins remain live while the sequencer owns the accepted request, passes through result 0xA5, retains it after RESULT changes, then resets the interface. Every preparation, edge and hold checkpoint is included in the scrollable table, with its recorded time.

Embedded simulation

Open MemoryTransactionPort in the simulator

Source and tests

Test Coverage
MemoryTransactionPortTest Complete sequence above; live request and held response
MemoryEndpointContractTest Frontend connected to native devices and compared with compact endpoints
MemoryEndpointTimingTest Reset, backpressure, native operations in flight and refresh
MemoryEndpointIntegrationTest Execute the documented frontend reference at W=8/16/32; verify bypass, response capture and handshake behavior

3.2.2. MemoryResponseBuffer<W>

One entry of response storage, shared by the zero-wait and delayed controllers. It is not a request queue or memory array.

IO pins

Pin Direction Width Description
CLK, RST Input 1 Shared clock and interface reset
IN_VALID, OUT_READY Input 1 Producer presents data / consumer accepts data
IN_DATA, IN_STATUS Input W, 3 New response payload
IN_READY, OUT_VALID Output 1 Input capacity / response available
OUT_DATA, OUT_STATUS Output W, 3 Live or held response payload
FULL Output 1 An older response occupies the register

Behavior

When empty, the input passes directly to the output. An available receiver can consume it without filling the register. Backpressure captures one response on the edge; subsequent input changes cannot alter it. When a full buffer consumes and replaces on the same edge, the old response is delivered first and the new response is retained. Reset clears occupancy and retained data/status without touching device contents.

C++ example

ResponseBuffer<W> in the preceding excerpt is the complete state/transition reference. This wrapper selects its 32-bit instance; it adds no behavior or additional state.

namespace memory_example {
using ResponseBuffer32 = ResponseBuffer<32>;
}

Unknown inputs and timing

Controls must be known binary values. Payload FFs preserve unknown data bits. The bypass and selection gates need time to settle; “zero-wait” means no extra clock cycle, not zero propagation delay. Data/status outside OUT_VALID have no transaction meaning.

Implementation

Structural implementation

OCCUPIED is a ClockedStateBit; HELD_DATA and HELD_STATUS are FF-based registers. SELECT_DATA and SELECT_STATUS choose held versus live inputs. The lower control row computes space, capture, hold and next occupancy. The buffer itself is structural; its state components can be selected independently by fidelity.

HELD_DATA retains W bits and HELD_STATUS retains 3 bits using the register family defined in 4.3.1. Including OCCUPIED, the buffer retains W+4 bits: 12, 20, or 36 at W=8, 16, or 32. An empty buffer bypasses this storage; a blocked response needs it to remain stable. Selecting compact storage or a behavioral enclosing endpoint changes simulation detail, not the number of logical storage bits or the response contract.

Examples

All 11 recorded rows of MemoryResponseBufferTest, 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 / structural (d57a5ed1eeab12fc). Changing fidelity or the circuit can change these times.

Checkpointzero-based number · timeline label Timesimulator units CLKInput · 1 bit IN_DATAInput · 8 bit IN_STATUSInput · 3 bit IN_VALIDInput · 1 bit OUT_READYInput · 1 bit RSTInput · 1 bit FULLExpected output · 1 bit IN_READYExpected output · 1 bit OUT_DATAExpected output · 8 bit OUT_STATUSExpected output · 3 bit OUT_VALIDExpected output · 1 bit
#0reset9 0 0x00 0x0 0 0 1 0 0 not checked not checked 0
#1empty-bypass14 0 0x31 0x0 1 1 0 not checked 1 0x31 not checked 1
#2bypass-transfer19 1 0x31 0x0 1 1 0 0 not checked not checked not checked not checked
#3empty24 0 0x31 0x0 0 1 0 not checked not checked not checked not checked 0
#4blocked-input35 0 0x31 0x0 1 0 0 not checked 1 not checked not checked not checked
#5capture49 1 0x31 0x0 1 0 0 1 0 not checked not checked not checked
#6hold54 0 0x42 0x5 1 0 0 not checked not checked 0x31 0x0 not checked
#7consume-and-replace69 0 0x42 0x5 1 1 0 not checked 1 0x31 not checked not checked
#8replacement77 1 0x42 0x5 1 1 0 1 not checked 0x42 0x5 not checked
#9retain-new88 0 0x42 0x5 0 0 0 not checked not checked not checked not checked 1
#10reset-held-response98 0 0x42 0x5 0 0 1 0 not checked not checked not checked 0

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.

The checkpoints show empty bypass, a transfer without filling, capture under backpressure, stable holding, same-edge consume/replace and reset.

Open response buffer

Source and tests

MemoryController.cpp defines the circuit. MemoryResponseBufferTest supplies the recorded rows; MemoryEndpointContractTest repeats the structural checks at W=8/16/32. The executable documentation fixture checks the same bypass and replacement rules.