Skip to content
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

Merged
merged 16 commits into from
Oct 11, 2024
Merged

Feat: Implement JAL opcode #305

merged 16 commits into from
Oct 11, 2024

Conversation

bgillesp
Copy link
Collaborator

@bgillesp bgillesp commented Oct 3, 2024

Implement a J-type instruction base type and the JAL opcode.

@bgillesp bgillesp changed the base branch from master to bg-refactor-instruction-types October 3, 2024 21:28
@bgillesp bgillesp changed the title [WIP] Feat: JAL [WIP] Feat: Implement JAL opcode Oct 4, 2024
Base automatically changed from bg-refactor-instruction-types to master October 4, 2024 01:56
@bgillesp bgillesp changed the title [WIP] Feat: Implement JAL opcode Feat: Implement JAL opcode Oct 5, 2024
@bgillesp bgillesp marked this pull request as ready for review October 5, 2024 22:24
@bgillesp bgillesp requested a review from naure October 5, 2024 22:24
add_records.push(record.clone());
} else if kind == BLTU {
bltu_records.push(record.clone());
match kind {
Copy link
Collaborator

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
Copy link
Collaborator

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.

Copy link
Collaborator Author

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);
Copy link
Collaborator

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(),
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See PR: #333

@@ -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,
Copy link
Collaborator

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.

@bgillesp
Copy link
Collaborator Author

bgillesp commented Oct 7, 2024

Note: I realized that there is an issue with the handling of immediates and pc in the JAL opcode in this PR -- representing these as native WitIn values instead of as UInts means that the addition does not wrap properly mod 2^32. The fix is not too bad, and I should be able to push an update tonight or tomorrow morning US time.

@kunxian-xia kunxian-xia self-requested a review October 8, 2024 06:06
Bryan Gillespie added 2 commits October 8, 2024 11:35
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.
@bgillesp
Copy link
Collaborator Author

bgillesp commented Oct 8, 2024

Okay, the most recent commits should fix the issue I mentioned -- PR should be ready again for review.

hero78119 added a commit that referenced this pull request Oct 9, 2024
…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]>
ceno_zkvm/src/instructions/riscv/jump/jal.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/jump/jal.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/jump/jal.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@kunxian-xia kunxian-xia left a 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.

ceno_zkvm/src/instructions/riscv/jump/jal.rs Outdated Show resolved Hide resolved
ceno_zkvm/examples/riscv_opcodes.rs Outdated Show resolved Hide resolved
ceno_zkvm/examples/riscv_opcodes.rs Outdated Show resolved Hide resolved
@kunxian-xia
Copy link
Collaborator

Note: I realized that there is an issue with the handling of immediates and pc in the JAL opcode in this PR -- representing these as native WitIn values instead of as UInts means that the addition does not wrap properly mod 2^32. The fix is not too bad, and I should be able to push an update tonight or tomorrow morning US time.

The case pc + imm wraps mod 2^32 happens most due to imm is negative signed number. If we encode negative number in prime field then we can avoid this UInt overhead. For example, previously -1 is encoded as 0xffffffff_u32 notation, if we store pc with native witIn and encode -1 as p - 1 then it's easy to see this is equivalent to UInt approach.

@kunxian-xia kunxian-xia linked an issue Oct 10, 2024 that may be closed by this pull request
@bgillesp
Copy link
Collaborator Author

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 2^32" -- can we make any explicit assumptions about the range of values for the program counter in our VM? The possible offsets for JAL range between -2^20 and 2^20 - 2, so if we can be sure that pc for an instruction can't be smaller than 2^20 or larger than 2^32 - (2^20 - 2), then there's no issue of mod 2^32 arithmetic.

Digging a little deeper, in ceno_emul/src/platform.rs the spec gives 0x2000_0000 to 0x3000_0000 - 1 for the start and end addresses for the ROM, which would certainly do it -- does anyone know if this program address range is enforced in the program lookup table logic?

@bgillesp
Copy link
Collaborator Author

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,
Copy link
Collaborator

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.

Copy link
Collaborator

@kunxian-xia kunxian-xia Oct 11, 2024

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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.

Bryan Gillespie added 2 commits October 11, 2024 07:22
@kunxian-xia kunxian-xia merged commit cdb771a into master Oct 11, 2024
6 checks passed
@kunxian-xia kunxian-xia deleted the feat/jal-opcode branch October 11, 2024 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

jal: Jump and link
5 participants