Add support for targeting 6502, for which the JMP (indirect) opcode

takes 5 cycles instead of 6.
This commit is contained in:
kris 2020-12-28 13:23:57 +00:00
parent 38280e7d93
commit ccba51eead
2 changed files with 43 additions and 29 deletions

View File

@ -99,7 +99,7 @@ def frame_horizon(frame_offset: int, lookahead_steps: int):
def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
sample_rate: int):
sample_rate: int, is_6502: bool):
"""Computes optimal sequence of player opcodes to reproduce audio data."""
dlen = len(data)
@ -108,8 +108,7 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
# TODO: avoid temporarily doubling memory footprint to concatenate
data = numpy.ascontiguousarray(numpy.concatenate(
[data, numpy.zeros(max(lookahead_steps, opcodes.cycle_length(
opcodes.Opcode.SLOWPATH)),
dtype=numpy.float32)]))
opcodes.Opcode.SLOWPATH, is_6502)), dtype=numpy.float32)]))
# Starting speaker position and applied voltage.
position = 0.0
@ -121,7 +120,7 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
for i in range(2048):
for voltage in [-1.0, 1.0]:
opcode_hash, _, voltages = opcodes.candidate_opcodes(
frame_horizon(i, lookahead_steps), lookahead_steps)
frame_horizon(i, lookahead_steps), lookahead_steps, is_6502)
delta_powers, partial_positions = _partial_positions(
voltage * voltages, step)
@ -135,12 +134,14 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
delta_powers, partial_positions)
opcode_partial_positions = {}
for op, voltages in opcodes.VOLTAGE_SCHEDULE.items():
all_opcodes = opcodes.Opcode.__members__.values()
for op in set(all_opcodes) - {opcodes.Opcode.EXIT}:
voltages = opcodes.voltage_schedule(op, is_6502)
for voltage in [-1.0, 1.0]:
delta_powers, partial_positions = _partial_positions(
voltage * voltages, step)
assert delta_powers.shape == partial_positions.shape
assert delta_powers.shape[-1] == opcodes.cycle_length(op)
assert delta_powers.shape[-1] == opcodes.cycle_length(op, is_6502)
opcode_partial_positions[op, voltage] = (
delta_powers, partial_positions, voltage * voltages[-1])
@ -158,7 +159,8 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
# Compute all possible opcode sequences for this frame offset
opcode_hash, candidate_opcodes, _ = opcodes.candidate_opcodes(
frame_horizon(frame_offset, lookahead_steps), lookahead_steps)
frame_horizon(frame_offset, lookahead_steps), lookahead_steps,
is_6502)
# Look up the precomputed partial values for these candidate opcode
# sequences.
delta_powers, partial_positions = all_partial_positions[opcode_hash,
@ -175,7 +177,7 @@ def audio_bytestream(data: numpy.ndarray, step: int, lookahead_steps: int,
total_error(all_positions, data[i:i + lookahead_steps])).item()
# Next opcode
opcode = candidate_opcodes[opcode_idx][0]
opcode_length = opcodes.cycle_length(opcode)
opcode_length = opcodes.cycle_length(opcode, is_6502)
opcode_counts[opcode] += 1
toggles += opcodes.TOGGLES[opcode]
@ -251,7 +253,7 @@ def main():
with open(args.output, "wb+") as f:
for opcode in audio_bytestream(
preprocess(args.input, sample_rate), args.step_size,
args.lookahead_cycles, sample_rate):
args.lookahead_cycles, sample_rate, args.cpu == '6502'):
f.write(bytes([opcode.value]))

View File

@ -7,7 +7,7 @@ from typing import Dict, List, Tuple, Iterable
def make_slowpath_voltages() -> numpy.ndarray:
"""Voltage sequence for slowpath TCP processing."""
length = 4 + 14 * 10 + 6 # TODO: 6502
length = 4 + 14 * 10 + 6
c = numpy.full(length, 1.0, dtype=numpy.float32)
voltage_high = True
toggles = 0
@ -21,18 +21,27 @@ def make_slowpath_voltages() -> numpy.ndarray:
Opcode = opcodes_generated.Opcode
TOGGLES = opcodes_generated.TOGGLES
VOLTAGE_SCHEDULE = opcodes_generated.VOLTAGE_SCHEDULE
VOLTAGE_SCHEDULE[Opcode.SLOWPATH], TOGGLES[Opcode.SLOWPATH] = (
_VOLTAGE_SCHEDULE = opcodes_generated.VOLTAGE_SCHEDULE
_VOLTAGE_SCHEDULE[Opcode.SLOWPATH], TOGGLES[Opcode.SLOWPATH] = (
make_slowpath_voltages())
def cycle_length(op: Opcode) -> int:
"""Returns the 65C02 cycle length of a player opcode."""
return len(VOLTAGE_SCHEDULE[op])
def cycle_length(op: Opcode, is_6502: bool) -> int:
"""Returns the 65[C]02 cycle length of a player opcode."""
l = len(_VOLTAGE_SCHEDULE[op])
# JMP (indirect) is 5 cycles for 6502 instead of 6
return l - 1 if is_6502 else l
def voltage_schedule(op: Opcode, is_6502: bool) -> numpy.ndarray:
"""Returns the 65[C]02 applied voltage schedule of a player opcode."""
v = _VOLTAGE_SCHEDULE[op]
# JMP (indirect) is 5 cycles for 6502 instead of 6
return v[:-1] if is_6502 else v
@functools.lru_cache(None)
def opcode_choices(frame_offset: int) -> List[Opcode]:
def opcode_choices(frame_offset: int, is_6502: bool) -> List[Opcode]:
"""Returns sorted list of valid opcodes for given frame offset.
Sorted by decreasing cycle length, so that if two opcodes produce equally
@ -42,24 +51,28 @@ def opcode_choices(frame_offset: int) -> List[Opcode]:
if frame_offset == 2047:
return [Opcode.SLOWPATH]
opcodes = set(VOLTAGE_SCHEDULE.keys()) - {Opcode.SLOWPATH}
return sorted(list(opcodes), key=cycle_length, reverse=True)
def _cycle_length(op: Opcode) -> int:
return cycle_length(op, is_6502)
opcodes = set(_VOLTAGE_SCHEDULE.keys()) - {Opcode.SLOWPATH}
return sorted(list(opcodes), key=_cycle_length, reverse=True)
@functools.lru_cache(None)
def opcode_lookahead(
frame_offset: int,
lookahead_cycles: int) -> Tuple[Tuple[Opcode]]:
lookahead_cycles: int, is_6502: bool) -> Tuple[Tuple[Opcode]]:
"""Recursively enumerates all valid opcode sequences."""
ch = opcode_choices(frame_offset)
ch = opcode_choices(frame_offset, is_6502)
ops = []
for op in ch:
if cycle_length(op) >= lookahead_cycles:
if cycle_length(op, is_6502) >= lookahead_cycles:
ops.append((op,))
else:
for res in opcode_lookahead((frame_offset + 1) % 2048,
lookahead_cycles - cycle_length(op)):
for res in opcode_lookahead(
(frame_offset + 1) % 2048,
lookahead_cycles - cycle_length(op, is_6502), is_6502):
ops.append((op,) + res)
return tuple(ops) # TODO: fix return type
@ -67,8 +80,7 @@ def opcode_lookahead(
@functools.lru_cache(None)
def cycle_lookahead(
opcodes: Tuple[Opcode],
lookahead_cycles: int
) -> Tuple[float]:
lookahead_cycles: int, is_6502: bool) -> Tuple[float]:
"""Computes the applied voltage effects of a sequence of opcodes.
i.e. produces the sequence of applied voltage changes that will result
@ -77,26 +89,26 @@ def cycle_lookahead(
cycles = []
last_voltage = 1.0
for op in opcodes:
cycles.extend(last_voltage * VOLTAGE_SCHEDULE[op])
cycles.extend(last_voltage * voltage_schedule(op, is_6502))
last_voltage = cycles[-1]
return tuple(cycles[:lookahead_cycles])
@functools.lru_cache(None)
def candidate_opcodes(
frame_offset: int, lookahead_cycles: int
frame_offset: int, lookahead_cycles: int, is_6502: bool
) -> Tuple[int, Tuple[Tuple[Opcode]], numpy.ndarray]:
"""Deduplicate a tuple of opcode sequences that are equivalent.
For each opcode sequence whose effect is the same when truncated to
lookahead_cycles, retains the first such opcode sequence.
"""
opcodes = opcode_lookahead(frame_offset, lookahead_cycles)
opcodes = opcode_lookahead(frame_offset, lookahead_cycles, is_6502)
seen_cycles = set()
pruned_opcodes = []
pruned_cycles = []
for ops in opcodes:
cycles = cycle_lookahead(ops, lookahead_cycles)
cycles = cycle_lookahead(ops, lookahead_cycles, is_6502)
if cycles in seen_cycles:
continue
seen_cycles.add(cycles)