From e1e067619177b2806b57e5607b967059cf84a037 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Fri, 10 Jan 2020 21:20:44 -0500 Subject: [PATCH] emul/hw/sms: add A/B ports and a Genesis pad This makes the emulator suitable to run the base SMS recipe. --- emul/emul.h | 7 +++++ emul/hw/sms/Makefile | 2 +- emul/hw/sms/README.md | 25 ++++++++++++++++ emul/hw/sms/pad.c | 30 +++++++++++++++++++ emul/hw/sms/pad.h | 23 ++++++++++++++ emul/hw/sms/port.c | 70 +++++++++++++++++++++++++++++++++++++++++++ emul/hw/sms/port.h | 21 +++++++++++++ emul/hw/sms/sms.c | 62 +++++++++++++++++++++++++++++++++++++- 8 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 emul/hw/sms/README.md create mode 100644 emul/hw/sms/pad.c create mode 100644 emul/hw/sms/pad.h create mode 100644 emul/hw/sms/port.c create mode 100644 emul/hw/sms/port.h diff --git a/emul/emul.h b/emul/emul.h index 3134804..174bdf5 100644 --- a/emul/emul.h +++ b/emul/emul.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include "libz80/z80.h" @@ -19,6 +20,12 @@ typedef struct { IOWR iowr[0x100]; } Machine; +typedef enum { + TRI_HIGH, + TRI_LOW, + TRI_HIGHZ +} Tristate; + Machine* emul_init(); bool emul_step(); bool emul_steps(unsigned int steps); diff --git a/emul/hw/sms/Makefile b/emul/hw/sms/Makefile index 3744818..ec296fd 100644 --- a/emul/hw/sms/Makefile +++ b/emul/hw/sms/Makefile @@ -1,5 +1,5 @@ EXTOBJS = ../../emul.o ../../libz80/libz80.o -OBJS = sms.o vdp.o +OBJS = sms.o vdp.o port.o pad.o TARGET = sms CFLAGS += `pkg-config --cflags xcb` LDFLAGS += `pkg-config --libs xcb` diff --git a/emul/hw/sms/README.md b/emul/hw/sms/README.md new file mode 100644 index 0000000..606b354 --- /dev/null +++ b/emul/hw/sms/README.md @@ -0,0 +1,25 @@ +# Sega Master System emulator + +This emulates a Sega Master system with a monochrome screen and a Genesis pad +hooked to port A. + +## Build + +You need `xcb` and `pkg-config` to build this. If you have them, run `make`. +You'll get a `sms` executable. + +## Usage + +Launch the emulator with `./sms /path/to/rom` (you can use the binary from the +`sms` recipe. + +This will show a window with the screen's content on it. The mappings to the +pad are: + +* Arrows +* Z --> A +* X --> B +* C --> C +* S --> Start + +Press ESC to quit. diff --git a/emul/hw/sms/pad.c b/emul/hw/sms/pad.c new file mode 100644 index 0000000..e835b88 --- /dev/null +++ b/emul/hw/sms/pad.c @@ -0,0 +1,30 @@ +#include "pad.h" + +void pad_init(Pad *pad, Tristate *TH) +{ + pad->pressed = 0xff; + pad->TH = TH; +} + +void pad_setbtn(Pad *pad, PAD_BTN btn, bool pressed) +{ + if (pressed) { + pad->pressed &= ~(1 << btn); + } else { + pad->pressed |= (1 << btn); + } +} + +uint8_t pad_rd(Pad *pad) +{ + uint8_t res; + if (*pad->TH == TRI_LOW) { // TH selected + // A and START shifted in from bits 7:6 into 5:4 + res = (pad->pressed & 0xf) | ((pad->pressed & 0xc0) >> 2); + } else { + res = pad->pressed & 0x3f; + } + // Bits 7:6 are always high + res |= 0b11000000; + return res; +} diff --git a/emul/hw/sms/pad.h b/emul/hw/sms/pad.h new file mode 100644 index 0000000..cfcd9ad --- /dev/null +++ b/emul/hw/sms/pad.h @@ -0,0 +1,23 @@ +#include +#include +#include "port.h" + +typedef enum { + PAD_BTN_UP = 0, + PAD_BTN_DOWN = 1, + PAD_BTN_LEFT = 2, + PAD_BTN_RIGHT = 3, + PAD_BTN_B = 4, + PAD_BTN_C = 5, + PAD_BTN_A = 6, + PAD_BTN_START = 7 +} PAD_BTN; + +typedef struct { + uint8_t pressed; + Tristate *TH; +} Pad; + +void pad_init(Pad *pad, Tristate *TH); +void pad_setbtn(Pad *pad, PAD_BTN btn, bool pressed); +uint8_t pad_rd(Pad *pad); diff --git a/emul/hw/sms/port.c b/emul/hw/sms/port.c new file mode 100644 index 0000000..67fa96e --- /dev/null +++ b/emul/hw/sms/port.c @@ -0,0 +1,70 @@ +#include "port.h" + +void ports_init(Ports *ports) +{ + ports->ctl = 0xff; + ports->TRA = TRI_HIGHZ; + ports->THA = TRI_HIGHZ; + ports->TRB = TRI_HIGHZ; + ports->THB = TRI_HIGHZ; +} + +uint8_t ports_ctl_rd(Ports *ports) +{ + return ports->ctl; +} + +void ports_ctl_wr(Ports *ports, uint8_t val) +{ + ports->ctl = val; + ports->TRA = TRI_HIGHZ; + ports->THA = TRI_HIGHZ; + ports->TRB = TRI_HIGHZ; + ports->THB = TRI_HIGHZ; + if ((val & 0x01) == 0) { + ports->TRA = ((val & 0x10) == 0) ? TRI_LOW : TRI_HIGH; + } + if ((val & 0x02) == 0) { + ports->THA = ((val & 0x20) == 0) ? TRI_LOW : TRI_HIGH; + } + if ((val & 0x04) == 0) { + ports->TRB = ((val & 0x40) == 0) ? TRI_LOW : TRI_HIGH; + } + if ((val & 0x08) == 0) { + ports->THB = ((val & 0x80) == 0) ? TRI_LOW : TRI_HIGH; + } +} + +uint8_t ports_A_rd(Ports *ports) +{ + // Bits 7:6 are port B's Down/Up + // Bits 5:0 are port A's TR/TL/R/L/D/U + uint8_t res = 0xff; + if (ports->portA_rd != NULL) { + res &= ports->portA_rd() | 0b11000000; + } + if (ports->portB_rd != NULL) { + res &= (ports->portB_rd() << 6) | 0b00111111; + } + return res; + +} + +uint8_t ports_B_rd(Ports *ports) +{ + // Bit 7: Port B's TH + // Bit 6: Port A's TH + // Bit 5: unused + // Bit 4: unused (reset button) + // Bits 3:0 are port B's TR/TL/R/L + uint8_t res = 0xff; + if (ports->portA_rd != NULL) { + res &= ports->portA_rd() | 0b10111111; + } + if (ports->portB_rd != NULL) { + uint8_t portb = ports->portB_rd(); + res &= (portb << 1) | 0b01111111; // TH + res &= (portb >> 2) | 0b11110000; // TR/TL/R/L + } + return res; +} diff --git a/emul/hw/sms/port.h b/emul/hw/sms/port.h new file mode 100644 index 0000000..5679354 --- /dev/null +++ b/emul/hw/sms/port.h @@ -0,0 +1,21 @@ +#pragma once +#include "../../emul.h" + +// Each port is a bitmask of each pin's status. 1 means high. +// From Bit 0 to 6: up, down, left, right, TL, TR, TH + +typedef struct { + uint8_t ctl; + Tristate TRA; + Tristate THA; + Tristate TRB; + Tristate THB; + IORD portA_rd; + IORD portB_rd; +} Ports; + +void ports_init(Ports *ports); +uint8_t ports_ctl_rd(Ports *ports); +void ports_ctl_wr(Ports *ports, uint8_t val); +uint8_t ports_A_rd(Ports *ports); +uint8_t ports_B_rd(Ports *ports); diff --git a/emul/hw/sms/sms.c b/emul/hw/sms/sms.c index 25c414a..ff9e9d7 100644 --- a/emul/hw/sms/sms.c +++ b/emul/hw/sms/sms.c @@ -6,10 +6,15 @@ #include "../../emul.h" #include "vdp.h" +#include "port.h" +#include "pad.h" #define RAMSTART 0xc000 #define VDP_CMD_PORT 0xbf #define VDP_DATA_PORT 0xbe +#define PORTS_CTL_PORT 0x3f +#define PORTS_IO1_PORT 0xdc +#define PORTS_IO2_PORT 0xdd #define MAX_ROMSIZE 0x8000 static xcb_connection_t *conn; @@ -26,6 +31,8 @@ static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH]; static Machine *m; static VDP vdp; static bool vdp_changed; +static Ports ports; +static Pad pad; static uint8_t iord_vdp_cmd() { @@ -37,6 +44,21 @@ static uint8_t iord_vdp_data() return vdp_data_rd(&vdp); } +static uint8_t iord_ports_io1() +{ + return ports_A_rd(&ports); +} + +static uint8_t iord_ports_io2() +{ + return ports_B_rd(&ports); +} + +static uint8_t iord_pad() +{ + return pad_rd(&pad); +} + static void iowr_vdp_cmd(uint8_t val) { vdp_cmd_wr(&vdp, val); @@ -48,6 +70,11 @@ static void iowr_vdp_data(uint8_t val) vdp_data_wr(&vdp, val); } +static void iowr_ports_ctl(uint8_t val) +{ + ports_ctl_wr(&ports, val); +} + void create_window() { uint32_t mask; @@ -150,7 +177,34 @@ void event_loop() case XCB_KEY_RELEASE: case XCB_KEY_PRESS: { xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e; - if (ev->detail == 0x09) return; + bool ispressed = e->response_type == XCB_KEY_PRESS; + switch (ev->detail) { + case 0x09: return; // ESC + case 0x27: // S + pad_setbtn(&pad, PAD_BTN_START, ispressed); + break; + case 0x34: // Z + pad_setbtn(&pad, PAD_BTN_A, ispressed); + break; + case 0x35: // X + pad_setbtn(&pad, PAD_BTN_B, ispressed); + break; + case 0x36: // C + pad_setbtn(&pad, PAD_BTN_C, ispressed); + break; + case 0x62: + pad_setbtn(&pad, PAD_BTN_UP, ispressed); + break; + case 0x64: + pad_setbtn(&pad, PAD_BTN_LEFT, ispressed); + break; + case 0x66: + pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed); + break; + case 0x68: + pad_setbtn(&pad, PAD_BTN_DOWN, ispressed); + break; + } break; } case XCB_EXPOSE: { @@ -190,10 +244,16 @@ int main(int argc, char *argv[]) } vdp_init(&vdp); vdp_changed = false; + ports_init(&ports); + ports.portA_rd = iord_pad; + pad_init(&pad, &ports.THA); m->iord[VDP_CMD_PORT] = iord_vdp_cmd; m->iord[VDP_DATA_PORT] = iord_vdp_data; + m->iord[PORTS_IO1_PORT] = iord_ports_io1; + m->iord[PORTS_IO2_PORT] = iord_ports_io2; m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd; m->iowr[VDP_DATA_PORT] = iowr_vdp_data; + m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl; conn = xcb_connect(NULL, NULL); screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; create_window();