Skip to content

Commit

Permalink
fixed runtime slices
Browse files Browse the repository at this point in the history
  • Loading branch information
cnvogelg committed Sep 30, 2024
1 parent 1751f5c commit 0ca43b8
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 70 deletions.
4 changes: 3 additions & 1 deletion amitools/vamos/machine/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ def _init_cpu(self):
self.cpu.w_isp(0x700)
self.cpu.w_msp(0x780)
# trigger reset (read sp and init pc)
self.cpu.pulse_reset()
reset_cycles = self.cpu.pulse_reset()
# consume reset cycles
self.cpu.execute(reset_cycles)
# drop supervisor
if not self.supervisor:
sr = self.cpu.r_sr()
Expand Down
116 changes: 64 additions & 52 deletions amitools/vamos/machine/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
class RunState:
pc: int
sp: int
max_cycles: int
nesting: int
name: str
cycles: int = 0
total_cycles: int = 0
slice_cycles: int = 0
user_end: bool = False
regs: dict = None


Expand All @@ -24,7 +25,7 @@ class Runtime:
trigger nested m68k code.
The runtime counts the cycles for the main run and nested runs.
Whenever the cycle limit is reached then an optional callback is
Whenever the slice cycle limit is reached then an optional callback is
triggered. This callback may be used by a scheduler to issue a task switch.
PyTraps that are encountered during the run are executed after the m68k
Expand All @@ -37,32 +38,36 @@ class Runtime:
MachineError or another Python exception.
"""

def __init__(self, machine, default_sp=None):
def __init__(self, machine, run_cycles=1000, default_sp=None):
self.machine = machine
self.default_sp = default_sp
self.max_cycle_hook = None

self.slice_hook = None
self.slice_cycles = 0

self.running = False
self.run_states = []

self.max_cycles = 0
self.cur_cycles = 0
self.run_cycles = run_cycles
self.left_cycles = 0

def set_max_cycle_hook(self, func):
self.max_cycle_hook = func
def set_slice_hook(self, slice_cycles, func):
"""setup slice hook to get a callback after slice cycles"""
self.slice_cycles = slice_cycles
self.slice_hook = func

def run(
self, pc, sp, set_regs=None, get_regs=None, max_cycles=1000, name=None
) -> RunState:
def reset_slice(self):
"""allow to reset slice reporting"""
self.left_cycles = self.slice_cycles

def run(self, pc, sp, set_regs=None, get_regs=None, name=None) -> RunState:
"""convenience method to dispatch either to start or nested_run"""
if not self.running:
return self.start(pc, sp, set_regs, get_regs, max_cycles, name)
return self.start(pc, sp, set_regs, get_regs, name)
else:
return self.nested_run(pc, sp, set_regs, get_regs, max_cycles, name)
return self.nested_run(pc, sp, set_regs, get_regs, name)

def start(
self, pc, sp, set_regs=None, get_regs=None, max_cycles=1000, name=None
) -> RunState:
def start(self, pc, sp, set_regs=None, get_regs=None, name=None) -> RunState:
"""start the main run at given pc with stack and return result"""
if self.running:
raise RuntimeError("start() only allowed inside idle runtime.")
Expand All @@ -76,16 +81,15 @@ def start(

# setup run state
self.running = True
run_state = RunState(pc, sp, max_cycles, 0, name)
run_state = RunState(pc, sp, 0, name)
self.run_states = [run_state]

# init cycle counting
self.max_cycles = max_cycles
self.cur_cycles = 0
# init cycle counting for slice hook
self.left_cycles = self.slice_cycles

# execute
log_machine.info("runtime start: %s", run_state)
run_state.regs = self._run_loop(pc, sp, set_regs, get_regs, max_cycles)
run_state.regs = self._run_loop(run_state, set_regs, get_regs)
log_machine.info("runtime end: %s", run_state)

# clean up run state
Expand All @@ -95,7 +99,7 @@ def start(
return run_state

def nested_run(
self, pc, sp=None, set_regs=None, get_regs=None, max_cycles=0, name=None
self, pc, sp=None, set_regs=None, get_regs=None, name=None
) -> RunState:
"""in a PyTrap code run a nested piece of m68k code"""
if not self.running:
Expand All @@ -104,20 +108,27 @@ def nested_run(
# if no own stack is given then re-use current stack
if not sp:
sp = self.machine.get_sp() - 4
if max_cycles == 0:
max_cycles = self.max_cycles

# save CPU context
ctx = self.machine.cpu.get_cpu_context()

# get old run state and take cycles
old_run_state = self.run_states[-1]
old_total = old_run_state.total_cycles

# calc current slice cycles
slice_cycles = self.slice_cycles - self.left_cycles

# create new run state
nesting = len(self.run_states)
run_state = RunState(pc, sp, max_cycles, nesting, name)
run_state = RunState(
pc, sp, nesting, name, total_cycles=old_total, slice_cycles=slice_cycles
)
self.run_states.append(run_state)

# perform nested run
log_machine.info("runtime nested start %s", run_state)
run_state.regs = self._run_loop(pc, sp, set_regs, get_regs, max_cycles)
run_state.regs = self._run_loop(run_state, set_regs, get_regs)
log_machine.info("runtime nested end %s", run_state)

# pop current run state
Expand All @@ -126,9 +137,8 @@ def nested_run(
# restore CPU context
self.machine.cpu.set_cpu_context(ctx)

# account cycles to old run state
old_run_state = self.run_states[-1]
old_run_state.total_cycles += run_state.total_cycles
# take over total cycles
old_run_state.total_cycles = run_state.total_cycles

return run_state

Expand All @@ -143,9 +153,9 @@ def cycles_run(self):
else:
return 0

def _run_loop(self, pc, sp, set_regs, get_regs, max_cycles):
def _run_loop(self, run_state, set_regs, get_regs):
# setup pc and sp
self.machine.prepare(pc, sp)
self.machine.prepare(run_state.pc, run_state.sp)

# setup regs
if set_regs:
Expand All @@ -155,45 +165,47 @@ def _run_loop(self, pc, sp, set_regs, get_regs, max_cycles):
self.machine.cpu.w_reg(reg, val)

while True:
# are we handling slices?
if self.slice_cycles > 0:
# nothing left? report
if self.left_cycles <= 0:
log_machine.debug("report slice cycles: %s", run_state)
# report that we reached max cycles
if self.slice_hook:
self.slice_hook(run_state)
# reset slice cycles
self.left_cycles = self.slice_cycles
# slice runs use the left cycles
run_cycles = self.left_cycles
else:
# non-slice runs use run_cycles
run_cycles = self.run_cycles

# let m68k run
er = self.machine.execute(max_cycles)
er = self.machine.execute(run_cycles)

# account cycles
run_state = self.run_states[-1]
run_state.cycles += er.cycles
run_state.total_cycles += er.cycles
if self.left_cycles > 0:
self.left_cycles -= er.cycles
run_state.slice_cycles = self.slice_cycles - self.left_cycles

# update run state pc, sp
run_state.pc = self.machine.get_pc()
run_state.sp = self.machine.get_sp()

# report cycles?
self.cur_cycles += er.cycles
if self.cur_cycles > self.max_cycles:
log_machine.debug(
"report max cycles: last=%d sum=%d (local %d, total %d)",
er.cycles,
self.cur_cycles,
run_state.cycles,
run_state.total_cycles,
)
# report that we reached max cycles
if self.max_cycle_hook:
self.max_cycle_hook(
self.cur_cycles, run_state.cycles, run_state.nesting
)
self.cur_cycles = 0

# machine run has ended?
if er.user_end:
run_state.user_end = True
if run_state.pc == self.machine.get_run_exit_addr() + 2:
log_machine.debug("exit code reached. (%s)", er)
break
else:
log_machine.debug("unknown user end. (%s)", er)
# max cycles reached. report and continue
# run cycles reached. report and continue
else:
log_machine.debug("max cycles reached: %s", er)
log_machine.debug("run cycles reached: %s", er)

# return regs?
if get_regs:
Expand Down
35 changes: 18 additions & 17 deletions test/unit/machine_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
from amitools.vamos.log import log_machine


def create_runtime(cpu_type=CPUType.M68000, supervisor=False):
def create_runtime(cpu_type=CPUType.M68000, supervisor=False, run_cycles=1000):
m = Machine(cpu_type, supervisor=supervisor)
cpu = m.get_cpu()
mem = m.get_mem()
code = m.get_ram_begin()
stack = m.get_scratch_top()
r = Runtime(m)
r = Runtime(m, run_cycles=run_cycles)
return r, m, cpu, mem, code, stack


def machine_runtime_rts_test():
r, m, cpu, mem, code, stack = create_runtime()
@pytest.mark.parametrize("run_cycles", (10, 20, 40, 80, 100, 200, 500, 1000))
def machine_runtime_rts_test(run_cycles):
r, m, cpu, mem, code, stack = create_runtime(run_cycles=run_cycles)
# single RTS to immediately return from run
mem.w16(code, op_rts)
rs = r.start(code, stack)
Expand Down Expand Up @@ -254,28 +255,27 @@ def func(op, pc):
m.cleanup()


def machine_runtime_max_cycle_hook_test():
@pytest.mark.parametrize("slice_cycles", (10, 20, 40, 80, 100, 200))
def machine_runtime_slice_hook_test(slice_cycles):
r, m, cpu, mem, code, stack = create_runtime()

cycle_reports = []
slice_reports = []

def hook(*args):
cycle_reports.append(args)
def hook(rs):
slice_reports.append(rs)

r.set_max_cycle_hook(hook)
r.set_slice_hook(slice_cycles, hook)

def func2(op, pc):
# cycles is not updated yet
rs = r.get_current_run_state()
# cycles is not updated yet
assert rs.cycles == 400
# but cycles_run does account current cycles
assert r.cycles_run() == 404

def func(op, pc):
rs = r.get_current_run_state()
# cycles is not updated yet
assert rs.cycles == 200
# but cycles_run
# check run cycles
assert r.cycles_run() == 224

rs = r.nested_run(code + 110, name="foo")
Expand Down Expand Up @@ -303,7 +303,7 @@ def func(op, pc):
mem.w16(code + 306, op_rts)

# start
rs = r.start(code, stack, name="go", max_cycles=100)
rs = r.start(code, stack, name="go")

# main run results
assert rs.cycles == 260
Expand All @@ -313,6 +313,7 @@ def func(op, pc):
m.cleanup()

# check cycle reports
assert len(cycle_reports) > 0
for report in cycle_reports:
assert report[0] > 100
assert len(slice_reports) > 0
for run_state in slice_reports:
if not run_state.user_end:
assert run_state.slice_cycles >= slice_cycles

0 comments on commit 0ca43b8

Please sign in to comment.