WIP - refactor player generation to be a) deterministic b) use opcodes_6502 c) statically enumerate a minimal set of opcode sequences. Since we're giving up on using arbitrary opcode combinations this is much simpler, and maybe more correct.

This commit is contained in:
kris 2022-06-25 15:14:17 +01:00
parent bc032ae3ca
commit e0908a9ced
3 changed files with 507 additions and 433 deletions

View File

@ -4,278 +4,368 @@ import itertools
import numpy
from typing import Iterable, List, Tuple
import opcodes_6502
class Opcode(enum.Enum):
NOP = 1
NOP3 = 2
STA = 3
STAX = 4
INC = 5
INCX = 6
JMP_INDIRECT = 7
NOPNOP = 8
# Byte length of opcodes
OPCODE_LEN = {
Opcode.NOP: 1, Opcode.NOP3: 2, Opcode.STA: 3, Opcode.STAX: 3,
Opcode.INC: 3, Opcode.INCX: 3, Opcode.JMP_INDIRECT: 3, Opcode.NOPNOP: 2
}
ASM = {
Opcode.NOP: "NOP", Opcode.NOP3: "STA zpdummy",
Opcode.STA: "STA $C030", Opcode.STAX: "STA $C030,X",
Opcode.INC: "INC $C030", Opcode.INCX: "INC $C030,X",
Opcode.JMP_INDIRECT: "JMP (WDATA)", Opcode.NOPNOP: "NOP NOP"
}
# Applied speaker voltages resulting from executing opcode
VOLTAGES = {
Opcode.STA: [1, 1, 1, -1],
Opcode.INC: [1, 1, 1, -1, 1, -1],
Opcode.INCX: [1, 1, 1, -1, 1, -1, 1],
Opcode.STAX: [1, 1, 1, -1, 1],
Opcode.NOP: [1, 1],
Opcode.NOP3: [1, 1, 1],
# TODO: support 6502 cycle counts as well
Opcode.JMP_INDIRECT: [1, 1, 1, 1, 1, 1],
Opcode.NOPNOP: [1, 1, 1, 1],
}
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
def all_opcodes(
max_len: int, opcodes: Iterable[Opcode], start_opcodes: Iterable[int]
) -> Iterable[Tuple[Opcode]]:
"""Enumerate all combinations of opcodes up to max_len cycles"""
num_opcodes = 0
while True:
found_one = False
for ops in itertools.product(opcodes, repeat=num_opcodes):
ops = tuple(list(ops) + [Opcode.JMP_INDIRECT])
if ops[0] not in start_opcodes:
continue
if sum(len(VOLTAGES[o]) for o in ops) <= max_len:
found_one = True
yield ops
if not found_one:
break
num_opcodes += 1
def _make_end_of_frame_voltages(cycles) -> numpy.ndarray:
"""Voltage sequence for end-of-frame TCP processing."""
c = []
voltage_high = True
for i, skip_cycles in enumerate(cycles):
c.extend([1.0 if voltage_high else -1.0] * (skip_cycles - 1))
if i != len(cycles) - 1:
voltage_high = not voltage_high
c.append(1.0 if voltage_high else -1.0)
return numpy.array(c, dtype=numpy.float32)
# These are duty cycles
eof_cycles = [
# (16,6),
# (14,6),
# (12,8), # -0.15
# (14, 10), # -0.10
# (12,10), # -0.05
# (4, 40, 4, 40, 4, 40, 4, 6),
# (4, 38, 6, 38, 6, 38, 6, 6),
# (4, 36, 8, 36, 8, 36, 8, 6),
# (4, 34, 10, 34, 10, 34, 10, 6),
# (4, 32, 12, 32, 12, 32, 12, 6),
# (4, 30, 14, 30, 14, 30, 14, 6),
(4, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 6), # 0.0
(4, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 6), # 0.046
(4, 24, 20, 24, 20, 24, 20, 6), # 0.09
(4, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 6), # 0.11
(4, 13, 10, 13, 10, 13, 10, 13, 10, 13, 10, 13, 10, 13, 6), # 0.13
(4, 28, 20, 28, 20, 28, 20, 6), # 0.166
(4, 26, 18, 26, 18, 26, 18, 6), # 0.18
(4, 24, 16, 24, 16, 24, 16, 6), # 0.2
# (10, 8, 10, 10, 10, 8), # 0.05
# (12, 10, 12, 8, 10, 10), # 0.1
# (4, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 6), # 0.15
# (10, 6, 12, 6), # 0.20
# (10, 4), # 0.25
# (14, 4, 10, 6), # 0.30
# (12, 4), # 0.35
# (14, 4), # 0.40
SLOW_PATH_TRAMPOLINE = [
opcodes_6502.Literal("eof_slow_path:", indent=0),
opcodes_6502.STA_C030,
opcodes_6502.Opcode(4, 3, "LDX WDATA"),
opcodes_6502.Opcode(4, 3, "STX @jmp+1"),
opcodes_6502.Literal("@jmp:", indent=0),
opcodes_6502.Opcode(6, 3, "JMP ($2000)") # TODO: 5 cycles on 6502
]
import itertools
SLOW_PATH_EOF = [
opcodes_6502.Literal(
"; We've read exactly 2KB from the socket buffer. Before continuing "
"we need to ACK this read,"),
opcodes_6502.Literal(
"; and make sure there's at least another 2KB in the buffer."),
opcodes_6502.Literal(";"),
opcodes_6502.Literal(
"; Save the W5100 address pointer so we can continue reading the "
"socket buffer once we are done."),
opcodes_6502.Literal(
"; We know the low-order byte is 0 because Socket RX memory is "
"page-aligned and so is 2K frame."),
opcodes_6502.Literal(
"; IMPORTANT - from now on until we restore this below, we can't "
"trash the Y register!"),
opcodes_6502.Opcode(4, 4, "LDY WADRH"),
opcodes_6502.Literal("; Update new Received Read pointer."),
opcodes_6502.Literal(";"),
opcodes_6502.Literal(
"; We know we have received exactly 2KB, so we don't need to read the "
"current value from the"),
opcodes_6502.Literal(
"; hardware. We can track it ourselves instead, which saves a "
"few cycles."),
opcodes_6502.Opcode(2, 2, "LDA #>S0RXRD"),
opcodes_6502.Opcode(4, 3, "STA WADRH"),
opcodes_6502.Opcode(2, 2, "LDA #<S0RXRD"),
opcodes_6502.Opcode(4, 3, "STA WADRL"),
opcodes_6502.Opcode(4, 3,
"LDA RXRD ; TODO: in principle we could update RXRD outside of "
"the EOF path"),
opcodes_6502.Opcode(2, 1, "CLC"),
opcodes_6502.Opcode(2, 2, "ADC #$08"),
opcodes_6502.Opcode(4, 3,
"STA WDATA ; Store new high byte of received read pointer"),
opcodes_6502.Opcode(4, 3, "STA RXRD ; Save for next time"),
opcodes_6502.Literal("; Send the Receive command"),
opcodes_6502.Opcode(2, 2, "LDA #<S0CR"),
opcodes_6502.Opcode(4, 3, "STA WADRL"),
opcodes_6502.Opcode(2, 2, "LDA #SCRECV"),
opcodes_6502.Opcode(4, 3, "STA WDATA"),
opcodes_6502.Literal(
"; Make sure we have at least 2KB more in the socket buffer so we can "
"start another frame."
),
opcodes_6502.Opcode(2, 2, "LDA #$07"),
opcodes_6502.Opcode(2, 2, "LDX #<S0RXRSR ; Socket 0 Received Size "
"register"),
opcodes_6502.Literal(
"; we might loop an unknown number of times here waiting for data but "
"the default should be to"),
opcodes_6502.Literal("; fall straight through"),
opcodes_6502.Literal("@0:", indent=0),
opcodes_6502.Opcode(4, 3, "STX WADRL"),
opcodes_6502.Opcode(4, 3, "CMP WDATA ; High byte of received size"),
opcodes_6502.Opcode(2, 2,
"BCS @0 ; 2 cycles in common case when there is already sufficient "
"data waiting."),
opcodes_6502.Literal(
"; We're good to go for another frame. Restore W5100 address pointer "
"where we last found it, to"),
opcodes_6502.Literal(
"; begin iterating through the next 2KB of the socket buffer."),
opcodes_6502.Literal(";"),
opcodes_6502.Literal(
"; It turns out that the W5100 automatically wraps the address pointer "
"at the end of the 8K"),
opcodes_6502.Literal(
"; RX/TX buffers. Since we're using an 8K socket, that means we don't "
"have to do any work to"),
opcodes_6502.Literal("; manage the read pointer!"),
opcodes_6502.Opcode(4, 3, "STY WADRH"),
opcodes_6502.Opcode(2, 2, "LDA #$00"),
opcodes_6502.Opcode(4, 3, "STA WADRL"),
opcodes_6502.Opcode(6, 3, "JMP (WDATA)"),
]
# Fast path really only requires 1 byte but we have to burn an extra one to
# sync up to 2KB boundary
FAST_PATH_EOF = [opcodes_6502.Opcode(4, 3, "LDA WDATA")] + SLOW_PATH_EOF
def _make_end_of_frame_voltages2(cycles) -> numpy.ndarray:
"""Voltage sequence for end-of-frame TCP processing."""
max_len = 140
voltage_high = False
c = [1.0, 1.0, 1.0, -1.0] # STA $C030
for i, skip_cycles in enumerate(itertools.cycle(cycles)):
c.extend([1.0 if voltage_high else -1.0] * (skip_cycles - 1))
voltage_high = not voltage_high
c.append(1.0 if voltage_high else -1.0)
if len(c) >= max_len:
break
c.extend([1.0 if voltage_high else -1.0] * 6) # JMP (WDATA)
return numpy.array(c, dtype=numpy.float32)
def fast_path_trampoline(label: str) -> List[opcodes_6502.Opcode]:
return [
opcodes_6502.Literal("eof_fast_path_%s:", indent=0),
opcodes_6502.STA_C030,
opcodes_6502.Opcode(3, 3, "JMP _eof_fast_path_%s" % label)
]
def _duty_cycles():
res = {}
# def _make_end_of_frame_voltages(cycles) -> numpy.ndarray:
# """Voltage sequence for end-of-frame TCP processing."""
# c = []
# voltage_high = True
# for i, skip_cycles in enumerate(cycles):
# c.extend([1.0 if voltage_high else -1.0] * (skip_cycles - 1))
# if i != len(cycles) - 1:
# voltage_high = not voltage_high
# c.append(1.0 if voltage_high else -1.0)
# return numpy.array(c, dtype=numpy.float32)
for i in range(4, 50, 2):
for j in range(i, 50, 2):
if i + j < 20 or i + j > 50:
continue
duty = j / (i + j) * 2 - 1
res.setdefault(duty, []).append((i + j, i, j))
cycles = []
for c in sorted(list(res.keys())):
pair = sorted(sorted(res[c], reverse=False)[0][1:], reverse=True)
cycles.append(pair)
# return [(10, 10), (12, 10), (12, 8), (14, 10), (14, 6), (14, 8)]
return cycles
#
# # These are duty cycles
# eof_cycles = [
# # (16,6),
# # (14,6),
# # (12,8), # -0.15
# # (14, 10), # -0.10
# # (12,10), # -0.05
# # (4, 40, 4, 40, 4, 40, 4, 6),
# # (4, 38, 6, 38, 6, 38, 6, 6),
# # (4, 36, 8, 36, 8, 36, 8, 6),
# # (4, 34, 10, 34, 10, 34, 10, 6),
# # (4, 32, 12, 32, 12, 32, 12, 6),
# # (4, 30, 14, 30, 14, 30, 14, 6),
# (4, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 6), # 0.0
# (4, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 6), # 0.046
# (4, 24, 20, 24, 20, 24, 20, 6), # 0.09
# (4, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 6), # 0.11
# (4, 13, 10, 13, 10, 13, 10, 13, 10, 13, 10, 13, 10, 13, 6), # 0.13
# (4, 28, 20, 28, 20, 28, 20, 6), # 0.166
# (4, 26, 18, 26, 18, 26, 18, 6), # 0.18
# (4, 24, 16, 24, 16, 24, 16, 6), # 0.2
#
# # (10, 8, 10, 10, 10, 8), # 0.05
# # (12, 10, 12, 8, 10, 10), # 0.1
# # (4, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 6), # 0.15
# # (10, 6, 12, 6), # 0.20
# # (10, 4), # 0.25
# # (14, 4, 10, 6), # 0.30
# # (12, 4), # 0.35
# # (14, 4), # 0.40
# ]
#
# import itertools
#
#
# def _make_end_of_frame_voltages2(cycles) -> numpy.ndarray:
# """Voltage sequence for end-of-frame TCP processing."""
# max_len = 140
# voltage_high = False
# c = [1.0, 1.0, 1.0, -1.0] # STA $C030
# for i, skip_cycles in enumerate(itertools.cycle(cycles)):
# c.extend([1.0 if voltage_high else -1.0] * (skip_cycles - 1))
# voltage_high = not voltage_high
# c.append(1.0 if voltage_high else -1.0)
# if len(c) >= max_len:
# break
# c.extend([1.0 if voltage_high else -1.0] * 6) # JMP (WDATA)
# return numpy.array(c, dtype=numpy.float32)
#
#
# def _duty_cycles():
# res = {}
#
# for i in range(4, 50, 2):
# for j in range(i, 50, 2):
# if i + j < 20 or i + j > 50:
# continue
# duty = j / (i + j) * 2 - 1
# res.setdefault(duty, []).append((i + j, i, j))
#
# cycles = []
# for c in sorted(list(res.keys())):
# pair = sorted(sorted(res[c], reverse=False)[0][1:], reverse=True)
# cycles.append(pair)
#
# # return [(10, 10), (12, 10), (12, 8), (14, 10), (14, 6), (14, 8)]
# return cycles
#
#
# eof_cycles = _duty_cycles()
eof_cycles = _duty_cycles()
# 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
#
#
# def all_opcodes(
# max_len: int, opcodes: Iterable[Opcode], start_opcodes: Iterable[int]
# ) -> Iterable[Tuple[Opcode]]:
# """Enumerate all combinations of opcodes up to max_len cycles"""
# num_opcodes = 0
# while True:
# found_one = False
# for ops in itertools.product(opcodes, repeat=num_opcodes):
# ops = tuple(list(ops) + [Opcode.JMP_INDIRECT])
# if ops[0] not in start_opcodes:
# continue
# if sum(len(VOLTAGES[o]) for o in ops) <= max_len:
# found_one = True
# yield ops
# if not found_one:
# break
# num_opcodes += 1
def generate_player(player_ops: List[Tuple[Opcode]], opcode_filename: str,
player_filename: str):
def audio_opcodes() -> Iterable[opcodes_6502.Opcode]:
# Interleave 3 x STA_C030 with 0,2,4 intervening NOP cycles
# We don't need to worry about 6 or more cycle paddings because
# these can always be achieved by chaining JMP (WDATA) to itself
for i in range(0, 6, 2):
for j in range(0, 6, 2):
ops = []
# don't need to worry about 0 or 2 since they are subsequences
ops.extend(opcodes_6502.nops(4))
ops.append(opcodes_6502.STA_C030)
if i:
ops.extend(opcodes_6502.nops(i))
ops.append(opcodes_6502.STA_C030)
if j:
ops.extend(opcodes_6502.nops(j))
ops.append(opcodes_6502.STA_C030)
ops.append(opcodes_6502.JMP_WDATA)
yield tuple(ops)
# Add a NOP sled so we can more efficiently chain together longer
# runs of NOPs without wasting bytes in the TCP frame chaining
# together JMP (WDATA)
yield tuple(
[nop for nop in opcodes_6502.nops(20)] + [opcodes_6502.JMP_WDATA])
def generate_player(
player_ops: Iterable[Tuple[opcodes_6502.Opcode]],
opcode_filename: str,
player_filename: str
):
num_bytes = 0
seqs = {}
num_op = 0
seen_op_suffix_toggles = set()
offset = 0
unique_opcodes = {}
toggles = {}
done = False
with open(player_filename, "w+") as f:
for i, k in enumerate(player_ops):
new_unique = []
for i, ops in enumerate(player_ops):
unique_entrypoints = []
player_op = []
player_op_len = 0
new_offset = offset
for j, o in enumerate(k):
seq, tog = voltage_sequence(k[j:], 1.0)
dup = seqs.setdefault(seq, i)
if dup == i:
player_op.append("tick_%02x: ; voltages %s" % (
new_offset, seq))
new_unique.append((new_offset, seq, tog))
player_op.append(" %s" % ASM[o])
player_op_len += OPCODE_LEN[o]
new_offset += OPCODE_LEN[o]
player_op.append("\n")
for j, op in enumerate(ops):
op_suffix_toggles = opcodes_6502.toggles(ops[j:])
if op_suffix_toggles not in seen_op_suffix_toggles:
# new subsequence
seen_op_suffix_toggles.add(op_suffix_toggles)
player_op.append(
opcodes_6502.Literal(
"tick_%02x: ; voltages %s" % (
offset, op_suffix_toggles), indent=0))
unique_entrypoints.append((offset, op_suffix_toggles))
player_op.append(op)
offset += op.bytes
# If at least one of the partial opcode sequences was not
# a dup, then add it to the player
if new_unique:
# Reserve 9 bytes for END_OF_FRAME and EXIT
if (num_bytes + player_op_len) > (256 - 9):
print("Out of space, truncating.")
break
num_op += 1
f.write("\n".join(player_op))
num_bytes += player_op_len
for op_offset, seq, tog in new_unique:
unique_opcodes[op_offset] = seq
toggles[op_offset] = tog
offset = new_offset
assert unique_entrypoints
player_op_len = opcodes_6502.total_bytes(player_op)
# Make sure we reserve 9 bytes for END_OF_FRAME and EXIT
assert (num_bytes + player_op_len) <= (256 - 9)
for op in player_op:
f.write("%s\n" % str(op))
num_bytes += player_op_len
for op_offset, seq in unique_entrypoints:
unique_opcodes[op_offset] = seq
# toggles[op_offset] = tog
f.write("\n")
f.write("; %d bytes\n" % num_bytes)
with open(opcode_filename, "w") as f:
f.write("import enum\nimport numpy\n\n\n")
f.write("class Opcode(enum.Enum):\n")
for o in unique_opcodes.keys():
f.write(" TICK_%02x = 0x%02x\n" % (o, o))
f.write(" EXIT = 0x%02x\n" % num_bytes)
# f.write(" END_OF_FRAME = 0x%02x\n" % (num_bytes + 3))
for i, _ in enumerate(eof_cycles):
f.write(" END_OF_FRAME_%d = 0x%02x\n" % (i, num_bytes + 4 + i))
f.write("\n\nVOLTAGE_SCHEDULE = {\n")
for o, v in unique_opcodes.items():
f.write(
" Opcode.TICK_%02x: numpy.array(%s, dtype=numpy.float32),"
"\n" % (o, v))
for i, skip_cycles in enumerate(eof_cycles):
f.write(" Opcode.END_OF_FRAME_%d: numpy.array([%s], "
"dtype=numpy.float32), # %s\n" % (i, ", ".join(
str(f) for f in _make_end_of_frame_voltages2(
skip_cycles)), skip_cycles))
f.write("}\n")
f.write("\n\nTOGGLES = {\n")
for o, v in toggles.items():
f.write(
" Opcode.TICK_%02x: %d,\n" % (o, v)
)
f.write("}\n")
f.write("\n\nEOF_OPCODES = (\n")
for i in range(len(eof_cycles)):
f.write(" Opcode.END_OF_FRAME_%d,\n" % i)
f.write(")\n")
# with open(opcode_filename, "w") as f:
# f.write("import enum\nimport numpy\n\n\n")
# f.write("class Opcode(enum.Enum):\n")
# for o in unique_opcodes.keys():
# f.write(" TICK_%02x = 0x%02x\n" % (o, o))
# f.write(" EXIT = 0x%02x\n" % num_bytes)
# # f.write(" END_OF_FRAME = 0x%02x\n" % (num_bytes + 3))
# for i, _ in enumerate(eof_cycles):
# f.write(" END_OF_FRAME_%d = 0x%02x\n" % (i, num_bytes + 4 + i))
#
# f.write("\n\nVOLTAGE_SCHEDULE = {\n")
# for o, v in unique_opcodes.items():
# f.write(
# " Opcode.TICK_%02x: numpy.array(%s, dtype=numpy.float32),"
# "\n" % (o, v))
# for i, skip_cycles in enumerate(eof_cycles):
# f.write(" Opcode.END_OF_FRAME_%d: numpy.array([%s], "
# "dtype=numpy.float32), # %s\n" % (i, ", ".join(
# str(f) for f in _make_end_of_frame_voltages2(
# skip_cycles)), skip_cycles))
# f.write("}\n")
#
# f.write("\n\nTOGGLES = {\n")
# for o, v in toggles.items():
# f.write(
# " Opcode.TICK_%02x: %d,\n" % (o, v)
# )
# f.write("}\n")
#
# f.write("\n\nEOF_OPCODES = (\n")
# for i in range(len(eof_cycles)):
# f.write(" Opcode.END_OF_FRAME_%d,\n" % i)
# f.write(")\n")
def all_opcode_combinations(
max_cycles: int, opcodes: Iterable[Opcode], start_opcodes: List[int]
) -> List[Tuple[Opcode]]:
return sorted(
list(all_opcodes(max_cycles, opcodes, start_opcodes)),
key=lambda o: len(o), reverse=True)
def sort_by_opcode_count(
player_opcodes: List[Tuple[Opcode]], count_opcodes: List[int]
) -> List[Tuple[Opcode]]:
return sorted(
player_opcodes, key=lambda ops: sum(o in count_opcodes for o in ops),
reverse=True)
# def all_opcode_combinations(
# max_cycles: int, opcodes: Iterable[Opcode], start_opcodes: List[int]
# ) -> List[Tuple[Opcode]]:
# return sorted(
# list(all_opcodes(max_cycles, opcodes, start_opcodes)),
# key=lambda o: len(o), reverse=True)
#
#
# def sort_by_opcode_count(
# player_opcodes: List[Tuple[Opcode]], count_opcodes: List[int]
# ) -> List[Tuple[Opcode]]:
# return sorted(
# player_opcodes, key=lambda ops: sum(o in count_opcodes for o in ops),
# reverse=True)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--max_cycles", type=int, required=True,
help="Maximum cycle length of player opcodes")
parser.add_argument("opcodes", nargs="+",
choices=Opcode.__members__.keys(),
help="6502 opcodes to use when generating player "
"opcodes")
args = parser.parse_args()
# parser = argparse.ArgumentParser()
# parser.add_argument("--max_cycles", type=int, required=True,
# help="Maximum cycle length of player opcodes")
# parser.add_argument("opcodes", nargs="+",
# choices=Opcode.__members__.keys(),
# help="6502 opcodes to use when generating player "
# "opcodes")
# args = parser.parse_args()
opcodes = set(Opcode.__members__[op] for op in args.opcodes)
# TODO: use Opcode instead of int values
non_nops = [Opcode.STA, Opcode.INC, Opcode.INCX, Opcode.STAX,
Opcode.JMP_INDIRECT]
# opcodes = set(Opcode.__members__[op] for op in args.opcodes)
# # TODO: use Opcode instead of int values
# non_nops = [Opcode.STA, Opcode.INC, Opcode.INCX, Opcode.STAX,
# Opcode.JMP_INDIRECT]
player_ops = sort_by_opcode_count(all_opcode_combinations(
max_cycles=args.max_cycles, opcodes=opcodes, start_opcodes=non_nops),
non_nops)
# player_ops = sort_by_opcode_count(all_opcode_combinations(
# max_cycles=args.max_cycles, opcodes=opcodes, start_opcodes=non_nops),
# non_nops)
player_ops = audio_opcodes()
generate_player(
player_ops,
opcode_filename="opcodes_generated.py",

View File

@ -5,8 +5,9 @@ from typing import Iterable, List
class Opcode:
"""6502 assembly language opcode with cycle length"""
def __init__(self, cycles, asm="", indent=4, toggle=False):
def __init__(self, cycles, bytes, asm="", indent=4, toggle=False):
self.cycles = cycles
self.bytes = bytes
self.asm = asm
self.indent = indent
# Assume toggles speaker on the last cycle
@ -24,7 +25,7 @@ class Opcode:
class Literal(Opcode):
def __init__(self, asm, indent=4):
super(Literal, self).__init__(cycles=0, asm=asm, indent=indent)
super(Literal, self).__init__(cycles=0, bytes=0, asm=asm, indent=indent)
def __repr__(self):
return "<literal>"
@ -54,10 +55,10 @@ def interleave_opcodes(
return
continue
if padding_cycles == 3:
yield PaddingOpcode(3, "STA zpdummy")
yield PaddingOpcode(3, 2, "STA zpdummy")
padding_cycles -= 3
else:
yield PaddingOpcode(2, "NOP")
yield PaddingOpcode(2, 1, "NOP")
padding_cycles -= 2
assert padding_cycles == 0
else:
@ -67,83 +68,41 @@ def interleave_opcodes(
raise OverflowError
STA_C030 = Opcode(4, "STA $C030", toggle=True)
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)")
def padding(cycles):
return PaddingOpcode(cycles, "; pad %d cycles" % cycles)
return PaddingOpcode(cycles, None, "; pad %d cycles" % cycles)
CORE_EOF = [
Literal(
"; We've read exactly 2KB from the socket buffer. Before continuing "
"we need to ACK this read,"),
Literal("; and make sure there's at least another 2KB in the buffer."),
Literal(";"),
Literal("; Save the W5100 address pointer so we can continue reading the "
"socket buffer once we are done."),
Literal(
"; We know the low-order byte is 0 because Socket RX memory is "
"page-aligned and so is 2K frame."),
Literal(
"; IMPORTANT - from now on until we restore this below, we can't "
"trash the Y register!"),
Opcode(4, "LDY WADRH"),
Literal("; Update new Received Read pointer."),
Literal(";"),
Literal(
"; We know we have received exactly 2KB, so we don't need to read the "
"current value from the"),
Literal("; hardware. We can track it ourselves instead, which saves a "
"few cycles."),
Opcode(2, "LDA #>S0RXRD"),
Opcode(4, "STA WADRH"),
Opcode(2, "LDA #<S0RXRD"),
Opcode(4, "STA WADRL"),
Opcode(4, "LDA RXRD ; TODO: in principle we could update RXRD outside of "
"the EOF path"),
Opcode(2, "CLC"),
Opcode(2, "ADC #$08"),
Opcode(4, "STA WDATA ; Store new high byte of received read pointer"),
Opcode(4, "STA RXRD ; Save for next time"),
Literal("; Send the Receive command"),
Opcode(2, "LDA #<S0CR"),
Opcode(4, "STA WADRL"),
Opcode(2, "LDA #SCRECV"),
Opcode(4, "STA WDATA"),
Literal(
"; Make sure we have at least 2KB more in the socket buffer so we can "
"start another frame."
),
Opcode(2, "LDA #$07 ; 2"),
Opcode(2, "LDX #<S0RXRSR ; Socket 0 Received Size register"),
Literal(
"; we might loop an unknown number of times here waiting for data but "
"the default should be to"),
Literal("; fall straight through"),
Literal("@0:", indent=0),
Opcode(4, "STX WADRL"),
Opcode(4, "CMP WDATA ; High byte of received size"),
Opcode(2,
"BCS @0 ; 2 cycles in common case when there is already sufficient "
"data waiting."),
Literal(
"; We're good to go for another frame. Restore W5100 address pointer "
"where we last found it, to"),
Literal("; begin iterating through the next 2KB of the socket buffer."),
Literal(";"),
Literal(
"; It turns out that the W5100 automatically wraps the address pointer "
"at the end of the 8K"),
Literal(
"; RX/TX buffers. Since we're using an 8K socket, that means we don't "
"have to do any work to"),
Literal("; manage the read pointer!"),
Opcode(4, "STY WADRH"),
Opcode(2, "LDA #$00"),
Opcode(4, "STA WADRL"),
Opcode(6, "JMP (WDATA)"),
]
def nops(cycles: int) -> Iterable[Opcode]:
print(cycles)
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
def toggles(opcodes: Iterable[Opcode]) -> List[bool]:
@ -156,13 +115,12 @@ def toggles(opcodes: Iterable[Opcode]) -> List[bool]:
if op.toggle:
speaker = not speaker
res.append(speaker)
return res
return tuple(res)
base = itertools.cycle([STA_C030, PaddingOpcode(6)])
def total_bytes(opcodes: Iterable[Opcode]) -> int:
return sum(op.bytes for op in opcodes)
eof = list(interleave_opcodes(base, CORE_EOF))
for op in eof:
print(op)
print(toggles(eof))
def total_cycles(opcodes: Iterable[Opcode]) -> int:
return sum(op.cycles for op in opcodes)

View File

@ -1,118 +1,144 @@
tick_00: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
tick_03: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
tick_06: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
tick_09: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
NOP
tick_0a: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
JMP (WDATA)
tick_00: ; voltages (True, True, True, True, True, True, True, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_01: ; voltages (True, True, True, True, True, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_02: ; voltages (True, True, True, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_05: ; voltages (True, True, True, False, False, False, False, True, True, True, True, True, True, True)
STA $C030 ; 4 cycles
tick_08: ; voltages (True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_0b: ; voltages (True, True, True, True, True, True)
JMP (WDATA) ; 6 cycles
tick_0d: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
tick_10: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
tick_13: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
NOP
tick_14: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
JMP (WDATA)
tick_0e: ; voltages (True, True, True, True, True, True, True, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_0f: ; voltages (True, True, True, True, True, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_10: ; voltages (True, True, True, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_13: ; voltages (True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, True)
STA $C030 ; 4 cycles
tick_16: ; voltages (True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_1a: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
tick_1d: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
NOP
tick_1e: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
STA $C030
JMP (WDATA)
tick_1d: ; voltages (True, True, True, True, True, True, True, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_1e: ; voltages (True, True, True, True, True, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_1f: ; voltages (True, True, True, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_22: ; voltages (True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, True)
STA $C030 ; 4 cycles
tick_25: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_27: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
STA $C030
STA $C030
JMP (WDATA)
tick_2d: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_2e: ; voltages (True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_2f: ; voltages (True, True, True, False, False, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_32: ; voltages (True, True, True, True, True, False, False, False, False, True, True, True, True, True, True, True)
NOP ; 2 cycles
STA $C030 ; 4 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_33: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
tick_36: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
tick_39: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
NOP
tick_3a: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
NOP
NOP
JMP (WDATA)
tick_3c: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_3d: ; voltages (True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_3e: ; voltages (True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_41: ; voltages (True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, True)
NOP ; 2 cycles
STA $C030 ; 4 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_3f: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
tick_42: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
NOP
tick_43: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
NOP
NOP
JMP (WDATA)
tick_4c: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_4d: ; voltages (True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_4e: ; voltages (True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_51: ; voltages (True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, True)
NOP ; 2 cycles
STA $C030 ; 4 cycles
NOP ; 2 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_4b: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
tick_4e: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
NOP
tick_4f: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
NOP
STA $C030
NOP
JMP (WDATA)
tick_5d: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_5e: ; voltages (True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_5f: ; voltages (True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_62: ; voltages (True, True, True, True, True, True, True, False, False, False, False, True, True, True, True, True, True, True)
NOP ; 2 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_57: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
tick_5a: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
NOP
tick_5b: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
NOP
NOP
STA $C030
JMP (WDATA)
tick_6d: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_6e: ; voltages (True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_6f: ; voltages (True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_72: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, True)
NOP ; 2 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_63: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
STA $C030
NOP
NOP
JMP (WDATA)
tick_7e: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_7f: ; voltages (True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
NOP ; 2 cycles
tick_80: ; voltages (True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, False, False, False)
STA $C030 ; 4 cycles
tick_83: ; voltages (True, True, True, True, True, True, True, False, False, False, False, False, False, False, False, True, True, True, True, True, True, True)
NOP ; 2 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
NOP ; 2 cycles
NOP ; 2 cycles
STA $C030 ; 4 cycles
JMP (WDATA) ; 6 cycles
tick_6e: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
NOP
STA $C030
NOP
JMP (WDATA)
tick_90: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_91: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_92: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_93: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_94: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_95: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_96: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_97: ; voltages (True, True, True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_98: ; voltages (True, True, True, True, True, True, True, True, True, True)
NOP ; 2 cycles
tick_99: ; voltages (True, True, True, True, True, True, True, True)
NOP ; 2 cycles
JMP (WDATA) ; 6 cycles
tick_79: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
STA $C030
NOP
NOP
STA $C030
JMP (WDATA)
tick_84: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
tick_87: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
NOP
tick_88: ; voltages (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
NOP
NOP
NOP
NOP
JMP (WDATA)
tick_8f: ; voltages (1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
STA $C030
NOP
NOP
NOP
NOP
JMP (WDATA)
; 153 bytes
; 157 bytes