import enum import itertools import numpy from typing import Iterable, List, Tuple import opcodes_6502 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 ] 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 # 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 _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 # ] # 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, 35, 1): if i == 5: continue for j in range(i, 35, 1): if j == 5: continue if i + j < 20 or i + j > 40: 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=False) cycles.append(pair) if pair[0] != pair[1]: cycles.append([pair[1], pair[0]]) print(c, pair) # return [(10, 10), (12, 10), (12, 8), (14, 10), (14, 6), (14, 8)] return sorted(cycles, key=lambda p: p[0] + p[1]) 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 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 by chaining # together JMP (WDATA) yield tuple( [nop for nop in opcodes_6502.nops(4)] + [ opcodes_6502.STA_C030, opcodes_6502.JMP_WDATA]) yield tuple( [nop for nop in opcodes_6502.nops(4)] + [ opcodes_6502.Opcode(3, 2, "STA zpdummy"), opcodes_6502.STA_C030, opcodes_6502.JMP_WDATA]) # yield tuple( # [nop for nop in opcodes_6502.nops(20)] + [opcodes_6502.JMP_WDATA]) # # yield tuple( # [nop for nop in opcodes_6502.nops(18)] + [ # opcodes_6502.Opcode(3, 2, "STA zpdummy"), opcodes_6502.JMP_WDATA]) def generate_player( player_ops: Iterable[Tuple[opcodes_6502.Opcode]], opcode_filename: str, player_filename: str ): num_bytes = 0 seen_op_suffix_toggles = set() offset = 0 unique_entrypoints = {} toggles = {} with open(player_filename, "w+") as f: for i, ops in enumerate(player_ops): player_op = [] 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[offset] = op_suffix_toggles player_op.append(op) offset += op.bytes 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 f.write("\n") f.write("; %d entrypoints, %d bytes\n" % ( len(unique_entrypoints), 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_entrypoints.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_entrypoints.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 main(): player_ops = audio_opcodes() generate_player( player_ops, opcode_filename="opcodes_generated.py", player_filename="player/player_generated.s" ) if __name__ == "__main__": main()