aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2018-10-29 21:59:09 -0700
committerH. Peter Anvin <hpa@zytor.com>2018-10-29 21:59:09 -0700
commit8ffd9584a896906871a31359dbc11d84b3acf550 (patch)
tree53cac40c277e867fb8a7a9334564765dc35e5223
parent7d8a51b69c9a49435d39494e44824133261013fd (diff)
downloadabc80sim-8ffd9584a896906871a31359dbc11d84b3acf550.tar.gz
abc80sim-8ffd9584a896906871a31359dbc11d84b3acf550.tar.xz
abc80sim-8ffd9584a896906871a31359dbc11d84b3acf550.zip
Better, asynchronous IRQ handling; move SDL handling to sep. thread
Make the IRQ system significantly more accurate with regards to how a real Z80 system works. Make the system reentrant. Move the SDL event and video handling into a separate thread.
-rw-r--r--Makefile.in2
-rw-r--r--abc80.c20
-rw-r--r--abcio.c159
-rw-r--r--abcio.h27
-rw-r--r--cas.c55
-rw-r--r--clock.c51
-rw-r--r--clock.h2
-rw-r--r--compiler.h15
-rw-r--r--rtc.c2
-rw-r--r--screen.h2
-rw-r--r--sdlscrn.c194
-rw-r--r--simprint.c7
-rw-r--r--z80.c92
-rw-r--r--z80.h33
-rw-r--r--z80irq.c132
-rw-r--r--z80irq.h31
16 files changed, 549 insertions, 275 deletions
diff --git a/Makefile.in b/Makefile.in
index 355fafb..af1feee 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -29,7 +29,7 @@ X = @EXEEXT@
.SUFFIXES: .c .h .$(O) .bin $(X) .i .s .asm
GENC = $(patsubst %.rom,%.c,$(wildcard roms/*.rom))
-SRCS = abc80.c clock.c sdlscrn.c screenshot.c z80.c abcmem.c abcio.c \
+SRCS = abc80.c clock.c sdlscrn.c screenshot.c z80.c z80irq.c abcmem.c abcio.c \
abcfont.c disk.c rtc.c simprint.c print.c fileop.c z80dis.c \
nstime.c hostfile.c console.c cas.c abcfile.c filelist.c \
clib/asprintf.c $(GENC)
diff --git a/abc80.c b/abc80.c
index 82e3cb5..ba5db4b 100644
--- a/abc80.c
+++ b/abc80.c
@@ -12,6 +12,9 @@
#include "clock.h"
#include <SDL_main.h>
+#include <SDL_thread.h>
+
+static int z80_thread(void *);
double ns_per_tstate = 1000.0/3.0; /* Nanoseconds per tstate (clock cycle) */
double tstate_per_ns = 3.0/1000.0; /* Inverse of the above = freq in GHz */
@@ -321,6 +324,7 @@ int main(int argc, char **argv)
bool color = true;
bool console = false;
bool faketype_set = false;
+ SDL_Thread *cpu_thread;
attach_console();
@@ -525,11 +529,23 @@ int main(int argc, char **argv)
/*
* Off we go...
*/
+ cpu_thread = SDL_CreateThread(z80_thread, NULL);
+ event_loop(); /* Handling external events and screen */
+ z80_quit = true;
+ SDL_WaitThread(cpu_thread, NULL);
+
+ screen_reset();
+ exit(0);
+}
+
+int z80_thread(void *data)
+{
+ (void)data;
+
z80_reset();
timer_init();
z80_run(true, false);
- screen_reset();
- exit(0);
+ return 0;
}
diff --git a/abcio.c b/abcio.c
index 0c46428..21ee564 100644
--- a/abcio.c
+++ b/abcio.c
@@ -1,25 +1,36 @@
#include "compiler.h"
#include "z80.h"
+#include "z80irq.h"
#include "screen.h"
#include "abcio.h"
#include "clock.h"
#include "trace.h"
-#define READ_MODE 0
-#define WRITE_MODE 1
-
/* Select code for ABC/4680 bus */
static int8_t abcbus_select = -1;
/* Keyboard IRQ vector */
-static uint8_t keyb_irq = 0xff; /* = no IRQ vector set */
-static uint8_t keyb_data;
-static bool keyb_new, keyb_down;
+static volatile unsigned int keyb_data;
+static uint8_t keyb_fakedata;
+
+/* These constants are designed to make dart_keyb_in() as simple as possible */
+#define KEYB_NEW 0x100
+#define KEYB_DOWN 0x800
/* Fake minimal-touch input */
bool faketype;
+static int keyb_intack_fake(unsigned int prio, struct z80_irq *irq);
+static struct z80_irq *keyb_irq;
+
+static struct z80_irq keyb_irq_80 =
+ { NULL, NULL, NULL, -1, IRQ80_PIOA };
+static struct z80_irq keyb_irq_fake =
+ { keyb_intack_fake, NULL, NULL, -1, IRQ80_PIOA };
+static struct z80_irq keyb_irq_800 =
+ { NULL, NULL, NULL, -1, IRQ800_DARTA };
+
static inline uint8_t abc800_mangle_port(uint8_t port)
{
if ((port & 0xe0) == 0x00)
@@ -90,7 +101,7 @@ static void abc80_out(uint8_t port, uint8_t value)
case (57 & 0x17): /* Keyboard control port */
if (!(value & 1)) {
- keyb_irq = value;
+ keyb_irq->vector = value;
}
break;
@@ -135,7 +146,7 @@ static void dart_keyb_out(uint8_t port, uint8_t value)
vsync = false;
break;
case 3:
- keyb_irq = -1;
+ keyb_irq->vector = -1;
memset(dart_keyb_ctl, 0, sizeof dart_keyb_ctl);
return;
case 4:
@@ -153,49 +164,69 @@ static void dart_keyb_out(uint8_t port, uint8_t value)
}
if ((dart_keyb_ctl[1] & 0x18) == 0) {
- keyb_irq = 0;
+ keyb_irq->vector = 0;
} else {
if (dart_keyb_ctl[1] & 0x04) {
/* Status affects vector */
- keyb_irq = (dart_keyb_ctl[2] & ~0x0f) | 0x04;
+ keyb_irq->vector = (dart_keyb_ctl[2] & ~0x0f) | 0x04;
} else {
- keyb_irq = (dart_keyb_ctl[1] & ~0x01);
+ keyb_irq->vector = (dart_keyb_ctl[1] & ~0x01);
}
}
}
-static uint8_t dart_keyb_in(uint8_t port)
+/* Get the keyboard data, clearing the KEYB_NEW flag */
+static unsigned int get_key(void)
{
- uint8_t v, reg;
+ unsigned int rv, kbd;
- if ((port & 1) == 0) {
- /* Data register */
- keyb_new = false;
- v = keyb_data;
- return keyb_data;
- }
+ rv = kbd = keyb_data;
+ cmpxchg(&keyb_data, &kbd, kbd & ~KEYB_NEW);
- /* Control register */
+ return rv;
+}
- reg = dart_keyb_ctl[0] & 7;
- dart_keyb_ctl[0] &= ~7; /* Restore register 0 */
+static int keyb_intack_fake(unsigned int prio, struct z80_irq *irq)
+{
+ unsigned int data = get_key();
- switch (reg) {
- case 0:
- v = ((keyb_new) << 0) +
- (1 << 2) + /* Transmit buffer empty */
- (keyb_down << 3) + /* DCD -> key down */
- (dart_keyb_vsync << 4) + /* RI -> vsync */
- (1 << 5); /* CTS -> 60 Hz */
- break;
- case 1:
- v = (1 << 0); /* All sent */
- break;
- case 2:
- v = dart_keyb_ctl[2];
+ (void)prio;
+
+ keyb_fakedata = (data & 0x7f) | ((data & KEYB_NEW) ? 0x80 : 0x00);
+
+ return irq->vector;
+}
+
+static uint8_t dart_keyb_in(uint8_t port)
+{
+ uint8_t v, reg;
+
+ switch (port & 1) {
+ case 0: /* Data register */
+ v = get_key();
break;
- default:
- v = 0;
+
+ case 1: /* Control register */
+ reg = dart_keyb_ctl[0] & 7;
+ dart_keyb_ctl[0] &= ~7; /* Restore register 0 */
+
+ switch (reg) {
+ case 0:
+ v = (keyb_data >> 8) +
+ (1 << 2) + /* Transmit buffer empty */
+ (dart_keyb_vsync << 4) + /* RI -> vsync */
+ (1 << 5); /* CTS -> 60 Hz */
+ break;
+ case 1:
+ v = (1 << 0); /* All sent */
+ break;
+ case 2:
+ v = dart_keyb_ctl[2];
+ break;
+ default:
+ v = 0;
+ break;
+ }
break;
}
@@ -324,9 +355,13 @@ static uint8_t abc80_in(uint8_t port)
break;
case (56 & 0x17):
- v = keyb_data;
- if (faketype)
- keyb_data &= ~0x80; /* Hack to avoid insanely fast repeat */
+ if (faketype) {
+ v = keyb_fakedata;
+ keyb_fakedata &= ~0x80;
+ } else {
+ unsigned int kbd = keyb_data;
+ v = (kbd & 0x7f) | ((kbd & KEYB_DOWN) ? 0x80 : 0);
+ }
break;
case (58 & 0x17):
@@ -410,39 +445,26 @@ int z80_in(int port)
return v;
}
+/* This is called in the event handler thread context! */
void keyboard_down(int sym)
{
- keyb_down = true;
-
- switch (model) {
- case MODEL_ABC80:
- if (sym <= 127) {
- keyb_data = sym | 0x80;
- z80_interrupt(keyb_irq);
- }
- break;
-
- case MODEL_ABC802:
- keyb_data = sym;
- keyb_new = true;
- z80_interrupt(keyb_irq);
- break;
+ if (model == MODEL_ABC80) {
+ if (sym & ~127)
+ return;
}
+
+ keyb_data = sym | KEYB_NEW | KEYB_DOWN;
+ z80_interrupt(keyb_irq->prio);
}
-void keyboard_up(void)
+unsigned int keyboard_up(void)
{
- keyb_down = false;
+ unsigned int rv, kbd;
- switch (model) {
- case MODEL_ABC80:
- keyb_data &= ~0x80;
- break;
+ rv = kbd = keyb_data;
+ cmpxchg(&keyb_data, &kbd, kbd & ~KEYB_DOWN);
- case MODEL_ABC802:
- /* Do nothing? */
- break;
- }
+ return rv;
}
void io_init(void)
@@ -451,10 +473,17 @@ void io_init(void)
case MODEL_ABC80:
do_out = abc80_out;
do_in = abc80_in;
+ keyb_data = 0;
+ z80_register_irq(keyb_irq = faketype ? &keyb_irq_fake : &keyb_irq_80);
+ abc80_cas_init();
break;
case MODEL_ABC802:
do_out = abc802_out;
do_in = abc802_in;
keyb_data = 0xff;
+ z80_register_irq(keyb_irq = &keyb_irq_800);
+ abc800_cas_init();
+ abc800_ctc_init();
+ break;
}
}
diff --git a/abcio.h b/abcio.h
index 787c420..4b7b1ef 100644
--- a/abcio.h
+++ b/abcio.h
@@ -33,6 +33,7 @@ extern void abc806_rtc_out(uint8_t port, uint8_t value);
extern void abc800_ctc_out(uint8_t, uint8_t);
extern uint8_t abc800_ctc_in(uint8_t);
+extern void abc800_ctc_init(void);
extern void printer_reset(void);
extern void printer_out(int sel, int port, int value);
@@ -41,7 +42,7 @@ extern void dart_pr_out(uint8_t port, uint8_t v);
extern uint8_t dart_pr_in(uint8_t port);
extern void keyboard_down(int sym);
-extern void keyboard_up(void);
+extern unsigned int keyboard_up(void);
extern bool faketype;
extern void abc802_vsync(void);
@@ -50,9 +51,33 @@ extern void dump_memory(bool ramonly);
extern void abc80_piob_out(uint8_t port, uint8_t v);
extern uint8_t abc80_piob_in(void);
+extern void abc80_cas_init(void);
extern void abc800_sio_cas_out(uint8_t port, uint8_t v);
extern uint8_t abc800_sio_cas_in(uint8_t port);
+extern void abc800_cas_init(void);
+
+
+/*
+ * Z80 has fixed priorities based on the device daisy chain, but the vectors
+ * can be different, so we identify interrupts by their priority level.
+ *
+ * XXX: This ordering is almost certainly completely wrong.
+ */
+enum abc80_irq {
+ IRQ80_PIOA,
+ IRQ80_PIOB
+};
+enum abc800_irq {
+ IRQ800_DARTA,
+ IRQ800_DARTB,
+ IRQ800_SIOA,
+ IRQ800_SIOB,
+ IRQ800_CTC0,
+ IRQ800_CTC1,
+ IRQ800_CTC2,
+ IRQ800_CTC3
+};
/* Directory and filenames */
extern const char *fileop_path, *disk_path, *screen_path, *memdump_path;
diff --git a/cas.c b/cas.c
index b218983..38d3ca2 100644
--- a/cas.c
+++ b/cas.c
@@ -5,6 +5,7 @@
#include "hostfile.h"
#include "abcio.h"
#include "z80.h"
+#include "z80irq.h"
#include "abcfile.h"
#include "trace.h"
@@ -169,6 +170,7 @@ static bool cas_edge(void)
/*
* ABC80 PIO interfacing
*/
+static inline int pio_eoi(unsigned int, struct z80_irq *);
enum pioctl_state {
pcs_init,
@@ -179,19 +181,23 @@ enum pioctl_state {
struct pio {
uint8_t out, in, mask;
uint8_t mode;
- uint8_t irq, irqmask, irqctl, irqprev;
+ uint8_t irqmask, irqctl, irqprev;
enum pioctl_state ctlstate;
+ struct z80_irq irq;
+};
+static struct pio portb = {
+ .in = 0xff,
+ .irq.eoi = pio_eoi,
+ .irq.pvt = &portb,
+ .irq.vector = -1,
+ .irq.prio = IRQ80_PIOB
};
-
-static struct pio portb = { .in = 0xff };
static inline uint8_t pio_readval(const struct pio *pio)
{
return (pio->out & pio->mask) | (pio->in & ~pio->mask);
}
-static void pio_eoi(uint8_t vector, void *arg);
-
static void pio_check_interrupt(struct pio *pio)
{
uint8_t val = pio_readval(pio);
@@ -207,15 +213,16 @@ static void pio_check_interrupt(struct pio *pio)
(pio->irqctl & 0x40) ? (masked == pio->irqmask) : (masked != 0);
if (trigger)
- z80_interrupt_eoi(pio->irq, pio_eoi, (void *)pio);
+ z80_interrupt(pio->irq.prio);
else
- z80_clear_interrupt(pio->irq);
+ z80_clear_interrupt(pio->irq.prio);
}
-static void pio_eoi(uint8_t vector, void *arg)
+static int pio_eoi(unsigned int prio, struct z80_irq *irq)
{
- (void)vector;
- pio_check_interrupt((struct pio *)arg);
+ (void)prio;
+ pio_check_interrupt((struct pio *)(irq->pvt));
+ return 0;
}
static void pio_control(struct pio *pio, uint8_t v)
@@ -248,7 +255,7 @@ static void pio_control(struct pio *pio, uint8_t v)
break;
default:
if ((v & 1) == 0)
- pio->irq = v;
+ pio->irq.vector = v;
break;
}
break;
@@ -307,6 +314,11 @@ uint8_t abc80_piob_in(void)
return pio_readval(&portb);
}
+void abc80_cas_init(void)
+{
+ z80_register_irq(&portb.irq);
+}
+
/*
* ABC800 SIO/2 cassette interface
*
@@ -317,9 +329,14 @@ uint8_t abc80_piob_in(void)
* - At end of block either hardware or software go back to need sync
*/
+static int sio_cas_eoi(unsigned int prio, struct z80_irq *irq);
+
static uint8_t sio_cas_ctl[8];
static bool cas_first_rx_armed = true;
+static struct z80_irq sio_cas_irq =
+{ NULL, sio_cas_eoi, NULL, -1, IRQ800_SIOB };
+
static inline bool cas_have_sync(void)
{
return !cas_idle() && (sio_cas_ctl[3] && 1);
@@ -337,12 +354,13 @@ static inline bool cas_rx_interrupt(bool huntok)
static void cas_poll_interrupt(void);
-static void sio_cas_eoi(uint8_t vector, void *dummy)
+static int sio_cas_eoi(unsigned int prio, struct z80_irq *irq)
{
- (void)vector;
- (void)dummy;
+ (void)prio;
+ (void)irq;
cas_poll_interrupt();
+ return 0;
}
static void cas_poll_interrupt(void)
@@ -353,8 +371,8 @@ static void cas_poll_interrupt(void)
/* Actually signal a receive data interrupt */
sio_cas_ctl[3] &= ~0x10; /* Not hunting anymore */
cas_first_rx_armed = false;
- z80_interrupt_eoi((sio_cas_ctl[2] & ~0x0f) | 0x04,
- sio_cas_eoi, NULL);
+ sio_cas_irq.vector = (sio_cas_ctl[2] & ~0x0f) | 0x04;
+ z80_interrupt(sio_cas_irq.prio);
}
void abc800_sio_cas_out(uint8_t port, uint8_t v)
@@ -480,3 +498,8 @@ uint8_t abc800_sio_cas_in(uint8_t port)
cas_poll_interrupt();
return v;
}
+
+void abc800_cas_init(void)
+{
+ z80_register_irq(&sio_cas_irq);
+}
diff --git a/clock.c b/clock.c
index 80defd0..14eaa0e 100644
--- a/clock.c
+++ b/clock.c
@@ -1,4 +1,6 @@
+#include "compiler.h"
#include "z80.h"
+#include "z80irq.h"
#include "screen.h"
#include "abcio.h"
#include "clock.h"
@@ -97,8 +99,8 @@ static void consider_napping(uint64_t now, uint64_t next)
behind = now - when;
ahead = when - next;
- /* Sanity range check: 250 ms behind or 100 ms ahead of schedule */
- if (unlikely(behind >= MS(250) || ahead >= MS(100)))
+ /* Sanity range check: 100 ms behind or 100 ms ahead of schedule */
+ if (unlikely(behind >= MS(200) || ahead >= MS(100)))
goto weird;
/* If we are ahead of the next event, hold off and wait for it */
@@ -120,7 +122,9 @@ static void consider_napping(uint64_t now, uint64_t next)
#define CHECK_FREQUENCY 64
/* Poll for timers - these the only external event we look for */
-void z80_poll_external(void)
+volatile bool z80_quit;
+
+bool z80_poll_external(void)
{
uint64_t now;
static uint64_t next = 0;
@@ -128,8 +132,11 @@ void z80_poll_external(void)
bool sleepy = limit_speed;
static uint64_t next_check_tstate;
+ if (z80_quit)
+ return true; /* Terminate CPU loop */
+
if (likely(TSTATE < next_check_tstate))
- return;
+ return false;
next_check_tstate = TSTATE + poll_tstate_period;
@@ -162,6 +169,8 @@ void z80_poll_external(void)
if (sleepy)
consider_napping(now, next);
+
+ return false;
}
/*
@@ -173,18 +182,22 @@ static void abc80_clock_tick(void)
z80_nmi();
}
-static uint8_t ctc_ctl[4], ctc_div[4], ctc_vector;
-
/*
* ABC800: Clock interrupt through the CTC
*/
-
-static uint8_t ctc_ctl[4], ctc_div[4], ctc_vector;
+static uint8_t ctc_ctl[4], ctc_div[4];
+static struct z80_irq ctc_irq[4] =
+{
+ { NULL, NULL, NULL, -1, IRQ800_CTC0 },
+ { NULL, NULL, NULL, -1, IRQ800_CTC1 },
+ { NULL, NULL, NULL, -1, IRQ800_CTC2 },
+ { NULL, NULL, NULL, -1, IRQ800_CTC3 }
+};
static void abc800_clock_tick(void)
{
- if ((ctc_ctl[3] & 0xc0) == 0x80)
- z80_interrupt(ctc_vector | (3 << 1)); /* 3 = channel */
+ if ((ctc_ctl[3] & 0xc0) == 0x80)
+ z80_interrupt(IRQ800_CTC3);
}
/*
@@ -192,10 +205,13 @@ static void abc800_clock_tick(void)
*/
void abc800_ctc_out(uint8_t port, uint8_t v)
{
- if ((v & 1) == 0) {
- ctc_vector = v;
- return;
- }
+ if ((v & 1) == 0) {
+ int i;
+ v &= ~7;
+ for (i = 0; i <= 3; i++)
+ ctc_irq[i].vector = v | (i << 1);
+ return;
+ }
port &= 3; /* Get channel */
@@ -235,3 +251,10 @@ uint8_t abc800_ctc_in(uint8_t port)
return v;
}
+
+void abc800_ctc_init(void)
+{
+ int i;
+ for (i = 0; i < 4; i++)
+ z80_register_irq(&ctc_irq[i]);
+}
diff --git a/clock.h b/clock.h
index b143c6f..31812dc 100644
--- a/clock.h
+++ b/clock.h
@@ -9,4 +9,6 @@ extern double ns_per_tstate;
extern double tstate_per_ns;
extern bool limit_speed;
+extern volatile bool z80_quit;
+
#endif /* CLOCK_H */
diff --git a/compiler.h b/compiler.h
index 9507a5d..5367fdc 100644
--- a/compiler.h
+++ b/compiler.h
@@ -186,4 +186,19 @@ typedef int mode_t;
# define is_constant(x) false
#endif
+/* Simple atomic operations */
+#ifdef __GNUC__
+# define atomic_load(p) __atomic_load_n((p), __ATOMIC_ACQUIRE)
+# define atomic_store(p,v) __atomic_store_n((p), (v), __ATOMIC_RELEASE)
+# define cmpxchg(p, e, d) \
+ likely(__atomic_compare_exchange_n((p), (e), (d), false, \
+ __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE))
+# define xchg(p, v) \
+ __atomic_exchange_n((p), (v), __ATOMIC_ACQ_REL)
+#define barrier() __atomic_thread_fence(__ATOMIC_ACQ_REL)
+
+#else
+/* ? */
+#endif
+
#endif /* COMPILER_H */
diff --git a/rtc.c b/rtc.c
index a8d5f9c..d3e753a 100644
--- a/rtc.c
+++ b/rtc.c
@@ -11,7 +11,7 @@ static int ptr;
* ASCII string - will be converted to BCD
* __ is a placeholder for the command phase
*/
-static uint8_t e05time[9*2+1]; /* __ HH MM dd mm YY ?? SS ?? */
+static uint8_t e05time[17*2]; /* __ HH MM dd mm YY ?? SS ?? */
static unsigned int e05bit;
static uint8_t e05cmd;
diff --git a/screen.h b/screen.h
index 357dfe8..6c4b445 100644
--- a/screen.h
+++ b/screen.h
@@ -11,7 +11,7 @@ extern void screen_write(int, int);
extern void screen_flush(void);
extern void setmode40(bool);
-extern void get_event(void);
+extern void event_loop(void);
extern void key_check(void);
extern volatile int event_pending;
diff --git a/sdlscrn.c b/sdlscrn.c
index fda8d20..a7607bd 100644
--- a/sdlscrn.c
+++ b/sdlscrn.c
@@ -33,7 +33,7 @@
extern const unsigned char abc_font[256][FONT_YSIZE];
-static void check_event(void);
+static void trigger_refresh(bool);
#define NCOLORS 8
@@ -48,10 +48,26 @@ static struct argb { uint8_t a, r, g, b; } rgbcolors[NCOLORS] = {
{0x00,0xff,0xff,0xff}, /* white */
};
+/* Mutex for interaction with the CPU thread */
+static SDL_mutex *screen_mutex;
+
#define VRAM_SIZE 2048
#define VRAM_MASK (VRAM_SIZE-1)
+
+/* Video RAM accessed by the CPU */
unsigned char video_ram[VRAM_SIZE];
+/*
+ * Buffer for passing video RAM between the CPU and screen threads,
+ * protected by screen_mutex
+ */
+static unsigned char vram_queue[VRAM_SIZE]; /* CPU->screen video RAM copy */
+
+/*
+ * Copy of video RAM used to draw on screen (or screenshot)
+ */
+static unsigned char vram_shadow[VRAM_SIZE]; /* Screen accessed video RAM */
+
union crtc {
uint8_t regs[18];
struct {
@@ -73,7 +89,7 @@ union crtc {
uint8_t curl; /* Low half of cursor address */
} r;
};
-static union crtc crtc;
+static union crtc crtc, crtc_shadow;
static uint8_t crtc_addr;
static uint16_t startaddr, curaddr;
struct xy {
@@ -81,10 +97,6 @@ struct xy {
};
static struct xy addr_to_xy_tbl[2][2048];
-struct do_event {
- void (*func)(void);
-};
-
/* A local abstraction of a drawing surface */
struct surface {
SDL_Surface *surf; /* SDL_Surface object */
@@ -96,15 +108,15 @@ struct surface {
};
static struct surface rscreen;
-static bool blink_on = true;
-static bool mode40;
+static volatile bool blink_on = true;
+static volatile bool mode40, mode40_cpu;
/*
- * Give the x,y coordinates for a given location in video RAM
+ * Give the x,y coordinates for a given location in shadow video RAM
*/
static inline struct xy addr_to_xy(const uint8_t *p)
{
- uint16_t addr = p - video_ram;
+ uint16_t addr = p - vram_shadow;
addr = (addr - startaddr) & VRAM_MASK;
return addr_to_xy_tbl[mode40][addr];
}
@@ -140,7 +152,7 @@ static inline unsigned int screenoffs(uint8_t y, uint8_t x)
*/
static inline uint8_t screendata(uint8_t y, uint8_t x)
{
- return video_ram[screenoffs(y,x) & VRAM_MASK];
+ return vram_shadow[screenoffs(y,x) & VRAM_MASK];
}
/*
@@ -199,7 +211,7 @@ put_screen(struct surface *s, unsigned int tx, unsigned int ty, bool blink)
}
voffs = screenoffs(ty,tx);
- cc = video_ram[voffs & VRAM_MASK];
+ cc = vram_shadow[voffs & VRAM_MASK];
fontp = abc_font[(cc & 0x7f) + gmode];
invmask = (blink || model != MODEL_ABC80) ? 0x80 : 0;
invmask = (cc & invmask) ? 7 : 0;
@@ -220,9 +232,9 @@ put_screen(struct surface *s, unsigned int tx, unsigned int ty, bool blink)
curmask = 0;
if (unlikely(voffs == curaddr)) {
- if (blink | (crtc.r.curstart & 0x40)) {
- curmask = (~0U << (crtc.r.curstart & 0x1f));
- curmask &= (2U << (crtc.r.curend & 0x1f))-1;
+ if (blink | (crtc_shadow.r.curstart & 0x40)) {
+ curmask = (~0U << (crtc_shadow.r.curstart & 0x1f));
+ curmask &= (2U << (crtc_shadow.r.curend & 0x1f))-1;
}
}
@@ -288,10 +300,17 @@ static void screen_dirty(struct surface *s)
static void refresh_screen(struct surface *s, bool blink)
{
unsigned int x, y;
- unsigned int width = TS_WIDTH >> mode40;
+ unsigned int width;
screen_dirty(s);
+ SDL_mutexP(screen_mutex);
+ memcpy(vram_shadow, vram_queue, sizeof vram_shadow);
+ crtc_shadow = crtc;
+ mode40 = mode40_cpu;
+ SDL_mutexV(screen_mutex);
+
+ width = TS_WIDTH >> mode40;
lock_screen(s);
for (y = 0; y < TS_HEIGHT; y++)
@@ -336,77 +355,18 @@ void write_screen(uint8_t *p, uint8_t v)
static void do_set_mode40(bool m)
{
- mode40 = m;
-
- refresh_screen(&rscreen, blink_on);
- if (model == MODEL_ABC80)
- abc80_mem_mode40(m);
+ mode40 = m;
+ refresh_screen(&rscreen, blink_on);
}
+/* Called from the CPU thread */
void setmode40(bool m)
{
- if (m != mode40)
- do_set_mode40(m);
-}
-
-/*
- * This routine switches the blink status, then goes around the screen
- * and updates all characters which has any kind of blink. Returns the
- * previous value.
- */
-static bool set_blink(bool to_what)
-{
- struct xy xy;
- int x, y;
- int width = TS_WIDTH >> mode40;
-
- if (likely(to_what == blink_on))
- return to_what;
-
- blink_on = to_what;
-
- lock_screen(&rscreen);
-
- switch (model) {
- case MODEL_ABC80:
- for ( y = 0 ; y < TS_HEIGHT ; y++ ) {
- for ( x = 0 ; x < width ; x++ ) {
- if ( screendata(y,x) & 0x80 )
- put_screen(&rscreen, x, y, blink_on);
- }
- }
- break;
-
- case MODEL_ABC802:
- if (!(crtc.r.curstart & 0x40)) {
- xy = addr_to_xy(curaddr + video_ram);
- put_screen(&rscreen, xy.x, xy.y, blink_on);
+ if (m != mode40_cpu) {
+ mode40_cpu = m;
+ if (model == MODEL_ABC80)
+ abc80_mem_mode40(m);
}
- break;
- }
-
- unlock_screen(&rscreen);
-
- return !to_what; /* We just flipped it... */
-}
-
-/* Called from the timer that corresponds to the simulated vsync */
-void vsync_screen(void)
-{
- const int blink_rate = 400/20; /* 400 ms/20 ms = 2.5 Hz */
- static int blink_ctr;
-
- check_event(); /* Poll for an SDL event */
-
- if (!blink_ctr--) {
- set_blink(!blink_on);
- blink_ctr += blink_rate;
- }
-
- update_screen(&rscreen);
-
- if (traceflags)
- fflush(tracef); /* So we don't buffer indefinitely */
}
/*
@@ -489,6 +449,8 @@ void screen_init(bool width40, bool color)
crtc.r.vscantotal = 24;
crtc.r.vdisplay = 24;
crtc.r.curstart = 0x1f; /* No CRTC cursor */
+ crtc_shadow = crtc;
+
startaddr = curaddr = 0;
/* Initialize reverse mapping table */
@@ -504,15 +466,18 @@ void screen_init(bool width40, bool color)
}
}
+ /* Create interlock mutex */
+ screen_mutex = SDL_CreateMutex();
+
+ if (!init_surface(&rscreen))
+ return;
+
/* Enable keyboard decoding */
SDL_EnableUNICODE(1);
/* Enable keyboard repeat */
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
- if (!init_surface(&rscreen))
- return;
-
/* Forcibly set the screen width and load the appropriate BASIC */
do_set_mode40(width40);
}
@@ -526,12 +491,9 @@ void screen_reset(void)
}
/*
- * Handle events. This is called from vsync_screen(), because
- * SDL_PollEvent() might be expensive on some platforms.
+ * Event-handling loop; main loop of the event/screen thread.
*/
-int keyboard_code; /* Keyboard code exported to PIO/DART */
-
-static void check_event(void)
+void event_loop(void)
{
SDL_Event event;
static int keyboard_scan = -1; /* No key currently down */
@@ -541,7 +503,7 @@ static void check_event(void)
KSH_ALT = 4
} kshift;
- while ( SDL_PollEvent(&event) ) {
+ while ( SDL_WaitEvent(&event) ) {
switch ( event.type ) {
case SDL_KEYDOWN:
kshift = \
@@ -555,7 +517,7 @@ static void check_event(void)
switch (event.key.keysym.sym) {
case SDLK_END:
case SDLK_q:
- exit(0);
+ return; /* Return to main() and exit simulator */
case SDLK_s:
abc_screenshot();
@@ -712,15 +674,55 @@ static void check_event(void)
if ( event.key.keysym.scancode == keyboard_scan )
keyboard_up();
break;
+ case SDL_USEREVENT:
+ /* Time to update the screen */
+ refresh_screen(&rscreen, blink_on);
+ break;
case SDL_QUIT:
- exit(1);
- break;
+ return; /* Return to main(), terminate */
default:
break;
}
}
}
+/*
+ * Called from the timer that corresponds to the simulated vsync
+ * in the CPU thread context
+ */
+void vsync_screen(void)
+{
+ const int blink_rate = 400/20; /* 400 ms/20 ms = 2.5 Hz */
+ static int blink_ctr;
+ bool blink;
+
+ blink = !blink_ctr--;
+ if (blink)
+ blink_ctr = blink_rate;
+
+ trigger_refresh(blink);
+
+ if (traceflags)
+ fflush(tracef); /* So we don't buffer indefinitely */
+}
+
+/* Used from the CPU thread context to cause a screen redraw */
+static void trigger_refresh(bool toggle_blink)
+{
+ SDL_Event trigger_redraw;
+
+ memset(&trigger_redraw, 0, sizeof trigger_redraw);
+ trigger_redraw.type = SDL_USEREVENT;
+
+ SDL_mutexP(screen_mutex);
+ memcpy(vram_queue, video_ram, sizeof vram_queue);
+ if (toggle_blink)
+ blink_on = !blink_on;
+ SDL_mutexV(screen_mutex);
+ SDL_PushEvent(&trigger_redraw);
+}
+
+/* Called in the CPU thread context */
void crtc_out(uint8_t port, uint8_t data)
{
if (!(port & 1)) {
@@ -731,12 +733,14 @@ void crtc_out(uint8_t port, uint8_t data)
if (crtc_addr >= sizeof crtc.regs)
return;
+ SDL_mutexP(screen_mutex);
+
crtc.regs[crtc_addr] = data;
startaddr = ((crtc.r.starth & 0x3f) << 8) + crtc.r.startl;
curaddr = ((crtc.r.curh & 0x3f) << 8) + crtc.r.curl;
- refresh_screen(&rscreen, blink_on);
+ SDL_mutexV(screen_mutex);
}
uint8_t crtc_in(uint8_t port)
diff --git a/simprint.c b/simprint.c
index bf1299b..b8714e3 100644
--- a/simprint.c
+++ b/simprint.c
@@ -1,5 +1,6 @@
#include "abcprintd.h"
#include "abcio.h"
+#include "z80irq.h"
#define BUF_SIZE 512
@@ -40,6 +41,8 @@ static bool abcprint_poll(void)
return output_head != output_tail;
}
+static struct z80_irq dart_pr_irq;
+
void printer_reset(void)
{
static bool init = false;
@@ -47,6 +50,8 @@ void printer_reset(void)
if (!init) {
init = true;
abcprint_init();
+ if (model != MODEL_ABC80)
+ z80_register_irq(&dart_pr_irq);
}
}
@@ -96,6 +101,8 @@ int printer_in(int sel, int port)
/* Hardware-like interface via the ABC800 PR: port */
static uint8_t dart_pr_ctl[8];
+static struct z80_irq dart_pr_irq = { NULL, NULL, NULL, -1, IRQ800_DARTB };
+
void dart_pr_out(uint8_t port, uint8_t v)
{
uint8_t r;
diff --git a/z80.c b/z80.c
index 3b31cb4..2073811 100644
--- a/z80.c
+++ b/z80.c
@@ -26,9 +26,7 @@
* please do send a report.
*/
#include "z80.h"
-#include "screen.h"
-
-#include <setjmp.h>
+#include "z80irq.h"
/*
* The state of our Z-80 registers is kept in this structure:
@@ -1219,6 +1217,11 @@ static void do_im2(void)
static void do_nmi(void)
{
+ bool nminterrupt = xchg(&z80_state.nminterrupt, false);
+
+ if (!nminterrupt)
+ return;
+
/* handle a non-maskable interrupt */
if (tracing(TRACE_IO|TRACE_CPU)) {
fprintf(tracef, "[%12"PRIu64"] NMI: PC=%04x\n", TSTATE, REG_PC);
@@ -1229,7 +1232,6 @@ static void do_nmi(void)
z80_state.iff2 = z80_state.iff1;
z80_state.iff1 = false;
z80_state.nmi_in_progress = true;
- z80_state.nminterrupt = false;
REG_PC = 0x66;
inc_r();
TSTATE += 11;
@@ -1240,6 +1242,11 @@ do_int(void)
{
uint16_t old_pc = REG_PC;
uint64_t when = TSTATE;
+ int i_vector;
+
+ i_vector = z80_intack();
+ if (i_vector < 0)
+ return;
switch (z80_state.interrupt_mode) {
case 0:
@@ -1247,7 +1254,7 @@ do_int(void)
do_di();
REG_SP -= 2;
mem_write_word(REG_SP, REG_PC);
- REG_PC = z80_state.i_vector & 0x38;
+ REG_PC = i_vector & 0x38;
TSTATE += 11;
break;
@@ -1263,7 +1270,7 @@ do_int(void)
do_di();
REG_SP -= 2;
mem_write_word(REG_SP, REG_PC);
- REG_PC = mem_read_word((z80_state.i << 8) | (z80_state.i_vector & ~1));
+ REG_PC = mem_read_word((z80_state.i << 8) | (i_vector & ~1));
TSTATE += 19;
break;
@@ -1271,36 +1278,17 @@ do_int(void)
break;
}
- z80_state.interrupt = false;
z80_state.iff1 = false;
- z80_state.int_in_progress = z80_state.i_vector & ~1;
if (tracing(TRACE_CPU|TRACE_IO)) {
fprintf(tracef, "[%12"PRIu64"] INT: "
"vector 0x%02x (%3d) I=%02x PC=%04x -> %04x\n",
- when,
- z80_state.i_vector, z80_state.i_vector,
- z80_state.i, old_pc, REG_PC);
+ when, i_vector, i_vector, z80_state.i, old_pc, REG_PC);
}
inc_r();
}
-
-void z80_interrupt_eoi(uint8_t vector, eoifunc do_eoi, void *eoi_arg)
-{
- if (!(vector & 1) && z80_state.int_in_progress != vector) {
- if (tracing(TRACE_CPU|TRACE_IO)) {
- fprintf(tracef, "IRQ: interrupt pending, vector 0x%02x (%3u)\n",
- vector, vector);
- }
- z80_state.interrupt = true;
- z80_state.i_vector = vector;
- z80_state.eoi.func = do_eoi;
- z80_state.eoi.arg = eoi_arg;
- }
-}
-
static uint16_t get_hl_addr(wordregister *ix)
{
if (ix == &z80_state.hl) {
@@ -2436,8 +2424,7 @@ static void do_ED_instruction(wordregister *ix)
REG_PC = mem_read_word(REG_SP);
REG_SP += 2;
z80_state.iff1 = z80_state.iff2;
- z80_state.eoi.trigger = z80_state.int_in_progress;
- z80_state.int_in_progress = -1;
+ z80_state.signal_eoi = true; /* Send EOI before next instruction */
}
break;
@@ -2478,6 +2465,16 @@ static void do_ED_instruction(wordregister *ix)
}
}
+
+static inline void check_eoi(void)
+{
+ if (!likely(z80_state.signal_eoi))
+ return;
+
+ z80_state.signal_eoi = false;
+ z80_eoi();
+}
+
int z80_run(bool continuous, bool halted)
{
uint8_t instruction;
@@ -2486,42 +2483,33 @@ int z80_run(bool continuous, bool halted)
/* loop to do a z80 instruction */
do {
- if (tracing(TRACE_CPU)) {
+ if (tracing(TRACE_CPU)) {
diffstate();
tracemem();
fputc('\n', tracef);
}
- if (z80_state.eoi.trigger >= 0) {
- struct eoi eoi = z80_state.eoi;
-
- /* We need to set these back *before* calling eoi.func */
- z80_state.eoi.func = NULL;
- z80_state.eoi.trigger = -1;
-
- if (tracing(TRACE_CPU|TRACE_IO)) {
- fprintf(tracef, "EOI: vector 0x%02x (%3u) PC=%04x\n",
- eoi.trigger, eoi.trigger, REG_PC);
- }
- if (eoi.func)
- eoi.func(eoi.trigger, eoi.arg);
- }
+ check_eoi();
for (;;) {
/* Poll for external event */
- z80_poll_external();
+ if (z80_poll_external())
+ return halted;
/* Check for an interrupt */
if (z80_state.nminterrupt && !z80_state.nmi_in_progress) {
- halted = false;
- do_nmi();
- } else if (z80_state.interrupt && z80_state.iff1 &&
- !z80_state.ei_shadow) {
- halted = false;
- do_int();
+ halted = false;
+ do_nmi();
+ } else if (z80_state.iff1 && !z80_state.ei_shadow &&
+ poll_irq()) {
+ halted = false;
+ do_int();
}
z80_state.ei_shadow = false;
if (!halted)
break;
TSTATE += 4;
+
+ if (!continuous)
+ return halted;
}
if (tracing(TRACE_CPU)) {
@@ -3686,9 +3674,7 @@ z80_reset(void)
z80_state.ei_shadow = false;
z80_state.interrupt_mode = 0;
z80_state.nmi_in_progress = false;
- z80_state.interrupt = false;
- z80_state.int_in_progress = -1;
- z80_state.eoi.trigger = -1;
+ z80_state.signal_eoi = false;
/* z80_state.r = 0; */
}
diff --git a/z80.h b/z80.h
index 71af95e..7024a1a 100644
--- a/z80.h
+++ b/z80.h
@@ -19,6 +19,8 @@
#include "compiler.h"
#include "trace.h"
+#include <SDL.h>
+
struct twobyte
{
#if WORDS_LITTLEENDIAN
@@ -42,6 +44,8 @@ struct eoi {
int trigger; /* Vector to call EOI for, otherwise -1 */
};
+struct z80_irq;
+
struct z80_state_struct
{
wordregister af;
@@ -63,16 +67,10 @@ struct z80_state_struct
uint8_t rf; /* fixed part of register R (bit 7) */
uint8_t interrupt_mode;
- bool iff1, iff2, ei_shadow;
+ bool iff1, iff2, ei_shadow, signal_eoi;
bool nmi_in_progress; /* to prevent multiple simultaneous NMIs */
-
- bool nminterrupt; /* used to signal a non maskable interrupt */
- bool interrupt; /* used to signal an interrupt */
-
- uint8_t i_vector; /* offset into interrupt-page from _external_ device */
- uint8_t int_in_progress; /* interrupt being serviced */
- struct eoi eoi; /* EOI (= RETI) callback */
+ volatile bool nminterrupt; /* used to signal a non maskable interrupt */
uint64_t tc; /* T-state (clock cycle) counter */
};
@@ -190,7 +188,7 @@ extern void z80_out(int, uint8_t);
extern int z80_in(int);
extern int disassemble(int);
extern int DAsm(uint16_t pc, char *T, int *target);
-extern void z80_poll_external(void);
+extern bool z80_poll_external(void);
extern uint8_t ram[]; /* Array for plain RAM */
@@ -198,21 +196,4 @@ extern void mem_init(unsigned int flags, const char *memfile);
#define MEMFL_NOBASIC 1
#define MEMFL_NODEV 2
-/* Signal an interrupt. If passed an odd value, e.g. -1, ignore. */
-extern void z80_interrupt_eoi(uint8_t vector, eoifunc do_eoi, void *eoi_arg);
-
-static inline void z80_interrupt(uint8_t vector)
-{
- z80_interrupt_eoi(vector, NULL, NULL);
-}
-
-static inline void z80_clear_interrupt(uint8_t vector)
-{
- if (!(vector & 1) && z80_state.interrupt == vector) {
- z80_state.interrupt = false;
- if (z80_state.int_in_progress & 1)
- z80_state.eoi.func = NULL;
- }
-}
-
#endif /* Z80_H */
diff --git a/z80irq.c b/z80irq.c
new file mode 100644
index 0000000..2919db3
--- /dev/null
+++ b/z80irq.c
@@ -0,0 +1,132 @@
+#include "compiler.h"
+#include "z80.h"
+#include "z80irq.h"
+
+volatile unsigned int irq_pending; /* Quick way to poll */
+static struct z80_irq *irqs[MAX_IRQ];
+static struct z80_irq *current_irq;
+static unsigned int current_prio;
+
+void z80_register_irq(struct z80_irq *irq)
+{
+ if (irq->prio < MAX_IRQ)
+ irqs[irq->prio] = irq;
+}
+
+/*
+ * Z80 interrupt acknowledge cycle. Return the vector from the highest
+ * priority pending interrupt, or -1 if spurious.
+ */
+int z80_intack(void)
+{
+ int prio, vector;
+ unsigned int irqmask;
+ struct z80_irq *irq;
+
+ do {
+ /* Find the highest priority (lowest numeric) interrupt pending */
+ irqmask = irq_pending;
+ do {
+ if (!irqmask)
+ return -1; /* All interrupts went away... */
+
+ prio = __builtin_ctz(irqmask);
+ } while (!cmpxchg(&irq_pending, &irqmask, irqmask & ~(1U << prio)));
+
+ irq = irqs[prio];
+
+ if (unlikely(irq->intack))
+ vector = irq->intack(prio, irq);
+ else
+ vector = irq->vector;
+ } while (vector < 0);
+
+ current_irq = irq;
+ current_prio = prio;
+
+ return vector;
+}
+
+/*
+ * A RETI instruction was invoked, which is interpreted as an EOI.
+ * In a real Z80 this is done by snooping the bus. Ick.
+ */
+void z80_eoi(void)
+{
+ struct z80_irq *irq = current_irq;
+
+ if (!irq)
+ return; /* No known interrupt to EOI */
+
+ if (irq->eoi)
+ irq->eoi(current_prio, irq);
+
+ current_irq = NULL;
+}
+
+/*
+ * Raise an interrupt with specific priority level; return true if
+ * interrupt raised, false if the interrupt was already pending or
+ * the argument is invalid.
+ */
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+
+bool z80_interrupt(unsigned int prio)
+{
+ bool raised;
+
+ asm volatile("lock btsl %2,%0"
+ : "+m" (irq_pending), "=@ccnc" (raised)
+ : "ri" (prio));
+
+ return raised;
+}
+
+bool z80_clear_interrupt(unsigned int prio)
+{
+ bool cleared;
+
+ asm volatile("lock btrl %2,%0"
+ : "+m" (irq_pending), "=@ccc" (cleared)
+ : "ri" (prio));
+
+ return cleared;
+}
+
+#else
+
+bool z80_interrupt(unsigned int prio)
+{
+ unsigned int irqmask, irqpend;
+
+ if (prio >= MAX_IRQ)
+ return false;
+
+ irqmask = 1U << prio;
+ irqpend = irq_pending;
+ do {
+ if (irqpend & irqmask)
+ return false;
+ } while (!cmpxchg(&irq_pending, &irqpend, irqpend | irqmask));
+
+ return true;
+}
+
+bool z80_clear_interrupt(unsigned int prio)
+{
+ unsigned int irqmask, irqpend;
+
+ if (prio >= MAX_IRQ)
+ return false;
+
+ irqmask = 1U << prio;
+ irqpend = irq_pending;
+ do {
+ if (!(irqpend & irqmask))
+ return false;
+ } while (!cmpxchg(&irq_pending, &irqpend, irqpend & ~irqmask));
+
+ return true;
+}
+
+#endif
diff --git a/z80irq.h b/z80irq.h
new file mode 100644
index 0000000..9e2c1dc
--- /dev/null
+++ b/z80irq.h
@@ -0,0 +1,31 @@
+#ifndef Z80IRQ_H
+#define Z80IRQ_H
+
+#include "compiler.h"
+#include "z80.h"
+
+typedef int (*irq_func)(unsigned int prio, struct z80_irq *irq);
+
+struct z80_irq {
+ irq_func intack;
+ irq_func eoi;
+ void *pvt; /* Available for user */
+ int vector; /* Available for user if intack defined */
+ unsigned int prio; /* Available for user after register_irq */
+};
+
+#define MAX_IRQ 32
+
+extern volatile unsigned int irq_pending;
+static inline bool poll_irq(void)
+{
+ return unlikely(irq_pending != 0);
+}
+
+void z80_register_irq(struct z80_irq *irq);
+int z80_intack(void);
+void z80_eoi(void);
+bool z80_interrupt(unsigned int prio);
+bool z80_clear_interrupt(unsigned int prio);
+
+#endif /* Z80IRQ_H */