§ 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
#0reset | 9 | 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-reset | 15 | 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 |
#2request | 23 | 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 |
#3accept | 33 | 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-payload | 38 | 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 |
#5pending | 43 | 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 |
#6done | 56 | 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-response | 69 | 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 |
#8hold | 74 | 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-response | 84 | 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¶
- Interface:
MemoryProtocol.hpp - Structural implementation:
MemoryController.cpp - Tests:
MemoryEndpointTests.cpp - Executable C++ excerpt checks, extracted and run by
MemoryEndpointIntegrationTest
| 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.
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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
#0reset | 9 | 0 |
0x00 |
0x0 |
0 |
0 |
1 |
0 |
0 |
not checked | not checked | 0 |
#1empty-bypass | 14 | 0 |
0x31 |
0x0 |
1 |
1 |
0 |
not checked | 1 |
0x31 |
not checked | 1 |
#2bypass-transfer | 19 | 1 |
0x31 |
0x0 |
1 |
1 |
0 |
0 |
not checked | not checked | not checked | not checked |
#3empty | 24 | 0 |
0x31 |
0x0 |
0 |
1 |
0 |
not checked | not checked | not checked | not checked | 0 |
#4blocked-input | 35 | 0 |
0x31 |
0x0 |
1 |
0 |
0 |
not checked | 1 |
not checked | not checked | not checked |
#5capture | 49 | 1 |
0x31 |
0x0 |
1 |
0 |
0 |
1 |
0 |
not checked | not checked | not checked |
#6hold | 54 | 0 |
0x42 |
0x5 |
1 |
0 |
0 |
not checked | not checked | 0x31 |
0x0 |
not checked |
#7consume-and-replace | 69 | 0 |
0x42 |
0x5 |
1 |
1 |
0 |
not checked | 1 |
0x31 |
not checked | not checked |
#8replacement | 77 | 1 |
0x42 |
0x5 |
1 |
1 |
0 |
1 |
not checked | 0x42 |
0x5 |
not checked |
#9retain-new | 88 | 0 |
0x42 |
0x5 |
0 |
0 |
0 |
not checked | not checked | not checked | not checked | 1 |
#10reset-held-response | 98 | 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.
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.