Skip to content

Commit 966124a

Browse files
Baekalfenthejomas
andcommitted
Implementing serial support over IP socket
Co-authored-by: thejomas <[email protected]>
1 parent c3098aa commit 966124a

File tree

5 files changed

+146
-5
lines changed

5 files changed

+146
-5
lines changed

pyboy/__main__.py

+8
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ def valid_file_path(path):
7474
parser.add_argument("--disable-renderer", action="store_true", help="Disables screen rendering for higher performance")
7575
parser.add_argument("--sound", action="store_true", help="Enable sound (beta)")
7676

77+
parser.add_argument("--serial-bind", action="store_true", help="Bind to this TCP addres for using Link Cable")
78+
parser.add_argument(
79+
"--serial-address", default=None, type=str, help="Connect (or bind) to this TCP addres for using Link Cable"
80+
)
81+
7782
gameboy_type_parser = parser.add_mutually_exclusive_group()
7883
gameboy_type_parser.add_argument(
7984
"--dmg", action="store_const", const=False, dest="cgb", help="Force emulator to run as original Game Boy (DMG)"
@@ -91,6 +96,9 @@ def valid_file_path(path):
9196

9297
def main():
9398
argv = parser.parse_args()
99+
if argv.serial_bind and not argv.serial_address:
100+
parser.error("--serial-bind requires --serial-address")
101+
94102
log_level(argv.log_level)
95103

96104
logger.info(

pyboy/core/mb.pxd

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ from pyboy.utils cimport WindowEvent
1919

2020

2121
cdef uint16_t STAT, LY, LYC
22-
cdef short VBLANK, LCDC, TIMER, SERIAL, HIGHTOLOW
2322
cdef int INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW
2423
cdef int STATE_VERSION
2524

@@ -33,6 +32,7 @@ cdef class Motherboard:
3332
cdef pyboy.core.timer.Timer timer
3433
cdef pyboy.core.sound.Sound sound
3534
cdef pyboy.core.cartridge.base_mbc.BaseMBC cartridge
35+
cdef object serial
3636
cdef bint bootrom_enabled
3737
cdef str serialbuffer
3838

pyboy/core/mb.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pyboy.core.opcodes import CPU_COMMANDS
99
from pyboy.utils import STATE_VERSION
1010

11-
from . import bootrom, cartridge, cpu, interaction, lcd, ram, sound, timer
11+
from . import bootrom, cartridge, cpu, interaction, lcd, ram, serial, sound, timer
1212

1313
INTR_VBLANK, INTR_LCDC, INTR_TIMER, INTR_SERIAL, INTR_HIGHTOLOW = [1 << x for x in range(5)]
1414

@@ -26,6 +26,8 @@ def __init__(
2626
cgb,
2727
randomize=False,
2828
profiling=False,
29+
serial_address=None,
30+
serial_bind=None,
2931
):
3032
if bootrom_file is not None:
3133
logger.info("Boot-ROM file provided")
@@ -43,6 +45,7 @@ def __init__(
4345
self.bootrom = bootrom.BootROM(bootrom_file, cgb)
4446
self.ram = ram.RAM(cgb, randomize=randomize)
4547
self.cpu = cpu.CPU(self, profiling)
48+
self.serial = serial.Serial(serial_address, serial_bind)
4649

4750
if cgb:
4851
self.lcd = lcd.CGBLCD(
@@ -105,6 +108,7 @@ def buttonevent(self, key):
105108

106109
def stop(self, save):
107110
self.sound.stop()
111+
self.serial.stop()
108112
if save:
109113
self.cartridge.stop()
110114

@@ -210,9 +214,10 @@ def tick(self):
210214
cycles = min(
211215
self.lcd.cycles_to_interrupt(),
212216
self.timer.cycles_to_interrupt(),
213-
# self.serial.cycles_to_interrupt(),
214-
mode0_cycles
217+
self.serial.cycles_to_transmit(),
218+
mode0_cycles,
215219
)
220+
cycles = 4
216221

217222
# Profiling
218223
self.cpu.add_opcode_hit(0x76, cycles // 4)
@@ -228,6 +233,8 @@ def tick(self):
228233

229234
if self.timer.tick(cycles):
230235
self.cpu.set_interruptflag(INTR_TIMER)
236+
if self.serial.tick(cycles):
237+
self.cpu.set_interruptflag(INTR_SERIAL)
231238

232239
lcd_interrupt = self.lcd.tick(cycles)
233240
if lcd_interrupt:
@@ -280,7 +287,13 @@ def getitem(self, i):
280287
elif 0xFEA0 <= i < 0xFF00: # Empty but unusable for I/O
281288
return self.ram.non_io_internal_ram0[i - 0xFEA0]
282289
elif 0xFF00 <= i < 0xFF4C: # I/O ports
283-
if i == 0xFF04:
290+
if i == 0xFF01:
291+
logger.info(f"get SB {self.serial.SB}")
292+
return self.serial.SB
293+
elif i == 0xFF02:
294+
logger.info(f"get SC {self.serial.SC}")
295+
return self.serial.SC
296+
elif i == 0xFF04:
284297
return self.timer.DIV
285298
elif i == 0xFF05:
286299
return self.timer.TIMA
@@ -398,8 +411,14 @@ def setitem(self, i, value):
398411
if i == 0xFF00:
399412
self.ram.io_ports[i - 0xFF00] = self.interaction.pull(value)
400413
elif i == 0xFF01:
414+
self.serial.SB = value
415+
logger.info(f"SB: {value:02x}")
416+
401417
self.serialbuffer += chr(value)
402418
self.ram.io_ports[i - 0xFF00] = value
419+
elif i == 0xFF02:
420+
self.serial.SC = value
421+
logger.info(f"SC: {value:02x}")
403422
elif i == 0xFF04:
404423
self.timer.reset()
405424
elif i == 0xFF05:

pyboy/core/serial.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import logging
2+
import os
3+
import socket
4+
import sys
5+
6+
logger = logging.getLogger(__name__)
7+
8+
SERIAL_FREQ = 8192 # Hz
9+
CPU_FREQ = 4213440 # Hz
10+
11+
12+
class Serial:
13+
def __init__(self, serial_address, serial_bind, serial_interrupt_based=True):
14+
self.SB = 0
15+
self.SC = 0
16+
self.connection = None
17+
18+
self.trans_bits = 0
19+
self.cycles_count = 0
20+
self.cycles_target = CPU_FREQ // SERIAL_FREQ
21+
self.serial_interrupt_based = serial_interrupt_based
22+
23+
if not serial_address:
24+
logger.info("No serial address supplied. Link Cable emulation disabled.")
25+
return
26+
27+
if not serial_address.count(".") == 3 and serial_address.count(":") == 1:
28+
logger.info("Only IP-addresses of the format x.y.z.w:abcd is supported")
29+
return
30+
31+
address_ip, address_port = serial_address.split(":")
32+
address_tuple = (address_ip, int(address_port))
33+
34+
if serial_bind:
35+
self.binding_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
36+
self.binding_connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
37+
logger.info(f"Binding to {serial_address}")
38+
self.binding_connection.bind(address_tuple)
39+
self.binding_connection.listen(1)
40+
self.connection, _ = self.binding_connection.accept()
41+
logger.info(f"Client has connected!")
42+
else:
43+
self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
44+
logger.info(f"Connecting to {serial_address}")
45+
self.connection.connect(address_tuple)
46+
logger.info(f"Connection successful!")
47+
# self.connection.setblocking(False)
48+
49+
def tick(self, cycles):
50+
# if self.serial_interrupt_based:
51+
# if self.SC & 1: # Master
52+
# if self.SC & 0x80:
53+
# logger.info(f'Master sending!')
54+
# self.connection.send(bytes([self.SB]))
55+
# # self.connection.setblocking(True)
56+
# data = self.connection.recv(1)
57+
# self.SB = data[0]
58+
# self.SC &= 0b0111_1111
59+
# return True
60+
# else:
61+
# try:
62+
# if self.SC & 0x80:
63+
# # self.connection.setblocking(False)
64+
# logger.info(f'Slave recv!')
65+
# self.connection.send(bytes([self.SB]))
66+
# data = self.connection.recv(1)
67+
# self.SB = data[0]
68+
# self.SC &= 0b0111_1111
69+
# return True
70+
# except BlockingIOError:
71+
# pass
72+
# return False
73+
# return False
74+
# else:
75+
# Check if serial is in progress
76+
77+
if self.connection is None:
78+
return
79+
80+
if self.SC & 0x80 == 0:
81+
return False
82+
83+
self.cycles_count += 1
84+
85+
if (self.cycles_to_transmit() == 0):
86+
# if self.SC & 1: # Master
87+
send_bit = bytes([(self.SB >> 7) & 1])
88+
self.connection.send(send_bit)
89+
90+
data = self.connection.recv(1)
91+
self.SB = ((self.SB << 1) & 0xFF) | data[0] & 1
92+
93+
logger.info(f"recv sb: {self.SB:08b}")
94+
self.trans_bits += 1
95+
96+
self.cycles_count = 0
97+
98+
if self.trans_bits == 8:
99+
self.trans_bits = 0
100+
self.SC &= 0b0111_1111
101+
return True
102+
return False
103+
104+
def cycles_to_transmit(self):
105+
if self.SC & 0x80:
106+
return max(self.cycles_target - self.cycles_count, 0)
107+
else:
108+
return 1 << 16
109+
110+
def stop(self):
111+
if self.connection:
112+
self.connection.close()

pyboy/pyboy.py

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ def __init__(
8888
cgb,
8989
randomize=randomize,
9090
profiling=profiling,
91+
serial_address=kwargs["serial_address"],
92+
serial_bind=kwargs["serial_bind"],
9193
)
9294

9395
# Performance measures

0 commit comments

Comments
 (0)