1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 14:30:55 +11:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Virgil Dupras
e1e0676191 emul/hw/sms: add A/B ports and a Genesis pad
This makes the emulator suitable to run the base SMS recipe.
2020-01-10 21:20:44 -05:00
Virgil Dupras
b60252e330 emul/hw/(ti|sms): detect window being closed
Previously, it would never get out of the event loop.
2020-01-10 16:59:48 -05:00
9 changed files with 258 additions and 2 deletions

View File

@ -1,3 +1,4 @@
#pragma once
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "libz80/z80.h" #include "libz80/z80.h"
@ -19,6 +20,12 @@ typedef struct {
IOWR iowr[0x100]; IOWR iowr[0x100];
} Machine; } Machine;
typedef enum {
TRI_HIGH,
TRI_LOW,
TRI_HIGHZ
} Tristate;
Machine* emul_init(); Machine* emul_init();
bool emul_step(); bool emul_step();
bool emul_steps(unsigned int steps); bool emul_steps(unsigned int steps);

View File

@ -1,5 +1,5 @@
EXTOBJS = ../../emul.o ../../libz80/libz80.o EXTOBJS = ../../emul.o ../../libz80/libz80.o
OBJS = sms.o vdp.o OBJS = sms.o vdp.o port.o pad.o
TARGET = sms TARGET = sms
CFLAGS += `pkg-config --cflags xcb` CFLAGS += `pkg-config --cflags xcb`
LDFLAGS += `pkg-config --libs xcb` LDFLAGS += `pkg-config --libs xcb`

25
emul/hw/sms/README.md Normal file
View File

@ -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.

30
emul/hw/sms/pad.c Normal file
View File

@ -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;
}

23
emul/hw/sms/pad.h Normal file
View File

@ -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);

70
emul/hw/sms/port.c Normal file
View File

@ -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;
}

21
emul/hw/sms/port.h Normal file
View File

@ -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);

View File

@ -6,10 +6,15 @@
#include "../../emul.h" #include "../../emul.h"
#include "vdp.h" #include "vdp.h"
#include "port.h"
#include "pad.h"
#define RAMSTART 0xc000 #define RAMSTART 0xc000
#define VDP_CMD_PORT 0xbf #define VDP_CMD_PORT 0xbf
#define VDP_DATA_PORT 0xbe #define VDP_DATA_PORT 0xbe
#define PORTS_CTL_PORT 0x3f
#define PORTS_IO1_PORT 0xdc
#define PORTS_IO2_PORT 0xdd
#define MAX_ROMSIZE 0x8000 #define MAX_ROMSIZE 0x8000
static xcb_connection_t *conn; static xcb_connection_t *conn;
@ -26,6 +31,8 @@ static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH];
static Machine *m; static Machine *m;
static VDP vdp; static VDP vdp;
static bool vdp_changed; static bool vdp_changed;
static Ports ports;
static Pad pad;
static uint8_t iord_vdp_cmd() static uint8_t iord_vdp_cmd()
{ {
@ -37,6 +44,21 @@ static uint8_t iord_vdp_data()
return vdp_data_rd(&vdp); 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) static void iowr_vdp_cmd(uint8_t val)
{ {
vdp_cmd_wr(&vdp, val); vdp_cmd_wr(&vdp, val);
@ -48,6 +70,11 @@ static void iowr_vdp_data(uint8_t val)
vdp_data_wr(&vdp, val); vdp_data_wr(&vdp, val);
} }
static void iowr_ports_ctl(uint8_t val)
{
ports_ctl_wr(&ports, val);
}
void create_window() void create_window()
{ {
uint32_t mask; uint32_t mask;
@ -99,6 +126,7 @@ void draw_pixels()
int innerh = psize * VDP_SCREENH; int innerh = psize * VDP_SCREENH;
int innerx = (geom->width - innerw) / 2; int innerx = (geom->width - innerw) / 2;
int innery = (geom->height - innerh) / 2; int innery = (geom->height - innerh) / 2;
free(geom);
int drawcnt = 0; int drawcnt = 0;
for (int i=0; i<VDP_SCREENW; i++) { for (int i=0; i<VDP_SCREENW; i++) {
for (int j=0; j<VDP_SCREENH; j++) { for (int j=0; j<VDP_SCREENH; j++) {
@ -131,6 +159,15 @@ void event_loop()
emul_steps(100); emul_steps(100);
draw_pixels(); draw_pixels();
} }
// A low tech way of checking when the window was closed. The proper way
// involving WM_DELETE is too complicated.
xcb_get_geometry_reply_t *geom;
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
if (geom == NULL) {
return; // window has been closed.
} else {
free(geom);
}
xcb_generic_event_t *e = xcb_poll_for_event(conn); xcb_generic_event_t *e = xcb_poll_for_event(conn);
if (!e) { if (!e) {
continue; continue;
@ -140,7 +177,34 @@ void event_loop()
case XCB_KEY_RELEASE: case XCB_KEY_RELEASE:
case XCB_KEY_PRESS: { case XCB_KEY_PRESS: {
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e; 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; break;
} }
case XCB_EXPOSE: { case XCB_EXPOSE: {
@ -180,10 +244,16 @@ int main(int argc, char *argv[])
} }
vdp_init(&vdp); vdp_init(&vdp);
vdp_changed = false; 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_CMD_PORT] = iord_vdp_cmd;
m->iord[VDP_DATA_PORT] = iord_vdp_data; 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_CMD_PORT] = iowr_vdp_cmd;
m->iowr[VDP_DATA_PORT] = iowr_vdp_data; m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
conn = xcb_connect(NULL, NULL); conn = xcb_connect(NULL, NULL);
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
create_window(); create_window();

View File

@ -199,6 +199,7 @@ void draw_pixels()
int innerh = psize * 64; int innerh = psize * 64;
int innerx = (geom->width - innerw) / 2; int innerx = (geom->width - innerw) / 2;
int innery = (geom->height - innerh) / 2; int innery = (geom->height - innerh) / 2;
free(geom);
int drawcnt = 0; int drawcnt = 0;
for (int i=0; i<96; i++) { for (int i=0; i<96; i++) {
for (int j=0; j<64; j++) { for (int j=0; j<64; j++) {
@ -231,6 +232,15 @@ void event_loop()
emul_steps(100); emul_steps(100);
draw_pixels(); draw_pixels();
} }
// A low tech way of checking when the window was closed. The proper way
// involving WM_DELETE is too complicated.
xcb_get_geometry_reply_t *geom;
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
if (geom == NULL) {
return; // window has been closed.
} else {
free(geom);
}
xcb_generic_event_t *e = xcb_poll_for_event(conn); xcb_generic_event_t *e = xcb_poll_for_event(conn);
if (!e) { if (!e) {
continue; continue;