2022-06-25 14:26:14 +00:00
|
|
|
from typing import Iterable, Tuple
|
2022-06-21 21:32:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Opcode:
|
|
|
|
"""6502 assembly language opcode with cycle length"""
|
|
|
|
|
2022-06-25 14:14:17 +00:00
|
|
|
def __init__(self, cycles, bytes, asm="", indent=4, toggle=False):
|
2022-06-21 21:32:12 +00:00
|
|
|
self.cycles = cycles
|
2022-06-25 14:14:17 +00:00
|
|
|
self.bytes = bytes
|
2022-06-21 21:32:12 +00:00
|
|
|
self.asm = asm
|
|
|
|
self.indent = indent
|
|
|
|
# Assume toggles speaker on the last cycle
|
|
|
|
self.toggle = toggle
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
asm = "[%s] " % self.asm if self.asm else ""
|
|
|
|
return "%s<%d cycles>" % (asm, self.cycles)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
indent = " " * self.indent
|
|
|
|
comment = ' ; %d cycles' % self.cycles if self.cycles else ""
|
|
|
|
return indent + self.asm + comment
|
|
|
|
|
|
|
|
|
|
|
|
class Literal(Opcode):
|
|
|
|
def __init__(self, asm, indent=4):
|
2022-06-25 14:14:17 +00:00
|
|
|
super(Literal, self).__init__(cycles=0, bytes=0, asm=asm, indent=indent)
|
2022-06-21 21:32:12 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "<literal>"
|
|
|
|
|
|
|
|
|
|
|
|
class PaddingOpcode(Opcode):
|
|
|
|
"""Opcode variant that can be replaced by other interleaved opcodes."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def interleave_opcodes(
|
|
|
|
base_opcodes: Iterable[Opcode],
|
|
|
|
interleaved_opcodes: Iterable[Opcode]) -> Iterable[Opcode]:
|
|
|
|
for op in base_opcodes:
|
|
|
|
if isinstance(op, PaddingOpcode):
|
|
|
|
padding_cycles = op.cycles
|
|
|
|
|
|
|
|
while padding_cycles > 0:
|
|
|
|
if interleaved_opcodes:
|
|
|
|
interleaved_op = interleaved_opcodes[0]
|
|
|
|
if (padding_cycles - interleaved_op.cycles) >= 0 and (
|
|
|
|
padding_cycles - interleaved_op.cycles) != 1:
|
|
|
|
yield interleaved_op
|
|
|
|
padding_cycles -= interleaved_op.cycles
|
|
|
|
interleaved_opcodes = interleaved_opcodes[1::]
|
|
|
|
if not interleaved_opcodes:
|
|
|
|
return
|
|
|
|
continue
|
|
|
|
if padding_cycles == 3:
|
2022-06-25 14:14:17 +00:00
|
|
|
yield PaddingOpcode(3, 2, "STA zpdummy")
|
2022-06-21 21:32:12 +00:00
|
|
|
padding_cycles -= 3
|
|
|
|
else:
|
2022-06-25 14:14:17 +00:00
|
|
|
yield PaddingOpcode(2, 1, "NOP")
|
2022-06-21 21:32:12 +00:00
|
|
|
padding_cycles -= 2
|
|
|
|
assert padding_cycles == 0
|
|
|
|
else:
|
|
|
|
yield op
|
|
|
|
if interleaved_opcodes:
|
|
|
|
print(interleaved_opcodes)
|
|
|
|
raise OverflowError
|
|
|
|
|
|
|
|
|
2022-06-25 14:14:17 +00:00
|
|
|
STA_C030 = Opcode(4, 3, "STA $C030", toggle=True)
|
|
|
|
# TODO: support 6502 cycle timings (5 cycles instead of 6)
|
|
|
|
JMP_WDATA = Opcode(6, 3, "JMP (WDATA)")
|
2022-06-21 21:32:12 +00:00
|
|
|
|
2022-07-02 10:42:36 +00:00
|
|
|
|
2022-06-21 21:32:12 +00:00
|
|
|
def padding(cycles):
|
2022-06-25 14:14:17 +00:00
|
|
|
return PaddingOpcode(cycles, None, "; pad %d cycles" % cycles)
|
|
|
|
|
|
|
|
|
|
|
|
def nops(cycles: int) -> Iterable[Opcode]:
|
|
|
|
if cycles < 2:
|
|
|
|
raise ValueError
|
|
|
|
while cycles:
|
|
|
|
if cycles == 3:
|
|
|
|
yield Opcode(3, 2, "STA zpdummy")
|
|
|
|
cycles -= 3
|
|
|
|
continue
|
|
|
|
yield Opcode(2, 1, "NOP")
|
|
|
|
cycles -= 2
|
|
|
|
|
|
|
|
|
|
|
|
# def voltage_sequence(
|
|
|
|
# opcodes: Iterable[Opcode], starting_voltage=1.0
|
|
|
|
# ) -> Tuple[numpy.float32, int]:
|
|
|
|
# """Voltage sequence for sequence of opcodes."""
|
|
|
|
# out = []
|
|
|
|
# v = starting_voltage
|
|
|
|
# toggles = 0
|
|
|
|
# for op in opcodes:
|
|
|
|
# for nv in v * numpy.array(VOLTAGES[op]):
|
|
|
|
# if v != nv:
|
|
|
|
# toggles += 1
|
|
|
|
# v = nv
|
|
|
|
# out.append(v)
|
|
|
|
# return tuple(numpy.array(out, dtype=numpy.float32)), toggles
|
2022-06-21 21:32:12 +00:00
|
|
|
|
2022-06-25 14:26:14 +00:00
|
|
|
def toggles(opcodes: Iterable[Opcode]) -> Tuple[float]:
|
2022-06-21 21:32:12 +00:00
|
|
|
res = []
|
2022-06-25 14:26:14 +00:00
|
|
|
speaker = 1.0
|
2022-06-21 21:32:12 +00:00
|
|
|
for op in opcodes:
|
|
|
|
if not op.cycles:
|
|
|
|
continue
|
|
|
|
res.extend([speaker] * (op.cycles - 1))
|
|
|
|
if op.toggle:
|
2022-06-25 14:26:14 +00:00
|
|
|
speaker *= -1
|
2022-06-21 21:32:12 +00:00
|
|
|
res.append(speaker)
|
2022-06-25 14:14:17 +00:00
|
|
|
return tuple(res)
|
2022-06-21 21:32:12 +00:00
|
|
|
|
|
|
|
|
2022-06-25 14:14:17 +00:00
|
|
|
def total_bytes(opcodes: Iterable[Opcode]) -> int:
|
|
|
|
return sum(op.bytes for op in opcodes)
|
2022-06-21 21:32:12 +00:00
|
|
|
|
|
|
|
|
2022-06-25 14:14:17 +00:00
|
|
|
def total_cycles(opcodes: Iterable[Opcode]) -> int:
|
|
|
|
return sum(op.cycles for op in opcodes)
|
2022-07-02 10:42:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
import numpy
|
|
|
|
|
|
|
|
|
|
|
|
def join_toggles(op_seq: Iterable[Iterable[Opcode]]) -> numpy.ndarray:
|
|
|
|
res = []
|
|
|
|
last_voltage = 1.0
|
|
|
|
for ops in op_seq:
|
|
|
|
op_toggles = toggles(ops)
|
|
|
|
res.extend(
|
|
|
|
numpy.array(op_toggles, dtype=numpy.float32) * last_voltage)
|
|
|
|
last_voltage = res[-1]
|
|
|
|
|
|
|
|
return numpy.array(res, dtype=numpy.float32)
|