From 473a1d550bc3ef5ce0ca579021089fef957a05ec Mon Sep 17 00:00:00 2001 From: Mads Ynddal <5528170+Baekalfen@users.noreply.github.com> Date: Thu, 15 Sep 2022 22:40:19 +0200 Subject: [PATCH] interrupt-based working. wip --- pyboy/__main__.py | 9 ++-- pyboy/core/mb.py | 3 +- pyboy/core/serial.py | 122 +++++++++++++++++++++++++++---------------- pyboy/pyboy.py | 8 ++- 4 files changed, 91 insertions(+), 51 deletions(-) diff --git a/pyboy/__main__.py b/pyboy/__main__.py index 0182b485d..299cb786d 100644 --- a/pyboy/__main__.py +++ b/pyboy/__main__.py @@ -90,7 +90,10 @@ def valid_file_path(path): parser.add_argument("--serial-bind", action="store_true", help="Bind to this TCP addres for using Link Cable") parser.add_argument( - "--serial-address", default=None, type=str, help="Connect (or bind) to this TCP addres for using Link Cable" + "--serial-address", default=None, type=str, help="Connect (or bind) to this TCP address for using Link Cable" +) +parser.add_argument( + "--serial-interrupt-based", action="store_true", help="Use only interrupt-based transfers for using Link Cable" ) gameboy_type_parser = parser.add_mutually_exclusive_group() @@ -111,8 +114,8 @@ def valid_file_path(path): def main(): argv = parser.parse_args() - if argv.serial_bind and not argv.serial_address: - parser.error("--serial-bind requires --serial-address") + if (argv.serial_bind or argv.serial_interrupt_based) and not argv.serial_address: + parser.error("--serial-bind and --serial-interrupt-based requires --serial-address") print( """ diff --git a/pyboy/core/mb.py b/pyboy/core/mb.py index dd50583c6..7af83a0d6 100644 --- a/pyboy/core/mb.py +++ b/pyboy/core/mb.py @@ -29,6 +29,7 @@ def __init__( randomize=False, serial_address=None, serial_bind=None, + serial_interrupt_based=False, ): if bootrom_file is not None: logger.info("Boot-ROM file provided") @@ -44,7 +45,7 @@ def __init__( self.bootrom = bootrom.BootROM(bootrom_file, cgb) self.ram = ram.RAM(cgb, randomize=randomize) self.cpu = cpu.CPU(self) - self.serial = serial.Serial(serial_address, serial_bind) + self.serial = serial.Serial(serial_address, serial_bind, serial_interrupt_based) if cgb: self.lcd = lcd.CGBLCD( diff --git a/pyboy/core/serial.py b/pyboy/core/serial.py index 1ba6e6631..ae3d5f4ba 100644 --- a/pyboy/core/serial.py +++ b/pyboy/core/serial.py @@ -1,16 +1,26 @@ import logging import os +import queue import socket import sys +import threading logger = logging.getLogger(__name__) SERIAL_FREQ = 8192 # Hz CPU_FREQ = 4213440 # Hz +async_recv = queue.Queue() + + +def async_comms(socket): + while True: + item = socket.recv(1) + async_recv.put(item) + class Serial: - def __init__(self, serial_address, serial_bind, serial_interrupt_based=True): + def __init__(self, serial_address, serial_bind, serial_interrupt_based): self.SB = 0 self.SC = 0 self.connection = None @@ -44,69 +54,91 @@ def __init__(self, serial_address, serial_bind, serial_interrupt_based=True): logger.info(f"Connecting to {serial_address}") self.connection.connect(address_tuple) logger.info(f"Connection successful!") - # self.connection.setblocking(False) - def tick(self, cycles): # if self.serial_interrupt_based: - # if self.SC & 1: # Master - # if self.SC & 0x80: - # logger.info(f'Master sending!') - # self.connection.send(bytes([self.SB])) - # # self.connection.setblocking(True) - # data = self.connection.recv(1) - # self.SB = data[0] - # self.SC &= 0b0111_1111 - # return True - # else: - # try: - # if self.SC & 0x80: - # # self.connection.setblocking(False) - # logger.info(f'Slave recv!') - # self.connection.send(bytes([self.SB])) - # data = self.connection.recv(1) - # self.SB = data[0] - # self.SC &= 0b0111_1111 - # return True - # except BlockingIOError: - # pass - # return False - # return False - # else: - # Check if serial is in progress + # logger.info("Interrupt-based serial emulation active!") + # self.recv_thread = threading.Thread(target=async_comms, args=(self.connection,)) + # self.recv_thread.start() + def tick(self, cycles): if self.connection is None: return - if self.SC & 0x80 == 0: + # self.cycles_count += 1 + + if self.serial_interrupt_based: + if self.SC & 0x80 == 0: # Performance optimization. Games might not set this on slave + return False + + self.cycles_count += 1 + + if (self.cycles_to_transmit() == 0): + if self.SC & 1: # Master + if self.SC & 0x80: + logger.info(f"Master sending!") + self.connection.send(bytes([self.SB])) + data = self.connection.recv(1) + self.SB = data[0] + self.SC &= 0b0111_1111 + self.cycles_count = 0 + return True + else: + # try: + # data = async_recv.get(block=False) + # except queue.Empty: + # return False + try: + data = self.connection.recv(1, socket.MSG_DONTWAIT) + except BlockingIOError: + return False + + logger.info(f"Slave recv!") + self.connection.send(bytes([self.SB])) + # data = self.connection.recv(1) + self.SB = data[0] + self.SC &= 0b0111_1111 + self.cycles_count = 0 + return True return False + else: + # Check if serial is in progress + if self.SC & 0x80 == 0: + return False - self.cycles_count += 1 + self.cycles_count += 1 - if (self.cycles_to_transmit() == 0): - # if self.SC & 1: # Master - send_bit = bytes([(self.SB >> 7) & 1]) - self.connection.send(send_bit) + if (self.cycles_to_transmit() == 0): + # if self.SC & 1: # Master + send_bit = bytes([(self.SB >> 7) & 1]) + self.connection.send(send_bit) - data = self.connection.recv(1) - self.SB = ((self.SB << 1) & 0xFF) | data[0] & 1 + data = self.connection.recv(1) + self.SB = ((self.SB << 1) & 0xFF) | data[0] & 1 - logger.info(f"recv sb: {self.SB:08b}") - self.trans_bits += 1 + logger.info(f"recv sb: {self.SB:08b}") + self.trans_bits += 1 - self.cycles_count = 0 + self.cycles_count = 0 - if self.trans_bits == 8: - self.trans_bits = 0 - self.SC &= 0b0111_1111 - return True + if self.trans_bits == 8: + self.trans_bits = 0 + self.SC &= 0b0111_1111 + return True + return False return False def cycles_to_transmit(self): - if self.SC & 0x80: - return max(self.cycles_target - self.cycles_count, 0) + if self.connection: + if self.SC & 0x80: + return max(self.cycles_target - self.cycles_count, 0) + # return CPU_FREQ // SERIAL_FREQ + else: + return 1 << 16 else: return 1 << 16 def stop(self): if self.connection: self.connection.close() + # if self.serial_interrupt_based and self.recv_thread: + # self.recv_thread.kill() diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 42f31b283..562da7eeb 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -51,6 +51,9 @@ def __init__( sound_emulated=False, cgb=None, log_level=defaults["log_level"], + serial_address=None, + serial_bind=None, + serial_interrupt_based=False, **kwargs ): """ @@ -143,8 +146,9 @@ def __init__( sound_emulated, cgb, randomize=randomize, - serial_address=kwargs["serial_address"], - serial_bind=kwargs["serial_bind"], + serial_address=serial_address, + serial_bind=serial_bind, + serial_interrupt_based=serial_interrupt_based, ) # Validate all kwargs