mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2024-06-26 00:29:29 +00:00
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:
parent
e0ab30d074
commit
4178c191db
166
opcodes.py
166
opcodes.py
|
@ -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
26
opcodes_test.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user