This makes the emulator suitable to run the base SMS recipe.pull/94/head
@@ -1,3 +1,4 @@ | |||
#pragma once | |||
#include <stdint.h> | |||
#include <stdbool.h> | |||
#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); | |||
@@ -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` | |||
@@ -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. |
@@ -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; | |||
} |
@@ -0,0 +1,23 @@ | |||
#include <stdint.h> | |||
#include <stdbool.h> | |||
#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); |
@@ -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; | |||
} |
@@ -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); |
@@ -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(); | |||