diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fddb93..05141ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Changed microarchitecture cycle overflow into a state-preserving fixed point derived from `uarch.cycle` +- Changed microarchitecture cycle overflow to take precedence over halt + ## [0.14.0] - 2026-04-13 ### Added - Added `advanceStatus` to return advance status (accepted, rejected, exception) diff --git a/Makefile b/Makefile index ef10306..ab24582 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ DOWNLOADDIR := downloads SRC_DIR := src EMULATOR_VERSION ?= v0.21.0 -EMULATOR_TAG ?= -test3 +EMULATOR_TAG ?= -test5 SOLIDITY_VERSION ?= 0.8.30 diff --git a/helper_scripts/generate_EmulatorConstants.lua b/helper_scripts/generate_EmulatorConstants.lua index 2b90f7e..7049659 100755 --- a/helper_scripts/generate_EmulatorConstants.lua +++ b/helper_scripts/generate_EmulatorConstants.lua @@ -18,8 +18,16 @@ local out = io.stdout out:write(' bytes32 constant UARCH_PRISTINE_STATE_HASH = 0x' .. hexstring(cartesi.UARCH_PRISTINE_STATE_HASH) .. ';\n') out:write(' uint64 constant UARCH_CYCLE_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("uarch_cycle")) .. ';\n') out:write(' uint64 constant UARCH_CYCLE_MAX = 0x' .. hex(cartesi.UARCH_CYCLE_MAX) .. ';\n') -out:write(' uint64 constant UARCH_HALT_FLAG_ADDRESS = 0x' .. - hex(cartesi.machine:get_reg_address("uarch_halt_flag")) .. ';\n') +out:write(' uint64 constant ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE = ' .. + cartesi.ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE .. ';\n') +out:write(' uint64 constant ROLLUP_LOG2_MAX_UARCH_CYCLES_PER_MCYCLE = ' .. + cartesi.ROLLUP_LOG2_MAX_UARCH_CYCLES_PER_MCYCLE .. ';\n') +out:write(' uint64 constant ROLLUP_LOG2_MAX_OUTPUT_COUNT = ' .. + cartesi.ROLLUP_LOG2_MAX_OUTPUT_COUNT .. ';\n') +out:write(' uint64 constant ROLLUP_LOG2_MAX_ADVANCE_STATES_PER_EPOCH = ' .. + cartesi.ROLLUP_LOG2_MAX_ADVANCE_STATES_PER_EPOCH .. ';\n') +out:write(' uint64 constant UARCH_HALT_ADDRESS = 0x' .. + hex(cartesi.machine:get_reg_address("uarch_halt")) .. ';\n') out:write(' uint64 constant UARCH_PC_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("uarch_pc")) .. ';\n') out:write(' uint64 constant UARCH_X0_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("uarch_x0")) .. ';\n') out:write(' uint64 constant UARCH_SHADOW_START_ADDRESS = 0x' .. hex(cartesi.UARCH_SHADOW_START_ADDRESS) .. ';\n') @@ -34,6 +42,9 @@ out:write(' uint64 constant UARCH_ECALL_FN_PUTCHAR = ' .. cartesi.UARCH_ECALL out:write(' uint64 constant UARCH_ECALL_FN_WRITE_TLB = ' .. cartesi.UARCH_ECALL_FN_WRITE_TLB .. ';\n') out:write(' uint64 constant HTIF_YIELD = 0x' .. hex(cartesi.machine:get_reg_address("htif_iyield")) .. ';\n') out:write(' uint64 constant IFLAGS_Y_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("iflags_Y")) .. ';\n') +out:write(' uint64 constant MCYCLE_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("mcycle")) .. ';\n') +out:write(' uint64 constant IMCYCLEMAX_ADDRESS = 0x' .. + hex(cartesi.machine:get_reg_address("imcyclemax")) .. ';\n') out:write(' uint64 constant HTIF_FROMHOST_ADDRESS = 0x' .. hex(cartesi.machine:get_reg_address("htif_fromhost")) .. ';\n') out:write(' uint64 constant HTIF_TOHOST_ADDRESS = 0x' .. diff --git a/helper_scripts/generate_UArchStep.sh b/helper_scripts/generate_UArchStep.sh index 11a18d8..ba23738 100755 --- a/helper_scripts/generate_UArchStep.sh +++ b/helper_scripts/generate_UArchStep.sh @@ -55,7 +55,7 @@ cpp_src=`echo "${BASH_REMATCH[1]}" \ | $SED -E "s/static inline (\w+) ($INTERNAL_FN)\(([^\n]*)\) \{/function \2\(\3\) internal pure returns \(\1\)\{/g" \ | $SED -E "s/static inline (\w+) (\w+)\(([^\n]*)\) \{/function \2\(\3\) private pure returns \(\1\)\{/g" \ | $SED -E "s/($COMPAT_FNS)/EmulatorCompat.\1/g" \ - | $SED -E "s/([^a-zA-Z])($CONSTANTS)([^a-zA-Z])/EmulatorConstants.\1\2\3/g" \ + | $SED -E "s/([^a-zA-Z])($CONSTANTS)([^a-zA-Z])/\1EmulatorConstants.\2\3/g" \ | $SED "s/ returns (void)//g"` # compose the solidity file from all components diff --git a/shasum-download b/shasum-download index 8777e7b..fc39192 100644 --- a/shasum-download +++ b/shasum-download @@ -1,2 +1,2 @@ -28fd67825be2bf53188326065a06f9a5019f7bd07426d21f6053f34e8652fb3e downloads/machine-emulator-tests-data.deb -f7d22eacba7c9df62fa6dfa63fe75bdc387f6c7a98f20606bcfd0b01a851830c downloads/uarch-riscv-tests-json-logs.tar.gz +182c7a57481daa4076a488b1c3f0b8ba01d0271c267f79c9f5ecb947cac6bd50 downloads/machine-emulator-tests-data.deb +79e0797c4a30c6a3f2523353006db0b8b99b6a6cdf17670634c6befa024905bf downloads/uarch-riscv-tests-json-logs.tar.gz diff --git a/src/EmulatorCompat.sol b/src/EmulatorCompat.sol index db3f0a5..9137e1d 100644 --- a/src/EmulatorCompat.sol +++ b/src/EmulatorCompat.sol @@ -56,22 +56,19 @@ library EmulatorCompat { ); } - function readHaltFlag(AccessLogs.Context memory a) + function readHalt(AccessLogs.Context memory a) internal pure returns (uint64) { return a.readWord( - EmulatorConstants.UARCH_HALT_FLAG_ADDRESS.toPhysicalAddress() + EmulatorConstants.UARCH_HALT_ADDRESS.toPhysicalAddress() ); } - function writeHaltFlag(AccessLogs.Context memory a, uint64 val) - internal - pure - { + function writeHalt(AccessLogs.Context memory a, uint64 val) internal pure { a.writeWord( - EmulatorConstants.UARCH_HALT_FLAG_ADDRESS.toPhysicalAddress(), val + EmulatorConstants.UARCH_HALT_ADDRESS.toPhysicalAddress(), val ); } @@ -178,6 +175,23 @@ library EmulatorCompat { a.writeWord(EmulatorConstants.IFLAGS_Y_ADDRESS.toPhysicalAddress(), val); } + function readMcycle(AccessLogs.Context memory a) + internal + pure + returns (uint64) + { + return a.readWord(EmulatorConstants.MCYCLE_ADDRESS.toPhysicalAddress()); + } + + function writeImcyclemax(AccessLogs.Context memory a, uint64 val) + internal + pure + { + a.writeWord( + EmulatorConstants.IMCYCLEMAX_ADDRESS.toPhysicalAddress(), val + ); + } + function writeHtifFromhost(AccessLogs.Context memory a, uint64 val) internal pure diff --git a/src/EmulatorConstants.sol b/src/EmulatorConstants.sol index 47f1993..7c1aa1d 100644 --- a/src/EmulatorConstants.sol +++ b/src/EmulatorConstants.sol @@ -25,10 +25,14 @@ library EmulatorConstants { // START OF AUTO-GENERATED CODE bytes32 constant UARCH_PRISTINE_STATE_HASH = - 0x42e6d54b3b07b4a1c5f2ed13aae7c8d924269c32728a5b66f54b70358b4d99e7; + 0xfecd1447b18725c91ba909a13b3d059d3628a625f668ce991e6ab7c901a65d2c; uint64 constant UARCH_CYCLE_ADDRESS = 0x400008; - uint64 constant UARCH_CYCLE_MAX = 0x100000; - uint64 constant UARCH_HALT_FLAG_ADDRESS = 0x400000; + uint64 constant UARCH_CYCLE_MAX = 0xfffff; + uint64 constant ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE = 48; + uint64 constant ROLLUP_LOG2_MAX_UARCH_CYCLES_PER_MCYCLE = 20; + uint64 constant ROLLUP_LOG2_MAX_OUTPUT_COUNT = 63; + uint64 constant ROLLUP_LOG2_MAX_ADVANCE_STATES_PER_EPOCH = 24; + uint64 constant UARCH_HALT_ADDRESS = 0x400000; uint64 constant UARCH_PC_ADDRESS = 0x400010; uint64 constant UARCH_X0_ADDRESS = 0x400018; uint64 constant UARCH_SHADOW_START_ADDRESS = 0x400000; @@ -41,10 +45,12 @@ library EmulatorConstants { uint64 constant UARCH_ECALL_FN_HALT = 1; uint64 constant UARCH_ECALL_FN_PUTCHAR = 2; uint64 constant UARCH_ECALL_FN_WRITE_TLB = 4; - uint64 constant HTIF_YIELD = 0x348; - uint64 constant IFLAGS_Y_ADDRESS = 0x300; - uint64 constant HTIF_FROMHOST_ADDRESS = 0x330; - uint64 constant HTIF_TOHOST_ADDRESS = 0x328; + uint64 constant HTIF_YIELD = 0x350; + uint64 constant IFLAGS_Y_ADDRESS = 0x308; + uint64 constant MCYCLE_ADDRESS = 0x100; + uint64 constant IMCYCLEMAX_ADDRESS = 0x2f8; + uint64 constant HTIF_FROMHOST_ADDRESS = 0x338; + uint64 constant HTIF_TOHOST_ADDRESS = 0x330; uint8 constant HTIF_YIELD_REASON_ADVANCE_STATE = 0x0; uint32 constant HASH_TREE_LOG2_WORD_SIZE = 0x5; uint32 constant HASH_TREE_WORD_SIZE = uint32(1) << HASH_TREE_LOG2_WORD_SIZE; diff --git a/src/SendCmioResponse.sol b/src/SendCmioResponse.sol index 4bcb0e7..ca5115b 100644 --- a/src/SendCmioResponse.sol +++ b/src/SendCmioResponse.sol @@ -78,6 +78,21 @@ library SendCmioResponse { return; } } + if (reason == EmulatorConstants.HTIF_YIELD_REASON_ADVANCE_STATE) { + uint64 mcycle = EmulatorCompat.readMcycle(a); + uint64 maxMcycles = + EmulatorCompat.uint64ShiftLeft( + 1, + uint32( + EmulatorConstants.ROLLUP_LOG2_MAX_MCYCLES_PER_ADVANCE_STATE + ) + ) - 1; + uint64 maxUint64 = ~uint64(0); + uint64 imcyclemax = mcycle > maxUint64 - maxMcycles + ? maxUint64 + : mcycle + maxMcycles; + EmulatorCompat.writeImcyclemax(a, imcyclemax); + } // Record the machine root hash to revert to in case the response is eventually rejected EmulatorCompat.writeRevertRootHash(a, revertRootHash); if (dataLength > 0) { diff --git a/src/UArchStep.sol b/src/UArchStep.sol index 65e3146..6e7b9ce 100644 --- a/src/UArchStep.sol +++ b/src/UArchStep.sol @@ -28,8 +28,8 @@ library UArchStep { enum UArchStepStatus { Success, // one micro instruction was executed successfully - CycleOverflow, // already at fixed point: uarch cycle has reached its maximum value - UArchHalted // already at fixed point: microarchitecture is halted + UArchCycleOverflow, // uarch cycle reached or was already at its maximum value + UArchHalted // microarchitecture reached or was already at its halted fixed point } // Memory read/write access @@ -1072,7 +1072,7 @@ library UArchStep { // return value is in a0 (and maybe also in a1) uint64 fn = EmulatorCompat.readX(a, 17); // a7 contains the function number if (fn == EmulatorConstants.UARCH_ECALL_FN_HALT) { - return EmulatorCompat.writeHaltFlag(a, 1); + return EmulatorCompat.writeHalt(a, 1); } if (fn == EmulatorConstants.UARCH_ECALL_FN_PUTCHAR) { uint64 c = EmulatorCompat.readX(a, 10); // a0 contains the character to print @@ -1106,7 +1106,7 @@ library UArchStep { pure returns (bool) { - return ((insn & 0x7f)) == opcode; + return (insn & 0x7f) == opcode; } /// \brief Returns true if the opcode and funct3 fields of an instruction match the provided arguments @@ -1129,7 +1129,7 @@ library UArchStep { uint32 funct7 ) private pure returns (bool) { uint32 mask = (0x7f << 25) | (7 << 12) | 0x7f; - return ((insn & mask)) + return (insn & mask) == (EmulatorCompat.uint32ShiftLeft(funct7, 25) | EmulatorCompat.uint32ShiftLeft(funct3, 12) | opcode); } @@ -1143,7 +1143,7 @@ library UArchStep { uint32 funct7Sr1 ) private pure returns (bool) { uint32 mask = (0x3f << 26) | (7 << 12) | 0x7f; - return ((insn & mask)) + return (insn & mask) == (EmulatorCompat.uint32ShiftLeft(funct7Sr1, 26) | EmulatorCompat.uint32ShiftLeft(funct3, 12) | opcode); } @@ -1317,22 +1317,31 @@ library UArchStep { pure returns (UArchStepStatus) { - // This must be the first read in order to match the first log access in machine.verify_step_uarch + // Report the derived overflow fixed point before all other break reasons. uint64 cycle = EmulatorCompat.readCycle(a); - // do not advance if cycle will overflow if (cycle >= EmulatorConstants.UARCH_CYCLE_MAX) { - return UArchStepStatus.CycleOverflow; + return UArchStepStatus.UArchCycleOverflow; } - // do not advance if machine is halted - if (EmulatorCompat.readHaltFlag(a) != 0) { + // Report an existing ordinary halt fixed point. + if (EmulatorCompat.readHalt(a) != 0) { return UArchStepStatus.UArchHalted; } - // execute next instruction + // Execute next instruction uint64 pc = EmulatorCompat.readPc(a); uint32 insn = readUint32(a, pc); executeInsn(a, insn, pc); cycle = cycle + 1; EmulatorCompat.writeCycle(a, cycle); + // Overflow has precedence when this step reaches the cycle limit. + if (cycle >= EmulatorConstants.UARCH_CYCLE_MAX) { + return UArchStepStatus.UArchCycleOverflow; + } + // ECALL is the only instruction that can halt the uarch. + if (insn == uint32(0x73)) { + if (EmulatorCompat.readHalt(a) != 0) { + return UArchStepStatus.UArchHalted; + } + } return UArchStepStatus.Success; } diff --git a/test/SendCmioResponse.t.sol b/test/SendCmioResponse.t.sol index 1b4086c..b038ebc 100644 --- a/test/SendCmioResponse.t.sol +++ b/test/SendCmioResponse.t.sol @@ -88,7 +88,7 @@ contract SendCmioResponse_Test is AccessLogJsonParse { // Prepare arguments for sendCmioResponse // These values are hard-coded in order to match the values used when generating the test log file - uint16 reason = 1; + uint16 reason = EmulatorConstants.HTIF_YIELD_REASON_ADVANCE_STATE; bytes memory response = bytes("This is a test cmio response"); require( response.length == 28, diff --git a/test/UArchInterpret.sol b/test/UArchInterpret.sol index 34d3037..4ca1dd3 100644 --- a/test/UArchInterpret.sol +++ b/test/UArchInterpret.sol @@ -28,7 +28,7 @@ library UArchInterpret { { UArchStep.UArchStepStatus status; - while (status != UArchStep.UArchStepStatus.CycleOverflow) { + while (status != UArchStep.UArchStepStatus.UArchCycleOverflow) { status = UArchStep.step(accessLogs); if (status == UArchStep.UArchStepStatus.UArchHalted) { diff --git a/test/UArchInterpret.t.sol b/test/UArchInterpret.t.sol index 5f3d243..2a1514f 100644 --- a/test/UArchInterpret.t.sol +++ b/test/UArchInterpret.t.sol @@ -86,7 +86,7 @@ contract UArchInterpretTest is Test { uint64(TEST_SUCCEEDED) ); - bool halt = EmulatorCompat.readHaltFlag(a) != 0; + bool halt = EmulatorCompat.readHalt(a) != 0; assertTrue(halt, "machine should halt"); uint64 cycle = EmulatorCompat.readCycle(a); @@ -99,27 +99,28 @@ contract UArchInterpretTest is Test { // init pc to ram start EmulatorCompat.writePc(a, EmulatorConstants.UARCH_RAM_START_ADDRESS); - // init cycle to uint64.max - EmulatorCompat.writeCycle(a, type(uint64).max); + // init cycle to the final allowed uarch cycle + EmulatorCompat.writeCycle(a, EmulatorConstants.UARCH_CYCLE_MAX); UArchStep.UArchStepStatus status = UArchInterpret.interpret(a); assertTrue( - status == UArchStep.UArchStepStatus.CycleOverflow, + status == UArchStep.UArchStepStatus.UArchCycleOverflow, "machine should be cycle overflow" ); uint64 cycle = EmulatorCompat.readCycle(a); assertEq( cycle, - type(uint64).max, - "step should not advance when cycle is uint64.max" + EmulatorConstants.UARCH_CYCLE_MAX, + "step should not advance when cycle is at its maximum" ); + assertEq(EmulatorCompat.readHalt(a), 0, "overflow should preserve halt"); // reset cycle to 0 EmulatorCompat.writeCycle(a, 0); // set machine to halt - EmulatorCompat.writeHaltFlag(a, 1); + EmulatorCompat.writeHalt(a, 1); status = UArchInterpret.interpret(a); @@ -127,6 +128,17 @@ contract UArchInterpretTest is Test { status == UArchStep.UArchStepStatus.UArchHalted, "machine should halt" ); + + // overflow takes precedence over halt + EmulatorCompat.writeCycle(a, EmulatorConstants.UARCH_CYCLE_MAX); + + status = UArchInterpret.interpret(a); + + assertTrue( + status == UArchStep.UArchStepStatus.UArchCycleOverflow, + "machine should be cycle overflow" + ); + assertEq(EmulatorCompat.readHalt(a), 1, "overflow should preserve halt"); } function testIllegalInstruction() public { @@ -141,6 +153,34 @@ contract UArchInterpretTest is Test { ExternalUArchInterpret.interpret(a); } + function testHaltEcallReachesCycleLimit() public { + AccessLogs.Context memory a = newAccessLogsContext(); + a.buffer.data = bytes.concat(a.buffer.data, new bytes(8)); + + EmulatorCompat.writePc(a, EmulatorConstants.UARCH_RAM_START_ADDRESS); + EmulatorCompat.writeWord( + a, EmulatorConstants.UARCH_RAM_START_ADDRESS, 0x73 + ); + EmulatorCompat.writeX(a, 17, EmulatorConstants.UARCH_ECALL_FN_HALT); + EmulatorCompat.writeCycle(a, EmulatorConstants.UARCH_CYCLE_MAX - 1); + + UArchStep.UArchStepStatus status = UArchStep.step(a); + + assertTrue( + status == UArchStep.UArchStepStatus.UArchCycleOverflow, + "cycle overflow should take precedence over halt" + ); + assertEq( + EmulatorCompat.readCycle(a), + EmulatorConstants.UARCH_CYCLE_MAX, + "step should reach the cycle limit" + ); + assertTrue( + EmulatorCompat.readHalt(a) != 0, + "halt ECALL should set a non-zero halt value" + ); + } + function testRemovedMarkDirtyPageEcall() public { // function code 3 was the mark_dirty_page ecall and has been removed, so the // binary that issues it must now revert as an unsupported ecall