Skip to content

Commit db2acd4

Browse files
committed
docs: add inline-pc memory access design spec
Design for moving PC register read/write from the MEMW bus (through MEMW_R) to direct interactions on the low-level memory bus within the CPU chip. Eliminates one MEMW_R row per CPU step (~50% MEMW_R reduction). Adds 2 CPU columns and 4 memory bus interactions. Based on spec PR #501 (spec/inline-pc).
1 parent 902e102 commit db2acd4

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Inline PC Memory Access into CPU
2+
3+
## Goal
4+
5+
Eliminate the MEMW_R table rows for the PC register by having the CPU chip interact directly with the low-level `memory` bus for PC reads and writes. This removes one MEMW_R row per CPU step, roughly halving the MEMW_R table size for most programs.
6+
7+
## Context
8+
9+
Currently, every CPU cycle writes `next_pc` to register x255 (address 510-511) via the `MEMW` bus. This routes through MEMW_R (register fast-path), which decomposes the 2-word register operation into two per-word interactions on the `memory` bus. Since the PC register has a completely predictable access pattern (read once, write once, every cycle, at known addresses), this intermediate step is unnecessary.
10+
11+
Two buses are involved:
12+
- **`MEMW` bus**: High-level, 24-element fingerprint. CPU sends multi-byte operations; MEMW/MEMW_R receive and decompose.
13+
- **`memory` bus**: Low-level, 6-element fingerprint `(is_register, addr_lo, addr_hi, ts_lo, ts_hi, value)`. MEMW_R, MEMW_A, and PAGE tables interact here. Token-based memory argument: every read consumes a token (sender, +1), every write emits a token (receiver, -1).
14+
15+
Based on spec PR #501 (`spec/inline-pc`).
16+
17+
## Design
18+
19+
### What gets removed
20+
21+
**One MEMW bus interaction from CPU** (currently CM54):
22+
```
23+
MEMW[pc; 1, 510, next_pc, timestamp+1, 1, 0, 0] multiplicity = (1 - pad)
24+
```
25+
This 24-element sender to `BusId::Memw` with all-ALU-flags multiplicity.
26+
27+
**One MEMW_R row per CPU cycle**: The trace builder currently creates a `MemwOperation` for the PC at every non-padding row. This routes through MEMW_R, producing one row there.
28+
29+
### What gets added
30+
31+
**Two new CPU columns** (74 → 76):
32+
33+
| Column | Type | Description |
34+
|--------|------|-------------|
35+
| `PREV_PC_TIMESTAMP_BORROW` | Bit | Borrow bit for 64-bit subtraction `timestamp - 3`. Set when `timestamp_lo < 3`. |
36+
| `PC_DOUBLE_READ` | Bit | 1 when `rs1 = 255` (AUIPC/JAL reads PC via M1). Affects previous timestamp. |
37+
38+
**Four new `memory` bus interactions** (replacing the one MEMW interaction):
39+
40+
For `i = 0, 1` (two words of address 510 and 511):
41+
42+
1. **PC read** (sender, +1 multiplicity, consumes old token):
43+
```
44+
memory[1, 510+i, 0, prev_ts_lo, prev_ts_hi, pc[i]]
45+
multiplicity = (1 - pad)
46+
```
47+
48+
2. **PC write** (receiver, -1 multiplicity, emits new token):
49+
```
50+
memory[1, 510+i, 0, timestamp + 1, 0, next_pc[i]]
51+
multiplicity = -(1 - pad)
52+
```
53+
54+
Where:
55+
```
56+
prev_ts_lo = timestamp - 3*(1 - pc_double_read) + 2^32 * borrow
57+
prev_ts_hi = 0 - borrow
58+
```
59+
(Since timestamps fit in 32 bits, `timestamp_hi = 0` always. The subtraction of 3 can only underflow in the lo word.)
60+
61+
**Four new constraints**:
62+
1. `pc_double_read * (rs1 - 255) = 0` — pc_double_read=1 only when rs1=255
63+
2. `pc_double_read * prev_pc_timestamp_borrow = 0` — no borrow when pc_double_read=1 (subtraction is 0)
64+
3. `IS_BIT[prev_pc_timestamp_borrow]`
65+
4. `IS_BIT[pc_double_read]`
66+
67+
### Impact summary
68+
69+
| Metric | Before | After | Delta |
70+
|--------|--------|-------|-------|
71+
| CPU main columns | 74 | 76 | +2 |
72+
| CPU MEMW bus interactions | 1 (24 elements) | 0 | -1 |
73+
| CPU memory bus interactions | 0 | 4 (6 elements each) | +4 |
74+
| CPU transition constraints | N | N+4 | +4 |
75+
| MEMW_R rows per CPU cycle | 1 | 0 | -1 |
76+
77+
Net trace savings: for N CPU steps, we add 2N field elements to CPU but remove 10N from MEMW_R (10 columns). Net saving: 8N field elements (~6% total trace reduction for typical programs).
78+
79+
## Timestamp Arithmetic
80+
81+
The CPU stores `timestamp` as a single field element. The memory bus needs `(ts_lo, ts_hi)` where lo/hi are 32-bit words. Since timestamps never exceed 2^32 for practical programs, `ts_hi = 0` always.
82+
83+
**Normal case** (`pc_double_read = 0`): The PC was last written at `T+1` in the previous cycle. With granularity 4, `T_prev + 1 = T - 3`:
84+
```
85+
prev_ts_lo = timestamp - 3 + 2^32 * borrow (field arithmetic)
86+
prev_ts_hi = 0 - borrow = -borrow (0 or p-1 in the field, but bus expects 0 or -1 as word)
87+
```
88+
When `timestamp < 3` (first cycle), `borrow = 1` corrects the underflow.
89+
90+
**AUIPC/JAL case** (`pc_double_read = 1`): The M1 register read already consumed the previous PC token (via MEMW→MEMW_R→memory bus at timestamp T+0) and emitted a new one at T+0. The inline PC read consumes that fresh token:
91+
```
92+
prev_ts_lo = timestamp (no subtraction)
93+
prev_ts_hi = 0
94+
borrow = 0 (forced by constraint)
95+
```
96+
97+
**Token chain for AUIPC/JAL**: Previous write (T_prev+1) → MEMW_R read (T_prev+1) → MEMW_R write (T+0) → inline read (T+0) → inline write (T+1). Each sender (+1) at timestamp X cancels the receiver (-1) that emitted at timestamp X.
98+
99+
## Files to Change
100+
101+
### 1. `prover/src/tables/cpu.rs`
102+
103+
**Column definitions** (`cols` module):
104+
- Add `PREV_PC_TIMESTAMP_BORROW` at index 74
105+
- Add `PC_DOUBLE_READ` at index 75
106+
- Update `NUM_COLUMNS` from 74 to 76
107+
108+
**Trace generation** (`generate_cpu_trace`):
109+
- For each row: compute `pc_double_read = (d.rs1 == 255 && d.read_register1) as u64`
110+
- Compute `prev_pc_timestamp_borrow = (op.timestamp < 3 && pc_double_read == 0) as u64`
111+
- Write both values to columns 74 and 75
112+
113+
**Bus interactions** (`cpu_bus_interactions`):
114+
- Remove CM54 (the MEMW sender for PC)
115+
- Add 4 memory bus interactions (2 reads + 2 writes to `BusId::Memory`)
116+
117+
### 2. `prover/src/constraints/cpu.rs`
118+
119+
- Add constraint: `pc_double_read * (rs1 - 255) = 0`
120+
- Add constraint: `pc_double_read * prev_pc_timestamp_borrow = 0`
121+
- Add IS_BIT constraints for both new columns
122+
123+
### 3. `prover/src/tables/trace_builder.rs`
124+
125+
- In `collect_memw_from_cpu` (lines 594-607): Remove the CM54 block that creates a `MemwOperation` for the PC register
126+
- Keep `register_state.write_pc(op.next_pc, op.timestamp + 1)` — the register state tracker is still needed for M1 interactions when `rs1=255` (AUIPC/JAL), which still go through MEMW_R
127+
128+
### 4. `prover/src/test_utils.rs`
129+
130+
- Update `create_cpu_air` to use 76 columns
131+
- Add new bus interactions and constraints to the CPU AIR
132+
133+
## Testing
134+
135+
1. **Existing prove-and-verify tests**: All passing tests must continue to pass. The memory bus must still balance globally.
136+
2. **AUIPC/JAL**: Test programs using `rs1=255` to verify the `pc_double_read` path and token chain correctness.
137+
3. **First cycle**: Verify `timestamp < 3` triggers the borrow bit correctly.
138+
4. **Bus balance**: The global LogUp sum must still be zero (or the expected commit offset).

0 commit comments

Comments
 (0)