Files
retroSoC/scripts/apu_interpreter.py
T
maksyuki 84332d4573 feat: enhance APU Codec Tests and Register Definitions
- Added new tests for FLAC encoding and decoding, including handling of rice parameters and partial LPC tail.
- Introduced WAV PCM encoding tests to ensure correct behavior across various configurations.
- Updated test cases to validate the handling of malformed FLAC files and metadata structures.
- Enhanced the APU register tests to include expanded P5 profile capabilities and ensure accurate register descriptions.
- Improved assertions for manifest checks in the APU release tests to validate ABI and instruction counts.
- Refactored existing tests for clarity and maintainability, ensuring they align with the latest APU specifications.
2026-09-08 14:45:40 +08:00

739 lines
30 KiB
Python

"""Reference interpreter for the enabled APU-P3/P4/P5 microcode targets."""
from __future__ import annotations
import argparse
import json
from dataclasses import dataclass, field
from pathlib import Path
from apu_isa import (
APUMC_ABI_V1,
APUMC_TARGETS,
BitstreamOpcode,
ControlOpcode,
EntropyOpcode,
Entry,
Instruction,
InstructionClass,
LocalOpcode,
ScalarOpcode,
TransportOpcode,
parse_apumc,
trap_detail,
validate_instruction,
)
from apu_primitives import PrimitiveBam, PrimitiveFault, PrimitiveResult
class SequencerTrap(RuntimeError):
def __init__(self, reason: int, pc: int, instruction: Instruction, detail: int | None = None):
self.reason = reason
self.pc = pc
self.instruction = instruction
self.detail = trap_detail(reason, pc, instruction) if detail is None else detail
super().__init__(f"APU sequencer trap {reason} at PC {pc}")
@dataclass
class PredicateInputs:
input_exhausted: bool = False
input_ready: bool = False
output_ready: bool = False
kernel_done: bool = False
transport_done: bool = False
@dataclass
class TransportCommand:
opcode: TransportOpcode
destination: int
offset: int
count: int
payload: bytes
cycles: int
@dataclass
class TransportModel:
input_data: bytes = b""
output_capacity: int = 0xFFFFFFFF
stream_output: bool = False
latency: int = 2
output_frame_bytes: int = 4
input_cursor: int = 0
output: bytearray = field(default_factory=bytearray)
frames: int = 0
logical_input: int = 0
logical_output: int = 0
result_code: int = 0
result_stage: int = 0
result_detail: int = 0
events: int = 0
pending: list[TransportCommand] = field(default_factory=list)
completed: list[tuple[int, int]] = field(default_factory=list)
output_commands: list[dict[str, int]] = field(default_factory=list)
@property
def idle(self) -> bool:
return not self.pending
def reset(self) -> None:
self.input_cursor = 0
self.output.clear()
self.frames = 0
self.logical_input = 0
self.logical_output = 0
self.result_code = 0
self.result_stage = 0
self.result_detail = 0
self.events = 0
self.pending.clear()
self.completed.clear()
self.output_commands.clear()
def abort(self) -> None:
self.pending.clear()
self.completed.clear()
def issue(
self,
instruction: Instruction,
registers: list[int],
entry: Entry,
primitives: PrimitiveBam,
) -> bool:
opcode = TransportOpcode(instruction.opcode)
offset = registers[instruction.src0]
count = registers[instruction.src1]
if opcode == TransportOpcode.INPUT_REFILL:
if (
count < 4
or count > 256
or count & 3
or offset & 3
or offset + count > entry.scratch_bytes
):
raise PrimitiveFault(11, 11, 5)
actual = min(count, len(self.input_data) - self.input_cursor)
needed = (actual + 3) // 4
if any(command.opcode == TransportOpcode.INPUT_REFILL for command in self.pending):
return False
if len(primitives.input_fifo.words) + needed > 64:
return False
payload = self.input_data[self.input_cursor : self.input_cursor + actual]
self.pending.append(
TransportCommand(
opcode, instruction.dst, offset, actual, payload, max(1, self.latency)
)
)
return True
if opcode in (TransportOpcode.OUTPUT_COMMIT, TransportOpcode.OUTPUT_STREAM):
if count < 1 or count > 256 or offset & 3 or offset + count > entry.scratch_bytes:
raise PrimitiveFault(11, 11, 5)
if count % self.output_frame_bytes or (
(opcode == TransportOpcode.OUTPUT_STREAM) != self.stream_output
):
raise PrimitiveFault(11, 11, 5)
if not primitives.output_fifo.empty:
raise PrimitiveFault(11, 11, 5)
if any(
command.opcode in (TransportOpcode.OUTPUT_COMMIT, TransportOpcode.OUTPUT_STREAM)
for command in self.pending
):
return False
if (
opcode == TransportOpcode.OUTPUT_COMMIT
and len(self.output) + count > self.output_capacity
):
raise PrimitiveFault(8, 8, 9)
address = entry.scratch_base + offset
payload = primitives.memory.read(address, count, entry)
self.output_commands.append({"opcode": int(opcode), "bytes": count})
self.pending.append(
TransportCommand(
opcode, instruction.dst, offset, count, payload, max(1, self.latency)
)
)
return True
if opcode == TransportOpcode.DMA_WAIT:
if not self.idle:
return False
self.completed.append((instruction.dst, 0))
elif opcode == TransportOpcode.FRAME_COMMIT:
if (
count != self.output_frame_bytes
or self.logical_input + offset > self.input_cursor
or self.logical_output + count > len(self.output)
):
raise PrimitiveFault(11, 11, 9)
self.logical_input = min(0xFFFFFFFF, self.logical_input + offset)
self.logical_output = min(0xFFFFFFFF, self.logical_output + count)
self.frames = min(0xFFFFFFFF, self.frames + 1)
self.completed.append((instruction.dst, 0))
elif opcode == TransportOpcode.JOB_RESULT:
code = offset & 0x3F
if offset >> 6 or code > 22:
raise PrimitiveFault(11, 11, 1)
self.result_code = code
self.result_stage = instruction.aux & 0xF
self.result_detail = count
self.completed.append((instruction.dst, 0))
else:
self.events |= instruction.immediate & 0x0C0
self.completed.append((instruction.dst, 0))
return True
def tick(self, entry: Entry, primitives: PrimitiveBam) -> None:
if not self.pending:
return
for command in self.pending:
command.cycles -= 1
ready = [command for command in self.pending if command.cycles == 0]
for command in ready:
if command.opcode == TransportOpcode.INPUT_REFILL:
primitives.memory.write(entry.scratch_base + command.offset, command.payload, entry)
for position in range(0, command.count, 4):
data = command.payload[position : position + 4]
last = self.input_cursor + position + len(data) == len(self.input_data)
primitives.input_fifo.push(
int.from_bytes(data.ljust(4, b"\0"), "little")
| (len(data) << 32)
| (int(last) << 40)
)
self.input_cursor += command.count
else:
self.output.extend(command.payload)
context_output = entry.scratch_base + entry.scratch_bytes - 64 + 44
primitives.memory.write(
context_output,
len(self.output).to_bytes(4, "little"),
entry,
)
self.completed.append((command.destination, command.count))
self.pending.remove(command)
@dataclass
class Machine:
instructions: list[Instruction]
entry: Entry
predicate_inputs: PredicateInputs = field(default_factory=PredicateInputs)
timeout: int = 0xFFFF
fetch_no_retirement_cycles: int = 2
stall_cycles: dict[int, int] = field(default_factory=dict)
registers: list[int] = field(default_factory=lambda: [0] * 16)
eq: bool = False
signed_lt: bool = False
unsigned_lt: bool = False
pc: int = 0
retired: int = 0
return_stack: list[int] = field(default_factory=list)
loops: dict[int, tuple[int, int]] = field(default_factory=dict)
watchdog: int = 0
trace: list[dict[str, object]] = field(default_factory=list)
target: str = "p3"
primitives: PrimitiveBam | None = None
transport: TransportModel | None = None
primitive_cycles: list[int] = field(default_factory=list)
kernel_result: PrimitiveResult | None = field(default=None, init=False)
kernel_fault: PrimitiveFault | None = field(default=None, init=False)
kernel_pending_fault: PrimitiveFault | None = field(default=None, init=False)
kernel_pending_dst: int | None = field(default=None, init=False)
kernel_cycles_remaining: int = field(default=0, init=False)
transport_pending_dst: set[int] = field(default_factory=set, init=False)
transport_fault: PrimitiveFault | None = field(default=None, init=False)
mc_abi: int = APUMC_ABI_V1
def __post_init__(self) -> None:
self.begin_entry(self.entry)
@staticmethod
def _signed(value: int) -> int:
return value if value < 0x80000000 else value - 0x100000000
def _predicate(self, predicate: int) -> bool:
input_ready = self.predicate_inputs.input_ready
output_ready = self.predicate_inputs.output_ready
kernel_done = self.predicate_inputs.kernel_done
input_exhausted = self.predicate_inputs.input_exhausted
if self.target in ("p4", "p5") and self.primitives is not None:
input_ready = not self.primitives.input_fifo.empty
output_ready = not self.primitives.output_fifo.full
kernel_done = self.primitives.kernel_done
input_exhausted = (
self.primitives.eof
and self.primitives.input_fifo.empty
and not self.primitives.bits
)
values = (
True,
self.eq,
not self.eq,
self.signed_lt,
not self.signed_lt,
self.unsigned_lt,
not self.unsigned_lt,
input_exhausted,
input_ready,
output_ready,
kernel_done,
self.transport.idle
if self.transport is not None
else self.predicate_inputs.transport_done,
)
if predicate >= len(values):
raise SequencerTrap(1, self.pc, self.instructions[self.pc])
return values[predicate]
def _write(self, register: int, value: int) -> None:
self.registers[register] = value & 0xFFFFFFFF
def _complete_kernel(self) -> None:
if self.kernel_pending_fault is not None:
self.kernel_fault = self.kernel_pending_fault
elif self.kernel_result is not None:
for register, value in self.kernel_result.writes.items():
self._write(register, value)
if self.primitives is not None:
self.primitives.kernel_done = True
self.kernel_result = None
self.kernel_pending_fault = None
self.kernel_pending_dst = None
self.kernel_cycles_remaining = 0
def _tick_kernel(self) -> None:
if self.kernel_pending_dst is None:
return
if self.kernel_cycles_remaining > 0:
self.kernel_cycles_remaining -= 1
if self.kernel_cycles_remaining == 0:
self._complete_kernel()
def _tick_transport(self) -> None:
if self.transport is None or self.primitives is None:
return
try:
self.transport.tick(self.entry, self.primitives)
except PrimitiveFault as error:
self.transport_fault = error
return
while self.transport.completed:
register, value = self.transport.completed.pop(0)
self._write(register, value)
self.transport_pending_dst.discard(register)
def _pending_dependency(self, instruction: Instruction) -> bool:
pending_registers = set(self.transport_pending_dst)
if self.kernel_pending_dst is not None:
pending_registers.add(self.kernel_pending_dst)
return any(
self._uses_pending_register(instruction, pending) for pending in pending_registers
)
@staticmethod
def _uses_pending_register(instruction: Instruction, pending: int) -> bool:
instruction_class = instruction.instruction_class
opcode = instruction.opcode
if instruction_class == InstructionClass.CONTROL:
if opcode == ControlOpcode.END:
return True
return opcode == ControlOpcode.LOOP_SETUP and instruction.src0 == pending
if instruction_class == InstructionClass.SCALAR:
if opcode == ScalarOpcode.MOV:
return instruction.src0 == pending or instruction.dst == pending
if opcode == ScalarOpcode.MOVI:
return instruction.dst == pending
if opcode == ScalarOpcode.CMP:
return instruction.src0 == pending or instruction.src1 == pending
if opcode == ScalarOpcode.SAT:
return instruction.src0 == pending or instruction.dst == pending
return (
instruction.src0 == pending
or instruction.src1 == pending
or instruction.dst == pending
)
return True
def begin_entry(self, entry: Entry | None = None) -> None:
if entry is not None:
self.entry = entry
self.pc = self.entry.entry_pc
self.retired = 0
self.registers = [0] * 16
self.eq = False
self.signed_lt = False
self.unsigned_lt = False
self.return_stack.clear()
self.loops.clear()
self.watchdog = 0
self.kernel_result = None
self.kernel_fault = None
self.kernel_pending_fault = None
self.kernel_pending_dst = None
self.kernel_cycles_remaining = 0
self.transport_pending_dst.clear()
self.transport_fault = None
if self.primitives is not None:
self.primitives.reset_mutable()
if self.transport is not None:
self.transport.reset()
def abort(self) -> None:
self.kernel_result = None
self.kernel_fault = None
self.kernel_pending_fault = None
self.kernel_pending_dst = None
self.kernel_cycles_remaining = 0
self.transport_pending_dst.clear()
self.transport_fault = None
if self.transport is not None:
self.transport.abort()
self.watchdog = 0
if self.primitives is not None:
self.primitives.reset_mutable()
def reset(self) -> None:
self.begin_entry(self.entry)
def _retire(self, instruction: Instruction, predicate_true: bool) -> None:
self.retired += 1
self.watchdog = 0
self.trace.append(
{
"pc": self.pc,
"instruction": f"0x{instruction.encode():016x}",
"predicate": predicate_true,
"retired": self.retired,
}
)
def _no_retirement_cycle(
self, instruction: Instruction, *, tick_kernel: bool = True, tick_transport: bool = True
) -> None:
if tick_kernel:
self._tick_kernel()
if tick_transport:
self._tick_transport()
self.watchdog += 1
if self.timeout <= 0 or self.watchdog >= self.timeout:
raise SequencerTrap(7, self.pc, instruction)
def _saturate(self, value: int, width: int, signed: bool) -> int:
width = 32 if width == 0 else width
if signed:
signed_value = self._signed(value)
maximum = (1 << (width - 1)) - 1
minimum = -(1 << (width - 1))
return min(max(signed_value, minimum), maximum) & 0xFFFFFFFF
return min(value, (1 << width) - 1)
def step(self) -> bool:
if self.pc < self.entry.first_pc or self.pc > self.entry.last_pc:
raise SequencerTrap(2, self.pc, Instruction(0, 0))
instruction = self.instructions[self.pc]
self._tick_kernel()
self._tick_transport()
if self.kernel_fault is not None:
fault = self.kernel_fault
self.kernel_fault = None
raise SequencerTrap(fault.reason, self.pc, instruction)
if self.transport_fault is not None:
fault = self.transport_fault
self.transport_fault = None
raise SequencerTrap(fault.reason, self.pc, instruction)
try:
validate_instruction(instruction, self.target, self.mc_abi)
except ValueError as error:
raise SequencerTrap(1, self.pc, instruction) from error
if self.retired >= self.entry.max_retired:
raise SequencerTrap(8, self.pc, instruction)
predicate_true = self._predicate(instruction.predicate)
if not predicate_true:
store_last = 0x7FF if self.mc_abi == APUMC_ABI_V1 else 0xFFF
if self.pc == self.entry.last_pc or self.pc == store_last:
raise SequencerTrap(2, self.pc, instruction)
self._retire(instruction, predicate_true)
self.pc += 1
return False
if self._pending_dependency(instruction):
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
next_pc = self.pc + 1
terminal = False
if instruction.instruction_class == InstructionClass.CONTROL:
opcode = ControlOpcode(instruction.opcode)
if opcode == ControlOpcode.END:
if self.transport is not None and not self.transport.idle:
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
terminal = True
elif opcode == ControlOpcode.TRAP:
raise SequencerTrap(10, self.pc, instruction, instruction.immediate)
elif opcode == ControlOpcode.JUMP_FWD:
next_pc += instruction.immediate
elif opcode == ControlOpcode.CALL_FWD:
if len(self.return_stack) == 4:
raise SequencerTrap(3, self.pc, instruction)
self.return_stack.append(next_pc)
next_pc += instruction.immediate
elif opcode == ControlOpcode.RET:
if not self.return_stack:
raise SequencerTrap(3, self.pc, instruction)
next_pc = self.return_stack.pop()
elif opcode == ControlOpcode.LOOP_SETUP:
slot = instruction.aux & 3
count = self.registers[instruction.src0] & 0xFFFF
if slot in self.loops or count == 0 or count > self.entry.max_loop_count:
raise SequencerTrap(4, self.pc, instruction)
self.loops[slot] = (count, next_pc)
elif opcode == ControlOpcode.LOOP_BACK:
slot = instruction.aux & 3
if slot not in self.loops:
raise SequencerTrap(4, self.pc, instruction)
count, start = self.loops[slot]
target = next_pc - instruction.immediate
if target != start or target < self.entry.first_pc:
raise SequencerTrap(4, self.pc, instruction)
if count > 1:
self.loops[slot] = (count - 1, start)
next_pc = target
else:
del self.loops[slot]
elif opcode == ControlOpcode.WAIT:
ready = False
if self.primitives is not None:
ready = (
(
instruction.aux == 0
and self.transport is not None
and self.transport.idle
)
or (instruction.aux == 1 and self.primitives.kernel_done)
or (instruction.aux == 2 and not self.primitives.input_fifo.empty)
or (instruction.aux == 3 and not self.primitives.output_fifo.full)
or (
instruction.aux == 4
and self.transport is not None
and self.transport.idle
)
or (
instruction.aux == 5
and self.transport is not None
and self.transport.idle
)
)
if not ready:
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
elif instruction.instruction_class == InstructionClass.SCALAR:
opcode = ScalarOpcode(instruction.opcode)
source0 = self.registers[instruction.src0]
source1 = self.registers[instruction.src1]
if opcode == ScalarOpcode.MOV:
self._write(instruction.dst, source0)
elif opcode == ScalarOpcode.MOVI:
self._write(instruction.dst, instruction.immediate)
elif opcode == ScalarOpcode.ADD:
self._write(instruction.dst, source0 + source1)
elif opcode == ScalarOpcode.SUB:
self._write(instruction.dst, source0 - source1)
elif opcode == ScalarOpcode.AND:
self._write(instruction.dst, source0 & source1)
elif opcode == ScalarOpcode.OR:
self._write(instruction.dst, source0 | source1)
elif opcode == ScalarOpcode.XOR:
self._write(instruction.dst, source0 ^ source1)
elif opcode == ScalarOpcode.SHL:
self._write(instruction.dst, source0 << (source1 & 31))
elif opcode == ScalarOpcode.SHR:
self._write(instruction.dst, source0 >> (source1 & 31))
elif opcode == ScalarOpcode.SAR:
self._write(instruction.dst, self._signed(source0) >> (source1 & 31))
elif opcode == ScalarOpcode.CMP:
self.eq = source0 == source1
self.signed_lt = self._signed(source0) < self._signed(source1)
self.unsigned_lt = source0 < source1
elif opcode in (ScalarOpcode.MIN, ScalarOpcode.MAX):
left = self._signed(source0) if instruction.aux else source0
right = self._signed(source1) if instruction.aux else source1
result = min(left, right) if opcode == ScalarOpcode.MIN else max(left, right)
self._write(instruction.dst, result)
elif opcode == ScalarOpcode.SAT:
self._write(
instruction.dst,
self._saturate(source0, instruction.aux & 0x1F, bool(instruction.aux & 0x20)),
)
elif instruction.instruction_class == InstructionClass.TRANSPORT:
if self.transport is None or self.primitives is None:
raise SequencerTrap(6, self.pc, instruction)
try:
accepted = self.transport.issue(
instruction, self.registers, self.entry, self.primitives
)
except PrimitiveFault as error:
raise SequencerTrap(error.reason, self.pc, instruction) from error
if not accepted:
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
if instruction.opcode <= TransportOpcode.OUTPUT_STREAM:
self.transport_pending_dst.add(instruction.dst)
else:
self._tick_transport()
else:
if self.primitives is None:
raise SequencerTrap(6, self.pc, instruction)
if instruction.instruction_class == InstructionClass.LOCAL:
opcode = LocalOpcode(instruction.opcode)
if (opcode == LocalOpcode.FIFO_POP and self.primitives.input_fifo.empty) or (
opcode == LocalOpcode.FIFO_PUSH and self.primitives.output_fifo.full
):
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
if instruction.instruction_class == InstructionClass.BITSTREAM:
opcode = BitstreamOpcode(instruction.opcode)
required = 0
if opcode in (
BitstreamOpcode.REFILL,
BitstreamOpcode.PEEK,
BitstreamOpcode.GET,
BitstreamOpcode.SKIP,
):
required = instruction.immediate & 0x3F
elif opcode == BitstreamOpcode.FRAME_SYNC:
required = instruction.aux & 0x1F
if required != 0:
self.primitives._refill(required)
if required != 0 and len(self.primitives.bits) < required and not self.primitives.eof:
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
if instruction.instruction_class == InstructionClass.ENTROPY:
opcode = EntropyOpcode(instruction.opcode)
if opcode <= EntropyOpcode.HUFF_QUAD:
required = instruction.aux
elif opcode == EntropyOpcode.UNARY:
required = instruction.immediate + 1
elif opcode == EntropyOpcode.SIGN_RESTORE:
required = (
0
if self.registers[instruction.src0] == 0
else (self.registers[instruction.src1] & 0x1F) + 1
)
else:
required = 1
self.primitives._refill(required)
if len(self.primitives.bits) < required and not self.primitives.eof:
self._no_retirement_cycle(instruction, tick_kernel=False, tick_transport=False)
return False
try:
if instruction.instruction_class == InstructionClass.KERNEL:
self.primitives.kernel_done = False
result = self.primitives.execute(instruction, self.registers, self.entry)
except PrimitiveFault as error:
if instruction.instruction_class != InstructionClass.KERNEL or error.reason == 5:
raise SequencerTrap(error.reason, self.pc, instruction) from error
result = PrimitiveResult(cycles=1, kernel_done=True)
self.kernel_pending_fault = error
self.primitive_cycles.append(result.cycles)
if instruction.instruction_class == InstructionClass.KERNEL:
self.primitives.kernel_done = False
self.kernel_result = result
self.kernel_pending_dst = instruction.dst
self.kernel_cycles_remaining = max(result.cycles - 1, 0)
if self.kernel_cycles_remaining == 0:
self._complete_kernel()
else:
for register, value in result.writes.items():
self._write(register, value)
for _ in range(max(result.cycles - 1, 0)):
self._no_retirement_cycle(instruction)
self._retire(instruction, predicate_true)
if not terminal:
if next_pc < self.entry.first_pc or next_pc > self.entry.last_pc:
raise SequencerTrap(2, self.pc, instruction)
self.pc = next_pc
return terminal
def run(self) -> dict[str, object]:
fetch_required = True
while True:
if self.pc < self.entry.first_pc or self.pc > self.entry.last_pc:
raise SequencerTrap(2, self.pc, Instruction(0, 0))
instruction = self.instructions[self.pc]
if fetch_required:
for _ in range(self.fetch_no_retirement_cycles):
self._no_retirement_cycle(instruction)
remaining_stalls = self.stall_cycles.get(self.pc, 0)
while remaining_stalls > 0:
self._no_retirement_cycle(instruction)
remaining_stalls -= 1
self.stall_cycles[self.pc] = 0
retired_before = self.retired
if self.step():
break
fetch_required = self.retired != retired_before
return {
"pc": self.pc,
"retired": self.retired,
"registers": self.registers,
"trace": self.trace,
"primitive_cycles": self.primitive_cycles,
"transport": None
if self.transport is None
else {
"input_used": self.transport.input_cursor,
"output": bytes(self.transport.output).hex(),
"frames": self.transport.frames,
"result_code": self.transport.result_code,
"result_stage": self.transport.result_stage,
"result_detail": self.transport.result_detail,
"events": self.transport.events,
"output_commands": self.transport.output_commands,
},
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("bundle", type=Path)
parser.add_argument("--target", choices=APUMC_TARGETS, default="p3")
parser.add_argument("--entry", type=int, choices=range(3), default=0)
parser.add_argument("--timeout", type=int, default=0xFFFF)
parser.add_argument("--input-exhausted", action="store_true")
parser.add_argument("--input-ready", action="store_true")
parser.add_argument("--output-ready", action="store_true")
parser.add_argument("--kernel-done", action="store_true")
parser.add_argument("--transport-done", action="store_true")
args = parser.parse_args()
instructions, entries, header = parse_apumc(args.bundle.read_bytes(), args.target)
inputs = PredicateInputs(
input_exhausted=args.input_exhausted,
input_ready=args.input_ready,
output_ready=args.output_ready,
kernel_done=args.kernel_done,
transport_done=args.transport_done,
)
print(
json.dumps(
Machine(
instructions,
entries[args.entry],
inputs,
args.timeout,
target=args.target,
primitives=PrimitiveBam() if args.target in ("p4", "p5") else None,
transport=TransportModel() if args.target == "p5" else None,
mc_abi=header[1],
).run(),
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())