ADDI x10 x8 3. Moreover, for the sake of this example, let us assume the following about the state of the VM.
- Current clock cycle:
- Current trace row:
- Total number of rows:
- was last updated with timestamp
- was last updated with timestamp
- has been accessed times before the current clock cycle
CPU Component Trace Columns and Constraints
In order to verify the correct execution of theADDI x10 x8 3 instruction at clock cycle , the CPU component will perform the following operations:
- Ensure a correct state transition;
- Fetch the instruction
ADDI x10 x8 3from the program memory component; - Decode the contents of the instruction and check the correctness of its format;
- Read contents of register from the register memory component;
- Interact with the execution component to execute the instruction
ADDI x10 x8 3; and - Update the contents of register based on the output of the execution component.
Ensuring a Correct State Transition
In this step, the CPU component ensures that the state transition is performed correctly in order to guarantee the correct ordering of instructions during the execution of a program. In order to do so, the CPU component performs the following checks:- It verifies that the program counter in the present row matches the value of the next program counter from the preceding row;
- It checks that clock values have been updated correctly; and
- It ensures that padding rows cannot be followed by a non-padding row.
ADDI x10 x8 3. Hence, the following holds:
Fetching the Instruction
In order to fecth the instruction being executed at the current clock cycle (ADDI x10 x8 3), the CPU component must interact with the program memory to read the instruction stored at the memory location pointed by the program counter. It must also ensure that the program counter is memory-aligned (i.e., a multiple of ).
Remark: Whenever constraints involve trace columns restricted to the same row, we omit the explicit index for readability. For instance, in the description below, we write instead of . All values set in the remainder of this section, unless explicitly specified, apply only to the row indexed by of the corresponding trace column.
In the specification, the CPU interaction with the program memory is captured by a call to the interface with parameters and to obtain . Note that the CPU component does not check the consistency of the program memory, which is handled separately by the program memory component.
In the actual implementation, this interface is not explicitly implemented since the trace columns for , , and are shared between the CPU and program memory components.
As a result of this interaction, the value of these columns will be as follows:
- = =
- = =
- = =
- = =
ADDI x10 x8 3 is as follows:
- Bits 0-6: corresponds to a constant associated with ADDI.
- Bits 7-11: corresponds to destination register . This should match the value of ;
- Bits 12-14: corresponds to a second constant associated with ADDI;
- Bits 15-19: corresponds to the source register . This should match the value of ;
- Bits 20-31: corresponds to the immediate value . This should match the value of ;
Decoding the Instruction
In this step, the CPU component decodes the instructionADDI x10 x8 3 that was fetched from the program memory and checks the correctness of the binary encoding.
To achieve this goal, the prover provides several auxiliary values (advices) to help verify the correctness of the binary encoding. More precisely, the prover will provide the following values:
- : The address of the destination register in the instruction
ADDI x10 x8 3. This corresponds to the destination register10. - : The address of the source register in the instruction
ADDI x10 x8 3. This corresponds to the destination register8. - : The address of the third operand. This corresponds to the immediate value
3 - : A flag indicating whether operand is used. Should be
1. - : A flag indicating whether operand is an immediate value, Should be
1. - : a selector flag which indicates an ADD or ADDI operation. Should be
1. - : a flag which indicates this is an ALU instruction with non-shift immediate values. Should be
1. - : a flag which indicates that this is a Type I instruction. Should be
1. - : a selector flag which indicates whether the current row is being used for padding. Should be
0. - : a flag which indicates whether the current row is the first row. Should be
0. - : a flag which indicates whether the current row is the last row. Should be
0.
ADDI x10 x8 3:
- =
0(bit 0 from ) - =
5(bits 1—4 from ) - =
0(bit 0 from ) - =
4(bits 1—4 from ) - =
3(bits 0—3 from ) - =
0(bits 4—7 from ) - =
0(bits 8—10 from ) - =
0(bit 11 from )
- Checking that only one instruction or padding flag is set
- Ensuring that the flag for operand is set to for all instructions except LUI, AUIPC, JAL, UNIMP
- Ensuring that for all non-ALU instructions
- Matching the instruction flag with the instruction opcode
- Checking ALU flags
- =
1 - =
0(all the flags on the right have already been set to 0) - =
1 - =
1 - =
1
- Making sure that the decomposition of each operand is correct
- Performing sign extension to derive operand from
- Checking the format of the instruction
Reading the Contents of Register
In order to read the contents of register at the current clock cycle, the CPU component must interact with the register memory component. In the specification, the interaction with the register memory is captured by a call to the interface with parameters , where indicates that this is the source register , to obtain the value . Like in the program memory case, the CPU component does not check the consistency of the register memory, which is handled separately by the register memory component. In the actual implementation, this interface is not explicitly implemented since the trace columns for , , , , and are shared between the CPU and the register memory components. As a result of this interaction, will be set to . Moreover, the value of the limbs for will be set to the limbs of :- = =
- = =
- = =
- = =
Executing the Instruction
In order to execute the instructionADDI x10 x8 3 at the current clock cycle, the CPU component must interact with the execution component.
In the specification, the interaction with the execution memory is captured by a call to the interface with parameters to obtain the value . Since this is an ADD operation, the execution component also updates the value of .
In the actual implementation, this interface is not explicitly implemented since the trace columns for , , , , , and are shared between the CPU and the execution components.
As a result of this interaction, the values of the limbs for will be updated as follows:
- =
- =
- =
- =
- =
- =
- =
- =
Updating the contents of register
In order to update the contents of register at the current clock cycle, the CPU component must interact with the register memory component. To do so, the CPU first needs to ensure that the value of register remains . For that, the CPU component uses an auxiliary value , which is supposed to be equal to whenever and otherwise. This is enforced via the following set of constraints. Since , the following must be true:- =
- =
- =
- = =
- = =
- = =
- = =
- = =
- = =
- = =
- = =
Execution Component Trace Columns and Constraints
In order to verify the correct execution of theADDI x10 x8 3 instruction at clock cycle , the execution component first enforces that the ADD operation is executed via the following set of constraints:
Since and , in order to satisfy the set of constraints above, it must be the case that:
- =
- =
- =
- =
- =
- =
- =
- =
- =
- =
- =
- =
- =
- =
Program Memory Component Trace Columns and Constraints
As mentioned in the Proving Memory section, the program memory component uses well-known offline memory checking techniques to maintain the consistency of the read accesses to the program memory. Since this is a read-only memory, the program memory component only requires a simplified version of the offline memory checking technique, in which each memory cell is associated with a counter that keeps track of the number of times a particular memory cell has been read. More precisely, the program memory component defines the following set of trace elements:- : the word-aligned base address associated with a program instruction
- : bits 0-7 of the instruction word stored at address
- : bits 8-15 of the instruction word stored at address
- : bits 16-23 of the instruction word stored at address
- : bits 24-31 of the instruction word stored at address
- : 4 limbs for the previous counter value associated with base address
- : 4 limbs for the current counter value associated with base address
- : a digest of the read set, used for logups
- : a digest of the write set, used for logups
- it checks that the counter associated with the address being accessed is updated correctly; and
- it verifies that the digests of the read and write sets are correctly updated.
Enforcing the correct update of access counters
In order to enforce the correct update of access counters, the program memory component verifies that the following set of constraints are satisfied. Since has been accessed times before the current clock cycle according to the initial assumption, we have:Enforcing the Correct Update of Read- and Write-Set Digests
Let denote a fingerprint function which takes as input the tuple , , , , , , , , and returns a field element in the secure extension field used by using a random value chosen by the verifier. In order to ensure that the digests for the read and write sets are updated correctly, one needs to make sure that the logup contribution associated with the entry , , gets added to the read set digest and that the logup contribution associated with the entry , , gets added to the write set digest. More precisely, the following set of transition constraints need to be enforced by the program memory component, where is a random value chosen by the verifier and is the row index: As stated before,- = =
- = =
- = =
- = =
Register Memory Component Trace Columns and Constraints
Like the program memory component, the register memory component uses well-known offline memory checking techniques to maintain the consistency of read and write accesses to the register memory. However, since this is a read-write memory, the register memory component needs to associate a timestamp to each memory cell in order to keep track of the last time a particular memory cell has been accessed. As in the program memory component, the register memory also makes use of logups to check the consistency between the read and write sets, where each element of the set has the form , , indicating that the value was written to address at time . Hence, to properly handle an access to a register address, one needs to maintain a set , , , , consisting of the register address, the previous and current values for that address, and previous and current time stamps. Moreover, since up to three register addresses can be accessed during an execution cycle, the register memory component defines 3 such sets of values. More precisely, the program memory component defines the following set of trace elements:- : the current execution time
- , , : register addresses
- , , : 32-bit values used to update register contents
- , , : current timestamps for the registers
- , , : previous 32-bit values stored at the registers
- , , : previous timestamps for the registers
- : a digest of the read set, used for logups.
- : a digest of the write set, used for logups.
- , , : flags indicating whether the set of trace elements , , , , for are being used
- it ensures that the current timestamps associated for , , satisfy the following constraints:
- it checks that the previous timestamps associated with the addresses being accessed preceed their current timestamps. That is,
- it verifies that the digests of the read and write sets are correctly updated.
Enforcing the Correct Update of Read- and Write-Set Digests
Let denote a fingerprint function which takes as input the tuple , , , , , , and returns a field element in the secure extension field used by using a random value chosen by the verifier. In order to ensure that the digests for the read and write sets are updated correctly, one needs to make sure that, whenever a register is accessed in a clock cycle, the logup contribution associated with the entry , , must be added to the read set digest and that the logup contribution associated with the entry , , must be added to the write set digest. More precisely, the following set of transition constraints need to be enforced by the register memory component, where is a random value chosen by the verifier and is the row index: According to the assumptions used for the current example, we have- was last updated with timestamp ;and
- was last updated with timestamp .
- gets updated to at the current clock cycle