Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 13 additions & 2 deletions helper_scripts/generate_EmulatorConstants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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' ..
Expand Down
2 changes: 1 addition & 1 deletion helper_scripts/generate_UArchStep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions shasum-download
Original file line number Diff line number Diff line change
@@ -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
28 changes: 21 additions & 7 deletions src/EmulatorCompat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}

Expand Down Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions src/EmulatorConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions src/SendCmioResponse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
33 changes: 21 additions & 12 deletions src/UArchStep.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion test/SendCmioResponse.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion test/UArchInterpret.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
54 changes: 47 additions & 7 deletions test/UArchInterpret.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -99,34 +99,46 @@ 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);

assertTrue(
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 {
Expand All @@ -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
Expand Down
Loading