diff --git a/emul/z80/Makefile b/emul/z80/Makefile index 070bc58..d686167 100644 --- a/emul/z80/Makefile +++ b/emul/z80/Makefile @@ -1,7 +1,7 @@ TARGETS = forth rc2014 sms ti84 OBJS = emul.o z80.o RC2014_OBJS = $(OBJS) sio.o acia.o sdc.o rc2014_spi.o -SMS_OBJS = $(OBJS) sms_vdp.o sms_ports.o sms_pad.o ps2_kbd.o sdc.o +SMS_OBJS = $(OBJS) sms_vdp.o sms_ports.o sms_pad.o ps2_kbd.o sdc.o sms_spi.o TI84_OBJS = $(OBJS) t6a04.o ti84_kbd.o CDIR = ../../cvm STAGE = $(CDIR)/stage diff --git a/emul/z80/sms.c b/emul/z80/sms.c index c118b83..23b31d6 100644 --- a/emul/z80/sms.c +++ b/emul/z80/sms.c @@ -11,6 +11,7 @@ #include "sms_vdp.h" #include "sms_ports.h" #include "sms_pad.h" +#include "sms_spi.h" #include "ps2_kbd.h" #include "sdc.h" @@ -43,6 +44,7 @@ static Pad pad; static Kbd kbd; static bool use_kbd = false; static SDC sdc; +static SPI spi; static uint8_t iord_vdp_cmd() { @@ -90,28 +92,12 @@ static void iowr_ports_ctl(uint8_t val) ports_ctl_wr(&ports, val); } -// TODO: re-add as controller-based SPI -/* static uint8_t iord_sdc_spi() +static byte iord_spi() { - return sdc_spi_rd(&sdc); + return spi_rd(&spi); } -static void iowr_sdc_spi(uint8_t val) -{ - sdc_spi_wr(&sdc, val); -} - -// in emulation, exchanges are always instantaneous, so we -// always report as ready. -static uint8_t iord_sdc_ctl() -{ - return 0; -} - -static void iowr_sdc_ctl(uint8_t val) -{ - sdc_ctl_wr(&sdc, val); -}*/ +static byte spix_sdc(byte val) { return sdc_spix(&sdc, val); } void create_window() { @@ -246,15 +232,25 @@ static bool _handle_keypress(xcb_generic_event_t *e) void event_loop() { while (1) { - if (!emul_step()) { - fprintf(stderr, "CPU halted, quitting\n"); - usleep(1000 * 1000); - break; + for (int i=0; i<100; i++) { + if (!emul_step()) { + fprintf(stderr, "CPU halted, quitting\n"); + usleep(1000 * 1000); + break; + } + spi_pulse(&spi); } if (vdp_changed) { // To avoid overdrawing, we'll let the CPU run a bit to finish its // drawing operation. - emul_steps(10000); + for (int i=0; i<10000; i++) { + if (!emul_step()) { + fprintf(stderr, "CPU halted, quitting\n"); + usleep(1000 * 1000); + break; + } + spi_pulse(&spi); + } draw_pixels(); } // A low tech way of checking when the window was closed. The proper way @@ -293,6 +289,12 @@ static void usage() fprintf(stderr, "Usage: ./sms [-k] [-c sdcard.img] /path/to/rom\n"); } +static byte spi_dbg(byte val) +{ + fprintf(stderr, "SPI XCH: %x\n", val); + return val+1; +} + int main(int argc, char *argv[]) { if (argc < 2) { @@ -305,6 +307,7 @@ int main(int argc, char *argv[]) pad_init(&pad, &ports.THA); kbd_init(&kbd, &ports.THA); sdc_init(&sdc); + spi_init(&spi, &ports.THB, &ports.TRB, spix_sdc); int ch; while ((ch = getopt(argc, argv, "kc:")) != -1) { @@ -348,6 +351,7 @@ int main(int argc, char *argv[]) } else { ports.portA_rd = iord_pad; } + ports.portB_rd = iord_spi; m->iord[VDP_CMD_PORT] = iord_vdp_cmd; m->iord[VDP_DATA_PORT] = iord_vdp_data; @@ -357,12 +361,6 @@ int main(int argc, char *argv[]) m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd; m->iowr[VDP_DATA_PORT] = iowr_vdp_data; m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl; - /* TODO: re-add - m->iord[SDC_SPI] = iord_sdc_spi; - m->iowr[SDC_SPI] = iowr_sdc_spi; - m->iord[SDC_CTL] = iord_sdc_ctl; - m->iowr[SDC_CTL] = iowr_sdc_ctl; - */ conn = xcb_connect(NULL, NULL); screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; diff --git a/emul/z80/sms_spi.c b/emul/z80/sms_spi.c new file mode 100644 index 0000000..e488452 --- /dev/null +++ b/emul/z80/sms_spi.c @@ -0,0 +1,39 @@ +#include "sms_spi.h" + +void spi_init(SPI *spi, Tristate *TH, Tristate *TR, EXCH spixfn) +{ + spi->TH = TH; + spi->TR = TR; + spi->lastTH = *TH; + spi->bitcnt = 0; + spi->bit = false; + spi->rx = 0; + spi->tx = 0xff; + spi->spixfn = spixfn; +} + +byte spi_rd(SPI *spi) +{ + + return (byte)spi->bit; // return in bit 0 +} + +void spi_pulse(SPI *spi) +{ + if (*spi->TH != spi->lastTH) { + spi->lastTH = *spi->TH; + if (spi->lastTH == TRI_HIGH) { + spi->rx <<= 1; + if (*spi->TR == TRI_HIGH) { + spi->rx++; + } + spi->bit = spi->tx >> 7; + spi->tx <<= 1; + spi->bitcnt++; + if (spi->bitcnt == 8) { + spi->tx = spi->spixfn(spi->rx); + spi->bitcnt = 0; + } + } + } +} diff --git a/emul/z80/sms_spi.h b/emul/z80/sms_spi.h new file mode 100644 index 0000000..df19d7e --- /dev/null +++ b/emul/z80/sms_spi.h @@ -0,0 +1,30 @@ +#include "emul.h" + +/* This emulates a SPI device being connected directly on a controller port. + + TH and TR control CLK and DI, input bit 0 (BTN_UP) is DO. + + This device has to "pulse" frequently so that it checks whether there's + been a change in CLK. + + We have a bit of a challenge here with regards to an important limitation + of the SMS: only 2 pins can be set as output. Without the ability to + programatically select the SPI slave, we have to keep it selected at all + times. This makes us vulnerable to going out of sync. This is a known + limitation, the user will have to re-insert the SD card when that happens. +*/ + +typedef struct { + Tristate *TH; // CLK + Tristate *TR; // DI + Tristate lastTH; // value of TH on last pulse + byte bitcnt; // current xchange bit count + bool bit; // bit to return in spi_rd() + byte rx; + byte tx; + EXCH spixfn; +} SPI; + +void spi_init(SPI *spi, Tristate *TH, Tristate *TR, EXCH spixfn); +byte spi_rd(SPI *spi); // for sms_ports' portX_rd +void spi_pulse(SPI *spi); diff --git a/recipes/sms/blk/622 b/recipes/sms/blk/622 new file mode 100644 index 0000000..f8742ac --- /dev/null +++ b/recipes/sms/blk/622 @@ -0,0 +1,12 @@ +: (spie) DROP ; ( always enabled ) +: (spix) ( x -- x, for port B ) + 0 SWAP ( rx tx ) 8 0 DO + ( send bit 7 to bit 6, TR's output bit ) + DUP 1 RSHIFT 0x40 AND ( rx tx bits ) 0x80 OR ( CLK hi ) + 0x3f PC@ OR 0xf3 AND ( TH and TR output ) + DUP 0x3f PC! ( rx tx bits ) + ( CLK low ) 0x7f AND 0x3f PC! ( rx tx ) + ( shift tx ) 1 LSHIFT ( rx tx<< ) + ( read into rx ) SWAP 1 LSHIFT 0xdc PC@ ( tx<< rx<< x ) + ( out bit is the 6th ) 6 RSHIFT 1 AND OR + SWAP LOOP ( rx tx ) DROP ;