-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Implement JAL opcode #305
Conversation
Still requires tests and integration
b10b9aa
to
f8f76ce
Compare
ddae3bc
to
3e1bdbd
Compare
add_records.push(record.clone()); | ||
} else if kind == BLTU { | ||
bltu_records.push(record.clone()); | ||
match kind { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, matching is better than chains of if-else.
// func7 rs2 rs1 f3 rd opcode | ||
0b_0000000_00100_00001_000_00100_0110011, // add x4, x4, x1 <=> addi x4, x4, 1 | ||
0b_0000000_00011_00010_000_00011_0110011, // add x3, x3, x2 <=> addi x3, x3, -1 | ||
0b_1_111111_00011_00000_110_1100_1_1100011, // bltu x0, x3, -8 | ||
0b_0_0000000010_0_00000000_00001_1101111, // jal x1, 4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not something introduced in this PR, but:
This style of specifying test data is approximately unreadable. At least for me. I can't (easily) tell whether these binary numbers here make any sense.
We can test the decoding from binary to something symbolic one by one for each instruction, but then when we test multiple instructions together, we should use the symbolic form as an intermediary input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point, and strong agree that we could use a better way to encode this data -- it's challenging both to read and write instructions like this. I propose leaving the formatting as is in this PR, and follow up with at least a small rework to make these values more legible and less error-prone.
Issue to keep track of the improvement: #331
@@ -118,6 +120,7 @@ fn main() { | |||
let mut zkvm_fixed_traces = ZKVMFixedTraces::default(); | |||
zkvm_fixed_traces.register_opcode_circuit::<AddInstruction<E>>(&zkvm_cs); | |||
zkvm_fixed_traces.register_opcode_circuit::<BltuInstruction>(&zkvm_cs); | |||
zkvm_fixed_traces.register_opcode_circuit::<JalInstruction<E>>(&zkvm_cs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continued: See, we even already have some symbolic form for AddInstruction
and JlaInstruction
etc, so might as well make use of them.
// Fetch instruction | ||
circuit_builder.lk_fetch(&InsnRecord::new( | ||
vm_state.pc.expr(), | ||
(insn_kind.codes().opcode as usize).into(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit suspicious. Why do we need to cast as usize
to just run .into()
afterwards? Can't we provide an Into
/ From
from instance that would let us get by without as usize
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point! The into
call here relies on the implementation of From<usize>
for the Expression
type right now, but it looks like it's a little complicated to specify generic behavior over all primitive integer types (as far as I can tell). I'll put an implementation of this into a separate small PR in case folks have opinions on how this should be handled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See PR: #333
ceno_zkvm/src/scheme/mock_prover.rs
Outdated
@@ -76,6 +76,8 @@ pub const MOCK_PROGRAM: &[u32] = &[ | |||
0b_1_111111 << 25 | MOCK_RS2 << 20 | MOCK_RS1 << 15 | 0b_111 << 12 | 0b_1100_1 << 7 | 0x63, | |||
// bge x2, x3, -8 | |||
0b_1_111111 << 25 | MOCK_RS2 << 20 | MOCK_RS1 << 15 | 0b_101 << 12 | 0b_1100_1 << 7 | 0x63, | |||
// jal x4, 0x10004 | |||
0b_0_0000000010_0_00010000 << 12 | MOCK_RD << 7 | 0x6f, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As said above, putting binary numbers into the test data here basically impossible to read.
Note: I realized that there is an issue with the handling of immediates and |
Previously JAL addition of pc with immediate used unqualified WitIn objects which would not behave properly in terms of wrapping behavior. UInt addition handles mod 2^32 wrapping directly by using table lookups to verify 16-bit limbs.
Okay, the most recent commits should fix the issue I mentioned -- PR should be ready again for review. |
…ant (#333) Current implementation only provides `From<usize>`, which requires explicit type conversions in various locations. This PR provides support for type conversions for arbitrary primitive integer types, specifically: `u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize` In reference to [this comment](#305 (comment)). --------- Co-authored-by: Bryan Gillespie <[email protected]> Co-authored-by: Ming <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proposed another approach to use 4 witIns only.
The case |
So the question I see here is definitely "is it possible for the JAL pc arithmetic to result in either an underflow or an overflow mod Digging a little deeper, in |
Okay, I revised the implementation to use native WitIns for the pc and immediate values as suggested -- under the assumption that program code lies in the expected address range 0x2000_0000 to 0x3000_000 - 1, I agree that the mod 2^32 pc arithmetic shouldn't be an issue. |
pub struct JInstructionConfig<E: ExtensionField> { | ||
pub vm_state: StateInOut<E>, | ||
pub rd: WriteRD<E>, | ||
pub imm: WitIn, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically the imm
witness is redundant since it equals a degree-1 expression (next_pc - pc
). You could use the expression directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we can use StateInOut::construct_circuit(circuit_builder, false)?;
to initialize vm_state which is more natural.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice find @naure! We can't use StateInOut::construct_circuit(circuit_builder, false)
@kunxian-xia because it imposes the constraint next_pc = pc + 4
, but we can put this direct check into the J-type instruction gadget since JAL is the only opcode that uses it. Pushing a commit with this change now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! This is ready to merge from my side.
Saves 1 WitIn due to not having to represent the immediate value directly.
Implement a J-type instruction base type and the
JAL
opcode.