Skip to content

Commit 0f90155

Browse files
RobinJadoulclaude[bot]erik-3milabs
authored
spec: Inline PC memory access into CPU (#501)
* spec: Inline PC memory access into CPU * Apply suggestions from code review Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Robin Jadoul <robin.jadoul@gmail.com> * Apply review suggestion Co-authored-by: Erik <159244975+erik-3milabs@users.noreply.github.com> * Remove `pc_double_read` constraints and clarify why in cpu.typ * Potential optimization -> subsubsection * Address review comments * Clarifying remark on register initialization --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Erik <159244975+erik-3milabs@users.noreply.github.com>
1 parent d23f806 commit 0f90155

4 files changed

Lines changed: 55 additions & 13 deletions

File tree

spec/cpu.typ

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,26 @@ The ALU functionality is then obtained through judicious dispatching to the corr
5656

5757
#render_constraint_table(chip, config, groups: "alu")
5858

59-
== Memory
59+
== Memory<cpu:memory>
6060

6161
The interactions with the memory, both for register loading and storing, as for `LOAD` and `STORE` instructions are handled.
6262
Note that since registers need no byte-addressing, we store them in the memory argument with `Word` limbs.
63-
The timestamps are ensured to be disjoint for disjoint memory locations.
63+
The `pc` register behaves very predictably with respect to its timestamps and when it is being read,
64+
so for performance reasons, we inline its memory interactions directly into the #cpu chip.
65+
66+
Potentially overlapping memory accesses are ensured to have disjoint timestamps.
6467
One consequence of that is that `next_pc` is written at `timestamp + 1`
65-
to ensure the access is disjoint with the `pc` read into `rv1` as part of the `AUIPC` instruction.
68+
to ensure the access is disjoint with the `pc` read into `rv1` as part of the `AUIPC` instruction (see @cpu:c:read_rv1 and @decode:decoding-overview).
69+
Constraints regarding whether `pc_double_read` corresponds to an `AUIPC` instruction are not necessary,
70+
as regardless of its value, the old timestamp is guaranteed smaller than the new timestamp,
71+
and the integrity of the memory argument therefore ensures the correctness of this bit.
6672

6773
#render_constraint_table(chip, config, groups: "mem")
6874

75+
=== Potential optimizations
76+
77+
- `double_pc_read` could be integrated into decoding, so that `AUIPC` could set `read_register1 = 0` and no extra MEMW access for `rv1` is needed at this point.
78+
6979
== System
7080

7181
The interactions with the wider system.

spec/decode.typ

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Given that `CPU` asserts that `EBREAK = 0` (see @cpu:c:ebreak_traps), using this
3939
Note moreover that the `pc` is set to $7$.
4040
This value is the _smallest odd number_ (i.e., not reachable during regular execution) that is more than _$4$_ (i.e., the max `pc`-increment) greater than _$1$_ (i.e., the `pc`-value used in the #link(<cpu-padding-decode-row>)[additional instruction] referred to by `CPU`-padding lines).
4141

42-
= Decoding
42+
= Decoding<decode:decoding-overview>
4343
For the purposes of explaining decoding, we decompress #decode's `packed_decode` variable into its constituent variables.
4444
Note that the below table is _not_ used in practice: it is solely used for the purposes of this explanation.
4545

@@ -64,12 +64,6 @@ For the purpose of brevity and readability, the table uses the following rules-o
6464

6565
Further clarification is provided in the notes following the table.
6666

67-
== C-type instructions
68-
The `RV64C` extension for compressed instructions specifies that \~50% of all instructions can be represented using a 16-bit instruction (rather than 32-bits), saving \~25% in code size.
69-
This execution of assembly code is _not_ agnostic to an instruction's compression state; after executing a compressed instruction, the `pc` should be incremented by $2$ rather than $4$.
70-
To indicate an instruction is provided in compressed form, the `c_type` flag is introduced.
71-
*This flag should be set to $1$ whenever the decoded instruction is provided in compressed form and $0$ otherwise.*
72-
7367
/// Add a reference to one or more notes following this table.
7468
#let ref_note(..refs) = {
7569
super("[" + refs.pos().map(r => ref(r)).join(",") + "]")
@@ -152,6 +146,12 @@ To indicate an instruction is provided in compressed form, the `c_type` flag is
152146

153147
#decoding_table(decoding)
154148

149+
== C-type instructions
150+
The `RV64C` extension for compressed instructions specifies that \~50% of all instructions can be represented using a 16-bit instruction (rather than 32-bits), saving \~25% in code size.
151+
This execution of assembly code is _not_ agnostic to an instruction's compression state; after executing a compressed instruction, the `pc` should be incremented by $2$ rather than $4$.
152+
To indicate an instruction is provided in compressed form, the `c_type` flag is introduced.
153+
*This flag should be set to $1$ whenever the decoded instruction is provided in compressed form and $0$ otherwise.*
154+
155155
// Construct a note that can be referenced through `lbl`
156156
#let referenceable_note(lbl, note) = {
157157
show figure: (it) => align(left, [#it])

spec/memory.typ

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ The initialization will need to correspond to a fixed initial register state for
136136
as well as the memory loaded from the program binary, zero-initialization of memory elsewhere, and private input provided by the prover.
137137
The contribution of initialization with static data from the ELF executable and the initial register state to the sum
138138
can be handled directly by the verifier, ensuring correctness corresponding to the ELF binary being proven.
139+
To enable the loading of the PC in @cpu:memory, register initialization happens at timestamp 1.
140+
Register finalization is made possible for the verifier by having a known state from the HALT chip (@halt).
139141
This leaves only zero-initialization and prover input as prover-side concerns for initialization,
140142
alongside the finalization of the entire used memory.
141143

spec/src/cpu.toml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,18 @@ desc = "The value to (maybe) be written back to rvd"
221221
pad = 0
222222

223223
# Auxiliary
224+
[[variables.auxiliary]]
225+
name = "prev_pc_timestamp_borrow"
226+
type = "Bit"
227+
desc = "The borrow bit for computing the previous timestamp the PC was accessed"
228+
pad = 0
229+
230+
[[variables.auxiliary]]
231+
name = "pc_double_read"
232+
type = "Bit"
233+
desc = "Whether the PC is being read as a general purpose register (`rs1`) this cycle"
234+
pad = 0
235+
224236
[[variables.auxiliary]]
225237
name = "rv1"
226238
type = "DWordWHH"
@@ -652,6 +664,7 @@ tag = "MEMW"
652664
input = [1, ["*", ["cast", 2, "DWordWL"], "rs1"], ["arr", ["idx", ["cast", "rv1", "DWordWL"], 0], ["idx", ["cast", "rv1", "DWordWL"], 1], 0, 0, 0, 0, 0, 0], ["+", "timestamp", ["cast", 0, "DWordWL"]], 1, 0, 0]
653665
output = ["arr", ["idx", ["cast", "rv1", "DWordWL"], 0], ["idx", ["cast", "rv1", "DWordWL"], 1], 0, 0, 0, 0, 0, 0]
654666
multiplicity = "read_register1"
667+
ref = "cpu:c:read_rv1"
655668

656669
[[constraints.mem]]
657670
kind = "arith"
@@ -691,12 +704,29 @@ tag = "MEMW"
691704
input = [0, ["cast", "res", "DWordWL"], ["cast", "arg2", ["Byte", 8]], ["+", "timestamp", ["cast", 1, "DWordWL"]], "memory_2bytes", "memory_4bytes", "memory_8bytes"]
692705
multiplicity = "STORE"
693706

707+
[[constraints.mem]]
708+
kind = "template"
709+
tag = "IS_BIT"
710+
input = ["pc_double_read"]
711+
712+
[[constraints.mem]]
713+
kind = "template"
714+
tag = "IS_BIT"
715+
input = ["prev_pc_timestamp_borrow"]
716+
694717
[[constraints.mem]]
695718
kind = "interaction"
696-
tag = "MEMW"
697-
input = [1, ["cast", ["*", 2, 255], "DWordWL"], ["arr", ["idx", "next_pc", 0], ["idx", "next_pc", 1], 0, 0, 0, 0, 0, 0], ["+", "timestamp", ["cast", 1, "DWordWL"]], 1, 0, 0]
698-
output = ["arr", ["idx", "pc", 0], ["idx", "pc", 1], 0, 0, 0, 0, 0, 0]
719+
tag = "memory"
720+
input = [1, ["arr", ["+", ["*", 2, 255], "i"], 0], ["arr", ["+", ["-", ["idx", "timestamp", 0], ["*", 3, ["not", "pc_double_read"]]], ["*", ["^", 2, 32], "prev_pc_timestamp_borrow"]], ["-", ["idx", "timestamp", 1], "prev_pc_timestamp_borrow"]], ["idx", "pc", "i"]]
699721
multiplicity = ["not", "pad"]
722+
iter = ["i", 0, 1]
723+
724+
[[constraints.mem]]
725+
kind = "interaction"
726+
tag = "memory"
727+
input = [1, ["arr", ["+", ["*", 2, 255], "i"], 0], ["+", "timestamp", ["cast", 1, "DWordWL"]], ["idx", "next_pc", "i"]]
728+
multiplicity = ["-", ["not", "pad"]]
729+
iter = ["i", 0, 1]
700730

701731

702732
[[constraint_groups]]

0 commit comments

Comments
 (0)