Update cycle timing from working ethernet player.

Add _START and _END addresses that are used by the byte stream to
vector the program counter to the next opcode in the stream.

Support equality testing of opcodes and add tests.

Add an ACK opcode for instructing the client to ACK the TCP stream.

Tick opcode now accepts a cycle argument, for experimenting with
audio support.
This commit is contained in:
kris 2019-02-23 23:38:14 +00:00
parent e0ab30d074
commit 4178c191db
2 changed files with 165 additions and 27 deletions

View File

@ -26,8 +26,8 @@ class State:
self.memmap = memmap
self.cycle_counter = cycle_counter
def emit(self, opcode: "Opcode") -> Iterator[int]:
cmd = opcode.emit_command()
def emit(self, last_opcode: "Opcode", opcode: "Opcode") -> Iterator[int]:
cmd = opcode.emit_command(last_opcode, opcode)
if cmd:
yield from cmd
data = opcode.emit_data()
@ -48,18 +48,45 @@ class OpcodeCommand(enum.Enum):
RLE = 0xfd
TICK = 0xfe # tick speaker
TERMINATE = 0xff
NOP = 0xfa
ACK = 0xf9
class Opcode:
COMMAND = None # type: OpcodeCommand
_CYCLES = None # type: int
# Offset of start byte in decoder
_START = None # type: int
# Offset of last byte in BRA instruction in decoder
_END = None # type: int
def __repr__(self):
return "Opcode(%s)" % self.COMMAND.name
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.__data_eq__(other)
def __data_eq__(self, other):
raise NotImplementedError
@property
def cycles(self) -> int:
return self._CYCLES
def emit_command(self) -> Iterator[int]:
yield self.COMMAND.value
@staticmethod
def emit_command(last_opcode: "Opcode",
opcode: "Opcode") -> Iterator[int]:
# Compute offset from last opcode's terminating BRA instruction to
# first instruction of this opcode.
offset = (opcode._START - last_opcode._END - 1) & 0xff
# print("%s -> %s = %02x" % (last_opcode, opcode, offset))
yield offset
def emit_data(self) -> Iterator[int]:
return
@ -68,68 +95,118 @@ class Opcode:
pass
class Nop(Opcode):
COMMAND = OpcodeCommand.NOP
_CYCLES = 11
_START = 0x819b
_END = 0x81a2
def __data_eq__(self, other):
return True
class Store(Opcode):
COMMAND = OpcodeCommand.STORE
_CYCLES = 36
_CYCLES = 20 # 36
_START = 0x81a3
_END = 0x81b0
def __init__(self, offset: int):
if offset < 0 or offset >255:
if offset < 0 or offset > 255:
raise ValueError("Invalid offset: %d" % offset)
self.offset = offset
def emit_command(self):
return
def __repr__(self):
return "Opcode(%s, %02x)" % (
self.COMMAND.name, self.offset)
def __data_eq__(self, other):
return self.offset == other.offset
def emit_data(self):
# print(" Store @ %02x" % self.offset)
yield self.offset
def apply(self, state):
state.memmap.write(state.page << 8 | self.offset, state.content)
class SetPage(Opcode):
COMMAND = OpcodeCommand.SET_PAGE
_CYCLES = 73
def __init__(self, page:int):
self.page = page
def emit_data(self):
yield self.page
def apply(self, state: State):
state.page = self.page
class SetContent(Opcode):
COMMAND = OpcodeCommand.SET_CONTENT
_CYCLES = 62
_CYCLES = 15 # 62
_START = 0x81b1
_END = 0x81bb
def __init__(self, content: int):
self.content = content
def __repr__(self):
return "Opcode(%s, %02x)" % (
self.COMMAND.name, self.content)
def __data_eq__(self, other):
return self.content == other.content
def emit_data(self):
yield self.content
def apply(self, state: State):
# print(" Set content %02x" % self.content)
state.content = self.content
class SetPage(Opcode):
COMMAND = OpcodeCommand.SET_PAGE
_CYCLES = 23 # 73
_START = 0x81bc
_END = 0x81cc
def __init__(self, page: int):
self.page = page
def __repr__(self):
return "Opcode(%s, %02x)" % (
self.COMMAND.name, self.page)
def __data_eq__(self, other):
return self.page == other.page
def emit_data(self):
yield self.page
def apply(self, state: State):
# print(" Set page %02x" % self.page)
state.page = self.page
class RLE(Opcode):
COMMAND = OpcodeCommand.RLE
_CYCLES = 22
_START = 0x81cd
_END = 0x81e3
def __init__(self, start_offset: int, run_length: int):
self.start_offset = start_offset
self.run_length = run_length
def __repr__(self):
return "Opcode(%s, %02x, %02x)" % (
self.COMMAND.name, self.start_offset, self.run_length)
def __data_eq__(self, other):
return (
self.start_offset == other.start_offset and
self.run_length == other.run_length)
def emit_data(self):
# print(" RLE @ %02x * %02x" % (self.start_offset, self.run_length))
yield self.start_offset
yield self.run_length
@property
def cycles(self):
return 98 + 9 * self.run_length
return 22 + 10 * self.run_length
def apply(self, state):
for i in range(self.run_length):
@ -141,12 +218,46 @@ class RLE(Opcode):
class Tick(Opcode):
COMMAND = OpcodeCommand.TICK
_CYCLES = 50
_TICK_ADDR = 0x81ee
_END = 0x81f8
def __init__(self, cycles: int):
self._START = self._TICK_ADDR - (cycles - 15) // 2
self._cycles = cycles
def __repr__(self):
return "Opcode(%s, %02x)" % (
self.COMMAND.name, self.cycles)
def __data_eq__(self, other):
return self._cycles == other._cycles
@property
def cycles(self):
return self._cycles
def emit_data(self):
print(" Tick @ %02x" % self.cycles)
class Terminate(Opcode):
COMMAND = OpcodeCommand.TERMINATE
_CYCLES = 50
_CYCLES = 6 # 50
_START = 0x81f9
_END = None
def __data_eq__(self, other):
return True
class Ack(Opcode):
COMMAND = OpcodeCommand.ACK
_CYCLES = 100 # XXX todo
_START = 0x81fa
_END = None
def __data_eq__(self, other):
return True
class Decoder:
@ -176,7 +287,8 @@ class Decoder:
num_rle_bytes += run_length
op = RLE(offset, run_length)
elif b == OpcodeCommand.TICK.value:
op = Tick()
cycles = next(stream)
op = Tick(cycles)
elif b == OpcodeCommand.TERMINATE.value:
op = Terminate()
terminate = True

26
opcodes_test.py Normal file
View File

@ -0,0 +1,26 @@
import unittest
import opcodes
class TestOpcodes(unittest.TestCase):
def test_equality(self):
op1 = opcodes.Terminate()
op2 = opcodes.Terminate()
self.assertEqual(op1, op2)
op1 = opcodes.SetPage(0x20)
op2 = opcodes.SetPage(0x20)
self.assertEqual(op1, op2)
op1 = opcodes.SetPage(0x20)
op2 = opcodes.SetPage(0x21)
self.assertNotEqual(op1, op2)
op1 = opcodes.SetPage(0x20)
op2 = opcodes.Terminate()
self.assertNotEqual(op1, op2)
if __name__ == '__main__':
unittest.main()