From aad5351080fa401fab551cd09a369d2a3b85c5b5 Mon Sep 17 00:00:00 2001 From: Patrick Linstruth Date: Fri, 21 Nov 2025 17:01:40 -0500 Subject: [PATCH] Altair8800: New Simulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the initial release of the Altair8800 simulator. Why another Altair simulator? AltairZ80 has been described as a “software simulator”, where the intent is to run software designed specifically for executing under a simulator. Altair8800 is intended to accurately simulate the Altair hardware and execute software that will run unchanged on real hardware. Software and disk images can be moved between the Altair8800 simulator and real Altair and other S-100 hardware without any changes. The Altair8800 simulator is a tool that can assist with the restoration of vintage Altair and other S-100 hardware and software along with the development of new hardware and software. The accomplish this, the following are major differences between AltairZ80 and Altair8800. * The monolithic design where devices access other devices directly through external variables and functions is no longer supported. All devices exchange data through a new BUS device. Memory and I/O address decoding and transfers are now handled by the BUS device. All interrupt requests are handled by the BUS device. * System RAM was moved from the CPU device to a new RAM device and managed by the BUS device. * Banked RAM was moved from the CPU device to a new BRAM device. * Banked RAM can only be accessed through the BUS device. Memory in banks that are not currently selected cannot be accessed. The AZ80 “banked” RAM was removed. * ROMs were moved from the CPU and DSK devices to the new ROM device. Mike Douglas’ Altmon Monitor is also available through the ROM device. The custom AltairZ80 ALTAIRROM, which is not compatible with original Altair disk images, is also available. * The custom ALTAIRROM boot loader was replaced with the original MITS Disk Boot Loader as the default ROM. * The monolithic Multiple-CPU/RAM/ROM/IO/BankedRAM CPU device has been replaced with a generic CPU device that provides an abstraction layer between SIMH and the supported CPU architectures (currently 8080 and Z80). All IO is handled through the BUS device. RAM, Banked RAM, and ROM are each handled by their own independent devices. * The AltairZ80 SIO device was replaced with the M2SIO0 and M2SIO1 devices. The M2SIO devices fully support TMXR. * A new SIO device was added to provide generic, programmable, Serial IO. TMXR is not supported on this device. * The Altair 8800 did not have PTR or PTP hardware devices. They have been removed and replaced with the M2SIO1 device. PTR and PTP devices are defined by software executing on the simulator. * Contention between multiple enabled serial devices checking the single host keyboard for input is now handled by the BUS device. Port 0xFF sense switches was moved to a new SSW device and IMSAI programmed output was moved to a new PO device. * The SIMH pseudo device no longer uses the removed PTR and PTP devices. The SIMH device has its own IO system. To avoid conflicts with other devices and remain compatible with the R and W utilities written for AltairZ80, SIMH “borrows” I/O ports 12H and 13H during file transfers. Only SIMH commands needed to support R and W file transfers are supported. All other SIMH commands were removed. * AltairZ80-specific versions of CP/M are not supported by Altair8800. * PC queue was removed from CPU device and replaced with CPU HISTORY. * The Altair8800 simulator only supports 16-bit address and 8-bit data buses. 8086 and 68K CPU architectures were removed. * All CPU timing (clockFrequency) and “sleeps” (SIO SLEEP) have been removed. SIMH THROTTLE is fully supported and is the recommended way to manage simulator speed and host CPU utilization. Executing “SET THROTTLE 100K/1”, for example, should provide ample speed without tasking the host CPU. * HEXLOAD and HEXSAVE commands were added. The LOAD “-h” option has been removed. Intel Hex and sRecord (coming soon) formats are supported. * The WD179X device was converted to an API. * A new DSK API was added to provide a consistent way to manage soft sector raw disk images. * Support for the proprietary IMD disk image format was removed. Only RAW disk images are supported. The following devices are supported by this initial release: BUS - Altair (S-100) Bus CPU - Intel 8080 / Zilog Z80 RAM - 64K RAM ROM - ROMs BRAM - Banked RAM DSK - MITS 88-DCDD Floppy Disk Controller M2SIO0 - MITS 88-2SIO Port 0 M2SIO1 - MITS 88-2SIO Port 1 SSW - Sense Switches PO - Programmed Output SIO - Generic Serial I/O SBC200 - SD Systems SBC-200 TARBELL - Tarbell SD and DD Floppy Disk Controller VFII - SD Systems VersaFloppy II SIMH - SIMH Pseudo Device --- Altair8800/altair8800_defs.h | 45 + Altair8800/altair8800_dsk.c | 435 ++ Altair8800/altair8800_dsk.h | 85 + Altair8800/altair8800_sys.c | 167 + Altair8800/altair8800_sys.h | 69 + Altair8800/mits_2sio.c | 931 +++ Altair8800/mits_2sio.h | 67 + Altair8800/mits_dsk.c | 631 +++ Altair8800/mits_dsk.h | 57 + Altair8800/s100_bram.c | 484 ++ Altair8800/s100_bram.h | 57 + Altair8800/s100_bus.c | 988 ++++ Altair8800/s100_bus.h | 156 + Altair8800/s100_cpu.c | 251 + Altair8800/s100_cpu.h | 62 + Altair8800/s100_po.c | 122 + Altair8800/s100_po.h | 37 + Altair8800/s100_ram.c | 325 ++ Altair8800/s100_ram.h | 42 + Altair8800/s100_rom.c | 252 + Altair8800/s100_rom.h | 59 + Altair8800/s100_roms.h | 373 ++ Altair8800/s100_simh.c | 505 ++ Altair8800/s100_simh.h | 37 + Altair8800/s100_sio.c | 432 ++ Altair8800/s100_sio.h | 60 + Altair8800/s100_ssw.c | 116 + Altair8800/s100_ssw.h | 37 + Altair8800/s100_z80.c | 6549 ++++++++++++++++++++++ Altair8800/s100_z80.h | 70 + Altair8800/sds_sbc200.c | 1294 +++++ Altair8800/sds_vfii.c | 340 ++ Altair8800/sds_vfii.h | 60 + Altair8800/tarbell_fdc.c | 471 ++ Altair8800/tarbell_fdc.h | 60 + Altair8800/wd_17xx.c | 860 +++ Altair8800/wd_17xx.h | 163 + Visual Studio Projects/Altair8800.vcproj | 464 ++ Visual Studio Projects/Simh.sln | 11 +- doc/altair8800_doc.docx | Bin 0 -> 59116 bytes makefile | 37 +- 41 files changed, 17258 insertions(+), 3 deletions(-) create mode 100644 Altair8800/altair8800_defs.h create mode 100644 Altair8800/altair8800_dsk.c create mode 100644 Altair8800/altair8800_dsk.h create mode 100644 Altair8800/altair8800_sys.c create mode 100644 Altair8800/altair8800_sys.h create mode 100644 Altair8800/mits_2sio.c create mode 100644 Altair8800/mits_2sio.h create mode 100644 Altair8800/mits_dsk.c create mode 100644 Altair8800/mits_dsk.h create mode 100644 Altair8800/s100_bram.c create mode 100644 Altair8800/s100_bram.h create mode 100644 Altair8800/s100_bus.c create mode 100644 Altair8800/s100_bus.h create mode 100644 Altair8800/s100_cpu.c create mode 100644 Altair8800/s100_cpu.h create mode 100644 Altair8800/s100_po.c create mode 100644 Altair8800/s100_po.h create mode 100644 Altair8800/s100_ram.c create mode 100644 Altair8800/s100_ram.h create mode 100644 Altair8800/s100_rom.c create mode 100644 Altair8800/s100_rom.h create mode 100644 Altair8800/s100_roms.h create mode 100644 Altair8800/s100_simh.c create mode 100644 Altair8800/s100_simh.h create mode 100644 Altair8800/s100_sio.c create mode 100644 Altair8800/s100_sio.h create mode 100644 Altair8800/s100_ssw.c create mode 100644 Altair8800/s100_ssw.h create mode 100644 Altair8800/s100_z80.c create mode 100644 Altair8800/s100_z80.h create mode 100644 Altair8800/sds_sbc200.c create mode 100644 Altair8800/sds_vfii.c create mode 100644 Altair8800/sds_vfii.h create mode 100644 Altair8800/tarbell_fdc.c create mode 100644 Altair8800/tarbell_fdc.h create mode 100644 Altair8800/wd_17xx.c create mode 100644 Altair8800/wd_17xx.h create mode 100644 Visual Studio Projects/Altair8800.vcproj create mode 100644 doc/altair8800_doc.docx diff --git a/Altair8800/altair8800_defs.h b/Altair8800/altair8800_defs.h new file mode 100644 index 00000000..932fa543 --- /dev/null +++ b/Altair8800/altair8800_defs.h @@ -0,0 +1,45 @@ +/* altair8800_defs.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _ALTAIR8800_DEFS_H +#define _ALTAIR8800_DEFS_H + +#include "sim_defs.h" + +#define KB 1024 /* kilo byte */ +#define KBLOG2 10 /* log2 of KB */ + +#define PLURAL(x) (x), (x) == 1 ? "" : "s" + +/* Keyboard Constants */ +#define KBD_BS 0x08 +#define KBD_DEL 0x7f + +#endif diff --git a/Altair8800/altair8800_dsk.c b/Altair8800/altair8800_dsk.c new file mode 100644 index 00000000..3ad89e0a --- /dev/null +++ b/Altair8800/altair8800_dsk.c @@ -0,0 +1,435 @@ +/* altair8800_dsk.c: Soft Sector Disk Library + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 13-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_dsk.h" + +static void calc_offset(DSK_INFO *d); + +/* + * INTERLEAVED disk images are structured as follows: + * + * +------------------+ + * | TRACK 0 / HEAD 0 | + * +------------------+ + * | TRACK 0 / HEAD 1 | + * +------------------+ + * | TRACK 1 / HEAD 0 | + * +------------------+ + * | TRACK 1 / HEAD 1 | + * +------------------+ + * | TRACK n / HEAD 0 | + * +------------------+ + * | TRACK n / HEAD 1 | + * +------------------+ + * + * NON-INTERLEAVED disk images are structured as follows: + * + * +------------------+ + * | TRACK 0 / HEAD 0 | + * +------------------+ + * | TRACK 1 / HEAD 0 | + * +------------------+ + * | TRACK n / HEAD 0 | + * +------------------+ + * | TRACK 0 / HEAD 1 | + * +------------------+ + * | TRACK 1 / HEAD 1 | + * +------------------+ + * | TRACK n / HEAD 1 | + * +------------------+ + * + */ + +t_stat dsk_init(DSK_INFO *d, UNIT *unit, int tracks, int heads, int interleaved) +{ + if (d == NULL) { + return SCPE_ARG; + } + if (tracks < 1 || tracks > DSK_MAX_TRACKS) { + return SCPE_ARG; + } + if (heads < 1 || heads > DSK_MAX_HEADS) { + return SCPE_ARG; + } + + d->unit = unit; + d->fmt.tracks = tracks; + d->fmt.heads = heads; + d->fmt.interleaved = interleaved; + + return SCPE_OK; +} + +t_stat dsk_init_format(DSK_INFO *d, int strack, int etrack, int shead, int ehead, int den, int secs, int secsize, int stsec) +{ + int tr, hd; + + if (d == NULL) { + return SCPE_ARG; + } + if (strack < 0 || strack > d->fmt.tracks - 1) { + return SCPE_ARG; + } + if (shead < 0 || shead > d->fmt.heads - 1) { + return SCPE_ARG; + } + if (strack > etrack || shead > ehead) { + return SCPE_ARG; + } + + if (d->fmt.tracks < (etrack - strack + 1)) { + d->fmt.tracks = (etrack - strack + 1); + } + + if (d->fmt.heads < (ehead - shead + 1)) { + d->fmt.heads = ehead - shead + 1; + } + + if (d->fmt.interleaved && d->fmt.heads > 1) { + for (tr = strack; tr <= etrack; tr++) { + for (hd = shead; hd <= ehead ; hd++) { + d->fmt.track[tr][hd].density = den; + d->fmt.track[tr][hd].sectors = secs; + d->fmt.track[tr][hd].sectorsize = secsize; + d->fmt.track[tr][hd].startsector = stsec; + } + } + } + else { + for (hd = shead; hd <= ehead; hd++) { + for (tr = strack; tr <= etrack; tr++) { + d->fmt.track[tr][hd].density = den; + d->fmt.track[tr][hd].sectors = secs; + d->fmt.track[tr][hd].sectorsize = secsize; + d->fmt.track[tr][hd].startsector = stsec; + } + } + } + + calc_offset(d); + + return SCPE_OK; +} + +static void calc_offset(DSK_INFO *d) { + int t, h, offset = 0; + + if (d->fmt.interleaved && d->fmt.heads > 1) { + for (t = 0; t < d->fmt.tracks; t++) { + for (h = 0; h < d->fmt.heads; h++) { + sim_debug(d->dbg_verbose, d->unit->dptr, "T:%02d H:%d O:%d\n", t, h, offset); + d->fmt.track[t][h].offset = offset; + + /* Set offset to start of next track */ + offset += d->fmt.track[t][h].sectors * d->fmt.track[t][h].sectorsize; + } + } + } + else { + for (h = 0; h < d->fmt.heads; h++) { + for (t = 0; t < d->fmt.tracks; t++) { + sim_debug(d->dbg_verbose, d->unit->dptr, "T:%02d H:%d O:%d\n", t, h, offset); + d->fmt.track[t][h].offset = offset; + + /* Set offset to start of next track */ + offset += d->fmt.track[t][h].sectors * d->fmt.track[t][h].sectorsize; + } + } + } +} + +t_stat dsk_validate(DSK_INFO *d, int track, int head, int sector) +{ + if (track < 0 || track > d->fmt.tracks - 1) { + sim_printf("DSK: ** Invalid track number %d\n", track); + return SCPE_IOERR; + } + if (head < 0 || head > (d->fmt.heads - 1)) { + sim_printf("DSK: ** Invalid head number %d\n", head); + return SCPE_IOERR; + } + if (sector < d->fmt.track[track][head].startsector || sector > (d->fmt.track[track][head].sectors - ((d->fmt.track[track][head].startsector) ? 0 : 1))) { + sim_printf("DSK: ** Invalid sector number. track/head %d/%d has %d sectors. %d requested.\n", track, head, d->fmt.track[track][head].sectors, sector); + return SCPE_IOERR; + } + + return SCPE_OK; +} + +int32 dsk_size(DSK_INFO *d) +{ + if (d != NULL && d->unit != NULL && d->unit->fileref != NULL) { + return sim_fsize(d->unit->fileref); + } + + return 0; +} + +int32 dsk_tracks(DSK_INFO *d) +{ + if (d != NULL) { + return d->fmt.tracks; + } + + return 0; +} + +int32 dsk_track_size(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].sectors * d->fmt.track[track][head].sectorsize; + } + + return 0; +} + +int32 dsk_sectors(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].sectors; + } + + return 0; +} + +int32 dsk_sector_size(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].sectorsize; + } + + return 0; +} + +int32 dsk_start_sector(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].startsector; + } + + return 0; +} + +int32 dsk_sector_offset(DSK_INFO *d, int32 track, int32 head, int32 sector) +{ + if (d != NULL) { + return d->fmt.track[track][head].offset + (dsk_sector_size(d, track, head) * (sector - dsk_start_sector(d, track, head))); + } + + return 0; +} + +t_stat dsk_read_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, uint8 *buf, int32 *bytesread) +{ + int32 b, ssize; + t_stat r = SCPE_OK; + + if (d == NULL || d->unit == NULL || d->unit->fileref == NULL) { + return SCPE_ARG; + } + + if ((r = dsk_validate(d, track, head, sector)) != 0) { + return SCPE_ARG; + } + + ssize = dsk_sector_size(d, track, head); + + fseek(d->unit->fileref, dsk_sector_offset(d, track, head, sector), SEEK_SET); + + if ((b = fread(buf, 1, ssize, d->unit->fileref)) != ssize) { + r = SCPE_IOERR; + } + + if (bytesread != NULL) { + *bytesread = b; + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK RD SEC: T:%d H:%d S:%d SS:%d READ:%d\n", track, head, sector, ssize, *bytesread); + } + + +// dsk_dump_buf(buf, ssize); + + return r; +} + +t_stat dsk_write_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, const uint8 *buf, int32 *byteswritten) +{ + int b, ssize, offset; + int r = SCPE_OK; + + if (d == NULL || d->unit == NULL || d->unit->fileref == NULL) { + return SCPE_ARG; + } + + if ((r = dsk_validate(d, track, head, sector)) != 0) { + return r; + } + + ssize = dsk_sector_size(d, track, head); + offset = dsk_sector_offset(d, track,head, sector); + + fseek(d->unit->fileref, offset, SEEK_SET); + + b = fwrite(buf, 1, ssize, d->unit->fileref); + + if (byteswritten != NULL) { + *byteswritten = b; + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK WR SEC: T:%d H:%d S:%d SS:%d O:%d WRITTEN:%d\n", track, head, sector, ssize, offset, *byteswritten); + } + + +// dsk_dump_buf(buf, ssize); + + return r; +} + +t_stat dsk_read_track(DSK_INFO *d, int32 track, int32 head, uint8 *buf) +{ + if (d == NULL || d->unit == NULL || d->unit->dptr == NULL) { + return SCPE_ARG; + } + + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK RD TRK: T:%d H:%d\n", track, head); + + return SCPE_OK; +} + +t_stat dsk_write_track(DSK_INFO *d, int32 track, int32 head, uint8 fill) +{ + int s, ssize, start; + unsigned char *b; + + if (d == NULL) { + return SCPE_ARG; + } + + ssize = dsk_sector_size(d, track, head); + start = dsk_start_sector(d, track, head); + + if ((b = malloc(ssize)) == NULL) { + return 0; + } + + memset(b, fill, ssize); + + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK WR TRK: T:%d H:%d SS:%d F:%02X\n", track, head, ssize, fill); + + for (s = 0; s < dsk_sectors(d, track, head); s++) { + dsk_write_sector(d, track, head, s + start, b, NULL); + } + + free(b); + + return 0; +} + +t_stat dsk_format(DSK_INFO *d, uint8 fill) +{ + int t, h; + + if (d == NULL) { + return SCPE_ARG; + } + + for (t = 0; t < d->fmt.tracks; t++) { + for (h = 0; h < d->fmt.heads; h++) { + dsk_write_track(d, t, h, fill); + } + } + + return SCPE_OK; +} + +void dsk_dump_buf(const uint8 *b, int32 size) +{ + int i; + + if (b == NULL) { + return; + } + + for (i = 0; i < size; i++) { + if ((i & 0x0f) == 0x00) { + sim_printf("%04X: ", i); + } + sim_printf("%02X%c", b[i], ((i & 0x0f) == 0x0f) ? '\n' : ' '); + } +} + +void dsk_show(DSK_INFO *d) +{ + int t, h; + + if (d != NULL) { + sim_printf("\n"); + sim_printf("fmt.tracks = %d\n", d->fmt.tracks); + sim_printf("fmt.heads = %d\n", d->fmt.heads); + + for (t = 0; t < d->fmt.tracks; t++) { + for (h = 0; h < d->fmt.heads; h++) { + sim_printf("T:%02d H:%d D:%s SECS:%02d SECSIZE:%04d OFFSET:%05X\n", t, h, + d->fmt.track[t][h].density == DSK_DENSITY_SD ? "SD" : "DD", + d->fmt.track[t][h].sectors, + d->fmt.track[t][h].sectorsize, + d->fmt.track[t][h].offset); + } + } + } +} + +void dsk_set_verbose_flag(DSK_INFO *d, uint32 flag) +{ + if (d != NULL) { + d->dbg_verbose = flag; + } +} + +t_stat dsk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + uint32 i; + + for (i=0; i < dptr->numunits; i++) { + if ((dptr->units[i].flags & UNIT_ATTABLE) && + !(dptr->units[i].flags & UNIT_DIS)) { + fprintf (st, " sim> ATTACH {switches} %s diskfile\n", sim_uname(&dptr->units[i])); + } + } + + fprintf (st, "\n%s attach command switches\n", dptr->name); + fprintf (st, " -E Must Exist (if not specified an attempt to create the indicated\n"); + fprintf (st, " disk container will be attempted).\n"); + fprintf (st, " -N New file. Existing file is overwritten.\n"); + fprintf (st, " -R Attach Read Only.\n"); + + fprintf (st, "\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/altair8800_dsk.h b/Altair8800/altair8800_dsk.h new file mode 100644 index 00000000..e4bb2de7 --- /dev/null +++ b/Altair8800/altair8800_dsk.h @@ -0,0 +1,85 @@ +/* altair8800_dsk.h: Soft Sector Disk Library + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 13-Nov-2025 Initial version + +*/ + +#ifndef _ALTAIR8800_DSK_H +#define _ALTAIR8800_DSK_H + +#include "sim_defs.h" + +#define DSK_MAX_TRACKS 80 +#define DSK_MAX_HEADS 2 + +#define DSK_DENSITY_SD 0x01 +#define DSK_DENSITY_DD 0x02 + +typedef struct { + int32 density; + int32 sectors; + int32 sectorsize; + int32 startsector; + int32 offset; +} DSK_TRACK; + +typedef struct { + int32 tracks; + int32 heads; + int32 interleaved; + DSK_TRACK track[DSK_MAX_TRACKS][DSK_MAX_HEADS]; +} DSK_FORMAT; + +typedef struct { + UNIT *unit; + DSK_FORMAT fmt; + uint32 dbg_verbose; +} DSK_INFO; + +extern t_stat dsk_init(DSK_INFO *d, UNIT *unit, int32 tracks, int32 heads, int32 interleaved); +extern t_stat dsk_init_format(DSK_INFO *d, int32 strack, int32 etrack, int32 shead, int32 ehead, + int32 den, int32 secs, int32 secsize, int32 stsec); +extern void dsk_set_verbose_flag(DSK_INFO *d, uint32 flag); +extern int32 dsk_size(DSK_INFO *d); +extern int32 dsk_tracks(DSK_INFO *d); +extern int32 dsk_track_size(DSK_INFO *d, int32 track, int32 head); +extern int32 dsk_sectors(DSK_INFO *d, int32 track, int32 head); +extern int32 dsk_sector_size(DSK_INFO *d, int32 track, int32 head); +extern int32 dsk_sector_offset(DSK_INFO *d, int32 track, int32 head, int32 sector); +extern int32 dsk_start_sector(DSK_INFO *d, int32 track, int32 head); +extern t_stat dsk_validate(DSK_INFO *d, int track, int head, int sector); +extern t_stat dsk_write_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, const uint8 *buf, int32 *byteswritten); +extern t_stat dsk_read_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, uint8 *buf, int32 *bytesread); +extern t_stat dsk_read_track(DSK_INFO *d, int32 track, int32 head, uint8 *buf); +extern t_stat dsk_write_track(DSK_INFO *d, int32 track, int32 head, uint8 fill); +extern t_stat dsk_format(DSK_INFO *d, uint8 fill); +extern void dsk_dump_buf(const uint8 *b, int32 size); +extern void dsk_show(DSK_INFO *d); +extern t_stat dsk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +#endif + diff --git a/Altair8800/altair8800_sys.c b/Altair8800/altair8800_sys.c new file mode 100644 index 00000000..373f1bca --- /dev/null +++ b/Altair8800/altair8800_sys.c @@ -0,0 +1,167 @@ +/* altair8800_sys.c: MITS Altair 8800 SIMH System Interface + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + ---------------------------------------------------------- + + This module of the simulator contains the glue between the + Altair8800 simulator and SIMH. + + To add a device, these modules must be modified: + + altair8800_sys.h add external DEVICE declaration + altair8800_sys.c add DEVICE to sim_devices + + ---------------------------------------------------------- + + History: + 11/07/25 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" + +#if 0 +static t_bool fprint_stopped(FILE *st, t_stat reason); +#endif + +/* SCP data structures + + sim_name simulator name string + sim_PC pointer to saved PC register descriptor + sim_emax number of words needed for examine + sim_devices array of pointers to simulated devices + sim_stop_messages array of pointers to stop messages + sim_load binary loader +*/ + +char sim_name[] = "Altair 8800 (BUS)"; + +int32 sim_emax = SIM_EMAX; + +DEVICE *sim_devices[] = { + &bus_dev, + &cpu_dev, + &ssw_dev, + &simh_dev, + &ram_dev, + &bram_dev, + &rom_dev, + &po_dev, + &mdsk_dev, + &m2sio0_dev, + &m2sio1_dev, + &sio_dev, + &sbc200_dev, + &tarbell_dev, + &vfii_dev, + NULL +}; + +char memoryAccessMessage[256]; +char instructionMessage[256]; + +const char *sim_stop_messages[SCPE_BASE] = { + "Unknown error", /* 0 is reserved/unknown */ + "Breakpoint", + memoryAccessMessage, + instructionMessage, + "Invalid Opcode", + "HALT instruction" +}; + + +/* find_unit_index find index of a unit + + Inputs: + uptr = pointer to unit + Outputs: + result = index of device +*/ +int32 sys_find_unit_index(UNIT* uptr) +{ + DEVICE *dptr = find_dev_from_unit(uptr); + + if (dptr == NULL) { + return -1; + } + + return (uptr - dptr->units); +} + +#if 0 + +// Need to figure out how to initize in altair8800_sys.c +// +static t_bool fprint_stopped(FILE *st, t_stat reason) +{ + fprintf(st, "Hey, it stopped!\n"); + + return FALSE; +} + +#endif + +char *sys_strupr(const char *str) +{ + static char s[128]; + int i; + + for (i = 0; i < sizeof(s) && str[i] != '\0'; i++) { + s[i] = sim_toupper(str[i]); + } + + s[i] = '\0'; + + return s; +} + + +uint8 sys_floorlog2(unsigned int n) +{ + /* Compute log2(n) */ + uint8 r = 0; + if (n >= 1<<16) { + n >>=16; + r += 16; + } + if (n >= 1<< 8) { + n >>= 8; + r += 8; + } + if (n >= 1<< 4) { + n >>= 4; + r += 4; + } + if (n >= 1<< 2) { + n >>= 2; + r += 2; + } + if (n >= 1<< 1) { + r += 1; + } + return ((n == 0) ? (0xFF) : r); /* 0xFF is error return value */ +} + diff --git a/Altair8800/altair8800_sys.h b/Altair8800/altair8800_sys.h new file mode 100644 index 00000000..34a1b025 --- /dev/null +++ b/Altair8800/altair8800_sys.h @@ -0,0 +1,69 @@ +/* altair8800_sys.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _ALTAIR8800_SYS_H +#define _ALTAIR8800_SYS_H + +#include "sim_defs.h" + +#define SIM_EMAX 6 + +extern DEVICE bus_dev; +extern DEVICE cpu_dev; +extern DEVICE ssw_dev; +extern DEVICE simh_dev; +extern DEVICE z80_dev; +extern DEVICE ram_dev; +extern DEVICE bram_dev; +extern DEVICE rom_dev; +extern DEVICE po_dev; +extern DEVICE mdsk_dev; +extern DEVICE m2sio0_dev; +extern DEVICE m2sio1_dev; +extern DEVICE sio_dev; +extern DEVICE sbc200_dev; +extern DEVICE tarbell_dev; +extern DEVICE vfii_dev; + +extern char memoryAccessMessage[256]; +extern char instructionMessage[256]; + +extern int32 sys_find_unit_index(UNIT* uptr); + +extern void sys_set_cpu_instr(t_stat (*routine)(void)); +extern void sys_set_cpu_pc(REG *reg); +extern void sys_set_cpu_pc_value(t_value (*routine)(void)); +extern void sys_set_cpu_parse_sym(t_stat (*routine)(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)); +extern void sys_set_cpu_dasm(int32 (*routine)(char *S, const uint32 *val, const int32 addr)); +extern void sys_set_cpu_is_subroutine_call(t_bool (*routine)(t_addr **ret_addrs)); +extern char *sys_strupr(const char *str); +extern uint8 sys_floorlog2(unsigned int n); + +#endif diff --git a/Altair8800/mits_2sio.c b/Altair8800/mits_2sio.c new file mode 100644 index 00000000..592f8ecc --- /dev/null +++ b/Altair8800/mits_2sio.c @@ -0,0 +1,931 @@ +/* mits_2sio.c: MITS Altair 8800 88-2SIO + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + + ================================================================== + + The 88-2 Serial Input/Output Board (88-2SIO) is designed around an + Asynchronous Communications Interface Adapter (ACIA). + + The card had up to two physical I/O ports which could be connected + to any serial I/O device that would connect to a current loop, + RS232, or TTY interface. Available baud rates were jumper + selectable for each port from 110 to 9600. + + All I/O is via programmed I/O. Each each port has a status port + and a data port. A write to the status port can select some + options for the device (0x03 will reset the port). A read of the + status port gets the port status: + + +---+---+---+---+---+---+---+---+ + | R P V F C D O I | + +---+---+---+---+---+---+---+---+ + + I - A 1 in this bit position means a character has been received + on the data port and is ready to be read. + O - A 1 in this bit means the port is ready to receive a character + on the data port and transmit it out over the serial line. + D - A 1 in this bit means Data Carrier Detect is present. + C - A 1 in this bit means Clear to Send is present. + F - A 1 in this bit means a Framing Error has occurred. + V - A 1 in this bit means an Overrun has occurred. + P - A 1 in this bit means a Parity Error has occurred. + R - A 1 in this bit means an Interrupt has occurred. + + A read to the data port gets the buffered character, a write + to the data port writes the character to the device. + + The following are excerpts from Computer Notes, Volume 2, Issue 8, + Jan-Feb '77: + + GLITCHES + Q&A from the Repair Department + By Bruce Fowler + + We get many calls on how to interface terminals to the 2SIO. The + problem is that the Asynchronous Communications Interface Adapter's + (ACIA) handshaking signals make interfacing with the 2SIO a + somewhat complicated matter. An explanation of the signals and + their function should make the job easier. The three handshaking + signals--Data Carrier Detect (DCD), Request to Send (RTS) and + Clear to Send (CTS)--permit limited control of a modem or + peripheral. RTS is an output signal, and DCD and CTS are input + signals. + + Data will only leave the ACIA when CTS is active. + + The ACIA will receive data only when DCD is active. DCD is normally + used with modems. As long as DCD is inactive, the ACIA's receiver + section is inhibited and no data can be received by the ACIA. + + Information from the two input signals, CTS and DCD, is present in + the ACIA status register. Bit 2 represents /DCD, and bit 3 + represents /CTS. When bit 2 is high, DCD is inactive. When bit 3 is high, + CTS is inactive. When bit 2 goes low, valid data is sent to the ACIA. + When bit 3 goes low, data can be transmitted. + + / = Active Low + +*/ + +#include "sim_defs.h" +#include "sim_tmxr.h" +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "mits_2sio.h" + +#define M2SIO_NAME "MITS 88-2SIO SERIAL ADAPTER" +#define M2SIO0_SNAME "M2SIO0" +#define M2SIO1_SNAME "M2SIO1" + +#define M2SIO_PORTS 2 + +#define M2SIO_WAIT 250 /* Service Wait Interval */ + +#define M2SIO0_IOBASE 0x10 +#define M2SIO0_IOSIZE 2 +#define M2SIO1_IOBASE 0x12 +#define M2SIO1_IOSIZE 2 + +#define M2SIO_RDRF 0x01 /* Receive Data Register Full */ +#define M2SIO_TDRE 0x02 /* Transmit Data Register Empty */ +#define M2SIO_DCD 0x04 /* Data Carrier Detect */ +#define M2SIO_CTS 0x08 /* Clear to Send */ +#define M2SIO_FE 0x10 /* Framing Error */ +#define M2SIO_OVRN 0x20 /* Overrun */ +#define M2SIO_PE 0x40 /* Parity Error */ +#define M2SIO_IRQ 0x80 /* Interrupt Request */ +#define M2SIO_RESET 0x03 /* Reset */ +#define M2SIO_CLK1 0x00 /* Divide Clock by 1 */ +#define M2SIO_CLK16 0x01 /* Divide Clock by 16 */ +#define M2SIO_CLK64 0x02 /* Divide Clock by 64 */ +#define M2SIO_72E 0x00 /* 7-2-E */ +#define M2SIO_72O 0x04 /* 7-2-O */ +#define M2SIO_71E 0x08 /* 7-1-E */ +#define M2SIO_71O 0x0C /* 7-1-O */ +#define M2SIO_82N 0x10 /* 8-2-N */ +#define M2SIO_81N 0x14 /* 8-1-N */ +#define M2SIO_81E 0x18 /* 8-1-E */ +#define M2SIO_81O 0x1C /* 8-1-O */ +#define M2SIO_FMTMSK 0x1c /* Length, Parity, Stop Mask */ +#define M2SIO_RTSLTID 0x00 /* RTS Low, Xmit Int Disabled */ +#define M2SIO_RTSLTIE 0x20 /* RTS Low, Xmit Int Enabled */ +#define M2SIO_RTSHTID 0x40 /* RTS High, Xmit Int Disabled */ +#define M2SIO_RTSHTBR 0x60 /* RTS High, Xmit Break */ +#define M2SIO_RTSMSK 0x60 /* RTS Bit Mask */ +#define M2SIO_RIE 0x80 /* Receive Int Enabled */ + +#define M2SIO_BAUD 9600 /* Default baud rate */ + +static const char* m2sio_description(DEVICE *dptr); +static t_stat m2sio_svc(UNIT *uptr); +static t_stat m2sio_reset(DEVICE *dptr, int32 (*routine)(const int32, const int32, const int32)); +static t_stat m2sio0_reset(DEVICE *dptr); +static t_stat m2sio1_reset(DEVICE *dptr); +static t_stat m2sio_attach(UNIT *uptr, const char *cptr); +static t_stat m2sio_detach(UNIT *uptr); +static t_stat m2sio_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat m2sio_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat m2sio_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc); +static t_stat m2sio_config_line(UNIT *uptr); +static t_stat m2sio_config_rts(DEVICE *dptr, char rts); +static int32 m2sio0_io(int32 addr, int32 io, int32 data); +static int32 m2sio1_io(int32 addr, int32 io, int32 data); +static int32 m2sio_io(DEVICE *dptr, int32 addr, int32 io, int32 data); +static int32 m2sio_stat(DEVICE *dptr, int32 io, int32 data); +static int32 m2sio_data(DEVICE *dptr, int32 io, int32 data); +static void m2sio_int(UNIT *uptr); +static int32 m2sio_map_kbdchar(UNIT *uptr, int32 ch); +static t_stat m2sio_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static M2SIO_REG m2sio0_reg; +static M2SIO_REG m2sio1_reg; + +/* Debug Flags */ +#define STATUS_MSG (1 << 0) +#define IRQ_MSG (1 << 1) +#define VERBOSE_MSG (1 << 2) + +/* Debug Table */ +static DEBTAB m2sio_dt[] = { + { "STATUS", STATUS_MSG, "Status messages" }, + { "IRQ", IRQ_MSG, "Interrupt messages" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +/* Terminal multiplexer library descriptors */ + +static TMLN m2sio0_tmln[] = { /* line descriptors */ + { 0 } +}; + +static TMLN m2sio1_tmln[] = { /* line descriptors */ + { 0 } +}; + +static TMXR m2sio0_tmxr = { /* multiplexer descriptor */ + 1, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + m2sio0_tmln, /* line descriptor array */ + NULL, /* line connection order */ + NULL /* multiplexer device (derived internally) */ +}; + +static TMXR m2sio1_tmxr = { /* multiplexer descriptor */ + 1, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + m2sio1_tmln, /* line descriptor array */ + NULL, /* line connection order */ + NULL /* multiplexer device (derived internally) */ +}; + + +static MTAB m2sio_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "Sets MITS 2SIO base I/O address" }, + + { UNIT_M2SIO_MAP, 0, "NOMAP", "NOMAP", NULL, NULL, NULL, + "Do not map any character" }, /* disable character mapping */ + { UNIT_M2SIO_MAP, UNIT_M2SIO_MAP, "MAP", "MAP", NULL, NULL, NULL, + "Enable mapping of characters" }, /* enable all character mapping */ + { UNIT_M2SIO_UPPER, 0, "NOUPPER", "NOUPPER", NULL, NULL, NULL, + "Console input remains unchanged" }, /* do not change case of input characters */ + { UNIT_M2SIO_UPPER, UNIT_M2SIO_UPPER, "UPPER", "UPPER", NULL, NULL, NULL, + "Convert console input to upper case" }, /* change input characters to upper case */ + { UNIT_M2SIO_BS, 0, "BS", "BS", NULL, NULL, NULL, + "Map delete to backspace" }, /* map delete to backspace */ + { UNIT_M2SIO_BS, UNIT_M2SIO_BS, "DEL", "DEL", NULL, NULL, NULL, + "Map backspace to delete" }, /* map backspace to delete */ + { UNIT_M2SIO_DTR, UNIT_M2SIO_DTR, "DTR", "DTR", NULL, NULL, NULL, + "DTR follows RTS" }, + { UNIT_M2SIO_DTR, 0, "NODTR", "NODTR", NULL, NULL, NULL, + "DTR does not follow RTS (default)" }, + { UNIT_M2SIO_DCD, UNIT_M2SIO_DCD, "DCD", "DCD", NULL, NULL, NULL, + "Force DCD active low" }, + { UNIT_M2SIO_DCD, 0, "NODCD", "NODCD", NULL, NULL, NULL, + "DCD follows status line (default)" }, + { UNIT_M2SIO_CTS, UNIT_M2SIO_CTS, "CTS", "CTS", NULL, NULL, NULL, + "Force CTS active low" }, + { UNIT_M2SIO_CTS, 0, "NOCTS", "NOCTS", NULL, NULL, NULL, + "CTS follows status line (default)" }, + + { MTAB_XTD | MTAB_VUN, UNIT_M2SIO_CONSOLE, NULL, "CONSOLE", &m2sio_set_console, NULL, NULL, "Set as CONSOLE" }, + { MTAB_XTD | MTAB_VUN, 0, NULL, "NOCONSOLE", &m2sio_set_console, NULL, NULL, "Remove as CONSOLE" }, + + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "BAUD", "BAUD", &m2sio_set_baud, &m2sio_show_baud, + NULL, "Set baud rate (default=9600)" }, + { 0 } +}; + +static RES m2sio0_res = { M2SIO0_IOBASE, M2SIO0_IOSIZE, 0, 0, &m2sio0_tmxr }; +static RES m2sio1_res = { M2SIO1_IOBASE, M2SIO1_IOSIZE, 0, 0, &m2sio1_tmxr }; + +static UNIT unit0[] = { + { UDATA (&m2sio_svc, UNIT_ATTABLE | UNIT_M2SIO_MAP | UNIT_M2SIO_CONSOLE | UNIT_M2SIO_DCD | UNIT_M2SIO_CTS , 0), M2SIO_WAIT }, +}; + +static UNIT unit1[] = { + { UDATA (&m2sio_svc, UNIT_ATTABLE | UNIT_M2SIO_DCD | UNIT_M2SIO_CTS, 0), M2SIO_WAIT }, +}; + +static REG reg0[] = { + { HRDATAD (M2STA0, m2sio0_reg.stb, 8, "2SIO port 0 status register"), }, + { HRDATAD (M2CTL0, m2sio0_reg.ctb, 8, "2SIO port 0 control register"), }, + { HRDATAD (M2RXD0, m2sio0_reg.rxb, 8, "2SIO port 0 rx data buffer"), }, + { HRDATAD (M2TXD0, m2sio0_reg.txb, 8, "2SIO port 0 tx data buffer"), }, + { FLDATAD (M2TXP0, m2sio0_reg.txp, 0, "2SIO port 0 tx data pending"), }, + { FLDATAD (M2CON0, m2sio0_reg.conn, 0, "2SIO port 0 connection status"), }, + { FLDATAD (M2RIE0, m2sio0_reg.rie, 0, "2SIO port 0 receive interrupt enable"), }, + { FLDATAD (M2TIE0, m2sio0_reg.tie, 0, "2SIO port 0 transmit interrupt enable"), }, + { FLDATAD (M2RTS0, m2sio0_reg.rts, 0, "2SIO port 0 RTS status (active low)"), }, + { FLDATAD (M2RDRF0, m2sio0_reg.stb, 0, "2SIO port 0 RDRF status"), }, + { FLDATAD (M2TDRE0, m2sio0_reg.stb, 1, "2SIO port 0 TDRE status"), }, + { FLDATAD (M2DCD0, m2sio0_reg.stb, 2, "2SIO port 0 DCD status (active low)"), }, + { FLDATAD (M2CTS0, m2sio0_reg.stb, 3, "2SIO port 0 CTS status (active low)"), }, + { FLDATAD (M2OVRN0, m2sio0_reg.stb, 4, "2SIO port 0 OVRN status"), }, + { FLDATAD (DCDL0, m2sio0_reg.dcdl, 0, "2SIO port 0 DCD latch"), }, + { DRDATAD (M2WAIT0, unit0[0].wait, 32, "2SIO port 0 wait cycles"), }, + { FLDATAD (M2INTEN0, m2sio0_reg.intenable, 1, "2SIO port 0 Global vectored interrupt enable"), }, + { DRDATAD (M2VEC0, m2sio0_reg.intvector, 8, "2SIO port 0 interrupt vector"), }, + { HRDATAD (M2DBVAL0, m2sio0_reg.databus, 8, "2SIO port 0 data bus value"), }, + { NULL } +}; +static REG reg1[] = { + { HRDATAD (M2STA1, m2sio1_reg.stb, 8, "2SIO port 1 status buffer"), }, + { HRDATAD (M2CTL1, m2sio1_reg.ctb, 8, "2SIO port 1 control register"), }, + { HRDATAD (M2RXD1, m2sio1_reg.rxb, 8, "2SIO port 1 rx data buffer"), }, + { HRDATAD (M2TXD1, m2sio1_reg.txb, 8, "2SIO port 1 tx data buffer"), }, + { FLDATAD (M2TXP1, m2sio1_reg.txp, 0, "2SIO port 1 tx data pending"), }, + { FLDATAD (M2CON1, m2sio1_reg.conn, 0, "2SIO port 1 connection status"), }, + { FLDATAD (M2RIE1, m2sio1_reg.rie, 0, "2SIO port 1 receive interrupt enable"), }, + { FLDATAD (M2TIE1, m2sio1_reg.tie, 0, "2SIO port 1 transmit interrupt enable"), }, + { FLDATAD (M2RTS1, m2sio1_reg.rts, 0, "2SIO port 1 RTS status (active low)"), }, + { FLDATAD (M2RDRF1, m2sio1_reg.stb, 0, "2SIO port 1 RDRF status"), }, + { FLDATAD (M2TDRE1, m2sio1_reg.stb, 1, "2SIO port 1 TDRE status"), }, + { FLDATAD (M2DCD1, m2sio1_reg.stb, 2, "2SIO port 1 DCD status (active low)"), }, + { FLDATAD (M2CTS1, m2sio1_reg.stb, 3, "2SIO port 1 CTS status (active low)"), }, + { FLDATAD (M2OVRN1, m2sio1_reg.stb, 4, "2SIO port 1 OVRN status"), }, + { FLDATAD (DCDL1, m2sio1_reg.dcdl, 0, "2SIO port 1 DCD latch"), }, + { DRDATAD (M2WAIT1, unit1[0].wait, 32, "2SIO port 1 wait cycles"), }, + { FLDATAD (M2INTEN1, m2sio1_reg.intenable, 1, "2SIO port 1 Global vectored interrupt enable"), }, + { DRDATAD (M2VEC1, m2sio1_reg.intvector, 8, "2SIO port 1 interrupt vector"), }, + { HRDATAD (M2DBVAL1, m2sio1_reg.databus, 8, "2SIO port 1 data bus value"), }, + { NULL } +}; + +DEVICE m2sio0_dev = { + M2SIO0_SNAME, /* name */ + unit0, /* unit */ + reg0, /* registers */ + m2sio_mod, /* modifiers */ + 1, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* address increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &m2sio0_reset, /* reset routine */ + NULL, /* boot routine */ + &m2sio_attach, /* attach routine */ + &m2sio_detach, /* detach routine */ + &m2sio0_res, /* context */ + (DEV_DISABLE | DEV_DEBUG | DEV_MUX), /* flags */ + 0, /* debug control */ + m2sio_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &m2sio_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &m2sio_description /* description */ +}; + +DEVICE m2sio1_dev = { + M2SIO1_SNAME, /* name */ + unit1, /* unit */ + reg1, /* registers */ + m2sio_mod, /* modifiers */ + 1, /* # units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &m2sio1_reset, /* reset routine */ + NULL, /* boot routine */ + &m2sio_attach, /* attach routine */ + &m2sio_detach, /* detach routine */ + &m2sio1_res, /* context */ + (DEV_DISABLE | DEV_DEBUG | DEV_MUX), /* flags */ + 0, /* debug control */ + m2sio_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &m2sio_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &m2sio_description /* description */ +}; + +static const char* m2sio_description(DEVICE *dptr) +{ + return M2SIO_NAME; +} + +static t_stat m2sio0_reset(DEVICE *dptr) +{ + dptr->units->up8 = &m2sio0_reg; + + return(m2sio_reset(dptr, &m2sio0_io)); +} + +static t_stat m2sio1_reset(DEVICE *dptr) +{ + dptr->units->up8 = &m2sio1_reg; + + return(m2sio_reset(dptr, &m2sio1_io)); +} + +static t_stat m2sio_reset(DEVICE *dptr, int32 (*routine)(const int32, const int32, const int32)) +{ + RES *res; + M2SIO_REG *reg; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + /* Connect/Disconnect I/O Ports at base address */ + if (dptr->flags & DEV_DIS) { /* Device is disabled */ + s100_bus_remio(res->io_base, res->io_size, routine); + s100_bus_noconsole(&dptr->units[0]); + + return SCPE_OK; + } + + /* Device is enabled */ + s100_bus_addio(res->io_base, res->io_size, routine, dptr->name); + + /* Set as CONSOLE unit */ + if (dptr->units[0].flags & UNIT_M2SIO_CONSOLE) { + s100_bus_console(&dptr->units[0]); + } + + /* Set DEVICE for this UNIT */ + dptr->units[0].dptr = dptr; + dptr->units[0].wait = M2SIO_WAIT; + + /* Enable TMXR modem control passthrough */ + tmxr_set_modem_control_passthru(res->tmxr); + + /* Reset status registers */ + reg->stb = M2SIO_CTS | M2SIO_DCD; + reg->txp = FALSE; + reg->dcdl = FALSE; + + if (dptr->units[0].flags & UNIT_ATT) { + m2sio_config_rts(dptr, 1); /* disable RTS */ + } + + /* Start service routine */ + sim_activate(&dptr->units[0], dptr->units[0].wait); + + sim_debug(STATUS_MSG, dptr, "reset adapter.\n"); + + return SCPE_OK; +} + + +static t_stat m2sio_svc(UNIT *uptr) +{ + DEVICE *dptr; + RES *res; + M2SIO_REG *reg; + int32 c,s,stb; + t_stat r; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + /* Check for new incoming connection */ + if (uptr->flags & UNIT_ATT) { + if (tmxr_poll_conn(res->tmxr) >= 0) { /* poll connection */ + + reg->conn = TRUE; /* set connected */ + + sim_debug(STATUS_MSG, uptr->dptr, "new connection.\n"); + } + } + + /* Update incoming modem status bits */ + if (uptr->flags & UNIT_ATT) { + tmxr_set_get_modem_bits(res->tmxr->ldsc, 0, 0, &s); + stb = reg->stb; + reg->stb &= ~M2SIO_CTS; + reg->stb |= ((s & TMXR_MDM_CTS) || (uptr->flags & UNIT_M2SIO_CTS)) ? 0 : M2SIO_CTS; /* Active Low */ + if ((stb ^ reg->stb) & M2SIO_CTS) { + sim_debug(STATUS_MSG, uptr->dptr, "CTS state changed to %s.\n", (reg->stb & M2SIO_CTS) ? "LOW" : "HIGH"); + } + + if (!reg->dcdl) { + reg->stb &= ~M2SIO_DCD; + reg->stb |= ((s & TMXR_MDM_DCD) || (uptr->flags & UNIT_M2SIO_DCD)) ? 0 : M2SIO_DCD; /* Active Low */ + if ((stb ^ reg->stb) & M2SIO_DCD) { + if ((reg->stb & M2SIO_DCD) == M2SIO_DCD) { + reg->dcdl = TRUE; + if (reg->rie) { + m2sio_int(uptr); + } + } + sim_debug(STATUS_MSG, uptr->dptr, "DCD state changed to %s.\n", (reg->stb & M2SIO_DCD) ? "LOW" : "HIGH"); + } + } + + /* Enable receiver if DCD is active low */ + res->tmxr->ldsc->rcve = !(reg->stb & M2SIO_DCD); + } + + /* TX data */ + if (reg->txp) { + if (uptr->flags & UNIT_ATT) { + if (!(reg->stb & M2SIO_CTS)) { /* Active low */ + r = tmxr_putc_ln(res->tmxr->ldsc, reg->txb); + reg->txp = FALSE; /* Reset TX Pending */ + } else { + r = SCPE_STALL; + } + } else { + r = sim_putchar(reg->txb); + reg->txp = FALSE; /* Reset TX Pending */ + } + + if (r == SCPE_LOST) { + reg->conn = FALSE; /* Connection was lost */ + sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n"); + } + + /* If TX buffer now empty, send interrupt */ + if ((!reg->txp) && (reg->tie)) { + m2sio_int(uptr); + } + + } + + /* Update TDRE if not set and no character pending */ + if (!reg->txp && !(reg->stb & M2SIO_TDRE)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_tx(res->tmxr); + reg->stb |= (tmxr_txdone_ln(res->tmxr->ldsc) && reg->conn) ? M2SIO_TDRE : 0; + } else { + reg->stb |= M2SIO_TDRE; + } + } + + /* Check for Data if RX buffer empty */ + if (!(reg->stb & M2SIO_RDRF)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_rx(res->tmxr); + + c = tmxr_getc_ln(res->tmxr->ldsc); + } else { + c = s100_bus_poll_kbd(uptr); + } + + if (c & (TMXR_VALID | SCPE_KFLAG)) { + reg->rxb = m2sio_map_kbdchar(uptr, c); + reg->stb |= M2SIO_RDRF; + reg->stb &= ~(M2SIO_FE | M2SIO_OVRN | M2SIO_PE); + if (reg->rie) { + m2sio_int(uptr); + } + } + } + + sim_activate_abs(uptr, uptr->wait); + + return SCPE_OK; +} + + +/* Attach routine */ +static t_stat m2sio_attach(UNIT *uptr, const char *cptr) +{ + DEVICE *dptr; + RES *res; + M2SIO_REG *reg; + t_stat r; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + sim_debug(VERBOSE_MSG, uptr->dptr, "attach (%s).\n", cptr); + + if ((r = tmxr_attach(res->tmxr, uptr, cptr)) == SCPE_OK) { + + if (res->tmxr->ldsc->serport) { + r = m2sio_config_rts(uptr->dptr, reg->rts); /* update RTS */ + } + + res->tmxr->ldsc->rcve = 1; + } + + return r; +} + + +/* Detach routine */ +static t_stat m2sio_detach(UNIT *uptr) +{ + DEVICE *dptr; + RES *res; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + + sim_debug(VERBOSE_MSG, uptr->dptr, "detach.\n"); + + if (uptr->flags & UNIT_ATT) { + sim_cancel(uptr); + + return (tmxr_detach(res->tmxr, uptr)); + } + + return SCPE_UNATT; +} + +static t_stat m2sio_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + if (value == UNIT_M2SIO_CONSOLE) { + s100_bus_console(uptr); + } + else { + s100_bus_noconsole(uptr); + } + + return SCPE_OK; +} + +static t_stat m2sio_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + M2SIO_REG *reg; + int32 baud; + t_stat r = SCPE_ARG; + + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + if (!(uptr->flags & UNIT_ATT)) { + return SCPE_UNATT; + } + + if (cptr != NULL) { + if (sscanf(cptr, "%d", &baud)) { + switch (baud) { + case 110: + case 150: + case 300: + case 1200: + case 1800: + case 2400: + case 4800: + case 9600: + case 19200: + reg->baud = baud; + r = m2sio_config_line(uptr); + + return r; + + default: + break; + } + } + } + + return r; +} + +static t_stat m2sio_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc) +{ + M2SIO_REG *reg; + + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + if (uptr->flags & UNIT_ATT) { + fprintf(st, "Baud rate: %d", reg->baud); + } + + return SCPE_OK; +} + +static t_stat m2sio_config_line(UNIT *uptr) +{ + DEVICE *dptr; + RES *res; + M2SIO_REG *reg; + char config[20]; + const char *fmt; + t_stat r = SCPE_IERR; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + if (reg != NULL) { + switch (reg->ctb & M2SIO_FMTMSK) { + case M2SIO_72E: + fmt = "7E2"; + break; + case M2SIO_72O: + fmt = "7O2"; + break; + case M2SIO_71E: + fmt = "7E1"; + break; + case M2SIO_71O: + fmt = "7O1"; + break; + case M2SIO_82N: + fmt = "8N2"; + break; + case M2SIO_81E: + fmt = "8E1"; + break; + case M2SIO_81O: + fmt = "8O1"; + break; + case M2SIO_81N: + default: + fmt = "8N1"; + break; + } + + sprintf(config, "%d-%s", reg->baud, fmt); + + r = tmxr_set_config_line(res->tmxr->ldsc, config); + + sim_debug(STATUS_MSG, uptr->dptr, "port configuration set to '%s'.\n", config); + } + + return r; +} + +/* +** RTS is active low +** 0 = RTS active +** 1 = RTS inactive +*/ +static t_stat m2sio_config_rts(DEVICE *dptr, char rts) +{ + RES *res; + M2SIO_REG *reg; + t_stat r = SCPE_OK; + int32 s; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + if (dptr->units[0].flags & UNIT_ATT) { + /* RTS Control */ + s = TMXR_MDM_RTS; + if (dptr->units[0].flags & UNIT_M2SIO_DTR) { + s |= TMXR_MDM_DTR; + } + + if (!rts) { + r = tmxr_set_get_modem_bits(res->tmxr->ldsc, s, 0, NULL); + if (reg->rts) { + sim_debug(STATUS_MSG, dptr, "RTS state changed to HIGH.\n"); + } + } else { + r = tmxr_set_get_modem_bits(res->tmxr->ldsc, 0, s, NULL); + if (!reg->rts) { + sim_debug(STATUS_MSG, dptr, "RTS state changed to LOW.\n"); + } + } + } + + reg->rts = rts; /* Active low */ + + return r; +} + +static int32 m2sio0_io(int32 addr, int32 io, int32 data) +{ + return(m2sio_io(&m2sio0_dev, addr, io, data)); +} + +static int32 m2sio1_io(int32 addr, int32 io, int32 data) +{ + return(m2sio_io(&m2sio1_dev, addr, io, data)); +} + +static int32 m2sio_io(DEVICE *dptr, int32 addr, int32 io, int32 data) +{ + int32 r; + + if (addr & 0x01) { + r = m2sio_data(dptr, io, data); + } else { + r = m2sio_stat(dptr, io, data); + } + + return(r); +} + +static int32 m2sio_stat(DEVICE *dptr, int32 io, int32 data) +{ + M2SIO_REG *reg; + int32 r; + + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + if (io == S100_IO_READ) { + r = reg->stb; + } else { + reg->ctb = data & 0xff; /* save control byte */ + + /* Master Reset */ + if ((data & M2SIO_RESET) == M2SIO_RESET) { + sim_debug(STATUS_MSG, dptr, "MC6850 master reset.\n"); + reg->stb &= (M2SIO_CTS | M2SIO_DCD); /* Reset status register */ + reg->rxb = 0x00; + reg->txp = FALSE; + reg->tie = FALSE; + reg->rie = FALSE; + reg->dcdl = FALSE; + m2sio_config_rts(dptr, 1); /* disable RTS */ + } else { + /* Interrupt Enable */ + reg->rie = (data & M2SIO_RIE) == M2SIO_RIE; /* Receive interrupt enable */ + reg->tie = (data & M2SIO_RTSMSK) == M2SIO_RTSLTIE; /* Transmit interrupt enable */ + switch (data & M2SIO_RTSMSK) { + case M2SIO_RTSLTIE: + case M2SIO_RTSLTID: + m2sio_config_rts(dptr, 0); /* enable RTS */ + break; + + case M2SIO_RTSHTID: + case M2SIO_RTSHTBR: + m2sio_config_rts(dptr, 1); /* disable RTS */ + break; + + default: + break; + } + + /* Set data bits, parity and stop bits format */ + m2sio_config_line(&dptr->units[0]); + } + + r = 0x00; + } + + return(r); +} + +static int32 m2sio_data(DEVICE *dptr, int32 io, int32 data) +{ + M2SIO_REG *reg; + int32 r; + + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + if (io == S100_IO_READ) { + r = reg->rxb; + reg->stb &= ~(M2SIO_RDRF | M2SIO_FE | M2SIO_OVRN | M2SIO_PE | M2SIO_IRQ); + reg->dcdl = FALSE; + } else { + reg->txb = data; + reg->stb &= ~(M2SIO_TDRE | M2SIO_IRQ); + reg->txp = TRUE; + r = 0x00; + } + + return r; +} + +static void m2sio_int(UNIT *uptr) +{ + M2SIO_REG *reg; + + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return; + } + + if (reg->intenable) { + s100_bus_int((1 << reg->intvector), reg->databus); /* Generate interrupt on the bus */ + reg->stb |= M2SIO_IRQ; + + sim_debug(IRQ_MSG, uptr->dptr, "%s: IRQ Vector=%d Status=%02X\n", sim_uname(uptr), reg->intvector, reg->stb); + } +} + +static int32 m2sio_map_kbdchar(UNIT *uptr, int32 ch) +{ + ch &= 0xff; + + if (uptr->flags & UNIT_M2SIO_MAP) { + if (uptr->flags & UNIT_M2SIO_BS) { + if (ch == KBD_BS) { + return KBD_DEL; + } + } + else if (ch == KBD_DEL) { + return KBD_BS; + } + + if (uptr->flags & UNIT_M2SIO_UPPER) { + return toupper(ch); + } + } + + return ch; +} + +static t_stat m2sio_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 88-2SIO (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + fprintf(st, "\n\n"); + tmxr_attach_help(st, dptr, uptr, flag, cptr); + + fprintf(st, "----- NOTES -----\n\n"); + fprintf(st, "Only one device may poll the host keyboard for CONSOLE input.\n"); + fprintf(st, "Use SET %s CONSOLE to select this UNIT as the CONSOLE device.\n", sim_dname(dptr)); + fprintf(st, "\nUse SHOW BUS CONSOLE to display the current CONSOLE device.\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/mits_2sio.h b/Altair8800/mits_2sio.h new file mode 100644 index 00000000..7be304c9 --- /dev/null +++ b/Altair8800/mits_2sio.h @@ -0,0 +1,67 @@ +/* mits_2sio.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _MITS_2SIO_H +#define _MITS_2SIO_H + +#define UNIT_V_M2SIO_CONSOLE (UNIT_V_UF + 0) /* Port checks console for input */ +#define UNIT_M2SIO_CONSOLE (1 << UNIT_V_M2SIO_CONSOLE) +#define UNIT_V_M2SIO_MAP (UNIT_V_UF + 1) /* map keyboard characters */ +#define UNIT_M2SIO_MAP (1 << UNIT_V_M2SIO_MAP) +#define UNIT_V_M2SIO_BS (UNIT_V_UF + 2) /* map delete to backspace */ +#define UNIT_M2SIO_BS (1 << UNIT_V_M2SIO_BS) +#define UNIT_V_M2SIO_UPPER (UNIT_V_UF + 3) /* map to upper case */ +#define UNIT_M2SIO_UPPER (1 << UNIT_V_M2SIO_UPPER) +#define UNIT_V_M2SIO_DTR (UNIT_V_UF + 4) /* DTR follows RTS */ +#define UNIT_M2SIO_DTR (1 << UNIT_V_M2SIO_DTR) +#define UNIT_V_M2SIO_DCD (UNIT_V_UF + 5) /* Force DCD active low */ +#define UNIT_M2SIO_DCD (1 << UNIT_V_M2SIO_DCD) +#define UNIT_V_M2SIO_CTS (UNIT_V_UF + 6) /* Force CTS active low */ +#define UNIT_M2SIO_CTS (1 << UNIT_V_M2SIO_CTS) + +typedef struct { + int32 port; /* Port 0 or 1 */ + t_bool conn; /* Connected Status */ + int32 baud; /* Baud rate */ + int32 rts; /* RTS Status */ + int32 rxb; /* Receive Buffer */ + int32 txb; /* Transmit Buffer */ + t_bool txp; /* Transmit Pending */ + int32 stb; /* Status Buffer */ + int32 ctb; /* Control Buffer */ + t_bool rie; /* Rx Int Enable */ + t_bool tie; /* Tx Int Enable */ + t_bool dcdl; /* DCD latch */ + uint8 intenable; /* Interrupt Enable */ + uint8 intvector; /* Interrupt Vector */ + uint8 databus; /* Data Bus Value */ +} M2SIO_REG; + +#endif diff --git a/Altair8800/mits_dsk.c b/Altair8800/mits_dsk.c new file mode 100644 index 00000000..cb07b689 --- /dev/null +++ b/Altair8800/mits_dsk.c @@ -0,0 +1,631 @@ +/* mits_dsk.c: MITS Altair 88-DCDD Simulator + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + Based on work by Charles E Owen (c) 1997 + Based on work by Peter Schorn (c) 2002-2023 + Minidisk support added by Mike Douglas + + History: + 07-Nov-2025 Initial version + + ================================================================== + + The 88-DCDD is a 8-inch floppy controller which can control up + to 16 daisy-chained Pertec FD-400 hard-sectored floppy drives. + Each diskette has physically 77 tracks of 32 137-byte sectors + each. + + The controller is interfaced to the CPU by use of 3 I/O addresses, + standardly, these are device numbers 10, 11, and 12 (octal). + + Address Mode Function + ------- ---- -------- + 10 Out Selects and enables Controller and Drive + 10 In Indicates status of Drive and Controller + 11 Out Controls Disk Function + 11 In Indicates current sector position of disk + 12 Out Write data + 12 In Read data + + Drive Select Out (Device 10 OUT): + + +---+---+---+---+---+---+---+---+ + | C | X | X | X | Device | + +---+---+---+---+---+---+---+---+ + + C = If this bit is 1, the disk controller selected by 'device' is + cleared. If the bit is zero, 'device' is selected as the + device being controlled by subsequent I/O operations. + X = not used + Device = value zero thru 15, selects drive to be controlled. + + Drive Status In (Device 10 IN): + + +---+---+---+---+---+---+---+---+ + | R | Z | I | X | X | H | M | W | + +---+---+---+---+---+---+---+---+ + + W - When 0, write circuit ready to write another byte. + M - When 0, head movement is allowed + H - When 0, indicates head is loaded for read/write + X - not used (will be 0) + I - When 0, indicates interrupts enabled (not used by this simulator) + Z - When 0, indicates head is on track 0 + R - When 0, indicates that read circuit has new byte to read + + Drive Control (Device 11 OUT): + + +---+---+---+---+---+---+---+---+ + | W | C | D | E | U | H | O | I | + +---+---+---+---+---+---+---+---+ + + I - When 1, steps head IN one track + O - When 1, steps head OUT one track + H - When 1, loads head to drive surface + U - When 1, unloads head + E - Enables interrupts (ignored by this simulator) + D - Disables interrupts (ignored by this simulator) + C - When 1 lowers head current (ignored by this simulator) + W - When 1, starts Write Enable sequence: W bit on device 10 + (see above) will go 1 and data will be read from port 12 + until 137 bytes have been read by the controller from + that port. The W bit will go off then, and the sector data + will be written to disk. Before you do this, you must have + stepped the track to the desired number, and waited until + the right sector number is presented on device 11 IN, then + set this bit. + + Sector Position (Device 11 IN): + + As the sectors pass by the read head, they are counted and the + number of the current one is available in this register. + + +---+---+---+---+---+---+---+---+ + | X | X | Sector Number | T | + +---+---+---+---+---+---+---+---+ + + X = Not used + Sector number = binary of the sector number currently under the + head, 0-31. + T = Sector True, is a 0 when the sector is positioned to read or + write. + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "altair8800_dsk.h" +#include "s100_bus.h" +#include "mits_dsk.h" + +static int32 poc = TRUE; /* Power On Clear */ + +/* Debug flags */ +#define IN_MSG (1 << 0) +#define OUT_MSG (1 << 1) +#define READ_MSG (1 << 2) +#define WRITE_MSG (1 << 3) +#define SECTOR_STUCK_MSG (1 << 4) +#define TRACK_STUCK_MSG (1 << 5) +#define VERBOSE_MSG (1 << 6) + +static int32 mdsk10(const int32 port, const int32 io, const int32 data); +static int32 mdsk11(const int32 port, const int32 io, const int32 data); +static int32 mdsk12(const int32 port, const int32 io, const int32 data); + +static t_stat mdsk_boot(int32 unitno, DEVICE *dptr); +static t_stat mdsk_reset(DEVICE *dptr); +static t_stat mdsk_attach(UNIT *uptr, CONST char *cptr); +static t_stat mdsk_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* mdsk_description(DEVICE *dptr); + +/* global data on status */ + +/* currently selected drive (values are 0 .. NUM_OF_DSK) + current_disk < NUM_OF_DSK implies that the corresponding disk is attached to a file */ +static int32 current_disk = NUM_OF_DSK; + +static int32 current_track [NUM_OF_DSK]; +static int32 current_sector [NUM_OF_DSK]; +static int32 current_byte [NUM_OF_DSK]; +static int32 current_flag [NUM_OF_DSK]; +static int32 sectors_per_track [NUM_OF_DSK]; +static int32 current_imageSize [NUM_OF_DSK]; +static int32 tracks [NUM_OF_DSK]; +static int32 in9_count = 0; +static int32 in9_message = FALSE; +static int32 dirty = FALSE; /* TRUE when buffer has unwritten data in it */ +static int32 warnLevelDSK = 3; +static int32 warnLock [NUM_OF_DSK]; +static int32 warnAttached [NUM_OF_DSK]; +static int32 warnDSK10 = 0; +static int32 warnDSK11 = 0; +static int32 warnDSK12 = 0; +static int8 dskbuf[DSK_SECTSIZE]; /* data Buffer */ +static int32 sector_true = 0; /* sector true flag for sector register read */ + +/* 88DSK Standard I/O Data Structures */ + +static UNIT mdsk_unit[NUM_OF_DSK] = { + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) } +}; + +static REG mdsk_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { DRDATAD (DISK, current_disk, 4, + "Selected disk register"), }, + { BRDATAD (CURTRACK, current_track, 10, 32, NUM_OF_DSK, + "Selected track register array"), REG_CIRC + REG_RO }, + { BRDATAD (CURSECTOR, current_sector, 10, 32, NUM_OF_DSK, + "Selected sector register array"), REG_CIRC + REG_RO }, + { BRDATAD (CURBYTE, current_byte, 10, 32, NUM_OF_DSK, + "Current byte register array"), REG_CIRC + REG_RO }, + { BRDATAD (CURFLAG, current_flag, 10, 32, NUM_OF_DSK, + "Current flag register array"), REG_CIRC + REG_RO }, + { BRDATAD (TRACKS, tracks, 10, 32, NUM_OF_DSK, + "Number of tracks register array"), REG_CIRC }, + { BRDATAD (SECTPERTRACK, sectors_per_track, 10, 32, NUM_OF_DSK, + "Number of sectors per track register array"), REG_CIRC }, + { BRDATAD (IMAGESIZE, current_imageSize, 10, 32, NUM_OF_DSK, + "Size of disk image array"), REG_CIRC + REG_RO }, + { DRDATAD (IN9COUNT, in9_count, 4, + "Count of IN(9) register"), REG_RO }, + { DRDATAD (IN9MESSAGE, in9_message, 4, + "BOOL for IN(9) message register"), REG_RO }, + { DRDATAD (DIRTY, dirty, 4, + "BOOL for write needed register"), REG_RO }, + { DRDATAD (DSKWL, warnLevelDSK, 32, + "Warn level register") }, + { BRDATAD (WARNLOCK, warnLock, 10, 32, NUM_OF_DSK, + "Count of write to locked register array"), REG_CIRC + REG_RO }, + { BRDATAD (WARNATTACHED, warnAttached, 10, 32, NUM_OF_DSK, + "Count for selection of unattached disk register array"), REG_CIRC + REG_RO }, + { DRDATAD (WARNDSK10, warnDSK10, 4, + "Count of IN(8) on unattached disk register"), REG_RO }, + { DRDATAD (WARNDSK11, warnDSK11, 4, + "Count of IN/OUT(9) on unattached disk register"), REG_RO }, + { DRDATAD (WARNDSK12, warnDSK12, 4, + "Count of IN/OUT(10) on unattached disk register"), REG_RO }, + { BRDATAD (DISKBUFFER, dskbuf, 10, 8, DSK_SECTSIZE, + "Disk data buffer array"), REG_CIRC + REG_RO }, + { NULL } +}; + +#define DSK_NAME "MITS 88-DCDD Floppy Disk Controller" +#define DEV_NAME "DSK" + +static const char* mdsk_description(DEVICE *dptr) { + return DSK_NAME; +} + +static MTAB mdsk_mod[] = { + { UNIT_DSK_WLK, 0, "WRTENB", "WRTENB", NULL, NULL, NULL, + "Enables " DSK_NAME "n for writing" }, + { UNIT_DSK_WLK, UNIT_DSK_WLK, "WRTLCK", "WRTLCK", NULL, NULL, NULL, + "Locks " DSK_NAME "n for writing" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB mdsk_dt[] = { + { "IN", IN_MSG, "IN operations" }, + { "OUT", OUT_MSG, "OUT operations" }, + { "READ", READ_MSG, "Read operations" }, + { "WRITE", WRITE_MSG, "Write operations" }, + { "SECTOR_STUCK", SECTOR_STUCK_MSG, "Sector stuck" }, + { "TRACK_STUCK", TRACK_STUCK_MSG, "Track stuck" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +DEVICE mdsk_dev = { + DEV_NAME, mdsk_unit, mdsk_reg, mdsk_mod, + NUM_OF_DSK, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &mdsk_reset, + &mdsk_boot, &mdsk_attach, NULL, + NULL, (DEV_DISABLE | DEV_DEBUG), 0, + mdsk_dt, NULL, NULL, &mdsk_show_help, &dsk_attach_help, NULL, + &mdsk_description +}; + +static const char* selectInOut(const int32 io) { + return io == 0 ? "IN" : "OUT"; +} + +/* service routines to handle simulator functions */ +/* reset routine */ + +static t_stat mdsk_reset(DEVICE *dptr) +{ + int32 i; + + if (dptr->flags & DEV_DIS) { + s100_bus_remio(0x08, 1, &mdsk10); + s100_bus_remio(0x09, 1, &mdsk11); + s100_bus_remio(0x0A, 1, &mdsk12); + + poc = TRUE; + } + else { + if (poc) { + s100_bus_addio(0x08, 1, &mdsk10, dptr->name); + s100_bus_addio(0x09, 1, &mdsk11, dptr->name); + s100_bus_addio(0x0A, 1, &mdsk12, dptr->name); + + for (i = 0; i < NUM_OF_DSK; i++) { + current_imageSize[i] = 0; + sectors_per_track[i] = DSK_SECT; + tracks[i] = MAX_TRACKS; + } + } + } + + for (i = 0; i < NUM_OF_DSK; i++) { + warnLock[i] = 0; + warnAttached[i] = 0; + current_track[i] = 0; + current_sector[i] = 0; + current_byte[i] = 0; + current_flag[i] = 0; + } + + warnDSK10 = 0; + warnDSK11 = 0; + warnDSK12 = 0; + current_disk = NUM_OF_DSK; + in9_count = 0; + in9_message = FALSE; + + return SCPE_OK; +} +/* mdsk_attach - determine type of drive attached based on disk image size */ + +static t_stat mdsk_attach(UNIT *uptr, CONST char *cptr) +{ + int32 thisUnitIndex; + int32 imageSize; + t_stat r; + + sim_switches |= SWMASK ('E'); /* File must exist */ + + r = attach_unit(uptr, cptr); /* attach unit */ + + if (r != SCPE_OK) { /* error? */ + return r; + } + + ASSURE(uptr != NULL); + thisUnitIndex = sys_find_unit_index(uptr); + ASSURE((0 <= thisUnitIndex) && (thisUnitIndex < NUM_OF_DSK)); + + /* If the file size is close to the mini-disk image size, set the number of + tracks to 16, otherwise, 32 sectors per track. */ + + imageSize = sim_fsize(uptr -> fileref); + current_imageSize[thisUnitIndex] = imageSize; + sectors_per_track[thisUnitIndex] = (((MINI_DISK_SIZE - MINI_DISK_DELTA < imageSize) && + (imageSize < MINI_DISK_SIZE + MINI_DISK_DELTA)) ? + MINI_DISK_SECT : DSK_SECT); + return SCPE_OK; +} + +static t_stat mdsk_boot(int32 unitno, DEVICE *dptr) +{ + *((int32 *) sim_PC->loc) = 0xff00; + return SCPE_OK; +} + +static int32 dskseek(const UNIT *xptr) +{ + return sim_fseek(xptr -> fileref, DSK_SECTSIZE * sectors_per_track[current_disk] * current_track[current_disk] + + DSK_SECTSIZE * current_sector[current_disk], SEEK_SET); +} + +/* precondition: current_disk < NUM_OF_DSK */ +static void writebuf(void) +{ + int32 i, rtn; + UNIT *uptr; + i = current_byte[current_disk]; /* null-fill rest of sector if any */ + while (i < DSK_SECTSIZE) + dskbuf[i++] = 0; + uptr = mdsk_dev.units + current_disk; + if (((uptr -> flags) & UNIT_DSK_WLK) == 0) { /* write enabled */ + sim_debug(WRITE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " OUT 0x0a (WRITE) D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + if (dskseek(uptr)) { + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " fseek failed D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + } + rtn = sim_fwrite(dskbuf, 1, DSK_SECTSIZE, uptr -> fileref); + if (rtn != DSK_SECTSIZE) { + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " sim_fwrite failed T%d S%d Return=%d\n", + current_disk, s100_bus_get_addr(), current_track[current_disk], + current_sector[current_disk], rtn); + } + } else if ( (mdsk_dev.dctrl & VERBOSE_MSG) && (warnLock[current_disk] < warnLevelDSK) ) { + /* write locked - print warning message if required */ + warnLock[current_disk]++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " Attempt to write to locked DSK%d - ignored.\n", + current_disk, s100_bus_get_addr(), current_disk); + } + current_flag[current_disk] &= 0xfe; /* ENWD off */ + current_byte[current_disk] = 0xff; + dirty = FALSE; +} + +/* I/O instruction handlers, called from the CPU module when an + IN or OUT instruction is issued. + + Each function is passed an 'io' flag, where 0 means a read from + the port, and 1 means a write to the port. On input, the actual + input is passed as the return value, on output, 'data' is written + to the device. +*/ + +/* Disk Controller Status/Select */ + +/* IMPORTANT: The status flags read by port 8 IN instruction are + INVERTED, that is, 0 is true and 1 is false. To handle this, the + simulator keeps it's own status flags as 0=false, 1=true; and + returns the COMPLEMENT of the status flags when read. This makes + setting/testing of the flag bits more logical, yet meets the + simulation requirement that they are reversed in hardware. +*/ + +static int32 mdsk10(const int32 port, const int32 io, const int32 data) +{ + int32 current_disk_flags; + in9_count = 0; + if (io == 0) { /* IN: return flags */ + if (current_disk >= NUM_OF_DSK) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK10 < warnLevelDSK)) { + warnDSK10++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt of IN 0x08 on unattached disk - ignored.\n", + current_disk, s100_bus_get_addr()); + } + return 0xff; /* no drive selected - can do nothing */ + } + return (~current_flag[current_disk]) & 0xff; /* return the COMPLEMENT! */ + } + + /* OUT: Controller set/reset/enable/disable */ + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + sim_debug(OUT_MSG, &mdsk_dev, "DSK%i: " ADDRESS_FORMAT " OUT 0x08: %x\n", current_disk, s100_bus_get_addr(), data); + current_disk = data & NUM_OF_DSK_MASK; /* 0 <= current_disk < NUM_OF_DSK */ + current_disk_flags = (mdsk_dev.units + current_disk) -> flags; + if ((current_disk_flags & UNIT_ATT) == 0) { /* nothing attached? */ + if ( (mdsk_dev.dctrl & VERBOSE_MSG) && (warnAttached[current_disk] < warnLevelDSK) ) { + warnAttached[current_disk]++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt to select unattached DSK%d - ignored.\n", + current_disk, s100_bus_get_addr(), current_disk); + } + current_disk = NUM_OF_DSK; + } else { + current_sector[current_disk] = 0xff; /* reset internal counters */ + current_byte[current_disk] = 0xff; + if (data & 0x80) /* disable drive? */ + current_flag[current_disk] = 0; /* yes, clear all flags */ + else { /* enable drive */ + current_flag[current_disk] = 0x1a; /* move head true */ + if (current_track[current_disk] == 0) /* track 0? */ + current_flag[current_disk] |= 0x40; /* yes, set track 0 true as well */ + if (sectors_per_track[current_disk] == MINI_DISK_SECT) /* drive enable loads head for Minidisk */ + current_flag[current_disk] |= 0x84; + } + } + return 0; /* ignored since OUT */ +} + +/* Disk Drive Status/Functions */ + +static int32 mdsk11(const int32 port, const int32 io, const int32 data) +{ + if (current_disk >= NUM_OF_DSK) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK11 < warnLevelDSK)) { + warnDSK11++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt of %s 0x09 on unattached disk - ignored.\n", + current_disk, s100_bus_get_addr(), selectInOut(io)); + } + return 0xff; /* no drive selected - can do nothing */ + } + + /* now current_disk < NUM_OF_DSK */ + if (io == 0) { /* read sector position */ + in9_count++; + if ((mdsk_dev.dctrl & SECTOR_STUCK_MSG) && (in9_count > 2 * DSK_SECT) && (!in9_message)) { + in9_message = TRUE; + sim_debug(SECTOR_STUCK_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " Looping on sector find.\n", + current_disk, s100_bus_get_addr()); + } + sim_debug(IN_MSG, &mdsk_dev, "DSK%i: " ADDRESS_FORMAT " IN 0x09\n", current_disk, s100_bus_get_addr()); + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + if (current_flag[current_disk] & 0x04) { /* head loaded? */ + sector_true ^= 1; /* return sector true every other entry */ + if (sector_true == 0) { /* true when zero */ + current_sector[current_disk]++; + if (current_sector[current_disk] >= sectors_per_track[current_disk]) + current_sector[current_disk] = 0; + current_byte[current_disk] = 0xff; + } + return (((current_sector[current_disk] << 1) & 0x3e) /* return sector number and...) */ + | 0xc0 | sector_true); /* sector true, and set 'unused' bits */ + } else + return 0xff; /* head not loaded - return 0xff */ + } + + in9_count = 0; + /* drive functions */ + + sim_debug(OUT_MSG, &mdsk_dev, "DSK%i: " ADDRESS_FORMAT " OUT 0x09: %x\n", current_disk, s100_bus_get_addr(), data); + if (data & 0x01) { /* step head in */ + if (current_track[current_disk] == (tracks[current_disk] - 1)) { + sim_debug(TRACK_STUCK_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " Unnecessary step in.\n", + current_disk, s100_bus_get_addr()); + } + current_track[current_disk]++; + current_flag[current_disk] &= 0xbf; /* mwd 1/29/13: track zero now false */ + if (current_track[current_disk] > (tracks[current_disk] - 1)) + current_track[current_disk] = (tracks[current_disk] - 1); + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + current_sector[current_disk] = 0xff; + current_byte[current_disk] = 0xff; + } + + if (data & 0x02) { /* step head out */ + if (current_track[current_disk] == 0) { + sim_debug(TRACK_STUCK_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " Unnecessary step out.\n", + current_disk, s100_bus_get_addr()); + } + current_track[current_disk]--; + if (current_track[current_disk] < 0) { + current_track[current_disk] = 0; + current_flag[current_disk] |= 0x40; /* track 0 if there */ + } + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + current_sector[current_disk] = 0xff; + current_byte[current_disk] = 0xff; + } + + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + + if (data & 0x04) { /* head load */ + current_flag[current_disk] |= 0x04; /* turn on head loaded bit */ + current_flag[current_disk] |= 0x80; /* turn on 'read data available' */ + } + + if ((data & 0x08) && (sectors_per_track[current_disk] != MINI_DISK_SECT)) { /* head unload */ + current_flag[current_disk] &= 0xfb; /* turn off 'head loaded' bit */ + current_flag[current_disk] &= 0x7f; /* turn off 'read data available' */ + current_sector[current_disk] = 0xff; + current_byte[current_disk] = 0xff; + } + + /* interrupts & head current are ignored */ + + if (data & 0x80) { /* write sequence start */ + current_byte[current_disk] = 0; + current_flag[current_disk] |= 0x01; /* enter new write data on */ + } + return 0; /* ignored since OUT */ +} + +/* Disk Data In/Out */ + +static int32 mdsk12(const int32 port, const int32 io, const int32 data) +{ + int32 i, rtn; + UNIT *uptr; + + if (current_disk >= NUM_OF_DSK) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK12 < warnLevelDSK)) { + warnDSK12++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt of %s 0x0a on unattached disk - ignored.\n", + current_disk, s100_bus_get_addr(), selectInOut(io)); + } + return 0; + } + + /* now current_disk < NUM_OF_DSK */ + in9_count = 0; + uptr = mdsk_dev.units + current_disk; + if (io == 0) { + if (current_byte[current_disk] >= DSK_SECTSIZE) { + /* physically read the sector */ + sim_debug(READ_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " IN 0x0a (READ) D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + for (i = 0; i < DSK_SECTSIZE; i++) + dskbuf[i] = 0; + if (dskseek(uptr)) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK12 < warnLevelDSK)) { + warnDSK12++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " fseek error D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + } + } + rtn = sim_fread(dskbuf, 1, DSK_SECTSIZE, uptr -> fileref); + if (rtn != DSK_SECTSIZE) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK12 < warnLevelDSK)) { + warnDSK12++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " sim_fread error D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + } + } + current_byte[current_disk] = 0; + } + return dskbuf[current_byte[current_disk]++] & 0xff; + } else { + if (current_byte[current_disk] >= DSK_SECTSIZE) + writebuf(); /* from above we have that current_disk < NUM_OF_DSK */ + else { + dirty = TRUE; /* this guarantees for the next call to writebuf that current_disk < NUM_OF_DSK */ + dskbuf[current_byte[current_disk]++] = data & 0xff; + } + return 0; /* ignored since OUT */ + } +} + +static t_stat mdsk_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 88-DCDD (%s)\n", sim_dname(dptr)); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/mits_dsk.h b/Altair8800/mits_dsk.h new file mode 100644 index 00000000..de843e5e --- /dev/null +++ b/Altair8800/mits_dsk.h @@ -0,0 +1,57 @@ +/* mits_dsk.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _MITS_DSK_H +#define _MITS_DSK_H + +#define UNIT_V_DSK_WLK (UNIT_V_UF + 0) /* write locked */ +#define UNIT_DSK_WLK (1 << UNIT_V_DSK_WLK) + +#define NUM_OF_DSK 4 /* NUM_OF_DSK must be power of two */ +#define NUM_OF_DSK_MASK (NUM_OF_DSK - 1) + +#define DSK_SECTSIZE 137 /* size of sector */ +#define DSK_SECT 32 /* sectors per track */ +#define MAX_TRACKS 2048 /* number of tracks, + original Altair has 77 tracks only */ +#define DSK_TRACSIZE (DSK_SECTSIZE * DSK_SECT) +#define MAX_DSK_SIZE (DSK_TRACSIZE * MAX_TRACKS) +#define BOOTROM_SIZE_DSK 256 /* size of boot rom */ + +#define MINI_DISK_SECT 16 /* mini disk sectors per track */ +#define MINI_DISK_TRACKS 35 /* number of tracks on mini disk */ +#define MINI_DISK_SIZE (MINI_DISK_TRACKS * MINI_DISK_SECT * DSK_SECTSIZE) +#define MINI_DISK_DELTA 4096 /* threshold for detecting mini disks */ + +#define ALTAIR_DISK_SIZE 337664 /* size of regular Altair disks */ +#define ALTAIR_DISK_DELTA 256 /* threshold for detecting regular Altair disks */ + +#endif + diff --git a/Altair8800/s100_bram.c b/Altair8800/s100_bram.c new file mode 100644 index 00000000..2388b1de --- /dev/null +++ b/Altair8800/s100_bram.c @@ -0,0 +1,484 @@ +/* s100_bram.c: MITS Altair 8800 Banked RAM + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "s100_bus.h" +#include "s100_bram.h" + +static t_stat bram_reset (DEVICE *dptr); +static t_stat bram_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); +static t_stat bram_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); +static int32 bram_io (const int32 addr, const int32 rw, const int32 data); +static int32 bram_memio (const int32 addr, const int32 rw, const int32 data); +static t_stat bram_set_banks (int32 banks); +static t_stat bram_clear_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat bram_enable_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat bram_randomize_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat bram_banks_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static void bram_addio (int32 type); +static void bram_remio (int32 type); +static t_stat bram_set_type (int32 type); +static t_stat bram_type_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static void bram_clear (void); +static void bram_randomize (void); +static t_stat bram_show_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* bram_description (DEVICE *dptr); + +static void PutBYTE(register uint32 Addr, const register uint32 Value); +static uint32 GetBYTE(register uint32 Addr); + +static int32 poc = TRUE; /* Power On Clear */ + +static int32 *M = NULL; /* RAM */ +static int32 bram_banks = 0; +static int32 bram_bank = 0; +static int32 bram_type = BRAM_TYPE_NONE; + +static BRAM B[BRAM_TYPE_MAX + 1] = { + { 0x00, 0, 0, "NONE" }, + { 0xff, 1, 8, "ERAM" }, + { 0x40, 1, 8, "VRAM" }, + { 0x40, 1, 7, "CRAM" }, + { 0xc0, 1, MAXBANK, "HRAM" }, + { 0x40, 1, MAXBANK, "B810" }, +}; + +#define DEV_NAME "BRAM" + +static const char* bram_description(DEVICE *dptr) { + return "Banked Random Access Memory"; +} + +static UNIT bram_unit = { + UDATA (NULL, UNIT_FIX | UNIT_BINK, MAXBANKSIZE) +}; + +static REG bram_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { HRDATAD (BANK, bram_bank, MAXBANKS2LOG, "Selected bank"), }, + { DRDATAD (BANKS, bram_banks, 8, "Number of banks"), }, + { DRDATAD (TYPE, bram_type, 8, "RAM type"), }, + { NULL } +}; + +static MTAB bram_mod[] = { + { UNIT_BRAM_VERBOSE, UNIT_BRAM_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_BRAM_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_B810 , NULL, "B810", &bram_type_command, + NULL, NULL, "Sets the RAM type to Digital Design B810" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_CRAM , NULL, "CRAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to Cromemco" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_ERAM , NULL, "ERAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to SD Systems ExpandoRAM" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_HRAM , NULL, "HRAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to NorthStar" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_VRAM , NULL, "VRAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to Vector" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_NONE , NULL, "NONE", &bram_type_command, + NULL, NULL, "Sets the RAM type to NONE" }, + + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "BANKS={1-16}", &bram_banks_command, + NULL, NULL, "Sets the RAM size" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, NULL, "ADDPAGE={PAGE | START-END | ALL}", &bram_enable_command, + NULL, NULL, "Enable RAM page(s)" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "REMPAGE={PAGE | START-END | ALL}", &bram_enable_command, + NULL, NULL, "Disable RAM page(s)" }, + { MTAB_VDV, 0, NULL, "CLEAR", &bram_clear_command, + NULL, NULL, "Sets RAM to 0x00" }, + { MTAB_VDV, 0, NULL, "RANDOMIZE", &bram_randomize_command, + NULL, NULL, "Sets RAM to random values" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB bram_dt[] = { + { NULL, 0 } +}; + +DEVICE bram_dev = { + DEV_NAME, /* name */ + &bram_unit, /* units */ + bram_reg, /* registers */ + bram_mod, /* modifiers */ + 1, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* addr increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + &bram_ex, /* examine routine */ + &bram_dep, /* deposit routine */ + &bram_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* context */ + (DEV_DISABLE | DEV_DIS), /* flags */ + 0, /* debug control */ + bram_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &bram_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context available to help routines */ + &bram_description /* device description */ +}; + +static t_stat bram_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { /* Disable Device */ + bram_set_type(BRAM_TYPE_NONE); + + poc = TRUE; + } + else { + if (poc) { + poc = FALSE; + } + else { + bram_bank = 0; + } + } + + return SCPE_OK; +} + +/* memory examine */ +static t_stat bram_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) +{ + *vptr = GetBYTE(addr & ADDRMASK) & DATAMASK; + + return SCPE_OK; +} + +/* memory deposit */ +static t_stat bram_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) +{ + PutBYTE(addr & ADDRMASK, val & DATAMASK); + + return SCPE_OK; +} + +static int32 bram_io(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_WRITE) { + + switch (bram_type) { + case BRAM_TYPE_HRAM: + if (data >= 0 && data < B[bram_type].banks) { + bram_bank = data; + } else { + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + } + break; + + case BRAM_TYPE_B810: + if (data >= 0 && data < B[bram_type].banks) { + bram_bank = data; + } else { + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + } + break; + + case BRAM_TYPE_ERAM: + if (data >= 0 && data < B[bram_type].banks) { + bram_bank = data; + if (bram_unit.flags & UNIT_BRAM_VERBOSE) { + sim_printf("%s selecting bank %d\n", B[bram_type].name, data); + } + } else { + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + } + break; + + case BRAM_TYPE_VRAM: + switch(data & 0xFF) { + case 0x01: + case 0x41: // OASIS uses this for some reason? */ + bram_bank = 0; + break; + case 0x02: + case 0x42: // OASIS uses this for some reason? */ + bram_bank = 1; + break; + case 0x04: + bram_bank = 2; + break; + case 0x08: + bram_bank = 3; + break; + case 0x10: + bram_bank = 4; + break; + case 0x20: + bram_bank = 5; + break; + case 0x40: + bram_bank = 6; + break; + case 0x80: + bram_bank = 7; + break; + default: + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + break; + } + break; + + case BRAM_TYPE_CRAM: + switch(data & 0x7F) { + case 0x01: + bram_bank = 0; + break; + case 0x02: + bram_bank = 1; + break; + case 0x04: + bram_bank = 2; + break; + case 0x08: + bram_bank = 3; + break; + case 0x10: + bram_bank = 4; + break; + case 0x20: + bram_bank = 5; + break; + case 0x40: + bram_bank = 6; + break; + default: + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + break; + } + break; + + default: + break; + } + } + + return DATAMASK; +} + +static int32 bram_memio(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_READ) { + return GetBYTE(addr); + } + + PutBYTE(addr, data); + + return DATAMASK; +} + +static uint32 GetBYTE(register uint32 Addr) +{ + t_addr bankAddr; + + if (M != NULL) { + Addr &= ADDRMASK; + + bankAddr = Addr + (bram_bank * MAXBANKSIZE); + + return M[bankAddr] & DATAMASK; + } + + return DATAMASK; +} + +static void PutBYTE(register uint32 Addr, const register uint32 Value) +{ + t_addr bankAddr; + + if (M != NULL) { + Addr &= ADDRMASK; + + bankAddr = Addr + (bram_bank * MAXBANKSIZE); + + M[bankAddr] = Value & DATAMASK; + } +} + +static void bram_addio(int32 type) +{ + if (type > BRAM_TYPE_NONE && type <= BRAM_TYPE_MAX) { + if (B[type].size) { + s100_bus_addio_out(B[type].baseport, B[type].size, &bram_io, B[type].name); + } + } +} + +static void bram_remio(int32 type) +{ + if (type > BRAM_TYPE_NONE && type <= BRAM_TYPE_MAX) { + s100_bus_remio_out(B[type].baseport, B[type].size, &bram_io); + } +} + +static t_stat bram_set_type(int32 type) +{ + if (bram_type == type) { /* No change */ + return SCPE_OK; + } + + bram_remio(bram_type); /* Changing type - remove previous IO */ + + bram_type = type; + bram_bank = 0; + + bram_set_banks(B[bram_type].banks); + bram_addio(bram_type); + + return SCPE_OK; +} + +static t_stat bram_type_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return bram_set_type(value); +} + +static t_stat bram_set_banks(int32 banks) { + if (banks > 0 && banks <= MAXBANK) { + M = realloc(M, banks * MAXBANKSIZE); + } + else if (M != NULL) { + free(M); + + M = NULL; + + s100_bus_remmem(0x0000, MAXBANKSIZE, &bram_memio); /* Remove enabled pages */ + } + + bram_banks = banks; + + return SCPE_OK; +} + +static t_stat bram_banks_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) { + int32 result, banks; + + if (cptr == NULL) { + sim_printf("Banks must be provided as SET %s BANKS=1-%d\n", DEV_NAME, MAXBANK); + return SCPE_ARG | SCPE_NOMESSAGE; + } + + result = sscanf(cptr, "%i", &banks); + + if (result == 1 && banks && banks <= MAXBANK) { + return bram_set_banks(banks); + } + + return SCPE_ARG | SCPE_NOMESSAGE; +} + +static t_stat bram_enable_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) { + int32 size; + t_addr start, end; + + if (cptr == NULL) { + sim_printf("Memory page(s) must be provided as SET %s [ADD|REM]PAGE=E0-EF\n", DEV_NAME); + return SCPE_ARG | SCPE_NOMESSAGE; + } + + if (get_range(NULL, cptr, &start, &end, 16, PAGEMASK, 0) == NULL) { + return SCPE_ARG; + } + + if (start < MAXPAGE) { + start = start << LOG2PAGESIZE; + } + if (end < MAXPAGE) { + end = end << LOG2PAGESIZE; + } + + start &= 0xff00; + end &= 0xff00; + + size = end - start + PAGESIZE; + + if (value) { + s100_bus_addmem(start, size, &bram_memio, DEV_NAME); /* Add pages */ + } + else { + s100_bus_remmem(start, size, &bram_memio); /* Remove pages */ + } + + return SCPE_OK; +} + +static t_stat bram_clear_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + bram_clear(); + + return SCPE_OK; +} + +static t_stat bram_randomize_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + bram_randomize(); + + return SCPE_OK; +} + +static void bram_clear() +{ + int32 i; + + for (i = 0; i < MAXBANKSIZE; i++) { + M[i] = 0; + } +} + +static void bram_randomize() +{ + int32 i; + + for (i = 0; i < bram_banks * MAXBANKSIZE; i++) { + if (M != NULL) { + M[i] = sim_rand() & DATAMASK; + } + } +} + +static t_stat bram_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 Banked RAM (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_bram.h b/Altair8800/s100_bram.h new file mode 100644 index 00000000..f25d15a9 --- /dev/null +++ b/Altair8800/s100_bram.h @@ -0,0 +1,57 @@ +/* s100_bram.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_BRAM_H +#define _S100_BRAM_H + +#include "sim_defs.h" + +#define UNIT_BRAM_V_VERBOSE (UNIT_V_UF+0) /* Enable verbose messagesto */ +#define UNIT_BRAM_VERBOSE (1 << UNIT_BRAM_V_VERBOSE) + +/* Supported Memory Boards */ + +#define BRAM_TYPE_NONE 0 /* No type selected */ +#define BRAM_TYPE_ERAM 1 /* SD Systems ExpandoRAM */ +#define BRAM_TYPE_VRAM 2 /* Vector Graphic RAM card */ +#define BRAM_TYPE_CRAM 3 /* Cromemco RAM card */ +#define BRAM_TYPE_HRAM 4 /* North Start Horizon RAM card */ +#define BRAM_TYPE_B810 5 /* AB Digital Design B810 RAM card */ +#define BRAM_TYPE_MAX BRAM_TYPE_B810 /* Maximum type */ + +typedef struct { + int32 baseport; /* Base IO address */ + int32 size; /* Number of addresses */ + int32 banks; /* Number of banks */ + char *name; /* Short name */ +} BRAM; + +#endif + diff --git a/Altair8800/s100_bus.c b/Altair8800/s100_bus.c new file mode 100644 index 00000000..cdbcd1a3 --- /dev/null +++ b/Altair8800/s100_bus.c @@ -0,0 +1,988 @@ +/* s100_bus.c - S100 Bus Simulator + + Copyright (c) 2025, Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "s100_z80.h" +#include "s100_bus.h" + +static t_stat bus_reset (DEVICE *dptr); +static t_stat bus_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); +static t_stat bus_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); +static t_stat bus_cmd_memory (int32 flag, CONST char *cptr); +static t_stat bus_show_config (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat bus_show_console (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat bus_show_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static t_stat bus_hexload_command (int32 flag, CONST char *cptr); +static t_stat bus_hexsave_command (int32 flag, CONST char *cptr); +static t_stat hexload (const char *filename, t_addr bias); +static t_stat hexsave (FILE *outFile, t_addr start, t_addr end); + +static ChipType chiptype = CHIP_TYPE_Z80; + +static MDEV mdev_table[MAXPAGE]; /* Active memory table */ +static MDEV mdev_dflt; /* Default memory table */ + +static uint32 bus_addr = 0x0000; + +static int32 poc = TRUE; /* Power On Clear */ + +/* Interrupts */ +uint32 nmiInterrupt = 0x00; /* NMI */ +uint32 vectorInterrupt = 0x00; /* Vector Interrupt bits */ +uint8 dataBus[MAX_INT_VECTORS]; /* Data bus value */ + +/* This is the I/O configuration table. There are 255 possible + device addresses, if a device is plugged to a port it's routine + address is here, 'nulldev' means no device is available +*/ +IDEV idev_in[MAXPAGE]; +IDEV idev_out[MAXPAGE]; + +int32 nulldev(CONST int32 addr, CONST int32 io, CONST int32 data) { return 0xff; } + +/* Which UNIT is the CONSOLE */ +UNIT *bus_console = NULL; + +static CONST char* bus_description(DEVICE *dptr) { + return "S100 Bus"; +} + +static UNIT bus_unit = { + UDATA (NULL, 0, 0) +}; + +static REG bus_reg[] = { + { HRDATAD (WRU, sim_int_char, 8, "Interrupt character pseudo register"), }, + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { HRDATAD(VECINT,vectorInterrupt, 8, "Vector Interrupt pseudo register"), }, + { BRDATAD (DATABUS, dataBus, 16, 8, MAX_INT_VECTORS, "Data bus pseudo register"), REG_RO + REG_CIRC }, + { HRDATAD(NMI, nmiInterrupt, 1, "NMI Interrupt pseudo register"), }, + { NULL } +}; + +static MTAB bus_mod[] = { + { UNIT_BUS_VERBOSE, UNIT_BUS_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_BUS_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "CONFIG", NULL, NULL, &bus_show_config, NULL, "Show BUS configuration" }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "CONSOLE", NULL, NULL, &bus_show_console, NULL, "Show CONSOLE unit" }, + + { 0 } +}; + +static DEBTAB bus_dt[] = { + { NULL, 0 } +}; + +DEVICE bus_dev = { + "BUS", &bus_unit, bus_reg, bus_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + &bus_ex, &bus_dep, &bus_reset, + NULL, NULL, NULL, + NULL, 0, 0, + bus_dt, NULL, NULL, &bus_show_help, NULL, NULL, &bus_description +}; + +/* Simulator-specific commands */ +static CTAB bus_cmd_tbl[] = { + { "REG", &z80_cmd_reg, 0, "REG Display registers\n" }, + { "MEM", &bus_cmd_memory, 0, "MEM
Dump a block of memory\n" }, + { "HEXLOAD", &bus_hexload_command, 0, "HEXLOAD [fname] Load Intel hex file\n" }, + { "HEXSAVE", &bus_hexsave_command, 0, "HEXSAVE [fname] [start-end] Save Intel hex file\n" }, + { NULL, NULL, 0, NULL } +}; + +/* bus reset */ +static t_stat bus_reset(DEVICE *dptr) { + int i; + + if (poc) { + sim_vm_cmd = bus_cmd_tbl; + + /* Clear MEM and IO table */ + for (i = 0; i < MAXPAGE; i++) { + mdev_table[i].routine = &nulldev; + mdev_table[i].name = "nulldev"; + + mdev_dflt.routine = &nulldev; + mdev_dflt.name = "nulldev"; + + idev_in[i].routine = &nulldev; + idev_in[i].name = "nulldev"; + idev_out[i].routine = &nulldev; + idev_out[i].name = "nulldev"; + } + + poc = FALSE; + } + + return SCPE_OK; +} + +/* memory examine */ +static t_stat bus_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) +{ + *vptr = s100_bus_memr(addr & ADDRMASK); + + return SCPE_OK; +} + +/* memory deposit */ +static t_stat bus_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) +{ + s100_bus_memw(addr & ADDRMASK, val); + + return SCPE_OK; +} + +static t_stat bus_show_config(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + CONST char *last = NULL; + int i, spage, epage; + + /* show memory */ + fprintf(st, "\nMEMORY:\n"); + + for (i = 0; i < MAXPAGE; i++) { + if (mdev_table[i].name != last) { + if (last != NULL) { + fprintf(st, "%04X-%04X: %s\n", spage << LOG2PAGESIZE, (epage << LOG2PAGESIZE) | 0xff, mdev_table[epage].routine != &nulldev ? sys_strupr(last) : ""); + } + + last = mdev_table[i].name; + spage = i; + } + + epage = i; + } + + fprintf(st, "%04X-%04X: %s\n", spage << LOG2PAGESIZE, (epage << LOG2PAGESIZE) | 0xff, mdev_table[epage].routine != &nulldev ? sys_strupr(last) : ""); + + fprintf(st, "\nDefault Memory Device: %s\n", sys_strupr(mdev_dflt.name)); + + /* show which ports are assigned */ + fprintf(st, "\nIO:\n"); + fprintf(st, "PORT %-8.8s %-8.8s\n", "IN", "OUT"); + for (i = 0; i < MAXPAGE; i++) { + if (idev_in[i].routine != &nulldev || idev_out[i].routine != &nulldev) { + fprintf(st, "%02X: ", i); + fprintf(st, "%-8.8s ", sys_strupr(idev_in[i].name)); /* strupr must be output before called again */ + fprintf(st, "%-8.8s\n", sys_strupr(idev_out[i].name)); + } + } + + fprintf(st, "\n"); + + bus_show_console(st, NULL, 0, NULL); + + return SCPE_OK; +} + +static t_stat bus_show_console(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + /* show current CONSOLE unit */ + fprintf(st, "CONSOLE Unit: %s\n", (bus_console == NULL) ? "NONE" : sim_uname(bus_console)); + + return SCPE_OK; +} + +void s100_bus_get_idev(int32 port, IDEV *idev_in, IDEV *idev_out) +{ + if (idev_in != NULL) { + idev_in->routine = idev_in[port & 0xff].routine; + idev_in->name = idev_in[port & 0xff].name; + } + if (idev_out != NULL) { + idev_out->routine = idev_out[port & 0xff].routine; + idev_out->name = idev_out[port & 0xff].name; + } +} + +t_stat s100_bus_addio(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32), CONST char *name) +{ + s100_bus_addio_in(port, size, routine, name); + s100_bus_addio_out(port, size, routine, name); + + return SCPE_OK; +} + +t_stat s100_bus_addio_in(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32), CONST char *name) +{ + int i; + + for (i = port; i < port + size; i++) { + if (bus_unit.flags & UNIT_BUS_VERBOSE) { + sim_printf("Mapping IO %04x IN, handler=%s\n", i, name); + } + + idev_in[i & 0xff].routine = routine; + idev_in[i & 0xff].name = name; + } + + return SCPE_OK; +} + +t_stat s100_bus_addio_out(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32), CONST char *name) +{ + int i; + + for (i = port; i < port + size; i++) { + if (bus_unit.flags & UNIT_BUS_VERBOSE) { + sim_printf("Mapping IO %04x OUT, handler=%s\n", i, name); + } + + idev_out[i & 0xff].routine = routine; + idev_out[i & 0xff].name = name; + } + + return SCPE_OK; +} + + +t_stat s100_bus_remio(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32)) +{ + s100_bus_remio_in(port, size, routine); + s100_bus_remio_out(port, size, routine); + + return SCPE_OK; +} + +t_stat s100_bus_remio_in(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32)) +{ + int i; + + for (i = port; i < port + size; i++) { + if (idev_in[i & 0xff].routine == routine) { + if (bus_unit.flags & UNIT_BUS_VERBOSE) { + sim_printf("Unmapping IO %04x IN, handler=%s\n", i, idev_in[i & 0xff].name); + } + + idev_in[i & 0xff].routine = &nulldev; + idev_in[i & 0xff].name = "nulldev"; + } + } + + return SCPE_OK; +} + +t_stat s100_bus_remio_out(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32)) +{ + int i; + + for (i = port; i < port + size; i++) { + if (idev_out[i & 0xff].routine == routine) { + if (bus_unit.flags & UNIT_BUS_VERBOSE) { + sim_printf("Unmapping IO %04x OUT, handler=%s\n", i, idev_out[i & 0xff].name); + } + + idev_out[i & 0xff].routine = &nulldev; + idev_out[i & 0xff].name = "nulldev"; + } + } + + return SCPE_OK; +} + +void s100_bus_get_mdev(int32 addr, MDEV *mdev) +{ + int32 page; + + page = (addr & ADDRMASK) >> LOG2PAGESIZE; + + if (mdev != NULL) { + mdev->routine = mdev_table[page].routine; + mdev->name = mdev_table[page].name; + } +} + +t_stat s100_bus_addmem(int32 baseaddr, uint32 size, + int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data), CONST char *name) +{ + int32 page; + uint32 i; + + page = (baseaddr & ADDRMASK) >> LOG2PAGESIZE; + + if (size < PAGESIZE) { + size = PAGESIZE; + } + + if (bus_unit.flags & UNIT_BUS_VERBOSE) { + sim_printf("addmem: baseaddr=%04X page=%02X size=%04X LOG2SIZE=%04X name=%s\n", baseaddr, page, size, size >> LOG2PAGESIZE, name); + } + + for (i = 0; i < (size >> LOG2PAGESIZE); i++) { + mdev_table[page + i].routine = routine; + mdev_table[page + i].name = name; + } + + return SCPE_OK; +} + +t_stat s100_bus_setmem_dflt(int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data), CONST char *name) +{ + mdev_dflt.routine = routine; + mdev_dflt.name = name; + + return SCPE_OK; +} + +t_stat s100_bus_remmem(int32 baseaddr, uint32 size, + int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data)) +{ + int32 page; + uint32 i; + + page = (baseaddr & ADDRMASK) >> LOG2PAGESIZE; + + for (i = 0; i < (size >> LOG2PAGESIZE); i++) { + if (mdev_table[page + i].routine == routine) { + mdev_table[page + i].routine = mdev_dflt.routine; + mdev_table[page + i].name = mdev_dflt.name; + } + } + + return SCPE_OK; +} + +t_stat s100_bus_remmem_dflt(int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data)) +{ + if (mdev_dflt.routine == routine) { + mdev_dflt.routine = &nulldev; + mdev_dflt.name = "nulldev"; + } + + return SCPE_OK; +} + +int32 s100_bus_in(int32 port) +{ + return idev_in[port].routine(port, S100_IO_READ, 0); +} + +void s100_bus_out(int32 port, int32 data) +{ + idev_out[port].routine(port, S100_IO_WRITE, data); +} + +int32 s100_bus_memr(t_addr addr) +{ + int32 page; + + page = (addr & ADDRMASK) >> LOG2PAGESIZE; + + return mdev_table[page].routine(addr, S100_IO_READ, 0); +} + +void s100_bus_memw(t_addr addr, int32 data) +{ + int32 page; + + page = (addr & ADDRMASK) >> LOG2PAGESIZE; + + mdev_table[page].routine(addr, S100_IO_WRITE, data); +} + +ChipType s100_bus_set_chiptype(ChipType new) +{ + chiptype = new; + + return chiptype; +} + +ChipType s100_bus_get_chiptype(void) +{ + return chiptype; +} + +uint32 s100_bus_set_addr(uint32 new) +{ + bus_addr = new; + + return bus_addr; +} + +uint32 s100_bus_get_addr(void) +{ + return bus_addr; +} + +uint32 s100_bus_int(int32 vector, int32 data) +{ + vectorInterrupt |= vector; + dataBus[vector] = data; + + return vectorInterrupt; +} + +uint32 s100_bus_get_int(void) +{ + return vectorInterrupt; +} + +uint32 s100_bus_get_int_data(int32 vector) +{ + return dataBus[vector]; +} + +uint32 s100_bus_clr_int(int32 vector) +{ + vectorInterrupt &= ~(1 << vector); + + return vectorInterrupt; +} + +void s100_bus_nmi() +{ + nmiInterrupt = TRUE; +} + +int32 s100_bus_get_nmi() +{ + return nmiInterrupt; +} + +void s100_bus_clr_nmi() +{ + nmiInterrupt = FALSE; +} + +static t_stat bus_cmd_memory(int32 flag, CONST char *cptr) +{ + char abuf[16]; + t_addr lo, hi, last; + t_value byte; + static t_addr disp_addr = 0; + + if (get_range(NULL, cptr, &lo, &hi, 16, ADDRMASK, 0) == NULL) { + lo = hi = disp_addr; + } + else { + disp_addr = lo & ~(0x0f); + } + + if (hi == lo) { + hi = (lo & ~(0x0f)) + 0xff; + } + + last = hi | 0x00000f; + + while (disp_addr <= last && disp_addr <= ADDRMASK) { + + if (!(disp_addr & 0x0f)) { + if (ADDRMASK+1 <= 0x10000) { + sim_printf("%04X ", disp_addr); + } + else { + sim_printf("%02X:%04X ", disp_addr >> 16, disp_addr & 0xffff); + } + } + + if (disp_addr < lo || disp_addr > hi) { + sim_printf(" "); + abuf[disp_addr & 0x0f] = ' '; + } + else { + byte = s100_bus_memr(disp_addr); + sim_printf("%02X ", byte); + abuf[disp_addr & 0x0f] = sim_isprint(byte) ? byte : '.'; + } + + if ((disp_addr & 0x000f) == 0x000f) { + sim_printf("%16.16s\n", abuf); + } + + disp_addr++; + } + + if (disp_addr > ADDRMASK) { + disp_addr = 0; + } + + return SCPE_OK | SCPE_NOMESSAGE; +} + +/* This is the binary loader. The input file is considered to be a string of + literal bytes with no special format. The load starts at the current value + of the PC if no start address is given. If the input string ends with ROM + (not case sensitive) the memory area is made read only. + ALTAIRROM/NOALTAIRROM settings are ignored. +*/ + +t_stat sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag) +{ + int32 i; + uint32 addr, cnt = 0, org; + t_addr j, lo, hi; + CONST char *result; + char gbuf[CBUFSIZE]; + + if (flag) { /* dump ram to file */ + result = get_range(NULL, cptr, &lo, &hi, 16, ADDRMASK, 0); + + if (result == NULL) { + return SCPE_ARG; + } + + for (j = lo; j <= hi; j++) { + if (putc(s100_bus_memr(j & ADDRMASK), fileref) == EOF) { + return SCPE_IOERR; + } + } + sim_printf("%d byte%s dumped [%x - %x] to %s.\n", PLURAL(hi + 1 - lo), lo, hi, fnam); + + return SCPE_OK; + } + + if (*cptr == 0) { + addr = s100_bus_get_addr(); + } + + else { + get_glyph(cptr, gbuf, 0); + + addr = strtotv(cptr, &result, 16) & ADDRMASK; + + if (cptr == result) { + return SCPE_ARG; + } + + while (isspace(*result)) { + result++; + } + } + + /* addr is start address to load to, makeROM == TRUE iff memory should become ROM */ + org = addr; + + while ((addr < MAXBANKSIZE) && ((i = getc(fileref)) != EOF)) { + s100_bus_memw(addr & ADDRMASK, i); + addr++; + cnt++; + } + + sim_printf("%d (%04X) byte%s [%d page%s] loaded at %04X.\n", cnt, PLURAL(cnt), PLURAL((cnt + 0xff) >> 8), org); + + return SCPE_OK; +} + +static t_stat bus_hexload_command(int32 flag, CONST char *cptr) +{ + char filename[4*CBUFSIZE]; + t_addr lo = 0, hi = 0; + + GET_SWITCHES(cptr); /* get switches */ + + if (*cptr == 0) { /* must be more */ + return SCPE_2FARG; + } + + cptr = get_glyph_quoted(cptr, filename, 0); /* get filename */ + sim_trim_endspc(filename); + + if (*cptr != 0) { /* bias available */ + get_range(NULL, cptr, &lo, &hi, ADDRRADIX, 0, 0); + } + + lo &= ADDRMASK; + + hexload(filename, lo); + + return SCPE_OK; +} + +static t_stat bus_hexsave_command(int32 flag, CONST char *cptr) +{ + char filename[4*CBUFSIZE]; + FILE *sfile; + t_addr lo = 0, hi = 0; + + GET_SWITCHES(cptr); /* get switches */ + + if (*cptr == 0) { /* must be more */ + return SCPE_2FARG; + } + + cptr = get_glyph_quoted(cptr, filename, 0); /* get filename */ + sim_trim_endspc(filename); + + if (*cptr == 0) { /* must be more */ + return SCPE_2FARG; + } + + get_range(NULL, cptr, &lo, &hi, ADDRRADIX, 0, 0); + + lo &= ADDRMASK; + hi &= ADDRMASK; + + if (hi < lo) { /* bad addresses */ + return SCPE_ARG; + } + + if ((sfile = sim_fopen(filename, "w")) == NULL) { /* try existing file */ + return SCPE_OPENERR; + } + + hexsave(sfile, lo, hi); + + sim_printf("Output file: %s\n", filename); + + fclose (sfile); + + return SCPE_OK; +} + +/* hexload will load an Intel hex file into RAM. + Based on HEX2BIN by Mike Douglas + https://deramp.com/downloads/misc_software/hex-binary utilities for the PC/ +*/ + +#define INBUF_LEN 600 + +static t_stat hexload(const char *filename, t_addr bias) +{ + FILE *sFile; + char inBuf[INBUF_LEN]; + char dataStr[INBUF_LEN]; + int sRecords = 0; + int byteCount, dataAddr, recType, dataByte, checkSum; + int lowAddr = ADDRMASK; + int highAddr = 0; + char *bufPtr; + + if ((sFile = sim_fopen(filename, "r")) == NULL) { /* try existing file */ + return SCPE_OPENERR; + } + + /* Read the hex or s-record file and put data into a memory image array */ + while (fgets(inBuf, INBUF_LEN, sFile)) { + inBuf[strcspn(inBuf, "\n")] = '\0'; /* end string at new line if present */ + + if (sRecords) { + sscanf(inBuf, "S%1X%2x%4x%s", &recType, &byteCount, &dataAddr, dataStr); + checkSum = byteCount + (dataAddr >> 8) + (dataAddr & 0xff) + 1; + byteCount -= 3; /* make byteCount = data bytes only */ + recType--; /* make S1 match .hex record type 0 */ + } + else { + sscanf(inBuf, ":%2x%4x%2x%s", &byteCount, &dataAddr, &recType, dataStr); + checkSum = byteCount + (dataAddr >> 8) + (dataAddr & 0xff) + recType; + } + + bufPtr = dataStr; + + if ((recType == 0) && (byteCount > 0) && (dataAddr+byteCount <= MAXADDR)) { + if (dataAddr+byteCount > highAddr) { + highAddr = dataAddr + byteCount; + } + + if (dataAddr < lowAddr) { + lowAddr = dataAddr; + } + + do { + sscanf(bufPtr, "%2x", &dataByte); + bufPtr += 2; + s100_bus_memw((dataAddr + bias) & ADDRMASK, dataByte); /* Write to memory */ + dataAddr++; + checkSum += dataByte; + } while (--byteCount != 0); + + sscanf(bufPtr, "%2x", &dataByte); /* checksum byte */ + + if (0 != ((checkSum+dataByte) & 0xff)) { + fprintf(stderr,"Checksum error\n %s\n", inBuf); + fclose (sFile); + + return SCPE_IERR; + } + } + } + + /* Display results */ + if (bias) { + sim_printf("%s: %04X (%04X+%04X)-%04X (%04X+%04X)\n", filename, + (lowAddr + bias) & ADDRMASK, lowAddr, bias, + (highAddr + bias - 1) & ADDRMASK, highAddr, bias); + } + else { + sim_printf("%s %04X-%04X\n", filename, lowAddr, highAddr-1); + } + + fclose (sFile); + + return SCPE_OK; +} + +/* hexsave will load an Intel hex file into RAM. + Based on HEX2BIN by Mike Douglas + https://deramp.com/downloads/misc_software/hex-binary utilities for the PC/ +*/ + +#define LINE_LEN 32 + +static t_stat hexsave(FILE *outFile, t_addr start, t_addr end) +{ + uint8 inBuf[INBUF_LEN]; + int sRecords = 0; + int checkSum; + uint32 i; + t_addr dataAddr; + uint32 byteCount; + + dataAddr = start; + + do { + for (byteCount = 0; byteCount < LINE_LEN && dataAddr + byteCount <= end; byteCount++) { + inBuf[byteCount] = s100_bus_memr(dataAddr + byteCount); + } + + + if (byteCount > 0) { + if (sRecords) { + fprintf(outFile, "S1%02X%04X", byteCount+3, dataAddr); + checkSum = byteCount + (dataAddr >> 8) + (dataAddr & 0xff) + 4; + } + else { + fprintf(outFile, ":%02X%04X00", byteCount, dataAddr); + checkSum = byteCount + (dataAddr >> 8) + (dataAddr & 0xff); + } + + for (i=0; ictxt; + + if (res == NULL) + return SCPE_IERR; + + newba = get_uint (cptr, 16, 0xFFFF, &r); + + if (r != SCPE_OK) + return r; + + if ((newba > 0xFFFF) || (newba % res->mem_size)) + return SCPE_ARG; + + if (dptr->flags & DEV_DIS) { + sim_printf("device not enabled yet.\n"); + res->mem_base = newba & ~(res->mem_size-1); + } else { + dptr->flags |= DEV_DIS; + dptr->reset(dptr); + res->mem_base = newba & ~(res->mem_size-1); + dptr->flags &= ~DEV_DIS; + dptr->reset(dptr); + } + + return SCPE_OK; +} + +/* Show Base Address routine */ +t_stat show_membase(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + DEVICE *dptr; + RES *res; + + if (uptr == NULL) + return SCPE_IERR; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + res = (RES *) dptr->ctxt; + + if (res == NULL) + return SCPE_IERR; + + fprintf(st, "MEM=0x%04X-0x%04X", res->mem_base, res->mem_base + res->mem_size-1); + + return SCPE_OK; +} + +/* Set Memory Base Address routine */ +t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + DEVICE *dptr; + RES *res; + uint32 newba; + t_stat r; + + if (cptr == NULL) + return SCPE_ARG; + + if (uptr == NULL) + return SCPE_IERR; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + res = (RES *) dptr->ctxt; + + if (res == NULL) + return SCPE_IERR; + + newba = get_uint (cptr, 16, 0xFF, &r); + + if (r != SCPE_OK) + return r; + + if ((newba > 0xFF) || (newba % res->io_size)) + return SCPE_ARG; + + if (dptr->flags & DEV_DIS) { + sim_printf("device not enabled yet.\n"); + res->io_base = newba & ~(res->io_size-1); + } else { + dptr->flags |= DEV_DIS; + dptr->reset(dptr); + res->io_base = newba & ~(res->io_size-1); + dptr->flags &= ~DEV_DIS; + dptr->reset(dptr); + } + + return SCPE_OK; +} + +/* Show I/O Base Address routine */ +t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + DEVICE *dptr; + RES *res; + + if (uptr == NULL) + return SCPE_IERR; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + res = (RES *) dptr->ctxt; + + if (res == NULL) + return SCPE_IERR; + + fprintf(st, "I/O=0x%02X-0x%02X", res->io_base, res->io_base + res->io_size-1); + + return SCPE_OK; +} + +/* Set new CONSOLE unit */ +t_stat s100_bus_console(UNIT *uptr) +{ + bus_console = uptr; + + return SCPE_ARG; +} + +/* Set new CONSOLE unit */ +UNIT *s100_bus_get_console() +{ + return bus_console; +} + +t_stat s100_bus_noconsole(UNIT *uptr) +{ + if (bus_console == uptr) { + bus_console = NULL; + + return SCPE_OK; + } + + return SCPE_ARG; +} + +t_stat s100_bus_poll_kbd(UNIT *uptr) +{ + if (bus_console == uptr) { + return sim_poll_kbd(); + } + + return SCPE_OK; +} + +static t_stat bus_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 Bus (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_bus.h b/Altair8800/s100_bus.h new file mode 100644 index 00000000..5165a975 --- /dev/null +++ b/Altair8800/s100_bus.h @@ -0,0 +1,156 @@ +/* s100_bus.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _SIM_BUS_H +#define _SIM_BUS_H + +#include "sim_defs.h" +#include "sim_tmxr.h" + +#define UNIT_BUS_V_VERBOSE (UNIT_V_UF+0) /* warn if ROM is written to */ +#define UNIT_BUS_VERBOSE (1 << UNIT_BUS_V_VERBOSE) + +/* S100 Bus Architecture */ + +#define ADDRWIDTH 16 +#define DATAWIDTH 8 + +#define ADDRRADIX 16 +#define DATARADIX 16 + +#define MAXADDR (1 << ADDRWIDTH) +#define MAXDATA (1 << DATAWIDTH) +#define ADDRMASK (MAXADDR - 1) +#define DATAMASK (MAXDATA - 1) + +#define LOG2PAGESIZE 8 +#define PAGESIZE (1 << LOG2PAGESIZE) + +#define MAXMEMORY MAXADDR +#define MAXBANKSIZE MAXADDR +#define MAXPAGE (MAXADDR >> LOG2PAGESIZE) +#define PAGEMASK (MAXPAGE - 1) + +#define MAXBANK 16 +#define MAXBANKS2LOG 5 + +#define ADDRESS_FORMAT "[0x%08x]" + +/* This is the I/O configuration table. There are 255 possible +device addresses, if a device is plugged to a port it's routine +address is here, 'nulldev' means no device is available +*/ + +#define S100_IO_READ 0 +#define S100_IO_WRITE 1 + +/* Interrupt Vectors */ +#define MAX_INT_VECTORS 32 /* maximum number of interrupt vectors */ + +extern uint32 nmiInterrupt; /* NMI */ +extern uint32 vectorInterrupt; /* Vector Interrupt bits */ +extern uint8 dataBus[MAX_INT_VECTORS]; /* Data bus value */ + +/* + * Generic device resource information. Pointed to by DEVICE *up7 + */ +typedef struct { + uint32 io_base; /* I/O Base Address */ + uint32 io_size; /* I/O Address Space requirement */ + uint32 mem_base; /* Memory Base Address */ + uint32 mem_size; /* Memory Address space requirement */ + TMXR *tmxr; /* TMXR pointer */ +} RES; + +/* data structure for IN/OUT instructions */ +typedef struct idev { + int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data); + CONST char *name; +} IDEV; + +typedef struct { /* Structure to describe memory device address space */ + int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data); + CONST char *name; /* name of handler routine */ +} MDEV; + +extern t_stat s100_bus_addio(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32), CONST char* name); +extern t_stat s100_bus_addio_in(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32), CONST char* name); +extern t_stat s100_bus_addio_out(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32), CONST char* name); +extern t_stat s100_bus_remio(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32)); +extern t_stat s100_bus_remio_in(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32)); +extern t_stat s100_bus_remio_out(int32 port, int32 size, int32 (*routine)(CONST int32, CONST int32, CONST int32)); +extern t_stat s100_bus_addmem(int32 baseaddr, uint32 size, + int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data), CONST char *name); +extern t_stat s100_bus_remmem(int32 baseaddr, uint32 size, + int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data)); +extern t_stat s100_bus_setmem_dflt(int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data), CONST char *name); +extern t_stat s100_bus_remmem_dflt(int32 (*routine)(CONST int32 addr, CONST int32 rw, CONST int32 data)); + +extern void s100_bus_get_idev(int32 port, IDEV *idev_in, IDEV *idev_out); +extern void s100_bus_get_mdev(int32 addr, MDEV *mdev); +extern int32 nulldev(CONST int32 addr, CONST int32 io, CONST int32 data); + +extern uint32 s100_bus_set_addr(uint32 pc); +extern uint32 s100_bus_get_addr(void); + +extern t_stat s100_bus_console(UNIT *uptr); +extern UNIT *s100_bus_get_console(void); +extern t_stat s100_bus_noconsole(UNIT *uptr); +extern t_stat s100_bus_poll_kbd(UNIT *uptr); + +extern int32 s100_bus_in(int32 port); +extern void s100_bus_out(int32 port, int32 data); +extern int32 s100_bus_memr(t_addr addr); +extern void s100_bus_memw(t_addr addr, int32 data); +extern uint32 s100_bus_int(int32 vector, int32 data); +extern uint32 s100_bus_get_int(void); +extern uint32 s100_bus_get_int_data(int32 vector); +extern uint32 s100_bus_clr_int(int32 vector); +extern void s100_bus_nmi(void); +extern int32 s100_bus_get_nmi(void); +extern void s100_bus_clr_nmi(void); + +#define S100_BUS_MEMR 0x01 +#define S100_BUS_MEMW 0x02 +#define S100_BUS_IN 0x04 +#define S100_BUS_OUT 0x08 + +#define RESOURCE_TYPE_MEMORY (S100_BUS_MEMR | S100_BUS_MEMW) +#define RESOURCE_TYPE_IO (S100_BUS_IN | S100_BUS_OUT) + +#define sim_map_resource(a,b,c,d,e,f) s100_map_resource(a,b,c,d,e,f) + +extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern t_stat set_membase(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat show_membase(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern void cpu_raise_interrupt(uint32 irq); + +#endif diff --git a/Altair8800/s100_cpu.c b/Altair8800/s100_cpu.c new file mode 100644 index 00000000..58f1706c --- /dev/null +++ b/Altair8800/s100_cpu.c @@ -0,0 +1,251 @@ +/* s100_cpu.c: MITS Altair CPU Management + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "s100_bus.h" +#include "s100_z80.h" +#include "s100_cpu.h" + +REG *sim_PC; + +static int32 poc = TRUE; /* Power On Clear */ + +static ChipType cpu_type = CHIP_TYPE_8080; + +static char *cpu_chipname[] = { + "Intel 8080", + "Zilog Z80" +}; + +static t_stat (*cpu_instr)(void) = NULL; +static t_stat (*cpu_parse_sym)(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) = NULL; +static int32 (*cpu_dasm)(char *S, const uint32 *val, const int32 addr) = NULL; + +static t_stat cpu_reset(DEVICE *dptr); +static void cpu_set_instr(t_stat (*routine)(void)); +static void cpu_set_pc(REG *reg); +static void cpu_set_pc_value(t_value (*routine)(void)); +static void cpu_set_parse_sym(t_stat (*routine)(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)); +static void cpu_set_dasm(int32 (*routine)(char *S, const uint32 *val, const int32 addr)); +static void cpu_set_is_subroutine_call(t_bool (*routine)(t_addr **ret_addrs)); + +static const char* cpu_description(DEVICE *dptr) { + return "Central Processing Unit"; +} + +static CPU cpu[] = { + { &z80_dev, &z80_pc_reg, &z80_chiptype, &z80_instr, &z80_pc_value, + &z80_parse_sym, &z80_dasm, &z80_is_pc_a_subroutine_call, &z80_show_help }, // 8080 + + { &z80_dev, &z80_pc_reg, &z80_chiptype, &z80_instr, &z80_pc_value, + &z80_parse_sym, &z80_dasm, &z80_is_pc_a_subroutine_call, &z80_show_help }, // Z80 + + { NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + +static UNIT cpu_unit = { + UDATA (NULL, 0, 0) +}; + +static REG cpu_reg[] = { + { NULL } +}; + +static MTAB cpu_mod[] = { + { 0 } +}; + +/* Debug Flags */ +static DEBTAB cpu_dt[] = { + { NULL, 0 } +}; + +DEVICE cpu_dev = { + "CPU", &cpu_unit, cpu_reg, cpu_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &cpu_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE | DEV_DEBUG), 0, + cpu_dt, NULL, NULL, NULL, NULL, NULL, &cpu_description +}; + +static t_stat cpu_reset(DEVICE *dptr) { + if (dptr->flags & DEV_DIS) { /* Disable Device */ + poc = TRUE; + } + else { + cpu_set_instr(cpu[cpu_type].instr); + cpu_set_pc(*cpu[cpu_type].pc_reg); + cpu_set_pc_value(cpu[cpu_type].pc_val); + cpu_set_parse_sym(cpu[cpu_type].parse_sym); + cpu_set_dasm(cpu[cpu_type].dasm); + cpu_set_is_subroutine_call(cpu[cpu_type].isc); + + dptr->units = cpu[cpu_type].dev->units; + dptr->registers = cpu[cpu_type].dev->registers; + dptr->modifiers = cpu[cpu_type].dev->modifiers; + dptr->help = cpu[cpu_type].dev->help; + dptr->help_ctx = cpu[cpu_type].dev->help_ctx; + dptr->description = cpu[cpu_type].dev->description; + + if (poc) { + poc = FALSE; + } + } + + /* Reset selected CPU */ + if (cpu[cpu_type].dev != NULL) { + cpu[cpu_type].dev->reset(cpu[0].dev); + } + + return SCPE_OK; +} + +void cpu_set_chiptype(ChipType new_type) +{ + ChipType old_type = cpu_type; + + if (cpu_type == new_type) { + return; + } + + switch (new_type) { + case CHIP_TYPE_8080: + case CHIP_TYPE_Z80: + cpu_type = new_type; + break; + + default: + break; + } + + if (cpu_dev.units[0].flags & UNIT_CPU_VERBOSE) { + sim_printf("CPU changed from %s to %s\n", cpu_get_chipname(old_type), cpu_get_chipname(new_type)); + } + + /* Install new CPU device */ + if (cpu[cpu_type].chiptype != NULL) { + *cpu[cpu_type].chiptype = cpu_type; + } + + cpu_reset(&cpu_dev); +} + +char * cpu_get_chipname(ChipType type) +{ + return cpu_chipname[type]; +} + +t_stat sim_instr() +{ + t_stat reason = SCPE_NXDEV; + + if (cpu_instr != NULL) { + reason = (*cpu_instr)(); + } + + return reason; +} + +static void cpu_set_instr(t_stat (*routine)(void)) +{ + cpu_instr = routine; +} + +static void cpu_set_pc(REG *reg) +{ + sim_PC = reg; +} + +static void cpu_set_pc_value(t_value (*routine)(void)) +{ + sim_vm_pc_value = routine; +} + +static void cpu_set_parse_sym(t_stat (*routine)(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)) +{ + cpu_parse_sym = routine; +} + +static void cpu_set_dasm(int32 (*routine)(char *S, const uint32 *val, const int32 addr)) +{ + cpu_dasm = routine; +} + +static void cpu_set_is_subroutine_call(t_bool (*routine)(t_addr **ret_addrs)) +{ + sim_vm_is_subroutine_call = routine; +} + +t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw) +{ + char disasm_result[128]; + int32 ch = val[0] & 0x7f; + long r = 1; + if (sw & (SWMASK('A') | SWMASK('C'))) { + fprintf(of, ((0x20 <= ch) && (ch < 0x7f)) ? "'%c'" : "%02x", ch); + return SCPE_OK; + } + if (!(sw & SWMASK('M'))) { + return SCPE_ARG; + } + + if (cpu_dasm != NULL) { + r = cpu_dasm(disasm_result, val, addr); + + fprintf(of, "%s", disasm_result); + } + + return 1 - r; +} + +t_stat parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) +{ + while (isspace(*cptr)) { + cptr++; /* absorb spaces */ + } + + if ((sw & (SWMASK('A') | SWMASK('C'))) || ((*cptr == '\'') && cptr++)) { /* ASCII char? */ + if (cptr[0] == 0) { + return SCPE_ARG; /* must have one char */ + } + + val[0] = (uint32) cptr[0]; + return SCPE_OK; + } + + if (cpu_parse_sym != NULL) { + return cpu_parse_sym(cptr, addr, uptr, val, sw); + } + + return SCPE_OK; +} + diff --git a/Altair8800/s100_cpu.h b/Altair8800/s100_cpu.h new file mode 100644 index 00000000..a526474e --- /dev/null +++ b/Altair8800/s100_cpu.h @@ -0,0 +1,62 @@ +/* s100_cpu.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_CPU_H +#define _S100_CPU_H + +#include "sim_defs.h" + +#define UNIT_CPU_V_VERBOSE (UNIT_V_UF+0) /* Enable verbose messagesto */ +#define UNIT_CPU_VERBOSE (1 << UNIT_CPU_V_VERBOSE) + +/* CPU chip types */ +typedef enum { + CHIP_TYPE_8080 = 0, + CHIP_TYPE_Z80, + NUM_CHIP_TYPE, /* must be last */ +} ChipType; + +typedef struct { + DEVICE *dev; + REG **pc_reg; + ChipType *chiptype; + t_stat (*instr)(void); + t_value (*pc_val)(void); + t_stat (*parse_sym)(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw); + int32 (*dasm)(char *S, const uint32 *val, const int32 addr); + t_bool (*isc)(t_addr **ret_addrs); + t_stat (*help)(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +} CPU; + +extern void cpu_set_chiptype(ChipType type); +extern char * cpu_get_chipname(ChipType type); + +#endif + diff --git a/Altair8800/s100_po.c b/Altair8800/s100_po.c new file mode 100644 index 00000000..7f7a0363 --- /dev/null +++ b/Altair8800/s100_po.c @@ -0,0 +1,122 @@ +/* s100_po.c: MITS Altair 8800 Programmed Output + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "s100_bus.h" +#include "s100_po.h" + +#define DEVICE_NAME "PO" + +static int32 poc = TRUE; /* Power On Clear */ + +static int32 PO = 0; /* programmed output register */ + +static t_stat po_reset (DEVICE *dptr); +static int32 po_io (const int32 addr, const int32 rw, const int32 data); + +static t_stat po_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static const char* po_description(DEVICE *dptr) { + return "Front Panel"; +} + +static UNIT po_unit = { + UDATA (NULL, UNIT_PO_VERBOSE, 0) +}; + +static REG po_reg[] = { + { HRDATAD (PO, PO, 8, "Programmed Output") }, + { NULL } +}; + +static MTAB po_mod[] = { + { UNIT_PO_VERBOSE, UNIT_PO_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_PO_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB po_dt[] = { + { NULL, 0 } +}; + +DEVICE po_dev = { + DEVICE_NAME, &po_unit, po_reg, po_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &po_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE | DEV_DIS), 0, + po_dt, NULL, NULL, &po_show_help, NULL, NULL, &po_description +}; + +static t_stat po_reset(DEVICE *dptr) { + if (dptr->flags & DEV_DIS) { /* Disable Device */ + s100_bus_remio_out(0xff, 1, &po_io); + + poc = TRUE; + } + else { + if (poc) { + s100_bus_addio_out(0xff, 1, &po_io, DEVICE_NAME); + + poc = FALSE; + } + } + + return SCPE_OK; +} + +static int32 po_io(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_WRITE) { + + PO = data & DATAMASK; + + if (po_unit.flags & UNIT_PO_VERBOSE) { + sim_printf("\n[PO %02X]\n", ~data & DATAMASK); /* IMSAI FP is Inverted */ + } + } + + return 0x0ff; +} + +static t_stat po_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nProgrammed Output (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_po.h b/Altair8800/s100_po.h new file mode 100644 index 00000000..4d64be58 --- /dev/null +++ b/Altair8800/s100_po.h @@ -0,0 +1,37 @@ +/* s100_po.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef S100_PO_H_ +#define S100_PO_H_ + +#define UNIT_PO_V_VERBOSE (UNIT_V_UF+0) +#define UNIT_PO_VERBOSE (1 << UNIT_PO_V_VERBOSE) + +#endif diff --git a/Altair8800/s100_ram.c b/Altair8800/s100_ram.c new file mode 100644 index 00000000..0246f86b --- /dev/null +++ b/Altair8800/s100_ram.c @@ -0,0 +1,325 @@ +/* s100_ram.c: MITS Altair 8800 RAM + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "s100_ram.h" + +static t_stat ram_reset (DEVICE *dptr); +static t_stat ram_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); +static t_stat ram_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); +static int32 ram_memio (const int32 addr, const int32 rw, const int32 data); +static t_stat ram_default_ena (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat ram_default_dis (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat ram_set_memsize (int32 value); +static t_stat ram_clear_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat ram_enable_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat ram_randomize_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat ram_size_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static void ram_clear (void); +static void ram_randomize (void); +static t_stat ram_show_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* ram_description (DEVICE *dptr); + +static void PutBYTE(register uint32 Addr, const register uint32 Value); +static uint32 GetBYTE(register uint32 Addr); + +static int32 poc = TRUE; /* Power On Clear */ + +static int32 M[MAXBANKSIZE]; /* RAM */ +static int32 P[MAXBANKSIZE >> LOG2PAGESIZE]; /* Active pages */ +static int32 memsize = MAXBANKSIZE; + +static const char* ram_description(DEVICE *dptr) { + return "Random Access Memory"; +} + +static UNIT ram_unit = { + UDATA (NULL, UNIT_FIX | UNIT_BINK | UNIT_RAM_DEFAULT, MAXBANKSIZE) +}; + +static REG ram_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { NULL } +}; + +static MTAB ram_mod[] = { + { UNIT_RAM_VERBOSE, UNIT_RAM_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_RAM_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + { UNIT_RAM_DEFAULT, UNIT_RAM_DEFAULT, "DEFAULT", "DEFAULT", &ram_default_ena, NULL, + NULL, "Enable RAM as default memory" }, + { UNIT_RAM_DEFAULT, 0, "NODEFAULT", "NODEFAULT", &ram_default_dis, NULL, + NULL, "Disable RAM as default memory" }, + + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "SIZE={1-64}", &ram_size_command, + NULL, NULL, "Sets the RAM size" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, NULL, "ADDRAM={PAGE | START-END | ALL}", &ram_enable_command, + NULL, NULL, "Enable RAM page(s)" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "REMRAM={PAGE | START-END | ALL}", &ram_enable_command, + NULL, NULL, "Disable RAM page(s)" }, + { MTAB_VDV, 0, NULL, "CLEAR", &ram_clear_command, + NULL, NULL, "Sets RAM to 0x00" }, + { MTAB_VDV, 0, NULL, "RANDOM", &ram_randomize_command, + NULL, NULL, "Sets RAM to random values" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB ram_dt[] = { + { NULL, 0 } +}; + +DEVICE ram_dev = { + "RAM", /* name */ + &ram_unit, /* units */ + ram_reg, /* registers */ + ram_mod, /* modifiers */ + 1, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* addr increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + &ram_ex, /* examine routine */ + &ram_dep, /* deposit routine */ + &ram_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* context */ + (DEV_DISABLE), /* flags */ + 0, /* debug control */ + ram_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &ram_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context available to help routines */ + &ram_description /* device description */ +}; + +static t_stat ram_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { /* Disable Device */ + s100_bus_remmem(0x0000, MAXBANKSIZE, &ram_memio); + ram_default_dis(NULL, 0, NULL, NULL); + + poc = TRUE; + } + else { + if (poc) { + ram_set_memsize(memsize); + + if (ram_unit.flags & UNIT_RAM_DEFAULT) { + ram_default_ena(NULL, 0, NULL, NULL); + } + + poc = FALSE; + } + } + + return SCPE_OK; +} + +/* memory examine */ +static t_stat ram_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) { + *vptr = GetBYTE(addr & ADDRMASK) & DATAMASK; + + return SCPE_OK; +} + +/* memory deposit */ +static t_stat ram_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) { + PutBYTE(addr & ADDRMASK, val & DATAMASK); + + return SCPE_OK; +} + +static int32 ram_memio(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_READ) { + return GetBYTE(addr); + } + + PutBYTE(addr, data); + + return 0x0ff; +} + +static void PutBYTE(register uint32 Addr, const register uint32 Value) +{ + M[Addr & ADDRMASK] = Value & DATAMASK; +} + +static uint32 GetBYTE(register uint32 Addr) +{ + return M[Addr & ADDRMASK] & DATAMASK; /* RAM */ +} + +static t_stat ram_default_ena(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + s100_bus_setmem_dflt(&ram_memio, "RAM"); /* Set RAM as default memory device */ + + return SCPE_OK; +} + +static t_stat ram_default_dis(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + s100_bus_remmem_dflt(&ram_memio); /* Remove RAM as default memory device */ + + return SCPE_OK; +} + +/* set memory to 'size' kilo byte */ +static t_stat ram_set_memsize(int32 size) { + int32 page; + + size <<= KBLOG2; + + if (size < KB) { + memsize = KB; + } + else if (size > MAXBANKSIZE) { + memsize = MAXBANKSIZE; + } + else { + memsize = size; + } + + s100_bus_remmem(0x0000, MAXBANKSIZE, &ram_memio); /* Remove all pages */ + s100_bus_addmem(0x0000, memsize, &ram_memio, "RAM"); /* Add memsize pages */ + + /* Keep track of active pages for SHOW */ + for (page = 0; page < (MAXBANKSIZE >> LOG2PAGESIZE); page++) { + P[page] = (page << LOG2PAGESIZE) <= memsize; + } + + ram_unit.capac = memsize; + + return SCPE_OK; +} + +static void ram_clear() +{ + uint32 i; + + for (i = 0; i < MAXBANKSIZE; i++) { + M[i] = 0; + } +} + +static void ram_randomize() +{ + uint32 i; + + for (i = 0; i < MAXBANKSIZE; i++) { + M[i] = sim_rand() & DATAMASK; + } +} + +static t_stat ram_size_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) { + int32 size, result; + + if (cptr == NULL) { + sim_printf("Memory size must be provided as SET RAM SIZE=1-64\n"); + return SCPE_ARG | SCPE_NOMESSAGE; + } + + result = sscanf(cptr, "%i", &size); + + if (result == 1) { + return ram_set_memsize(size); /* Set size in KB */ + } + + return SCPE_ARG | SCPE_NOMESSAGE; +} + +static t_stat ram_enable_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) { + int32 size; + t_addr start, end; + + if (cptr == NULL) { + sim_printf("Memory page(s) must be provided as SET RAM ENABLE=E0-EF\n"); + return SCPE_ARG | SCPE_NOMESSAGE; + } + + if (get_range(NULL, cptr, &start, &end, 16, PAGEMASK, 0) == NULL) { + return SCPE_ARG; + } + + if (start < MAXPAGE) { + start = start << LOG2PAGESIZE; + } + if (end < MAXPAGE) { + end = end << LOG2PAGESIZE; + } + + start &= 0xff00; + end &= 0xff00; + + size = end - start + PAGESIZE; + + if (value) { + s100_bus_addmem(start, size, &ram_memio, "RAM"); /* Add pages */ + } + else { + s100_bus_remmem(start, size, &ram_memio); /* Remove pages */ + } + + return SCPE_OK; +} + +static t_stat ram_clear_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + ram_clear(); + + return SCPE_OK; +} + +static t_stat ram_randomize_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + ram_randomize(); + + return SCPE_OK; +} + +static t_stat ram_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 RAM (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_ram.h b/Altair8800/s100_ram.h new file mode 100644 index 00000000..861c0c65 --- /dev/null +++ b/Altair8800/s100_ram.h @@ -0,0 +1,42 @@ +/* s100_ram.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_RAM_H +#define _S100_RAM_H + +#include "sim_defs.h" + +#define UNIT_RAM_V_VERBOSE (UNIT_V_UF+0) /* Enable verbose messagesto */ +#define UNIT_RAM_VERBOSE (1 << UNIT_RAM_V_VERBOSE) +#define UNIT_RAM_V_DEFAULT (UNIT_V_UF+1) /* Make RAM default */ +#define UNIT_RAM_DEFAULT (1 << UNIT_RAM_V_DEFAULT) + +#endif + diff --git a/Altair8800/s100_rom.c b/Altair8800/s100_rom.c new file mode 100644 index 00000000..31d5f369 --- /dev/null +++ b/Altair8800/s100_rom.c @@ -0,0 +1,252 @@ +/* s100_rom.c: MITS Altair 8800 ROM + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "s100_bus.h" +#include "s100_ram.h" +#include "s100_rom.h" +#include "s100_roms.h" + +static t_stat rom_reset (DEVICE *dptr); +static int32 rom_memio (const int32 addr, const int32 rw, const int32 data); + +static int32 poc = TRUE; /* Power On Clear */ + +static const char* rom_description (DEVICE *dptr); + +static uint32 GetBYTE(register uint32 Addr); + +static t_stat rom_enadis(int32 value, int32 ena); +static t_stat rom_ena(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat rom_dis_dbl(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat rom_dis_hdsk(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat rom_dis_altmon(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat rom_dis_turmon(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat rom_show_list(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat rom_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static int32 M[MAXBANKSIZE]; + +static ROM rom_table[] = { + { UNIT_ROM_ALTMON, rom_altmon, ROM_ALTMON_BASEADDR, ROM_ALTMON_SIZE, ROM_ALTMON_NAME, ROM_ALTMON_DESC }, + { UNIT_ROM_DBL, rom_mits_dbl, ROM_MITS_DBL_BASEADDR, ROM_MITS_DBL_SIZE, ROM_MITS_DBL_NAME, ROM_MITS_DBL_DESC }, + { UNIT_ROM_HDSK, rom_mits_hdsk, ROM_MITS_HDSK_BASEADDR, ROM_MITS_HDSK_SIZE, ROM_MITS_HDSK_NAME, ROM_MITS_HDSK_DESC }, + { UNIT_ROM_TURMON, rom_mits_turmon, ROM_MITS_TURMON_BASEADDR, ROM_MITS_TURMON_SIZE, ROM_MITS_TURMON_NAME, ROM_MITS_TURMON_DESC }, + { UNIT_ROM_AZ80DBL, rom_az80_dbl, ROM_AZ80_DBL_BASEADDR, ROM_AZ80_DBL_SIZE, ROM_AZ80_DBL_NAME, ROM_AZ80_DBL_DESC }, + + { 0, NULL, 0x0000, 0, "", "" } +}; + +static const char* rom_description(DEVICE *dptr) { + return "Read Only Memory"; +} + +static UNIT rom_unit = { + UDATA (NULL, UNIT_FIX | UNIT_BINK | UNIT_ROM_DBL, MAXBANKSIZE) +}; + +static REG rom_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { NULL } +}; + +static MTAB rom_mod[] = { + { UNIT_ROM_VERBOSE, UNIT_ROM_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_ROM_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + + { UNIT_ROM_DBL, UNIT_ROM_DBL, ROM_MITS_DBL_NAME, ROM_MITS_DBL_NAME, &rom_ena, NULL, + NULL, "Enable " ROM_MITS_DBL_DESC }, + { UNIT_ROM_DBL, 0, "NO" ROM_MITS_DBL_NAME, "NO" ROM_MITS_DBL_NAME, &rom_dis_dbl, NULL, + NULL, "Disable " ROM_MITS_DBL_DESC }, + + { UNIT_ROM_AZ80DBL, UNIT_ROM_AZ80DBL, ROM_AZ80_DBL_NAME, ROM_AZ80_DBL_NAME, &rom_ena, NULL, + NULL, "Enable " ROM_AZ80_DBL_DESC }, + { UNIT_ROM_AZ80DBL, 0, "NO" ROM_AZ80_DBL_NAME, "NO" ROM_AZ80_DBL_NAME, &rom_dis_dbl, NULL, + NULL, "Disable " ROM_AZ80_DBL_DESC }, + + { UNIT_ROM_HDSK, UNIT_ROM_HDSK, ROM_MITS_HDSK_NAME, ROM_MITS_HDSK_NAME, &rom_ena, NULL, + NULL, "Enable " ROM_MITS_HDSK_DESC }, + { UNIT_ROM_HDSK, 0, "NO" ROM_MITS_HDSK_NAME, "NO" ROM_MITS_HDSK_NAME, &rom_dis_hdsk, NULL, + NULL, "Disable " ROM_MITS_HDSK_DESC }, + + { UNIT_ROM_ALTMON, UNIT_ROM_ALTMON, ROM_ALTMON_NAME, ROM_ALTMON_NAME, &rom_ena, NULL, + NULL, "Enable " ROM_ALTMON_DESC }, + { UNIT_ROM_ALTMON, 0, "NO" ROM_ALTMON_NAME, "NO" ROM_ALTMON_NAME, &rom_dis_altmon, NULL, + NULL, "Disable " ROM_ALTMON_DESC }, + + { UNIT_ROM_TURMON, UNIT_ROM_TURMON, ROM_MITS_TURMON_NAME, ROM_MITS_TURMON_NAME, &rom_ena, NULL, + NULL, "Enable " ROM_MITS_TURMON_DESC }, + { UNIT_ROM_TURMON, 0, "NO" ROM_MITS_TURMON_NAME, "NO" ROM_MITS_TURMON_NAME, &rom_dis_turmon, NULL, + NULL, "Disable " ROM_MITS_TURMON_DESC }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LIST", NULL, NULL, &rom_show_list, NULL, "Show available ROMs" }, + + { 0 } +}; + +/* Debug Flags */ +static DEBTAB rom_dt[] = { + { NULL, 0 } +}; + +DEVICE rom_dev = { + "ROM", &rom_unit, rom_reg, rom_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &rom_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE), 0, + rom_dt, NULL, NULL, + &rom_show_help, NULL, NULL, + &rom_description +}; + +static t_stat rom_reset(DEVICE *dptr) { + if (dptr->flags & DEV_DIS) { /* Disable Device */ + rom_enadis(rom_unit.flags, FALSE); + + poc = TRUE; + } + else { + if (poc) { + rom_enadis(rom_unit.flags, TRUE); + + poc = FALSE; + } + } + + return SCPE_OK; +} + +static int32 rom_memio(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_READ) { + return GetBYTE(addr); + } + + return 0x0ff; +} + +uint32 GetBYTE(register uint32 Addr) +{ + return M[Addr & ADDRMASK]; /* ROM */ +} + +static t_stat rom_enadis(int32 value, int32 ena) +{ + ROM *r = rom_table; + int i; + + while (r->flag != 0) { + if (value & r->flag) { + if (ena) { + for (i = 0; i < r->size; i++) { + M[r->baseaddr + i] = r->rom[i]; + } + + s100_bus_addmem(r->baseaddr, r->size, &rom_memio, r->name); + + if (rom_unit.flags & UNIT_ROM_VERBOSE) { + sim_printf("Installed ROM %s @ %04X\n", r->name, r->baseaddr); + } + } + else { + s100_bus_remmem(r->baseaddr, r->size, &rom_memio); + + if (rom_unit.flags & UNIT_ROM_VERBOSE) { + sim_printf("Removed ROM %s @ %04X\n", r->name, r->baseaddr); + } + } + } + + r++; + } + + return SCPE_OK; +} + +static t_stat rom_ena(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return rom_enadis(value, TRUE); +} + +static t_stat rom_dis_dbl(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return rom_enadis(UNIT_ROM_DBL, FALSE); +} + +static t_stat rom_dis_hdsk(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return rom_enadis(UNIT_ROM_HDSK, FALSE); +} + +static t_stat rom_dis_turmon(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return rom_enadis(UNIT_ROM_TURMON, FALSE); +} + +static t_stat rom_dis_altmon(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return rom_enadis(UNIT_ROM_ALTMON, FALSE); +} + +static t_stat rom_show_list(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + ROM *r = rom_table; + + fprintf(st, "\n"); + + while (r->rom != NULL) { + fprintf(st, "%c %-8.8s: %-25.25s @ %04X-%04X\n", + rom_unit.flags & r->flag ? '*' : ' ', r->name, r->desc, r->baseaddr, r->baseaddr + r->size - 1); + r++; + } + + fprintf(st, "\n* = enabled\n"); + + return SCPE_OK; +} + +static t_stat rom_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 ROM (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + fprintf(st, "\nVarious ROMs are available through the ROM device. A list of ROMs is available using\n"); + fprintf(st, "the SHOW ROM LIST command. To enable a ROM, enter SET ROM . To disable a ROM,\n"); + fprintf(st, "enter SET ROM NO. Enabled ROMs can be seen with the SHOW BUS CONFIG command.\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_rom.h b/Altair8800/s100_rom.h new file mode 100644 index 00000000..6a7981a2 --- /dev/null +++ b/Altair8800/s100_rom.h @@ -0,0 +1,59 @@ +/* s100_rom.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_ROM_H +#define _S100_ROM_H + +#include "sim_defs.h" + +#define UNIT_ROM_V_VERBOSE (UNIT_V_UF+0) /* warn if ROM is written to */ +#define UNIT_ROM_VERBOSE (1 << UNIT_ROM_V_VERBOSE) +#define UNIT_ROM_V_DBL (UNIT_V_UF+1) /* Enable/Disable Disk Boot Loader */ +#define UNIT_ROM_DBL (1 << UNIT_ROM_V_DBL ) +#define UNIT_ROM_V_HDSK (UNIT_V_UF+2) /* Enable/Disable Hard Disk Boot Loader */ +#define UNIT_ROM_HDSK (1 << UNIT_ROM_V_HDSK ) +#define UNIT_ROM_V_ALTMON (UNIT_V_UF+3) /* Enable/Disable Altmon */ +#define UNIT_ROM_ALTMON (1 << UNIT_ROM_V_ALTMON ) +#define UNIT_ROM_V_TURMON (UNIT_V_UF+4) /* Enable/Disable Turnkey Monitor */ +#define UNIT_ROM_TURMON (1 << UNIT_ROM_V_TURMON ) +#define UNIT_ROM_V_AZ80DBL (UNIT_V_UF+5) /* Enable/Disable AltairZ80 Disk Boot Loader */ +#define UNIT_ROM_AZ80DBL (1 << UNIT_ROM_V_AZ80DBL) + +typedef struct { + uint32 flag; + int32 *rom; + int32 baseaddr; + int32 size; + char *name; + char *desc; +} ROM; + +#endif + diff --git a/Altair8800/s100_roms.h b/Altair8800/s100_roms.h new file mode 100644 index 00000000..0474ee20 --- /dev/null +++ b/Altair8800/s100_roms.h @@ -0,0 +1,373 @@ +/* s100_roms.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_ROMS_H +#define _S100_ROMS_H + +/* + * MITS Altair Disk Boot Loader Version 4.1 + * https://deramp.com/downloads/altair/software/roms/orginal_roms/DBL.ASM + */ +#define ROM_MITS_DBL_BASEADDR 0xff00 +#define ROM_MITS_DBL_SIZE 0x0100 +#define ROM_MITS_DBL_NAME "DBL" +#define ROM_MITS_DBL_DESC "MITS Disk Boot Loader 4.1" + +static int32 rom_mits_dbl[] = { + 0x21, 0x13, 0xff, 0x11, 0x00, 0x2c, 0x0e, 0xeb, + 0x7e, 0x12, 0x23, 0x13, 0x0d, 0xc2, 0x08, 0xff, + 0xc3, 0x00, 0x2c, 0xf3, 0xaf, 0xd3, 0x22, 0x2f, + 0xd3, 0x23, 0x3e, 0x2c, 0xd3, 0x22, 0x3e, 0x03, + 0xd3, 0x10, 0xdb, 0xff, 0xe6, 0x10, 0x0f, 0x0f, + 0xc6, 0x10, 0xd3, 0x10, 0x31, 0x79, 0x2d, 0xaf, + 0xd3, 0x08, 0xdb, 0x08, 0xe6, 0x08, 0xc2, 0x1c, + 0x2c, 0x3e, 0x04, 0xd3, 0x09, 0xc3, 0x38, 0x2c, + 0xdb, 0x08, 0xe6, 0x02, 0xc2, 0x2d, 0x2c, 0x3e, + 0x02, 0xd3, 0x09, 0xdb, 0x08, 0xe6, 0x40, 0xc2, + 0x2d, 0x2c, 0x11, 0x00, 0x00, 0x06, 0x00, 0x3e, + 0x10, 0xf5, 0xd5, 0xc5, 0xd5, 0x11, 0x86, 0x80, + 0x21, 0xeb, 0x2c, 0xdb, 0x09, 0x1f, 0xda, 0x50, + 0x2c, 0xe6, 0x1f, 0xb8, 0xc2, 0x50, 0x2c, 0xdb, + 0x08, 0xb7, 0xfa, 0x5c, 0x2c, 0xdb, 0x0a, 0x77, + 0x23, 0x1d, 0xca, 0x72, 0x2c, 0x1d, 0xdb, 0x0a, + 0x77, 0x23, 0xc2, 0x5c, 0x2c, 0xe1, 0x11, 0xee, + 0x2c, 0x01, 0x80, 0x00, 0x1a, 0x77, 0xbe, 0xc2, + 0xcb, 0x2c, 0x80, 0x47, 0x13, 0x23, 0x0d, 0xc2, + 0x79, 0x2c, 0x1a, 0xfe, 0xff, 0xc2, 0x90, 0x2c, + 0x13, 0x1a, 0xb8, 0xc1, 0xeb, 0xc2, 0xc2, 0x2c, + 0xf1, 0xf1, 0x2a, 0xec, 0x2c, 0xcd, 0xe5, 0x2c, + 0xd2, 0xbb, 0x2c, 0x04, 0x04, 0x78, 0xfe, 0x20, + 0xda, 0x44, 0x2c, 0x06, 0x01, 0xca, 0x44, 0x2c, + 0xdb, 0x08, 0xe6, 0x02, 0xc2, 0xad, 0x2c, 0x3e, + 0x01, 0xd3, 0x09, 0xc3, 0x42, 0x2c, 0x3e, 0x80, + 0xd3, 0x08, 0xc3, 0x00, 0x00, 0xd1, 0xf1, 0x3d, + 0xc2, 0x46, 0x2c, 0x3e, 0x43, 0x01, 0x3e, 0x4d, + 0xfb, 0x32, 0x00, 0x00, 0x22, 0x01, 0x00, 0x47, + 0x3e, 0x80, 0xd3, 0x08, 0x78, 0xd3, 0x01, 0xd3, + 0x11, 0xd3, 0x05, 0xd3, 0x23, 0xc3, 0xda, 0x2c, + 0x7a, 0xbc, 0xc0, 0x7b, 0xbd, 0xc9, 0x00, 0x00, +}; + +/* + * MITS Altair Turnkey Monitor + * https://deramp.com/downloads/altair/software/roms/orginal_roms/TURMON.ASM + */ +#define ROM_MITS_TURMON_BASEADDR 0xfd00 +#define ROM_MITS_TURMON_SIZE 0x0100 +#define ROM_MITS_TURMON_NAME "TURMON" +#define ROM_MITS_TURMON_DESC "MITS Turnkey Monitor" + +static int32 rom_mits_turmon[] = { + 0x3e, 0x03, 0xd3, 0x10, 0x3e, 0x11, 0xd3, 0x10, + 0x31, 0x00, 0xf8, 0xcd, 0x9d, 0xfd, 0x3e, 0x2e, + 0xcd, 0xf2, 0xfd, 0xcd, 0xe8, 0xfd, 0xfe, 0x4d, + 0xca, 0x29, 0xfd, 0xfe, 0x44, 0xcc, 0x4f, 0xfd, + 0xfe, 0x4a, 0xc2, 0x08, 0xfd, 0xcd, 0xa7, 0xfd, + 0xe9, 0xcd, 0xa7, 0xfd, 0x3e, 0x23, 0xcd, 0x9d, + 0xfd, 0x54, 0x5d, 0xcd, 0xc9, 0xfd, 0x1a, 0x67, + 0xcd, 0xcf, 0xfd, 0xcd, 0xa8, 0xfd, 0xeb, 0xda, + 0x2d, 0xfd, 0x77, 0xbe, 0xca, 0x2d, 0xfd, 0x3e, + 0x3f, 0xcd, 0xf2, 0xfd, 0xc3, 0x08, 0xfd, 0xcd, + 0xa7, 0xfd, 0xeb, 0xd4, 0xe3, 0xfd, 0xcd, 0xa7, + 0xfd, 0x3e, 0x0d, 0x06, 0x3c, 0xcd, 0xf2, 0xfd, + 0x05, 0xc2, 0x5d, 0xfd, 0xb8, 0x78, 0xc2, 0x5b, + 0xfd, 0x7d, 0x93, 0x6f, 0x7c, 0x9a, 0x67, 0x23, + 0x05, 0x7c, 0xb7, 0xc2, 0x77, 0xfd, 0x45, 0x3e, + 0x3c, 0xcd, 0xf2, 0xfd, 0x78, 0xcd, 0xf2, 0xfd, + 0x0e, 0x00, 0x7b, 0xcd, 0xf2, 0xfd, 0x7a, 0xcd, + 0xf2, 0xfd, 0x1a, 0xcd, 0xf2, 0xfd, 0x13, 0x2b, + 0x05, 0xc2, 0x8a, 0xfd, 0x79, 0xcd, 0xf2, 0xfd, + 0x7c, 0xb5, 0xc2, 0x70, 0xfd, 0x3e, 0x0d, 0xcd, + 0xf2, 0xfd, 0x3e, 0x0a, 0xc3, 0xf2, 0xfd, 0x06, + 0x06, 0x03, 0x21, 0x00, 0x00, 0xcd, 0xe8, 0xfd, + 0x4f, 0xfe, 0x20, 0x37, 0xc8, 0xe6, 0xb8, 0xee, + 0x30, 0xc2, 0x47, 0xfd, 0x79, 0xe6, 0x07, 0x29, + 0x29, 0x29, 0x85, 0x6f, 0x05, 0xc2, 0xad, 0xfd, + 0xc9, 0x06, 0x06, 0xaf, 0xc3, 0xd6, 0xfd, 0x06, + 0x03, 0xe6, 0x29, 0x17, 0x29, 0x17, 0x29, 0x17, + 0xe6, 0x07, 0xf6, 0x30, 0xcd, 0xf2, 0xfd, 0x05, + 0xc2, 0xd2, 0xfd, 0x3e, 0x20, 0xc3, 0xf2, 0xfd, + 0xdb, 0x10, 0x0f, 0xd2, 0xe8, 0xfd, 0xdb, 0x11, + 0xe6, 0x7f, 0xf5, 0x81, 0x4f, 0xdb, 0x10, 0x0f, + 0x0f, 0xd2, 0xf5, 0xfd, 0xf1, 0xd3, 0x11, 0xc9, +}; + +#define ROM_MITS_HDSK_BASEADDR 0xfc00 +#define ROM_MITS_HDSK_SIZE 0x0200 +#define ROM_MITS_HDSK_NAME "HDSK" +#define ROM_MITS_HDSK_DESC "MITS Hard Disk Boot ROM" + +static int32 rom_mits_hdsk[] = { + 0x31, 0x00, 0xc0, 0xcd, 0xa6, 0xfd, 0xcd, 0xbe, + 0xfd, 0xdb, 0xa0, 0x07, 0xda, 0x15, 0xfc, 0x11, + 0xe1, 0xfd, 0xcd, 0xb1, 0xfd, 0x21, 0x00, 0x00, + 0xcd, 0x5b, 0xfc, 0x06, 0x28, 0xdb, 0xa5, 0x05, + 0xc2, 0x1d, 0xfc, 0xdb, 0xa5, 0x6f, 0xdb, 0xa5, + 0x67, 0xdb, 0xa5, 0x5f, 0xdb, 0xa5, 0x57, 0xd5, + 0x06, 0xd4, 0xdb, 0xa5, 0x05, 0xc2, 0x32, 0xfc, + 0x11, 0xec, 0xfd, 0xcd, 0xb1, 0xfd, 0x11, 0x00, + 0x00, 0xcd, 0x5b, 0xfc, 0x06, 0x00, 0xdb, 0xa5, + 0x12, 0x13, 0x05, 0xc2, 0x46, 0xfc, 0x23, 0xe3, + 0x2b, 0x7c, 0xb5, 0xca, 0x5a, 0xfc, 0xe3, 0xc3, + 0x41, 0xfc, 0xe9, 0xe5, 0xd5, 0xc5, 0x06, 0x30, + 0x11, 0x10, 0x00, 0x29, 0x7a, 0x17, 0xb8, 0xda, + 0x6c, 0xfc, 0x90, 0x2c, 0x57, 0x1d, 0xc2, 0x63, + 0xfc, 0x47, 0x7d, 0xd3, 0xa7, 0xdb, 0xa3, 0x7c, + 0xf6, 0x00, 0xd3, 0xa3, 0xcd, 0xa9, 0xfc, 0x78, + 0xfe, 0x18, 0xda, 0x89, 0xfc, 0xd6, 0x18, 0xc6, + 0x20, 0xd3, 0xa7, 0xdb, 0xa3, 0x3e, 0x30, 0xd3, + 0xa3, 0xcd, 0xa9, 0xfc, 0xdb, 0xa3, 0xdb, 0xa5, + 0xaf, 0xd3, 0xa7, 0x3e, 0x50, 0xd3, 0xa3, 0xdb, + 0xa4, 0x07, 0xd2, 0x9f, 0xfc, 0xc1, 0xd1, 0xe1, + 0xc9, 0xdb, 0xa0, 0x07, 0xd2, 0xa9, 0xfc, 0xdb, + 0xa1, 0xe6, 0x7f, 0xc8, 0x11, 0xf5, 0xfd, 0xcd, + 0xb1, 0xfd, 0xcd, 0x8f, 0xfd, 0xcd, 0xbe, 0xfd, + 0xc3, 0x06, 0xfd, 0xaf, 0xd3, 0xa0, 0xd3, 0xa2, + 0xd3, 0xa4, 0xd3, 0xa6, 0xd3, 0xa1, 0xd3, 0xa5, + 0x2f, 0xd3, 0xa3, 0xd3, 0xa7, 0x3e, 0x2c, 0xd3, + 0xa0, 0xd3, 0xa4, 0xd3, 0xa6, 0x3e, 0x24, 0xd3, + 0xa2, 0xdb, 0xa1, 0xc9, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x00, 0xc0, 0xcd, 0xa6, 0xfd, 0xcd, 0xbe, + 0xfd, 0x3e, 0x2e, 0xcd, 0xd5, 0xfd, 0xcd, 0xc8, + 0xfd, 0xfe, 0x4c, 0xca, 0x06, 0xfc, 0xfe, 0x4d, + 0xca, 0x24, 0xfd, 0xfe, 0x4a, 0xc2, 0x06, 0xfd, + 0xcd, 0x54, 0xfd, 0xe9, 0xcd, 0xd3, 0xfd, 0xcd, + 0x54, 0xfd, 0x3e, 0x23, 0xcd, 0xbe, 0xfd, 0x7c, + 0xcd, 0x8f, 0xfd, 0x7d, 0xcd, 0x8f, 0xfd, 0xcd, + 0xd3, 0xfd, 0x7e, 0xcd, 0x8f, 0xfd, 0xcd, 0xd3, + 0xfd, 0xcd, 0x5d, 0xfd, 0xda, 0x2b, 0xfd, 0x77, + 0xbe, 0xca, 0x2b, 0xfd, 0x3e, 0x3f, 0xcd, 0xd5, + 0xfd, 0xc3, 0x06, 0xfd, 0xcd, 0x5d, 0xfd, 0x67, + 0xcd, 0x5d, 0xfd, 0x6f, 0xc9, 0xcd, 0xc8, 0xfd, + 0xfe, 0x20, 0x37, 0xc8, 0xcd, 0x74, 0xfd, 0x07, + 0x07, 0x07, 0x07, 0x4f, 0xcd, 0xc8, 0xfd, 0xcd, + 0x74, 0xfd, 0xb1, 0xc9, 0xfe, 0x30, 0xda, 0x4c, + 0xfd, 0xfe, 0x3a, 0xda, 0x8c, 0xfd, 0xe6, 0x5f, + 0xfe, 0x41, 0xda, 0x4c, 0xfd, 0xfe, 0x47, 0xd2, + 0x4c, 0xfd, 0xd6, 0x07, 0xe6, 0x0f, 0xc9, 0xf5, + 0x07, 0x07, 0x07, 0x07, 0xcd, 0x98, 0xfd, 0xf1, + 0xe6, 0x0f, 0xfe, 0x0a, 0xda, 0xa1, 0xfd, 0xc6, + 0x07, 0xc6, 0x30, 0xc3, 0xd5, 0xfd, 0x3e, 0x03, + 0xd3, 0x10, 0x3e, 0x11, 0xd3, 0x10, 0xc3, 0xc3, + 0xfc, 0xf5, 0x1a, 0xcd, 0xd5, 0xfd, 0x13, 0xe6, + 0x80, 0xca, 0xb2, 0xfd, 0xf1, 0xc9, 0x3e, 0x0d, + 0xcd, 0xd5, 0xfd, 0x3e, 0x0a, 0xc3, 0xd5, 0xfd, + 0xdb, 0x10, 0x0f, 0xd2, 0xc8, 0xfd, 0xdb, 0x11, + 0xe6, 0x7f, 0xda, 0x3e, 0x20, 0xf5, 0xdb, 0x10, + 0xe6, 0x02, 0xca, 0xd6, 0xfd, 0xf1, 0xd3, 0x11, + 0xc9, 0x52, 0x45, 0x53, 0x45, 0x54, 0x20, 0x43, + 0x54, 0x4c, 0x0d, 0x8a, 0x4c, 0x4f, 0x41, 0x44, + 0x49, 0x4e, 0x47, 0x0d, 0x8a, 0x4c, 0x4f, 0x41, + 0x44, 0x20, 0x45, 0x52, 0x52, 0x4f, 0x52, 0xba, +}; + +/* + * ALTMON 1.3 + * https://deramp.com/downloads/altair/software/roms/orginal_roms/DBL.ASM + */ +#define ROM_ALTMON_BASEADDR 0xf800 +#define ROM_ALTMON_SIZE 0x0400 +#define ROM_ALTMON_NAME "ALTMON" +#define ROM_ALTMON_DESC "Altmon 1.3" + +static int32 rom_altmon[ROM_ALTMON_SIZE] = { + 0x3e, 0x03, 0xd3, 0x10, 0xd3, 0x12, 0x3e, 0x11, + 0xd3, 0x10, 0xd3, 0x12, 0x31, 0x00, 0xc0, 0xcd, + 0xa5, 0xfb, 0x0d, 0x0a, 0x0a, 0x41, 0x4c, 0x54, + 0x4d, 0x4f, 0x4e, 0x20, 0x31, 0x2e, 0xb3, 0x31, + 0x00, 0xc0, 0x21, 0x1f, 0xf8, 0xe5, 0xcd, 0x63, + 0xfb, 0x3e, 0x2a, 0xcd, 0x48, 0xfb, 0xcd, 0xbc, + 0xfb, 0xe6, 0x5f, 0xfe, 0x42, 0xd8, 0xfe, 0x55, + 0xd0, 0x21, 0xc0, 0xf8, 0x87, 0x85, 0x6f, 0x5e, + 0x23, 0x56, 0xeb, 0xe9, 0x76, 0xf8, 0xcf, 0xf9, + 0xf1, 0xf8, 0x8f, 0xf9, 0xfb, 0xf9, 0x6a, 0xf8, + 0x76, 0xfa, 0x5c, 0xfa, 0x6a, 0xf8, 0x69, 0xf9, + 0x76, 0xfa, 0x84, 0xf9, 0xb6, 0xf9, 0x3f, 0xfa, + 0x32, 0xf9, 0x80, 0xf8, 0x00, 0xfc, 0xef, 0xf9, + 0x99, 0xf8, 0xcd, 0xa5, 0xfb, 0x47, 0x4f, 0x54, + 0xcf, 0xcd, 0x20, 0xfb, 0xeb, 0xe9, 0xcd, 0xa5, + 0xfb, 0x42, 0x4f, 0x4f, 0xd4, 0xc3, 0x00, 0xff, + 0xcd, 0xa5, 0xfb, 0x43, 0x53, 0x55, 0xcd, 0xcd, + 0x1d, 0xfb, 0x06, 0x00, 0x7e, 0x80, 0x47, 0xcd, + 0xea, 0xfb, 0xc2, 0x8c, 0xf8, 0x78, 0xc3, 0x79, + 0xfb, 0xcd, 0xa5, 0xfb, 0x54, 0x45, 0x53, 0xd4, + 0xcd, 0x1d, 0xfb, 0x01, 0x5a, 0x5a, 0xcd, 0xdf, + 0xf8, 0xc5, 0xe5, 0xd5, 0x7c, 0xfe, 0xbf, 0xca, + 0xb6, 0xf8, 0xcd, 0xdf, 0xf8, 0x70, 0xcd, 0xea, + 0xfb, 0xc2, 0xac, 0xf8, 0xd1, 0xe1, 0xc1, 0xe5, + 0xd5, 0x7c, 0xfe, 0xbf, 0xca, 0xcf, 0xf8, 0xcd, + 0xdf, 0xf8, 0x7e, 0xb8, 0xc4, 0x6d, 0xfb, 0xcd, + 0xea, 0xfb, 0xc2, 0xc1, 0xf8, 0xd1, 0xe1, 0x3e, + 0x2e, 0xcd, 0x48, 0xfb, 0xc3, 0xa6, 0xf8, 0xcd, + 0xc7, 0xfb, 0x78, 0xe6, 0xb4, 0xa7, 0xea, 0xea, + 0xf8, 0x37, 0x79, 0x17, 0x4f, 0x78, 0x17, 0x47, + 0xc9, 0xcd, 0xa5, 0xfb, 0x44, 0x55, 0x4d, 0xd0, + 0xcd, 0x1d, 0xfb, 0xe5, 0x0e, 0x10, 0xcd, 0x81, + 0xfb, 0x7e, 0xcd, 0x79, 0xfb, 0xcd, 0x46, 0xfb, + 0x23, 0x0d, 0xc2, 0x01, 0xf9, 0xcd, 0x46, 0xfb, + 0xe1, 0x0e, 0x10, 0x7e, 0xfe, 0x7f, 0xd2, 0x1e, + 0xf9, 0xfe, 0x20, 0xd2, 0x20, 0xf9, 0x3e, 0x2e, + 0xcd, 0x48, 0xfb, 0xcd, 0xea, 0xfb, 0x0d, 0xc2, + 0x13, 0xf9, 0xcd, 0xea, 0xfb, 0xc8, 0x2b, 0xc3, + 0xfb, 0xf8, 0xcd, 0xa5, 0xfb, 0x50, 0x47, 0xcd, + 0xcd, 0x20, 0xfb, 0xeb, 0xcd, 0x63, 0xfb, 0x7e, + 0xcd, 0x79, 0xfb, 0x3e, 0x2d, 0xcd, 0x48, 0xfb, + 0xcd, 0xb3, 0xfb, 0xfe, 0x20, 0xca, 0x65, 0xf9, + 0xfe, 0x0d, 0xc2, 0x5b, 0xf9, 0xcd, 0x63, 0xfb, + 0xc3, 0x48, 0xf9, 0xeb, 0x21, 0x00, 0x00, 0x0e, + 0x02, 0xcd, 0x28, 0xfb, 0x73, 0x23, 0xc3, 0x3f, + 0xf9, 0xcd, 0xa5, 0xfb, 0x46, 0x49, 0x4c, 0xcc, + 0xcd, 0x1d, 0xfb, 0xe5, 0x0e, 0x02, 0xcd, 0x22, + 0xfb, 0xeb, 0xe3, 0xc1, 0x71, 0xcd, 0xea, 0xfb, + 0xc8, 0xc3, 0x7c, 0xf9, 0xcd, 0xa5, 0xfb, 0x4d, + 0x4f, 0x56, 0xc5, 0xaf, 0xc3, 0x96, 0xf9, 0xcd, + 0xa5, 0xfb, 0x45, 0x58, 0x43, 0xc8, 0x47, 0xcd, + 0x1d, 0xfb, 0xe5, 0xcd, 0x20, 0xfb, 0xeb, 0xe3, + 0x4e, 0xe3, 0x78, 0xb7, 0xca, 0xab, 0xf9, 0x7e, + 0xe3, 0x77, 0xe3, 0x71, 0x23, 0xe3, 0xcd, 0xea, + 0xfb, 0xc2, 0xa0, 0xf9, 0xe1, 0xc9, 0xcd, 0xa5, + 0xfb, 0x52, 0x41, 0x4d, 0x54, 0x4f, 0xd0, 0x21, + 0xff, 0xff, 0x23, 0x7e, 0x47, 0x2f, 0x77, 0xbe, + 0x70, 0xca, 0xc2, 0xf9, 0xc3, 0x6d, 0xfb, 0xcd, + 0xa5, 0xfb, 0x43, 0x4f, 0x4d, 0xd0, 0xcd, 0x1d, + 0xfb, 0xe5, 0xcd, 0x20, 0xfb, 0xeb, 0x7e, 0x23, + 0xe3, 0xbe, 0x46, 0xc4, 0x6d, 0xfb, 0xcd, 0xea, + 0xfb, 0xe3, 0xc2, 0xde, 0xf9, 0xe1, 0xc9, 0xcd, + 0xa5, 0xfb, 0x46, 0x49, 0x4e, 0x44, 0xb1, 0xaf, + 0xc3, 0x03, 0xfa, 0xcd, 0xa5, 0xfb, 0x46, 0x49, + 0x4e, 0x44, 0xb2, 0xf5, 0xcd, 0x1d, 0xfb, 0xe5, + 0x0e, 0x02, 0xcd, 0x22, 0xfb, 0xeb, 0x45, 0xe1, + 0xf1, 0xb7, 0xf5, 0xca, 0x1f, 0xfa, 0xe5, 0x0e, + 0x02, 0xcd, 0x22, 0xfb, 0xeb, 0x4d, 0xe1, 0x7e, + 0xb8, 0xc2, 0x37, 0xfa, 0xf1, 0xb7, 0xf5, 0xca, + 0x31, 0xfa, 0x23, 0x7e, 0x2b, 0xb9, 0xc2, 0x37, + 0xfa, 0x23, 0x7e, 0x2b, 0xcd, 0x6d, 0xfb, 0xcd, + 0xea, 0xfb, 0xc2, 0x1f, 0xfa, 0xf1, 0xc9, 0xcd, + 0xa5, 0xfb, 0x4f, 0x55, 0xd4, 0x0e, 0x02, 0xcd, + 0x22, 0xfb, 0x0e, 0x02, 0xcd, 0x22, 0xfb, 0x55, + 0x21, 0xd0, 0xbf, 0x36, 0xc9, 0x2b, 0x72, 0x2b, + 0x36, 0xd3, 0x7b, 0xe9, 0xcd, 0xa5, 0xfb, 0x49, + 0xce, 0x0e, 0x02, 0xcd, 0x22, 0xfb, 0x21, 0xd0, + 0xbf, 0x36, 0xc9, 0x2b, 0x73, 0x2b, 0x36, 0xdb, + 0xcd, 0xce, 0xbf, 0xc3, 0x79, 0xfb, 0xcd, 0xa5, + 0xfb, 0x48, 0x45, 0x58, 0x4c, 0x4f, 0x41, 0xc4, + 0x0e, 0x01, 0xcd, 0x22, 0xfb, 0x21, 0xe0, 0xbf, + 0x73, 0xcd, 0x63, 0xfb, 0x0e, 0x00, 0xcd, 0xf3, + 0xfa, 0xd6, 0x3a, 0xc2, 0x8e, 0xfa, 0x57, 0xcd, + 0xd5, 0xfa, 0x7b, 0xb7, 0xca, 0xc3, 0xfa, 0x43, + 0x0c, 0xcd, 0xd5, 0xfa, 0x63, 0xcd, 0xd5, 0xfa, + 0x6b, 0x0d, 0xcd, 0xd5, 0xfa, 0xcd, 0xd5, 0xfa, + 0x73, 0x23, 0x05, 0xc2, 0xad, 0xfa, 0xcd, 0xd5, + 0xfa, 0xca, 0x89, 0xfa, 0xcd, 0xa5, 0xfb, 0x20, + 0x45, 0x52, 0xd2, 0xdb, 0x11, 0x11, 0xb1, 0x28, + 0xdb, 0x10, 0x0f, 0xda, 0xc3, 0xfa, 0x1b, 0x7a, + 0xb3, 0xc2, 0xc8, 0xfa, 0xc9, 0xcd, 0xf3, 0xfa, + 0xcd, 0xeb, 0xfa, 0x87, 0x87, 0x87, 0x87, 0x5f, + 0xcd, 0xf3, 0xfa, 0xcd, 0xeb, 0xfa, 0x83, 0x5f, + 0x82, 0x57, 0xc9, 0xd6, 0x30, 0xfe, 0x0a, 0xd8, + 0xd6, 0x07, 0xc9, 0xc5, 0x3a, 0xe0, 0xbf, 0xb7, + 0xc2, 0x04, 0xfb, 0xcd, 0xd6, 0xfb, 0xca, 0xfb, + 0xfa, 0xc3, 0x0f, 0xfb, 0xcd, 0xd6, 0xfb, 0xdb, + 0x12, 0x0f, 0xd2, 0x04, 0xfb, 0xdb, 0x13, 0x47, + 0x79, 0xb7, 0xca, 0x1a, 0xfb, 0x78, 0xc1, 0xc3, + 0x48, 0xfb, 0x78, 0xc1, 0xc9, 0xcd, 0x20, 0xfb, + 0x0e, 0x04, 0x21, 0x00, 0x00, 0xcd, 0xb3, 0xfb, + 0xfe, 0x30, 0xda, 0x1f, 0xf8, 0xfe, 0x3a, 0xd4, + 0x56, 0xfb, 0x29, 0x29, 0x29, 0x29, 0xd6, 0x30, + 0xfe, 0x0a, 0xda, 0x3f, 0xfb, 0xd6, 0x07, 0x85, + 0x6f, 0x0d, 0xc2, 0x25, 0xfb, 0xeb, 0x3e, 0x20, + 0xf5, 0xdb, 0x10, 0xe6, 0x02, 0xca, 0x49, 0xfb, + 0xf1, 0xe6, 0x7f, 0xd3, 0x11, 0xc9, 0xfe, 0x41, + 0xda, 0x1f, 0xf8, 0xe6, 0x5f, 0xfe, 0x47, 0xd2, + 0x1f, 0xf8, 0xc9, 0x3e, 0x0d, 0xcd, 0x48, 0xfb, + 0x3e, 0x0a, 0xc3, 0x48, 0xfb, 0xf5, 0xcd, 0x81, + 0xfb, 0x78, 0xcd, 0x79, 0xfb, 0xcd, 0x46, 0xfb, + 0xf1, 0xf5, 0xcd, 0x93, 0xfb, 0xf1, 0xc3, 0x97, + 0xfb, 0xcd, 0x63, 0xfb, 0xcd, 0xc7, 0xfb, 0x7c, + 0xcd, 0x79, 0xfb, 0x7d, 0xcd, 0x79, 0xfb, 0xcd, + 0x46, 0xfb, 0xc9, 0x1f, 0x1f, 0x1f, 0x1f, 0xe6, + 0x0f, 0xc6, 0x30, 0xfe, 0x3a, 0xda, 0x48, 0xfb, + 0xc6, 0x07, 0xc3, 0x48, 0xfb, 0xe1, 0x7e, 0xcd, + 0x48, 0xfb, 0xb6, 0x23, 0xf2, 0xa6, 0xfb, 0xcd, + 0x46, 0xfb, 0xe9, 0xcd, 0xbc, 0xfb, 0xfe, 0x1b, + 0xc8, 0xc3, 0x48, 0xfb, 0xdb, 0x10, 0x0f, 0xd2, + 0xbc, 0xfb, 0xdb, 0x11, 0xe6, 0x7f, 0xc9, 0xcd, + 0xd6, 0xfb, 0xfe, 0x20, 0xc0, 0xcd, 0xd6, 0xfb, + 0xfe, 0x20, 0xc2, 0xcd, 0xfb, 0xc9, 0xdb, 0x10, + 0xe6, 0x01, 0xc8, 0xdb, 0x11, 0xe6, 0x7f, 0xfe, + 0x03, 0xca, 0x1f, 0xf8, 0xfe, 0x1b, 0xca, 0x1f, + 0xf8, 0xc9, 0x7b, 0x95, 0xc2, 0xf1, 0xfb, 0x7a, + 0x9c, 0x23, 0xc0, 0x13, 0xc9, 0xff, 0xff, 0xff +}; + +#define ROM_AZ80_DBL_BASEADDR 0xff00 +#define ROM_AZ80_DBL_SIZE 0x0100 +#define ROM_AZ80_DBL_NAME "AZ80DBL" +#define ROM_AZ80_DBL_DESC "AltairZ80 Disk Boot Loader" + +int32 rom_az80_dbl[] = { + 0xf3, 0x06, 0x80, 0x3e, 0x0e, 0xd3, 0xfe, 0x05, /* ff00-ff07 */ + 0xc2, 0x05, 0xff, 0x3e, 0x16, 0xd3, 0xfe, 0x3e, /* ff08-ff0f */ + 0x12, 0xd3, 0xfe, 0xdb, 0xfe, 0xb7, 0xca, 0x20, /* ff10-ff17 */ + 0xff, 0x3e, 0x0c, 0xd3, 0xfe, 0xaf, 0xd3, 0xfe, /* ff18-ff1f */ + 0x21, 0x00, 0x5c, 0x11, 0x33, 0xff, 0x0e, 0x88, /* ff20-ff27 */ + 0x1a, 0x77, 0x13, 0x23, 0x0d, 0xc2, 0x28, 0xff, /* ff28-ff2f */ + 0xc3, 0x00, 0x5c, 0x31, 0x21, 0x5d, 0x3e, 0x00, /* ff30-ff37 */ + 0xd3, 0x08, 0x3e, 0x04, 0xd3, 0x09, 0xc3, 0x19, /* ff38-ff3f */ + 0x5c, 0xdb, 0x08, 0xe6, 0x02, 0xc2, 0x0e, 0x5c, /* ff40-ff47 */ + 0x3e, 0x02, 0xd3, 0x09, 0xdb, 0x08, 0xe6, 0x40, /* ff48-ff4f */ + 0xc2, 0x0e, 0x5c, 0x11, 0x00, 0x00, 0x06, 0x08, /* ff50-ff57 */ + 0xc5, 0xd5, 0x11, 0x86, 0x80, 0x21, 0x88, 0x5c, /* ff58-ff5f */ + 0xdb, 0x09, 0x1f, 0xda, 0x2d, 0x5c, 0xe6, 0x1f, /* ff60-ff67 */ + 0xb8, 0xc2, 0x2d, 0x5c, 0xdb, 0x08, 0xb7, 0xfa, /* ff68-ff6f */ + 0x39, 0x5c, 0xdb, 0x0a, 0x77, 0x23, 0x1d, 0xc2, /* ff70-ff77 */ + 0x39, 0x5c, 0xd1, 0x21, 0x8b, 0x5c, 0x06, 0x80, /* ff78-ff7f */ + 0x7e, 0x12, 0x23, 0x13, 0x05, 0xc2, 0x4d, 0x5c, /* ff80-ff87 */ + 0xc1, 0x21, 0x00, 0x5c, 0x7a, 0xbc, 0xc2, 0x60, /* ff88-ff8f */ + 0x5c, 0x7b, 0xbd, 0xd2, 0x80, 0x5c, 0x04, 0x04, /* ff90-ff97 */ + 0x78, 0xfe, 0x20, 0xda, 0x25, 0x5c, 0x06, 0x01, /* ff98-ff9f */ + 0xca, 0x25, 0x5c, 0xdb, 0x08, 0xe6, 0x02, 0xc2, /* ffa0-ffa7 */ + 0x70, 0x5c, 0x3e, 0x01, 0xd3, 0x09, 0x06, 0x00, /* ffa8-ffaf */ + 0xc3, 0x25, 0x5c, 0x3e, 0x80, 0xd3, 0x08, 0xfb, /* ffb0-ffb7 */ + 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffb8-ffbf */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffc0-ffc7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffc8-ffcf */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffd0-ffd7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffd8-ffdf */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffe0-ffe7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffe8-ffef */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* fff0-fff7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* fff8-ffff */ +}; + +#endif diff --git a/Altair8800/s100_simh.c b/Altair8800/s100_simh.c new file mode 100644 index 00000000..2b6ce598 --- /dev/null +++ b/Altair8800/s100_simh.c @@ -0,0 +1,505 @@ +/* s100_simh.c: MITS Altair 8800 SIMH Pseudo Device + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + Based on work by Charles E Owen (c) 1997 + Based on work by Peter Schorn (c) 2002-2023 + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "s100_bus.h" +#include "s100_cpu.h" +#include "s100_simh.h" + +static t_stat simh_dev_reset(DEVICE *dptr); +static int32 simh_io_status(const int32 port, const int32 io, const int32 data); +static int32 simh_io_data(const int32 port, const int32 io, const int32 data); +static int32 simh_io_cmd(const int32 port, const int32 io, const int32 data); +static int32 simh_cmd_in(const int32 port); +static int32 simh_cmd_out(const int32 port, const int32 data); +static t_stat simh_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static void createCPMCommandLine(void); +static void attachCPM(UNIT *uptr, int32 readOnly); +static void detachCPM(UNIT *uptr); + +static IDEV idev_stat[2] = { {NULL, NULL}, {NULL, NULL} }; /* Save IO devices on 0x12 and 0x13 */ +static IDEV idev_data[2] = { {NULL, NULL}, {NULL, NULL} }; /* Save IO devices on 0x12 and 0x13 */ + +/* Debug flags */ +#define IN_MSG (1 << 0) +#define OUT_MSG (1 << 1) +#define CMD_MSG (1 << 2) +#define VERBOSE_MSG (1 << 3) + +/* Debug Flags */ +static DEBTAB generic_dt[] = { + { "IN", IN_MSG, "IN messages" }, + { "OUT", OUT_MSG, "OUT messages" }, + { "CMD", CMD_MSG, "Commands" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +#define CPM_COMMAND_LINE_LENGTH 128 +#define CPM_FCB_ADDRESS 0x0080 /* Default FCB address for CP/M. */ + +#define SIMH_CAN_READ 0x01 /* bit 0 is set iff character available */ +#define SIMH_CAN_WRITE 0x02 /* bit 1 is set iff character can be sent */ +#define SIMH_RESET 0x03 /* Command to reset SIMH */ +#define CONTROLZ_CHAR 0x1a /* control Z character */ + +/* SIMH pseudo device status registers */ +/* miscellaneous */ +static int32 versionPos = 0; /* determines state for sending device identifier */ +static int32 lastCPMStatus = 0; /* result of last attachCPM command */ +static int32 lastCommand = 0; /* most recent command processed on port 0xfeh */ + +/* Set FCB Address (needed for MS-DOS READ and WRITE commands. */ +static int32 FCBAddress = CPM_FCB_ADDRESS; /* FCB Address */ + +static int32 warnLevelSIMH = 3; /* display at most 'warnLevelSIMH' times the same warning */ +static int32 warnUnattachedSIMH = 0; /* display a warning message if < warnLevel and SIMH set to + VERBOSE and output to SIMH without an attached file */ +static int32 warnSIMHEOF = 0; /* display a warning message if < warnLevel and SIMH set to + VERBOSE and attempt to read from SIMH past EOF */ + +/* Synthetic device SIMH for communication + between Altair and SIMH environment using port 0xfe */ +static UNIT simh_unit = { + UDATA (NULL, UNIT_ATTABLE | UNIT_ROABLE, 0) +}; + +static REG simh_reg[] = { + { DRDATAD (VPOS, versionPos, 8, + "Status register for sending version information"), REG_RO }, + { DRDATAD (LCPMS, lastCPMStatus, 8, + "Result of last attachCPM command"), REG_RO }, + { DRDATAD (LCMD, lastCommand, 8, + "Last command processed on SIMH port"), REG_RO }, + { HRDATAD (FCBA, FCBAddress, 16, + "Address of the FCB for file operations") }, + { NULL } +}; + +static MTAB simh_mod[] = { + { UNIT_SIMH_VERBOSE, UNIT_SIMH_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_SIMH_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + { 0 } +}; + +const char* simh_description(DEVICE *dptr) { + return "SIMH Pseudo Device"; +} + +DEVICE simh_dev = { + "SIMH", &simh_unit, simh_reg, simh_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &simh_dev_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE | DEV_DEBUG), 0, + generic_dt, NULL, NULL, &simh_show_help, NULL, NULL, &simh_description +}; + +static char cpmCommandLine[CPM_COMMAND_LINE_LENGTH]; + +/* Z80 or 8080 programs communicate with the SIMH pseudo device via port 0xfe. + The following principles apply: + + 1) For commands that do not require parameters and do not return results + ld a, + out (0feh),a + Special case is the reset command which needs to be send 128 times to make + sure that the internal state is properly reset. + + 2) For commands that require parameters and do not return results + ld a, + out (0feh),a + ld a, + out (0feh),a + ld a, + out (0feh),a + ... + Note: The calling program must send all parameter bytes. Otherwise + the pseudo device is left in an undefined state. + + 3) For commands that do not require parameters and return results + ld a, + out (0feh),a + in a,(0feh) ; contains first byte of result + in a,(0feh) ; contains second byte of result + ... + Note: The calling program must request all bytes of the result. Otherwise + the pseudo device is left in an undefined state. + + 4) For commands that do require parameters and return results + ld a, + out (0feh),a + ld a, + out (0feh),a + ld a, + out (0feh),a + ... ; send all parameters + in a,(0feh) ; contains first byte of result + in a,(0feh) ; contains second byte of result + ... + +*/ + +/* simhPseudoDeviceCommands - do not change command numbers */ +#define resetPTRCmd 3 /* 3 reset the PTR device */ +#define attachPTRCmd 4 /* 4 attach the PTR device */ +#define detachPTRCmd 5 /* 5 detach the PTR device */ +#define getSIMHVersionCmd 6 /* 6 get the current version of the SIMH pseudo device */ +#define resetSIMHInterfaceCmd 14 /* 14 reset the SIMH pseudo device */ +#define attachPTPCmd 16 /* 16 attach PTP to the file with name at beginning of CP/M command line*/ +#define detachPTPCmd 17 /* 17 detach PTP */ +#define setZ80CPUCmd 19 /* 19 set the CPU to a Z80 */ +#define set8080CPUCmd 20 /* 20 set the CPU to an 8080 */ +#define getHostOSPathSeparatorCmd 28 /* 28 obtain the file path separator of the OS under which SIMH runs */ +#define kSimhPseudoDeviceCommands 35 /* Highest AltairZ80 SIMH command */ + +static const char *cmdNames[kSimhPseudoDeviceCommands] = { + "Undefined", + "Undefined", + "Undefined", + "resetPTR", + "attachPTR", + "detachPTR", + "getSIMHVersion", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "resetSIMHInterface", + "Undefined", + "attachPTP", + "detachPTP", + "Undefined", + "setZ80CPU", + "set8080CPU", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "getHostOSPathSeparator", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", + "Undefined", +}; + +static char version[] = "SIMH005"; + +static t_stat simh_dev_reset(DEVICE *dptr) { + if (dptr->flags & DEV_DIS) { + s100_bus_remio(0xfe, 1, &simh_io_cmd); /* Command Port */ + } + else { + s100_bus_addio(0xfe, 1, &simh_io_cmd, "SIMH"); /* Command Port */ + } + + versionPos = 0; + lastCommand = 0; + lastCPMStatus = SCPE_OK; + FCBAddress = CPM_FCB_ADDRESS; + + if (simh_unit.flags & UNIT_ATT) { /* not attached */ + detachCPM(&simh_unit); + } + + return SCPE_OK; +} + +static void createCPMCommandLine(void) { + int32 i, len = (s100_bus_memr(FCBAddress) & 0x7f); /* 0x80 contains length of command line, discard first char */ + for (i = 0; i < len - 1; i++) { + cpmCommandLine[i] = (char) s100_bus_memr(FCBAddress + 0x02 + i); /* the first char, typically ' ', is discarded */ + } + cpmCommandLine[i] = 0; /* make C string */ +} + +/* The CP/M command line is used as the name of a file and UNIT* uptr is attached to it. */ +static void attachCPM(UNIT *uptr, int32 readOnly) +{ + createCPMCommandLine(); + + sim_debug(VERBOSE_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " CP/M command line='%s'.\n", s100_bus_get_addr(), cpmCommandLine); + + if (readOnly) { + sim_switches = SWMASK('R') | SWMASK('Q'); + } + else { + sim_switches = SWMASK('W') | SWMASK('N') | SWMASK('Q'); + } + + /* 'N' option makes sure that file is properly truncated if it had existed before */ + sim_quiet = sim_switches & SWMASK('Q'); /* -q means quiet */ + + lastCPMStatus = attach_unit(uptr, cpmCommandLine); + + if (lastCPMStatus != SCPE_OK) { + sim_debug(VERBOSE_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " Cannot open '%s' (%s).\n", s100_bus_get_addr(), cpmCommandLine, + sim_error_text(lastCPMStatus)); + } + + /* Save any devices attached to IO Port 0x12 and 0x13 */ + s100_bus_get_idev(0x12, &idev_stat[0], &idev_stat[1]); + s100_bus_get_idev(0x13, &idev_data[0], &idev_data[1]); + + s100_bus_addio(0x12, 1, &simh_io_status, "SIMHS"); /* Status Port */ + s100_bus_addio(0x13, 1, &simh_io_data, "SIMHD"); /* Data Port */ + + simh_unit.u3 = FALSE; /* reset EOF indicator */ +} + +static void detachCPM(UNIT *uptr) +{ + detach_unit(&simh_unit); + + if (idev_stat[0].routine != NULL) { + s100_bus_addio_in(0x12, 1, idev_stat[0].routine, idev_stat[0].name); /* Status IN Port */ + idev_stat[0].routine = NULL; + } + if (idev_stat[1].routine != NULL) { + s100_bus_addio_out(0x12, 1, idev_stat[1].routine, idev_stat[1].name); /* Status OUT Port */ + idev_stat[1].routine = NULL; + } + if (idev_data[0].routine != NULL) { + s100_bus_addio_in(0x13, 1, idev_data[0].routine, idev_data[0].name); /* Data IN Port */ + idev_data[0].routine = NULL; + } + if (idev_data[1].routine != NULL) { + s100_bus_addio_out(0x13, 1, idev_data[1].routine, idev_data[1].name); /* Data OUT Port */ + idev_data[1].routine = NULL; + } +} + +static int32 simh_cmd_in(const int32 port) +{ + int32 result = 0; + + switch(lastCommand) { + case attachPTRCmd: + case attachPTPCmd: + result = lastCPMStatus; + lastCommand = 0; + break; + + case getSIMHVersionCmd: + result = version[versionPos++]; + if (result == 0) + versionPos = lastCommand = 0; + break; + + case getHostOSPathSeparatorCmd: + result = sim_file_path_separator; + break; + + default: + sim_debug(VERBOSE_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " Undefined IN from SIMH pseudo device on port %03xh ignored.\n", + s100_bus_get_addr(), port); + result = lastCommand = 0; + } + + return result; +} + +static int32 simh_cmd_out(const int32 port, const int32 data) { + + switch(lastCommand) { + default: /* lastCommand not yet set */ + sim_debug(CMD_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " CMD(0x%02x) <- %i (0x%02x, '%s')\n", + s100_bus_get_addr(), port, data, data, + (0 <= data) && (data < kSimhPseudoDeviceCommands) ? + cmdNames[data] : "Unknown command"); + + lastCommand = data; + + switch(data) { + case getSIMHVersionCmd: + versionPos = 0; + break; + + case resetPTRCmd: /* reset ptr device */ + break; + + case attachPTRCmd: /* attach ptr to the file with name at beginning of CP/M command line */ + attachCPM(&simh_unit, TRUE); + break; + + case detachPTRCmd: /* detach ptr */ + detachCPM(&simh_unit); + break; + + case attachPTPCmd: /* attach ptp to the file with name at beginning of CP/M command line */ + attachCPM(&simh_unit, FALSE); + break; + + case detachPTPCmd: /* detach ptp */ + detachCPM(&simh_unit); + break; + + case resetSIMHInterfaceCmd: + lastCommand = 0; + FCBAddress = CPM_FCB_ADDRESS; + break; + + case setZ80CPUCmd: + cpu_set_chiptype(CHIP_TYPE_Z80); + break; + + case set8080CPUCmd: + cpu_set_chiptype(CHIP_TYPE_8080); + break; + + case getHostOSPathSeparatorCmd: + break; + + default: + sim_debug(CMD_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " Unknown command (%i) to SIMH pseudo device on port %03xh ignored.\n", + s100_bus_get_addr(), data, port); + } + } + + return 0xff; /* ignored, since OUT */ +} + +/* port 0xfc is a device for communication SIMH <--> Altair machine */ +static int32 simh_io_status(const int32 port, const int32 io, const int32 data) +{ + if (io == S100_IO_READ) { /* IN */ + if ((simh_unit.flags & UNIT_ATT) == 0) { /* SIMH is not attached */ + if ((simh_dev.dctrl & VERBOSE_MSG) && (warnUnattachedSIMH < warnLevelSIMH)) { + warnUnattachedSIMH++; +/*06*/ sim_debug(VERBOSE_MSG, &simh_dev, "PTR: " ADDRESS_FORMAT + " Attempt to test status of unattached SIMH[0x%02x]. 0x02 returned.\n", s100_bus_get_addr(), port); + } + return SIMH_CAN_WRITE; + } + /* if EOF then SIMH_CAN_WRITE else + (SIMH_CAN_WRITE and SIMH_CAN_READ) */ + return simh_unit.u3 ? SIMH_CAN_WRITE : (SIMH_CAN_READ | SIMH_CAN_WRITE); + } /* OUT follows */ + if (data == SIMH_RESET) { + simh_unit.u3 = FALSE; /* reset EOF indicator */ + sim_debug(CMD_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " Command OUT(0x%03x) = 0x%02x\n", s100_bus_get_addr(), port, data); + } + return 0x00; /* ignored since OUT */ +} + +/* port 0xfd is a device for communication SIMH <--> Altair machine */ +static int32 simh_io_data(const int32 port, const int32 io, const int32 data) +{ + int32 ch; + + if (io == S100_IO_READ) { /* IN */ + if (simh_unit.u3) { /* EOF reached, no more data available */ + if ((simh_dev.dctrl & VERBOSE_MSG) && (warnSIMHEOF < warnLevelSIMH)) { + warnSIMHEOF++; +/*07*/ sim_debug(VERBOSE_MSG, &simh_dev, "PTR: " ADDRESS_FORMAT + " SIMH[0x%02x] attempted to read past EOF. 0x00 returned.\n", s100_bus_get_addr(), port); + } + return 0x00; + } + if ((simh_unit.flags & UNIT_ATT) == 0) { /* not attached */ + if ((simh_dev.dctrl & VERBOSE_MSG) && (warnUnattachedSIMH < warnLevelSIMH)) { + warnUnattachedSIMH++; +/*08*/ sim_debug(VERBOSE_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " Attempt to read from unattached SIMH[0x%02x]. 0x00 returned.\n", s100_bus_get_addr(), port); + } + return 0x00; + } + if ((ch = getc(simh_unit.fileref)) == EOF) { /* end of file? */ + simh_unit.u3 = TRUE; /* remember EOF reached */ + sim_debug(VERBOSE_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " EOF on read\n", s100_bus_get_addr()); + return CONTROLZ_CHAR; /* ^Z denotes end of text file in CP/M */ + } + return ch & 0xff; + } /* OUT follows */ + if (simh_unit.flags & UNIT_ATT) /* unit must be attached */ + putc(data, simh_unit.fileref); + /* else ignore data */ + else if ((simh_dev.dctrl & VERBOSE_MSG) && (warnUnattachedSIMH < warnLevelSIMH)) { + warnUnattachedSIMH++; +/*09*/ sim_debug(VERBOSE_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " Attempt to output '0x%02x' to unattached SIMH[0x%02x] - ignored.\n", s100_bus_get_addr(), data, port); + } + return 0x00; /* ignored since OUT */ +} + +/* port 0xfe is a device for communication SIMH <--> Altair machine */ +static int32 simh_io_cmd(const int32 port, const int32 io, const int32 data) +{ + int32 result = 0; + + if (io == S100_IO_READ) { + result = simh_cmd_in(port); + sim_debug(IN_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " IN(0x%02x) -> %i (0x%02x, '%c')\n", s100_bus_get_addr(), + port, result, result, + (32 <= (result & 0xff)) && ((result & 0xff) <= 127) ? (result & 0xff) : '?'); + + } else { + sim_debug(OUT_MSG, &simh_dev, "SIMH: " ADDRESS_FORMAT + " OUT(0x%02x) <- %i (0x%02x, '%c')\n", s100_bus_get_addr(), + port, data, data, + (32 <= (data & 0xff)) && ((data & 0xff) <= 127) ? (data & 0xff) : '?'); + simh_cmd_out(port, data); + } + + return result; +} + +static t_stat simh_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nSIMH Pseudo Device (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_simh.h b/Altair8800/s100_simh.h new file mode 100644 index 00000000..53c8b396 --- /dev/null +++ b/Altair8800/s100_simh.h @@ -0,0 +1,37 @@ +/* s100_simh.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_SIMH_H +#define _S100_SIMH_H + +#define UNIT_V_SIMH_VERBOSE (UNIT_V_UF + 0) /* verbose mode, i.e. show error messages */ +#define UNIT_SIMH_VERBOSE (1 << UNIT_V_SIMH_VERBOSE) + +#endif diff --git a/Altair8800/s100_sio.c b/Altair8800/s100_sio.c new file mode 100644 index 00000000..a4985f2b --- /dev/null +++ b/Altair8800/s100_sio.c @@ -0,0 +1,432 @@ +/* s100_sio.c: MITS Altair 8800 Generic SIO + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "s100_bus.h" +#include "s100_sio.h" + +static int32 poc = TRUE; /* Power On Clear */ + +#define SIO_TYPE_CUST 0 +#define SIO_TYPE_2502 1 +#define SIO_TYPE_2651 2 +#define SIO_TYPE_6850 3 +#define SIO_TYPE_8250 4 +#define SIO_TYPE_8251 5 +#define SIO_TYPE_NONE 0xff + +static SIO sio_types[] = { +/* TYPE NAME DESC BASE STAT DATA RDRE RDRF TDRE TDRF */ + { SIO_TYPE_CUST, "CUST", "CUSTOM", 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { SIO_TYPE_2502, "2502", "2502 UART", 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x08 }, + { SIO_TYPE_2651, "2651", "2651 UART", 0x00, 0x01, 0x00, 0xc0, 0xc2, 0xc1, 0xc0 }, + { SIO_TYPE_6850, "6850", "6850 ACIA", 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02 }, + { SIO_TYPE_8250, "8250", "8250 UART", 0x00, 0x05, 0x00, 0x00, 0x01, 0x60, 0x00 }, + { SIO_TYPE_8251, "8251", "8251 UART", 0x00, 0x01, 0x00, 0x80, 0x82, 0x85, 0x80 }, + { SIO_TYPE_NONE, "NONE", "NONE" , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +}; + +static SIO_BOARD sio_boards[] = { + /* MITS 88-SIO */ + { SIO_TYPE_2502, "SIO", "MITS 88-SIO", 0x00 }, + + /* CompuPro System Support 1 */ + { SIO_TYPE_2651, "SS1", "CompuPro System Support 1", 0x5c }, + + /* No type selected */ + { SIO_TYPE_NONE, "NONE", "NONE", 0x00 } +}; + +static SIO sio; /* Active SIO configuration */ + +static int32 sio_type = SIO_TYPE_NONE; + +static int32 sio_rdr; /* Receive Data Register */ +static int32 sio_rdre; /* Receive Data Register Empty Flag */ +static int32 sio_tdre; /* Transmit Buffer Full Empty */ + +static t_stat sio_reset(DEVICE *dptr); +static int32 sio_io(const int32 addr, const int32 rw, const int32 data); +static int32 sio_io_in(const int32 addr); +static void sio_io_out(const int32 addr, int32 data); +static t_stat sio_set_board(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat sio_set_type (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat sio_set_val(UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat sio_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat sio_show_config(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat sio_show_list(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat sio_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static const char* sio_description(DEVICE *dptr) { + return "Generic Serial IO"; +} + +static UNIT sio_unit = { + UDATA (NULL, 0, 0) +}; + +static REG sio_reg[] = { + { HRDATAD (TYPE, sio_type, 8, "SIO Board Type") }, + { HRDATAD (RDR, sio_rdr, 8, "Receive Data Register") }, + { HRDATAD (RDRE, sio_rdre, 1, "Receive Data Register Empty") }, + { HRDATAD (TDRE, sio_tdre, 1, "Transmit Data Register Empty") }, + { NULL } +}; + +static MTAB sio_mod[] = { + { UNIT_SIO_VERBOSE, UNIT_SIO_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_SIO_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + + { MTAB_XTD | MTAB_VUN, UNIT_SIO_CONSOLE, NULL, "CONSOLE", &sio_set_console, NULL, NULL, "Set as CONSOLE" }, + { MTAB_XTD | MTAB_VUN, 0, NULL, "NOCONSOLE", &sio_set_console, NULL, NULL, "Remove as CONSOLE" }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "CONFIG", NULL, NULL, &sio_show_config, NULL, "Show SIO configuration" }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LIST", NULL, NULL, &sio_show_list, NULL, "Show available types and boards" }, + + { MTAB_XTD | MTAB_VDV | MTAB_VALO, SIO_TYPE_2502, NULL, "2502={base}", &sio_set_type, NULL, NULL, "Configure SIO for 2502 at base" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALO, SIO_TYPE_2651, NULL, "2651={base}", &sio_set_type, NULL, NULL, "Configure SIO for 2651 at base" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALO, SIO_TYPE_6850, NULL, "6850={base}", &sio_set_type, NULL, NULL, "Configure SIO for 6850 at base" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALO, SIO_TYPE_8250, NULL, "8250={base}", &sio_set_type, NULL, NULL, "Configure SIO for 8250 at base" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALO, SIO_TYPE_8251, NULL, "8251={base}", &sio_set_type, NULL, NULL, "Configure SIO for 8251 at base" }, + { MTAB_XTD | MTAB_VDV , SIO_TYPE_NONE, NULL, "NONE", &sio_set_type, NULL, NULL, "No type selected" }, + + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "BOARD={name}", &sio_set_board, NULL, NULL, "Configure SIO for name" }, + + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, NULL, "IOBASE={base}", &sio_set_val, NULL, NULL, "Set BASE I/O Address" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 2, NULL, "STAT={offset}", &sio_set_val, NULL, NULL, "Set STAT I/O Offset" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 3, NULL, "DATA={offset}", &sio_set_val, NULL, NULL, "Set DATA I/O Offset" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 4, NULL, "RDRE={mask}", &sio_set_val, NULL, NULL, "Set RDRE Mask" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 5, NULL, "RDRF={mask}", &sio_set_val, NULL, NULL, "Set RDRF Mask" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 6, NULL, "TDRE={mask}", &sio_set_val, NULL, NULL, "Set TDRE Mask" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 7, NULL, "TDRF={mask}", &sio_set_val, NULL, NULL, "Set TDRF Mask" }, + + { 0 } +}; + +/* Debug Flags */ +#define STATUS_MSG (1 << 0) +#define IN_MSG (1 << 1) +#define OUT_MSG (1 << 2) + +/* Debug Flags */ +static DEBTAB sio_dt[] = { + { "STATUS", STATUS_MSG, "Status messages" }, + { "IN", IN_MSG, "IN operations" }, + { "OUT", OUT_MSG, "OUT operations" }, + { NULL, 0 } +}; + +#define SIO_SNAME "SIO" + +DEVICE sio_dev = { + SIO_SNAME, &sio_unit, sio_reg, sio_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &sio_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0, + sio_dt, NULL, NULL, &sio_show_help, NULL, NULL, &sio_description +}; + +static t_stat sio_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { /* Disable Device */ + if (sio_type != SIO_TYPE_NONE) { + s100_bus_remio(sio.stat, 1, &sio_io); + s100_bus_remio(sio.data, 1, &sio_io); + + s100_bus_noconsole(&dptr->units[0]); + } + + poc = TRUE; + + return SCPE_OK; + } + + /* Device is enabled */ + if (poc) { + /* Set board type */ + sio_set_type(NULL, sio_type, NULL, NULL); + + poc = FALSE; + } + + /* Set as CONSOLE unit */ + if (dptr->units[0].flags & UNIT_SIO_CONSOLE) { + s100_bus_console(&dptr->units[0]); + } + + sio_rdre = TRUE; + sio_tdre = TRUE; + + sim_debug(STATUS_MSG, dptr, "reset adapter.\n"); + + return SCPE_OK; +} + +static int32 sio_io(const int32 addr, const int32 rw, const int32 data) +{ + int32 c; + + if (sio_rdre) { /* If the receive data register is empty and this */ + c = s100_bus_poll_kbd(&sio_unit); /* is the CONSOLE, check for keyboard input */ + + if (c & SCPE_KFLAG) { + sio_rdre = FALSE; + sio_rdr = c & DATAMASK; + } + } + + if (rw == S100_IO_READ) { + return sio_io_in(addr); + } + + sio_io_out(addr, data); + + return 0x0ff; +} + +static int32 sio_io_in(const int32 addr) +{ + sim_debug(IN_MSG, &sio_dev, ADDRESS_FORMAT " Port %02X.\n", s100_bus_get_addr(), addr & DATAMASK); + + if (addr == sio.base + sio.stat) { + return ((sio_rdre) ? sio.rdre : sio.rdrf) | ((sio_tdre) ? sio.tdre : sio.tdrf); + } + else if (addr == sio.base + sio.data) { + sio_rdre = TRUE; /* Clear RDF status bit */ + + return sio_rdr; /* return byte */ + } + + return 0xff; +} + +static void sio_io_out(const int32 addr, int32 data) +{ + sim_debug(OUT_MSG, &sio_dev, ADDRESS_FORMAT " Port %02X.\n", s100_bus_get_addr(), addr & DATAMASK); + + if (addr == sio.base + sio.data) { + sim_putchar(data & DATAMASK); + + sio_tdre = TRUE; /* Transmit buffer is always empty */ + } +} + +static t_stat sio_set_type(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + int32 result, base; + + if (value == sio_type) { + return SCPE_OK; + } + + if (sio_type != SIO_TYPE_NONE) { + s100_bus_remio(sio.base + sio.stat, 1, &sio_io); + s100_bus_remio(sio.base + sio.data, 1, &sio_io); + } + + sio_type = value; + + if (sio_type != SIO_TYPE_NONE) { + sio.type = sio_type; + sio.name = sio_types[sio_type].name; + sio.desc = sio_types[sio_type].desc; + sio.base = sio_types[sio_type].base; + sio.stat = sio_types[sio_type].stat; + sio.data = sio_types[sio_type].data; + sio.rdre = sio_types[sio_type].rdre; + sio.rdrf = sio_types[sio_type].rdrf; + sio.tdre = sio_types[sio_type].tdre; + sio.tdrf = sio_types[sio_type].tdrf; + + if (cptr != NULL) { + result = sscanf(cptr, "%x", &base); + + if (result == 1) { + sio.base = base & DATAMASK; + } + } + + s100_bus_addio(sio.base + sio.stat, 1, &sio_io, SIO_SNAME"S"); + s100_bus_addio(sio.base + sio.data, 1, &sio_io, SIO_SNAME"D"); + } + + return SCPE_OK; +} + +static t_stat sio_set_board(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + char cbuf[10]; + int i = 0; + + if (cptr == NULL) { + return SCPE_ARG; + } + + do { + if (sim_strcasecmp(cptr, sio_boards[i].name) == 0) { + sprintf(cbuf, "%04X", sio_boards[i].base); + return sio_set_type(uptr, sio_boards[i].type, cbuf, NULL); + } + } while (sio_boards[i++].type != SIO_TYPE_NONE); + + return SCPE_ARG; +} + +static t_stat sio_set_val(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + uint32 val; + + if (cptr == NULL || sscanf(cptr, "%02x", &val) == 0) { + return SCPE_ARG; + } + + val &= DATAMASK; + + switch (value) { + case 1: + sio.base = val; + break; + + case 2: + sio.stat = val; + break; + + case 3: + sio.data = val; + break; + + case 4: + sio.rdre = val; + break; + + case 5: + sio.rdrf = val; + break; + + case 6: + sio.tdre = val; + break; + + case 7: + sio.tdrf = val; + break; + + default: + return SCPE_ARG; + } + + sio.name = sio_types[SIO_TYPE_CUST].name; + sio.desc = sio_types[SIO_TYPE_CUST].desc; + sio.type = SIO_TYPE_CUST; + sio_type = SIO_TYPE_CUST; + + return SCPE_OK; +} + +static t_stat sio_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + if (value == UNIT_SIO_CONSOLE) { + s100_bus_console(uptr); + } + else { + s100_bus_noconsole(uptr); + } + + return SCPE_OK; +} + +static t_stat sio_show_config(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (sio_type != SIO_TYPE_NONE) { + sim_printf("SIO Base Address: %02X\n\n", sio.base); + sim_printf("SIO Status Register: %02X\n", sio.base + sio.stat); + sim_printf("SIO Data Register: %02X\n", sio.base + sio.data); + + sim_printf("SIO RDRE Mask: %02X\n", sio.rdre); + sim_printf("SIO RDRF Mask: %02X\n\n", sio.rdrf); + + sim_printf("SIO TDRE Mask: %02X\n", sio.tdre); + sim_printf("SIO TDRF Mask: %02X\n\n", sio.tdrf); + + sim_printf("%sCONSOLE\n", (uptr->flags & UNIT_SIO_CONSOLE) ? "" : "NO"); + } + else { + sim_printf("\n\tNot configured.\n"); + } + + return SCPE_OK; +} + +static t_stat sio_show_list(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + int i; + + sim_printf("\nAvailable types:\n"); + + i = 0; + + do { + if (sio_types[i].type != SIO_TYPE_CUST) { + sim_printf("%-8.8s %s\n", sio_types[i].name, sio_types[i].desc); + } + } while (sio_types[i++].type != SIO_TYPE_NONE); + + sim_printf("\nAvailable boards:\n"); + + i = 0; + + do { + sim_printf("%-8.8s %s\n", sio_boards[i].name, sio_boards[i].desc); + } while (sio_boards[i++].type != SIO_TYPE_NONE); + + return SCPE_OK; +} + +static t_stat sio_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 Generic SIO Device (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + fprintf(st, "\n"); + fprintf(st, "----- NOTES -----\n\n"); + fprintf(st, "Only one device may poll the host keyboard for CONSOLE input.\n"); + fprintf(st, "Use SET %s CONSOLE to select this UNIT as the CONSOLE device.\n", sim_dname(dptr)); + fprintf(st, "\nUse SHOW BUS CONSOLE to display the current CONSOLE device.\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_sio.h b/Altair8800/s100_sio.h new file mode 100644 index 00000000..cae135cf --- /dev/null +++ b/Altair8800/s100_sio.h @@ -0,0 +1,60 @@ +/* s100_sio.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef _S100_SIO_H +#define _S100_SIO_H + +#define UNIT_SIO_V_VERBOSE (UNIT_V_UF+0) +#define UNIT_SIO_VERBOSE (1 << UNIT_SIO_V_VERBOSE) +#define UNIT_SIO_V_CONSOLE (UNIT_V_UF+1) +#define UNIT_SIO_CONSOLE (1 << UNIT_SIO_V_CONSOLE) + +typedef struct { + uint8 type; /* Type Value */ + char *name; /* Name */ + char *desc; /* Description */ + int32 base; /* Base Port */ + int32 stat; /* Status Port Offset */ + int32 data; /* Data Port Offset */ + int32 rdre; /* Receive Data Register Empty Mask */ + int32 rdrf; /* Receive Data Register Full Mask */ + int32 tdre; /* Transmit Data Register Empty Mask */ + int32 tdrf; /* Transmit Data Register Full Mask */ +} SIO; + +typedef struct { + uint8 type; /* Board SIO Configuration Type */ + char *name; /* Board Name */ + char *desc; /* Board Description */ + int32 base; /* Board Base I/O Address */ +} SIO_BOARD; + +#endif + diff --git a/Altair8800/s100_ssw.c b/Altair8800/s100_ssw.c new file mode 100644 index 00000000..e8fae5cd --- /dev/null +++ b/Altair8800/s100_ssw.c @@ -0,0 +1,116 @@ +/* s100_ssw.c: MITS Altair 8800 Sense Switches + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "s100_bus.h" +#include "s100_ssw.h" + +#define DEVICE_NAME "SSW" + +static int32 poc = TRUE; /* Power On Clear */ + +static int32 SSW = 0; /* sense switch register */ + +static t_stat ssw_reset (DEVICE *dptr); +static int32 ssw_io (const int32 addr, const int32 rw, const int32 data); + +static t_stat ssw_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static const char* ssw_description(DEVICE *dptr) { + return "Front Panel Sense Switches"; +} + +static UNIT ssw_unit = { + UDATA (NULL, 0, 0) +}; + +static REG ssw_reg[] = { + { HRDATAD (SSWVAL, SSW, 8, "Front panel sense switches pseudo register") }, + { NULL } +}; + +static MTAB ssw_mod[] = { + { 0 } +}; + +/* Debug Flags */ +static DEBTAB ssw_dt[] = { + { NULL, 0 } +}; + +DEVICE ssw_dev = { + DEVICE_NAME, &ssw_unit, ssw_reg, ssw_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &ssw_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE), 0, + ssw_dt, NULL, NULL, + &ssw_show_help, NULL, NULL, + &ssw_description +}; + +static t_stat ssw_reset(DEVICE *dptr) { + if (dptr->flags & DEV_DIS) { /* Disable Device */ + s100_bus_remio_in(0xff, 1, &ssw_io); + + poc = TRUE; + } + else { + if (poc) { + s100_bus_addio_in(0xff, 1, &ssw_io, DEVICE_NAME); + + poc = FALSE; + } + } + + return SCPE_OK; +} + +static int32 ssw_io(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_READ) { + return SSW; + } + + return 0x0ff; +} + +static t_stat ssw_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 Front Panel Sense Switches (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + fprintf (st, "\nUse DEP SSWVAL to set the value returned by an IN 0FFH instruction.\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_ssw.h b/Altair8800/s100_ssw.h new file mode 100644 index 00000000..039fcddd --- /dev/null +++ b/Altair8800/s100_ssw.h @@ -0,0 +1,37 @@ +/* s100_ssw.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef S100_SSW_H_ +#define S100_SSW_H_ + +#define UNIT_SSW_V_VERBOSE (UNIT_V_UF+0) +#define UNIT_SSW_VERBOSE (1 << UNIT_SSW_V_VERBOSE) + +#endif diff --git a/Altair8800/s100_z80.c b/Altair8800/s100_z80.c new file mode 100644 index 00000000..078f850c --- /dev/null +++ b/Altair8800/s100_z80.c @@ -0,0 +1,6549 @@ +/* s100_z80.c: MITS Altair 8080/Z80 CPU + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + Based on work by Charles E Owen (c) 1997 + Based on work by Peter Schorn (c) 2002-2024 + Code for Z80 CPU from Frank D. Cringle ((c) 1995 under GNU license) + Contributions from Thomas Eberhardt + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "s100_bus.h" +#include "s100_cpu.h" +#include "s100_z80.h" +#include "s100_ram.h" + +/* Debug flags */ +#define INT_MSG (1 << 2) + +ChipType z80_chiptype = CHIP_TYPE_8080; + +static int32 poc = TRUE; /* Power On Clear */ + +#define INST_MAX_BYTES 4 /* instruction max bytes */ + +#define IFF1 1 /* Interrupt flip-flop 1 */ +#define IFF2 2 /* Interrupt flip-flop 2 */ + +#define FLAG_C 1 +#define FLAG_N 2 +#define FLAG_P 4 +#define FLAG_H 16 +#define FLAG_Z 64 +#define FLAG_S 128 + +#define SETFLAG(f,c) AF = (c) ? AF | FLAG_ ## f : AF & ~FLAG_ ## f +#define TSTFLAG(f) ((AF & FLAG_ ## f) != 0) +#define TSTFLAG2(a, f) ((a & FLAG_ ## f) != 0) + +#define LOW_DIGIT(x) ((x) & 0xf) +#define HIGH_DIGIT(x) (((x) >> 4) & 0xf) +#define LOW_REGISTER(x) ((x) & 0xff) +#define HIGH_REGISTER(x) (((x) >> 8) & 0xff) + +#define SET_LOW_REGISTER(x, v) x = (((x) & 0xff00) | ((v) & 0xff)) +#define SET_HIGH_REGISTER(x, v) x = (((x) & 0xff) | (((v) & 0xff) << 8)) + +#define PARITY(x) parityTable[(x) & 0xff] + +/* SET_PV and SET_PV2 are used to provide correct PARITY flag semantics for the 8080 in cases + where the Z80 uses the overflow flag. +*/ +#define SET_PVS(s) ((z80_chiptype == CHIP_TYPE_Z80) ? (((cbits >> 6) ^ (cbits >> 5)) & 4) : (PARITY(s))) +#define SET_PV (SET_PVS(sum)) +#define SET_PV2(x) ((z80_chiptype == CHIP_TYPE_Z80) ? (((temp == (x)) << 2)) : (PARITY(temp))) + +/* CHECK_CPU_8080 must be invoked whenever a Z80 only instruction is executed + In case a Z80 instruction is executed on an 8080 there are two cases: + 1) Trapping is enabled: execution stops + 2) Trapping is not enabled: decoding continues with the next byte, i.e. interpret as NOP + Note: in some cases different instructions need to be chosen on an 8080. +*/ + +#define CHECK_CPU_8080 \ + if ((z80_chiptype == CHIP_TYPE_8080) && (z80_unit.flags & UNIT_Z80_OPSTOP)) { \ + reason = STOP_OPCODE; \ + goto end_decode; \ + } + +/* CHECK_CPU_Z80 must be invoked whenever a non Z80 instruction is executed */ +#define CHECK_CPU_Z80 \ + if (z80_unit.flags & UNIT_Z80_OPSTOP) { \ + reason = STOP_OPCODE; \ + goto end_decode; \ + } + +#define POP(x) { \ + register uint32 y = RAM_PP(SP); \ + x = y + (RAM_PP(SP) << 8); \ +} + +#define JPC(cond) { \ + tStates += 10; \ + if (cond) { \ + PC = GET_WORD(PC); \ + } else { \ + PC += 2; \ + } \ +} + +#define CALLC(cond) { \ + if (cond) { \ + register uint32 adrr = GET_WORD(PC); \ + CHECK_BREAK_WORD(SP - 2); \ + PUSH(PC + 2); \ + PC = adrr; \ + tStates += 17; \ + } else { \ + PC += 2; \ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 11 : 10); \ + } \ +} + +/* increase R by val */ +#define INCR(val) IR_S = (IR_S & ~0x7f) | ((IR_S + (val)) & 0x7f) + +/* function prototypes */ +static t_stat z80_set_chiptype (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat z80_set_hist (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat z80_show_hist (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat z80_show (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat chip_show (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat z80_reset(DEVICE *dptr); +static const char* z80_description(DEVICE *dptr); + +static int32 parse_X80(const char *cptr, const int32 addr, uint32 *val, const char *const Mnemonics[]); + +static void prepareMemoryAccessMessage(const t_addr loc); +static void prepareInstructionMessage(const t_addr loc, const uint32 op); + +/* Z80 data structures + z80_dev Z80 device descriptor + z80_unit Z80 unit descriptor + z80_reg Z80 register list + z80_mod Z80 modifiers list +*/ + +static UNIT z80_unit = { + UDATA (NULL, UNIT_Z80_STOPONHALT, 0) +}; + +static uint32 PCX = 0; /* external view of PC */ +static int32 AF_S; /* AF register */ +static int32 BC_S; /* BC register */ +static int32 DE_S; /* DE register */ +static int32 HL_S; /* HL register */ +static int32 IX_S; /* IX register */ +static int32 IY_S; /* IY register */ +static int32 PC_S = 0; /* 8080 / Z80 program counter */ +static int32 SP_S; /* SP register */ +static int32 AF1_S; /* alternate AF register */ +static int32 BC1_S; /* alternate BC register */ +static int32 DE1_S; /* alternate DE register */ +static int32 HL1_S; /* alternate HL register */ +static int32 IFF_S; /* Interrupt Flip Flop */ +static int32 IM_S; /* Interrupt Mode register */ +static int32 IR_S; /* Interrupt (upper) / Refresh (lower) register */ + +static uint32 executedTStates = 0; /* executed t-states */ + +#define HIST_MIN 16 +#define HIST_MAX 8192 + +typedef struct { + uint8 valid; + uint16 af; + uint16 bc; + uint16 de; + uint16 hl; + t_addr pc; + t_addr sp; + uint16 af1; + uint16 bc1; + uint16 de1; + uint16 hl1; + uint16 ix; + uint16 iy; + t_value op[INST_MAX_BYTES]; +} insthist_t; + +static uint32 hst_p = 0; /* history pointer */ +static uint32 hst_lnt = 0; /* history length */ +static insthist_t *hst = NULL; /* instruction history */ + +static REG z80_reg[] = { + // 8080 and Z80 registers + { HRDATAD (AF, AF_S, 16, "8080 / Z80 Accumulator Flag register") }, + { HRDATAD (BC, BC_S, 16, "8080 / Z80 BC register") }, + { HRDATAD (DE, DE_S, 16, "8080 / Z80 DE register") }, + { HRDATAD (HL, HL_S, 16, "8080 / Z80 HL register") }, + { HRDATAD (PC, PC_S, 16, "8080 / Z80 Program Counter register") }, + { HRDATAD (SP, SP_S, 16, "8080 / Z80 Stack Pointer register") }, + + // Z80 registers + { HRDATAD (IX, IX_S, 16, "Z80 IX register") }, + { HRDATAD (IY, IY_S, 16, "Z80 IY register") }, + { HRDATAD (AF1, AF1_S, 16, "Z80 Alternate Accumulator Flag register") }, + { HRDATAD (BC1, BC1_S, 16, "Z80 Alternate BC register") }, + { HRDATAD (DE1, DE1_S, 16, "Z80 Alternate DE register") }, + { HRDATAD (HL1, HL1_S, 16, "Z80 Alternate HL register") }, + { GRDATAD (IFF, IFF_S, 2, 2, 0, "Z80 Interrupt Flip Flop register") }, + { HRDATAD (IM, IM_S, 2, "Z80 Interrupt Mode register") }, + { HRDATAD (IR, IR_S, 16, "Z80 Interrupt (upper) / Refresh (lower) register") }, + + // Pseudo registers + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { FLDATAD (OPSTOP, z80_unit.flags, UNIT_Z80_V_OPSTOP, "Stop on invalid operation pseudo register"), REG_HRO }, + { DRDATAD (TSTATES, executedTStates, 32, "Executed t-states for 8080 / Z80 pseudo register"), REG_RO }, + { NULL } +}; + +REG *z80_pc_reg = &z80_reg[CPU_INDEX_8080]; + +static const char* z80_description(DEVICE *dptr) { + return "8080/Z80 CPU"; +} + +static MTAB z80_mod[] = { + { MTAB_XTD | MTAB_VDV, CHIP_TYPE_8080, NULL, "8080", &z80_set_chiptype, + NULL, NULL, "Chooses 8080 CPU"}, + { MTAB_XTD | MTAB_VDV, CHIP_TYPE_Z80, NULL, "Z80", &z80_set_chiptype, + NULL, NULL, "Chooses Z80 CPU" }, + { UNIT_Z80_OPSTOP, UNIT_Z80_OPSTOP, "ITRAP", "ITRAP", NULL, &chip_show, + NULL, "Stop on illegal instruction" }, + { UNIT_Z80_OPSTOP, 0, "NOITRAP", "NOITRAP", NULL, &chip_show, + NULL, "Do not stop on illegal instruction" }, + { UNIT_Z80_STOPONHALT, UNIT_Z80_STOPONHALT,"STOPONHALT", "STOPONHALT", NULL, + NULL, NULL, "Stop on halt instruction" }, + { UNIT_Z80_STOPONHALT, 0, "LOOPONHALT", "LOOPONHALT", NULL, + NULL, NULL, "Enter loop on halt instruction" }, + { UNIT_CPU_VERBOSE, UNIT_CPU_VERBOSE, "VERBOSE", "VERBOSE", NULL, &z80_show, + NULL, "Enable verbose messages" }, + { UNIT_CPU_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_VALO|MTAB_SHP, 0, "HISTORY", "HISTORY", &z80_set_hist, &z80_show_hist, + NULL, "Instruction history buffer"}, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB z80_dt[] = { + { "LOG_INT", INT_MSG, "Log interrupts" }, + { NULL, 0 } +}; + +DEVICE z80_dev = { + "Z80", &z80_unit, z80_reg, z80_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &z80_reset, + NULL, NULL, NULL, + NULL, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0, + z80_dt, NULL, NULL, &z80_show_help, NULL, NULL, &z80_description +}; + +/* the following tables precompute some common subexpressions + parityTable[i] 0..255 (number of 1's in i is odd) ? 0 : 4 + incTable[i] 0..256! (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0) << 4) + decTable[i] 0..255 (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0xf) << 4) | 2 + cbitsTable[i] 0..511 (i & 0x10) | ((i >> 8) & 1) + cbitsDup8Table[i] 0..511 (i & 0x10) | ((i >> 8) & 1) | ((i & 0xff) << 8) | (i & 0xa8) | + (((i & 0xff) == 0) << 6) + cbitsDup16Table[i] 0..511 (i & 0x10) | ((i >> 8) & 1) | (i & 0x28) + cbits2Table[i] 0..511 (i & 0x10) | ((i >> 8) & 1) | 2 + rrcaTable[i] 0..255 ((i & 1) << 15) | ((i >> 1) << 8) | ((i >> 1) & 0x28) | (i & 1) + rraTable[i] 0..255 ((i >> 1) << 8) | ((i >> 1) & 0x28) | (i & 1) + addTable[i] 0..511 ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) + subTable[i] 0..255 ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) | 2 + andTable[i] 0..255 (i << 8) | (i & 0xa8) | ((i == 0) << 6) | 0x10 | parityTable[i] + xororTable[i] 0..255 (i << 8) | (i & 0xa8) | ((i == 0) << 6) | parityTable[i] + rotateShiftTable[i] 0..255 (i & 0xa8) | (((i & 0xff) == 0) << 6) | parityTable[i & 0xff] + incZ80Table[i] 0..256! (i & 0xa8) | (((i & 0xff) == 0) << 6) | + (((i & 0xf) == 0) << 4) | ((i == 0x80) << 2) + decZ80Table[i] 0..255 (i & 0xa8) | (((i & 0xff) == 0) << 6) | + (((i & 0xf) == 0xf) << 4) | ((i == 0x7f) << 2) | 2 + cbitsZ80Table[i] 0..511 (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) + cbitsZ80DupTable[i] 0..511 (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | + ((i >> 8) & 1) | (i & 0xa8) + cbits2Z80Table[i] 0..511 (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | 2 + cbits2Z80DupTable[i] 0..511 (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | 2 | + (i & 0xa8) + negTable[i] 0..255 (((i & 0x0f) != 0) << 4) | ((i == 0x80) << 2) | 2 | (i != 0) + rrdrldTable[i] 0..255 (i << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) | parityTable[i] + cpTable[i] 0..255 (i & 0x80) | (((i & 0xff) == 0) << 6) +*/ + +/* parityTable[i] = (number of 1's in i is odd) ? 0 : 4, i = 0..255 */ +static const uint8 parityTable[256] = { + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 0,4,4,0,4,0,0,4,4,0,0,4,0,4,4,0, + 4,0,0,4,0,4,4,0,0,4,4,0,4,0,0,4, +}; + +/* incTable[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0) << 4), i = 0..256 */ +static const uint8 incTable[257] = { + 80, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 16, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, 80 +}; + +/* decTable[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | (((i & 0xf) == 0xf) << 4) | 2, i = 0..255 */ +static const uint8 decTable[256] = { + 66, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, +}; + +/* cbitsTable[i] = (i & 0x10) | ((i >> 8) & 1), i = 0..511 */ +static const uint8 cbitsTable[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +}; + +/* cbitsDup8Table[i] = (i & 0x10) | ((i >> 8) & 1) | ((i & 0xff) << 8) | (i & 0xa8) | + (((i & 0xff) == 0) << 6), i = 0..511 */ +static const uint16 cbitsDup8Table[512] = { + 0x0040,0x0100,0x0200,0x0300,0x0400,0x0500,0x0600,0x0700, + 0x0808,0x0908,0x0a08,0x0b08,0x0c08,0x0d08,0x0e08,0x0f08, + 0x1010,0x1110,0x1210,0x1310,0x1410,0x1510,0x1610,0x1710, + 0x1818,0x1918,0x1a18,0x1b18,0x1c18,0x1d18,0x1e18,0x1f18, + 0x2020,0x2120,0x2220,0x2320,0x2420,0x2520,0x2620,0x2720, + 0x2828,0x2928,0x2a28,0x2b28,0x2c28,0x2d28,0x2e28,0x2f28, + 0x3030,0x3130,0x3230,0x3330,0x3430,0x3530,0x3630,0x3730, + 0x3838,0x3938,0x3a38,0x3b38,0x3c38,0x3d38,0x3e38,0x3f38, + 0x4000,0x4100,0x4200,0x4300,0x4400,0x4500,0x4600,0x4700, + 0x4808,0x4908,0x4a08,0x4b08,0x4c08,0x4d08,0x4e08,0x4f08, + 0x5010,0x5110,0x5210,0x5310,0x5410,0x5510,0x5610,0x5710, + 0x5818,0x5918,0x5a18,0x5b18,0x5c18,0x5d18,0x5e18,0x5f18, + 0x6020,0x6120,0x6220,0x6320,0x6420,0x6520,0x6620,0x6720, + 0x6828,0x6928,0x6a28,0x6b28,0x6c28,0x6d28,0x6e28,0x6f28, + 0x7030,0x7130,0x7230,0x7330,0x7430,0x7530,0x7630,0x7730, + 0x7838,0x7938,0x7a38,0x7b38,0x7c38,0x7d38,0x7e38,0x7f38, + 0x8080,0x8180,0x8280,0x8380,0x8480,0x8580,0x8680,0x8780, + 0x8888,0x8988,0x8a88,0x8b88,0x8c88,0x8d88,0x8e88,0x8f88, + 0x9090,0x9190,0x9290,0x9390,0x9490,0x9590,0x9690,0x9790, + 0x9898,0x9998,0x9a98,0x9b98,0x9c98,0x9d98,0x9e98,0x9f98, + 0xa0a0,0xa1a0,0xa2a0,0xa3a0,0xa4a0,0xa5a0,0xa6a0,0xa7a0, + 0xa8a8,0xa9a8,0xaaa8,0xaba8,0xaca8,0xada8,0xaea8,0xafa8, + 0xb0b0,0xb1b0,0xb2b0,0xb3b0,0xb4b0,0xb5b0,0xb6b0,0xb7b0, + 0xb8b8,0xb9b8,0xbab8,0xbbb8,0xbcb8,0xbdb8,0xbeb8,0xbfb8, + 0xc080,0xc180,0xc280,0xc380,0xc480,0xc580,0xc680,0xc780, + 0xc888,0xc988,0xca88,0xcb88,0xcc88,0xcd88,0xce88,0xcf88, + 0xd090,0xd190,0xd290,0xd390,0xd490,0xd590,0xd690,0xd790, + 0xd898,0xd998,0xda98,0xdb98,0xdc98,0xdd98,0xde98,0xdf98, + 0xe0a0,0xe1a0,0xe2a0,0xe3a0,0xe4a0,0xe5a0,0xe6a0,0xe7a0, + 0xe8a8,0xe9a8,0xeaa8,0xeba8,0xeca8,0xeda8,0xeea8,0xefa8, + 0xf0b0,0xf1b0,0xf2b0,0xf3b0,0xf4b0,0xf5b0,0xf6b0,0xf7b0, + 0xf8b8,0xf9b8,0xfab8,0xfbb8,0xfcb8,0xfdb8,0xfeb8,0xffb8, + 0x0041,0x0101,0x0201,0x0301,0x0401,0x0501,0x0601,0x0701, + 0x0809,0x0909,0x0a09,0x0b09,0x0c09,0x0d09,0x0e09,0x0f09, + 0x1011,0x1111,0x1211,0x1311,0x1411,0x1511,0x1611,0x1711, + 0x1819,0x1919,0x1a19,0x1b19,0x1c19,0x1d19,0x1e19,0x1f19, + 0x2021,0x2121,0x2221,0x2321,0x2421,0x2521,0x2621,0x2721, + 0x2829,0x2929,0x2a29,0x2b29,0x2c29,0x2d29,0x2e29,0x2f29, + 0x3031,0x3131,0x3231,0x3331,0x3431,0x3531,0x3631,0x3731, + 0x3839,0x3939,0x3a39,0x3b39,0x3c39,0x3d39,0x3e39,0x3f39, + 0x4001,0x4101,0x4201,0x4301,0x4401,0x4501,0x4601,0x4701, + 0x4809,0x4909,0x4a09,0x4b09,0x4c09,0x4d09,0x4e09,0x4f09, + 0x5011,0x5111,0x5211,0x5311,0x5411,0x5511,0x5611,0x5711, + 0x5819,0x5919,0x5a19,0x5b19,0x5c19,0x5d19,0x5e19,0x5f19, + 0x6021,0x6121,0x6221,0x6321,0x6421,0x6521,0x6621,0x6721, + 0x6829,0x6929,0x6a29,0x6b29,0x6c29,0x6d29,0x6e29,0x6f29, + 0x7031,0x7131,0x7231,0x7331,0x7431,0x7531,0x7631,0x7731, + 0x7839,0x7939,0x7a39,0x7b39,0x7c39,0x7d39,0x7e39,0x7f39, + 0x8081,0x8181,0x8281,0x8381,0x8481,0x8581,0x8681,0x8781, + 0x8889,0x8989,0x8a89,0x8b89,0x8c89,0x8d89,0x8e89,0x8f89, + 0x9091,0x9191,0x9291,0x9391,0x9491,0x9591,0x9691,0x9791, + 0x9899,0x9999,0x9a99,0x9b99,0x9c99,0x9d99,0x9e99,0x9f99, + 0xa0a1,0xa1a1,0xa2a1,0xa3a1,0xa4a1,0xa5a1,0xa6a1,0xa7a1, + 0xa8a9,0xa9a9,0xaaa9,0xaba9,0xaca9,0xada9,0xaea9,0xafa9, + 0xb0b1,0xb1b1,0xb2b1,0xb3b1,0xb4b1,0xb5b1,0xb6b1,0xb7b1, + 0xb8b9,0xb9b9,0xbab9,0xbbb9,0xbcb9,0xbdb9,0xbeb9,0xbfb9, + 0xc081,0xc181,0xc281,0xc381,0xc481,0xc581,0xc681,0xc781, + 0xc889,0xc989,0xca89,0xcb89,0xcc89,0xcd89,0xce89,0xcf89, + 0xd091,0xd191,0xd291,0xd391,0xd491,0xd591,0xd691,0xd791, + 0xd899,0xd999,0xda99,0xdb99,0xdc99,0xdd99,0xde99,0xdf99, + 0xe0a1,0xe1a1,0xe2a1,0xe3a1,0xe4a1,0xe5a1,0xe6a1,0xe7a1, + 0xe8a9,0xe9a9,0xeaa9,0xeba9,0xeca9,0xeda9,0xeea9,0xefa9, + 0xf0b1,0xf1b1,0xf2b1,0xf3b1,0xf4b1,0xf5b1,0xf6b1,0xf7b1, + 0xf8b9,0xf9b9,0xfab9,0xfbb9,0xfcb9,0xfdb9,0xfeb9,0xffb9, +}; + +/* cbitsDup16Table[i] = (i & 0x10) | ((i >> 8) & 1) | (i & 0x28), i = 0..511 */ +static const uint8 cbitsDup16Table[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16,16,16,16,16,16,16,16,24,24,24,24,24,24,24,24, + 32,32,32,32,32,32,32,32,40,40,40,40,40,40,40,40, + 48,48,48,48,48,48,48,48,56,56,56,56,56,56,56,56, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16,16,16,16,16,16,16,16,24,24,24,24,24,24,24,24, + 32,32,32,32,32,32,32,32,40,40,40,40,40,40,40,40, + 48,48,48,48,48,48,48,48,56,56,56,56,56,56,56,56, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16,16,16,16,16,16,16,16,24,24,24,24,24,24,24,24, + 32,32,32,32,32,32,32,32,40,40,40,40,40,40,40,40, + 48,48,48,48,48,48,48,48,56,56,56,56,56,56,56,56, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16,16,16,16,16,16,16,16,24,24,24,24,24,24,24,24, + 32,32,32,32,32,32,32,32,40,40,40,40,40,40,40,40, + 48,48,48,48,48,48,48,48,56,56,56,56,56,56,56,56, + 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, + 17,17,17,17,17,17,17,17,25,25,25,25,25,25,25,25, + 33,33,33,33,33,33,33,33,41,41,41,41,41,41,41,41, + 49,49,49,49,49,49,49,49,57,57,57,57,57,57,57,57, + 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, + 17,17,17,17,17,17,17,17,25,25,25,25,25,25,25,25, + 33,33,33,33,33,33,33,33,41,41,41,41,41,41,41,41, + 49,49,49,49,49,49,49,49,57,57,57,57,57,57,57,57, + 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, + 17,17,17,17,17,17,17,17,25,25,25,25,25,25,25,25, + 33,33,33,33,33,33,33,33,41,41,41,41,41,41,41,41, + 49,49,49,49,49,49,49,49,57,57,57,57,57,57,57,57, + 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, + 17,17,17,17,17,17,17,17,25,25,25,25,25,25,25,25, + 33,33,33,33,33,33,33,33,41,41,41,41,41,41,41,41, + 49,49,49,49,49,49,49,49,57,57,57,57,57,57,57,57, +}; + +/* cbits2Table[i] = (i & 0x10) | ((i >> 8) & 1) | 2, i = 0..511 */ +static const uint8 cbits2Table[512] = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, +}; + +/* rrcaTable[i] = ((i & 1) << 15) | ((i >> 1) << 8) | ((i >> 1) & 0x28) | (i & 1), i = 0..255 */ +static const uint16 rrcaTable[256] = { + 0x0000,0x8001,0x0100,0x8101,0x0200,0x8201,0x0300,0x8301, + 0x0400,0x8401,0x0500,0x8501,0x0600,0x8601,0x0700,0x8701, + 0x0808,0x8809,0x0908,0x8909,0x0a08,0x8a09,0x0b08,0x8b09, + 0x0c08,0x8c09,0x0d08,0x8d09,0x0e08,0x8e09,0x0f08,0x8f09, + 0x1000,0x9001,0x1100,0x9101,0x1200,0x9201,0x1300,0x9301, + 0x1400,0x9401,0x1500,0x9501,0x1600,0x9601,0x1700,0x9701, + 0x1808,0x9809,0x1908,0x9909,0x1a08,0x9a09,0x1b08,0x9b09, + 0x1c08,0x9c09,0x1d08,0x9d09,0x1e08,0x9e09,0x1f08,0x9f09, + 0x2020,0xa021,0x2120,0xa121,0x2220,0xa221,0x2320,0xa321, + 0x2420,0xa421,0x2520,0xa521,0x2620,0xa621,0x2720,0xa721, + 0x2828,0xa829,0x2928,0xa929,0x2a28,0xaa29,0x2b28,0xab29, + 0x2c28,0xac29,0x2d28,0xad29,0x2e28,0xae29,0x2f28,0xaf29, + 0x3020,0xb021,0x3120,0xb121,0x3220,0xb221,0x3320,0xb321, + 0x3420,0xb421,0x3520,0xb521,0x3620,0xb621,0x3720,0xb721, + 0x3828,0xb829,0x3928,0xb929,0x3a28,0xba29,0x3b28,0xbb29, + 0x3c28,0xbc29,0x3d28,0xbd29,0x3e28,0xbe29,0x3f28,0xbf29, + 0x4000,0xc001,0x4100,0xc101,0x4200,0xc201,0x4300,0xc301, + 0x4400,0xc401,0x4500,0xc501,0x4600,0xc601,0x4700,0xc701, + 0x4808,0xc809,0x4908,0xc909,0x4a08,0xca09,0x4b08,0xcb09, + 0x4c08,0xcc09,0x4d08,0xcd09,0x4e08,0xce09,0x4f08,0xcf09, + 0x5000,0xd001,0x5100,0xd101,0x5200,0xd201,0x5300,0xd301, + 0x5400,0xd401,0x5500,0xd501,0x5600,0xd601,0x5700,0xd701, + 0x5808,0xd809,0x5908,0xd909,0x5a08,0xda09,0x5b08,0xdb09, + 0x5c08,0xdc09,0x5d08,0xdd09,0x5e08,0xde09,0x5f08,0xdf09, + 0x6020,0xe021,0x6120,0xe121,0x6220,0xe221,0x6320,0xe321, + 0x6420,0xe421,0x6520,0xe521,0x6620,0xe621,0x6720,0xe721, + 0x6828,0xe829,0x6928,0xe929,0x6a28,0xea29,0x6b28,0xeb29, + 0x6c28,0xec29,0x6d28,0xed29,0x6e28,0xee29,0x6f28,0xef29, + 0x7020,0xf021,0x7120,0xf121,0x7220,0xf221,0x7320,0xf321, + 0x7420,0xf421,0x7520,0xf521,0x7620,0xf621,0x7720,0xf721, + 0x7828,0xf829,0x7928,0xf929,0x7a28,0xfa29,0x7b28,0xfb29, + 0x7c28,0xfc29,0x7d28,0xfd29,0x7e28,0xfe29,0x7f28,0xff29, +}; + +/* rraTable[i] = ((i >> 1) << 8) | ((i >> 1) & 0x28) | (i & 1), i = 0..255 */ +static const uint16 rraTable[256] = { + 0x0000,0x0001,0x0100,0x0101,0x0200,0x0201,0x0300,0x0301, + 0x0400,0x0401,0x0500,0x0501,0x0600,0x0601,0x0700,0x0701, + 0x0808,0x0809,0x0908,0x0909,0x0a08,0x0a09,0x0b08,0x0b09, + 0x0c08,0x0c09,0x0d08,0x0d09,0x0e08,0x0e09,0x0f08,0x0f09, + 0x1000,0x1001,0x1100,0x1101,0x1200,0x1201,0x1300,0x1301, + 0x1400,0x1401,0x1500,0x1501,0x1600,0x1601,0x1700,0x1701, + 0x1808,0x1809,0x1908,0x1909,0x1a08,0x1a09,0x1b08,0x1b09, + 0x1c08,0x1c09,0x1d08,0x1d09,0x1e08,0x1e09,0x1f08,0x1f09, + 0x2020,0x2021,0x2120,0x2121,0x2220,0x2221,0x2320,0x2321, + 0x2420,0x2421,0x2520,0x2521,0x2620,0x2621,0x2720,0x2721, + 0x2828,0x2829,0x2928,0x2929,0x2a28,0x2a29,0x2b28,0x2b29, + 0x2c28,0x2c29,0x2d28,0x2d29,0x2e28,0x2e29,0x2f28,0x2f29, + 0x3020,0x3021,0x3120,0x3121,0x3220,0x3221,0x3320,0x3321, + 0x3420,0x3421,0x3520,0x3521,0x3620,0x3621,0x3720,0x3721, + 0x3828,0x3829,0x3928,0x3929,0x3a28,0x3a29,0x3b28,0x3b29, + 0x3c28,0x3c29,0x3d28,0x3d29,0x3e28,0x3e29,0x3f28,0x3f29, + 0x4000,0x4001,0x4100,0x4101,0x4200,0x4201,0x4300,0x4301, + 0x4400,0x4401,0x4500,0x4501,0x4600,0x4601,0x4700,0x4701, + 0x4808,0x4809,0x4908,0x4909,0x4a08,0x4a09,0x4b08,0x4b09, + 0x4c08,0x4c09,0x4d08,0x4d09,0x4e08,0x4e09,0x4f08,0x4f09, + 0x5000,0x5001,0x5100,0x5101,0x5200,0x5201,0x5300,0x5301, + 0x5400,0x5401,0x5500,0x5501,0x5600,0x5601,0x5700,0x5701, + 0x5808,0x5809,0x5908,0x5909,0x5a08,0x5a09,0x5b08,0x5b09, + 0x5c08,0x5c09,0x5d08,0x5d09,0x5e08,0x5e09,0x5f08,0x5f09, + 0x6020,0x6021,0x6120,0x6121,0x6220,0x6221,0x6320,0x6321, + 0x6420,0x6421,0x6520,0x6521,0x6620,0x6621,0x6720,0x6721, + 0x6828,0x6829,0x6928,0x6929,0x6a28,0x6a29,0x6b28,0x6b29, + 0x6c28,0x6c29,0x6d28,0x6d29,0x6e28,0x6e29,0x6f28,0x6f29, + 0x7020,0x7021,0x7120,0x7121,0x7220,0x7221,0x7320,0x7321, + 0x7420,0x7421,0x7520,0x7521,0x7620,0x7621,0x7720,0x7721, + 0x7828,0x7829,0x7928,0x7929,0x7a28,0x7a29,0x7b28,0x7b29, + 0x7c28,0x7c29,0x7d28,0x7d29,0x7e28,0x7e29,0x7f28,0x7f29, +}; + +/* addTable[i] = ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6), i = 0..511 */ +static const uint16 addTable[512] = { + 0x0040,0x0100,0x0200,0x0300,0x0400,0x0500,0x0600,0x0700, + 0x0808,0x0908,0x0a08,0x0b08,0x0c08,0x0d08,0x0e08,0x0f08, + 0x1000,0x1100,0x1200,0x1300,0x1400,0x1500,0x1600,0x1700, + 0x1808,0x1908,0x1a08,0x1b08,0x1c08,0x1d08,0x1e08,0x1f08, + 0x2020,0x2120,0x2220,0x2320,0x2420,0x2520,0x2620,0x2720, + 0x2828,0x2928,0x2a28,0x2b28,0x2c28,0x2d28,0x2e28,0x2f28, + 0x3020,0x3120,0x3220,0x3320,0x3420,0x3520,0x3620,0x3720, + 0x3828,0x3928,0x3a28,0x3b28,0x3c28,0x3d28,0x3e28,0x3f28, + 0x4000,0x4100,0x4200,0x4300,0x4400,0x4500,0x4600,0x4700, + 0x4808,0x4908,0x4a08,0x4b08,0x4c08,0x4d08,0x4e08,0x4f08, + 0x5000,0x5100,0x5200,0x5300,0x5400,0x5500,0x5600,0x5700, + 0x5808,0x5908,0x5a08,0x5b08,0x5c08,0x5d08,0x5e08,0x5f08, + 0x6020,0x6120,0x6220,0x6320,0x6420,0x6520,0x6620,0x6720, + 0x6828,0x6928,0x6a28,0x6b28,0x6c28,0x6d28,0x6e28,0x6f28, + 0x7020,0x7120,0x7220,0x7320,0x7420,0x7520,0x7620,0x7720, + 0x7828,0x7928,0x7a28,0x7b28,0x7c28,0x7d28,0x7e28,0x7f28, + 0x8080,0x8180,0x8280,0x8380,0x8480,0x8580,0x8680,0x8780, + 0x8888,0x8988,0x8a88,0x8b88,0x8c88,0x8d88,0x8e88,0x8f88, + 0x9080,0x9180,0x9280,0x9380,0x9480,0x9580,0x9680,0x9780, + 0x9888,0x9988,0x9a88,0x9b88,0x9c88,0x9d88,0x9e88,0x9f88, + 0xa0a0,0xa1a0,0xa2a0,0xa3a0,0xa4a0,0xa5a0,0xa6a0,0xa7a0, + 0xa8a8,0xa9a8,0xaaa8,0xaba8,0xaca8,0xada8,0xaea8,0xafa8, + 0xb0a0,0xb1a0,0xb2a0,0xb3a0,0xb4a0,0xb5a0,0xb6a0,0xb7a0, + 0xb8a8,0xb9a8,0xbaa8,0xbba8,0xbca8,0xbda8,0xbea8,0xbfa8, + 0xc080,0xc180,0xc280,0xc380,0xc480,0xc580,0xc680,0xc780, + 0xc888,0xc988,0xca88,0xcb88,0xcc88,0xcd88,0xce88,0xcf88, + 0xd080,0xd180,0xd280,0xd380,0xd480,0xd580,0xd680,0xd780, + 0xd888,0xd988,0xda88,0xdb88,0xdc88,0xdd88,0xde88,0xdf88, + 0xe0a0,0xe1a0,0xe2a0,0xe3a0,0xe4a0,0xe5a0,0xe6a0,0xe7a0, + 0xe8a8,0xe9a8,0xeaa8,0xeba8,0xeca8,0xeda8,0xeea8,0xefa8, + 0xf0a0,0xf1a0,0xf2a0,0xf3a0,0xf4a0,0xf5a0,0xf6a0,0xf7a0, + 0xf8a8,0xf9a8,0xfaa8,0xfba8,0xfca8,0xfda8,0xfea8,0xffa8, + 0x0040,0x0100,0x0200,0x0300,0x0400,0x0500,0x0600,0x0700, + 0x0808,0x0908,0x0a08,0x0b08,0x0c08,0x0d08,0x0e08,0x0f08, + 0x1000,0x1100,0x1200,0x1300,0x1400,0x1500,0x1600,0x1700, + 0x1808,0x1908,0x1a08,0x1b08,0x1c08,0x1d08,0x1e08,0x1f08, + 0x2020,0x2120,0x2220,0x2320,0x2420,0x2520,0x2620,0x2720, + 0x2828,0x2928,0x2a28,0x2b28,0x2c28,0x2d28,0x2e28,0x2f28, + 0x3020,0x3120,0x3220,0x3320,0x3420,0x3520,0x3620,0x3720, + 0x3828,0x3928,0x3a28,0x3b28,0x3c28,0x3d28,0x3e28,0x3f28, + 0x4000,0x4100,0x4200,0x4300,0x4400,0x4500,0x4600,0x4700, + 0x4808,0x4908,0x4a08,0x4b08,0x4c08,0x4d08,0x4e08,0x4f08, + 0x5000,0x5100,0x5200,0x5300,0x5400,0x5500,0x5600,0x5700, + 0x5808,0x5908,0x5a08,0x5b08,0x5c08,0x5d08,0x5e08,0x5f08, + 0x6020,0x6120,0x6220,0x6320,0x6420,0x6520,0x6620,0x6720, + 0x6828,0x6928,0x6a28,0x6b28,0x6c28,0x6d28,0x6e28,0x6f28, + 0x7020,0x7120,0x7220,0x7320,0x7420,0x7520,0x7620,0x7720, + 0x7828,0x7928,0x7a28,0x7b28,0x7c28,0x7d28,0x7e28,0x7f28, + 0x8080,0x8180,0x8280,0x8380,0x8480,0x8580,0x8680,0x8780, + 0x8888,0x8988,0x8a88,0x8b88,0x8c88,0x8d88,0x8e88,0x8f88, + 0x9080,0x9180,0x9280,0x9380,0x9480,0x9580,0x9680,0x9780, + 0x9888,0x9988,0x9a88,0x9b88,0x9c88,0x9d88,0x9e88,0x9f88, + 0xa0a0,0xa1a0,0xa2a0,0xa3a0,0xa4a0,0xa5a0,0xa6a0,0xa7a0, + 0xa8a8,0xa9a8,0xaaa8,0xaba8,0xaca8,0xada8,0xaea8,0xafa8, + 0xb0a0,0xb1a0,0xb2a0,0xb3a0,0xb4a0,0xb5a0,0xb6a0,0xb7a0, + 0xb8a8,0xb9a8,0xbaa8,0xbba8,0xbca8,0xbda8,0xbea8,0xbfa8, + 0xc080,0xc180,0xc280,0xc380,0xc480,0xc580,0xc680,0xc780, + 0xc888,0xc988,0xca88,0xcb88,0xcc88,0xcd88,0xce88,0xcf88, + 0xd080,0xd180,0xd280,0xd380,0xd480,0xd580,0xd680,0xd780, + 0xd888,0xd988,0xda88,0xdb88,0xdc88,0xdd88,0xde88,0xdf88, + 0xe0a0,0xe1a0,0xe2a0,0xe3a0,0xe4a0,0xe5a0,0xe6a0,0xe7a0, + 0xe8a8,0xe9a8,0xeaa8,0xeba8,0xeca8,0xeda8,0xeea8,0xefa8, + 0xf0a0,0xf1a0,0xf2a0,0xf3a0,0xf4a0,0xf5a0,0xf6a0,0xf7a0, + 0xf8a8,0xf9a8,0xfaa8,0xfba8,0xfca8,0xfda8,0xfea8,0xffa8, +}; + +/* subTable[i] = ((i & 0xff) << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) | 2, i = 0..255 */ +static const uint16 subTable[256] = { + 0x0042,0x0102,0x0202,0x0302,0x0402,0x0502,0x0602,0x0702, + 0x080a,0x090a,0x0a0a,0x0b0a,0x0c0a,0x0d0a,0x0e0a,0x0f0a, + 0x1002,0x1102,0x1202,0x1302,0x1402,0x1502,0x1602,0x1702, + 0x180a,0x190a,0x1a0a,0x1b0a,0x1c0a,0x1d0a,0x1e0a,0x1f0a, + 0x2022,0x2122,0x2222,0x2322,0x2422,0x2522,0x2622,0x2722, + 0x282a,0x292a,0x2a2a,0x2b2a,0x2c2a,0x2d2a,0x2e2a,0x2f2a, + 0x3022,0x3122,0x3222,0x3322,0x3422,0x3522,0x3622,0x3722, + 0x382a,0x392a,0x3a2a,0x3b2a,0x3c2a,0x3d2a,0x3e2a,0x3f2a, + 0x4002,0x4102,0x4202,0x4302,0x4402,0x4502,0x4602,0x4702, + 0x480a,0x490a,0x4a0a,0x4b0a,0x4c0a,0x4d0a,0x4e0a,0x4f0a, + 0x5002,0x5102,0x5202,0x5302,0x5402,0x5502,0x5602,0x5702, + 0x580a,0x590a,0x5a0a,0x5b0a,0x5c0a,0x5d0a,0x5e0a,0x5f0a, + 0x6022,0x6122,0x6222,0x6322,0x6422,0x6522,0x6622,0x6722, + 0x682a,0x692a,0x6a2a,0x6b2a,0x6c2a,0x6d2a,0x6e2a,0x6f2a, + 0x7022,0x7122,0x7222,0x7322,0x7422,0x7522,0x7622,0x7722, + 0x782a,0x792a,0x7a2a,0x7b2a,0x7c2a,0x7d2a,0x7e2a,0x7f2a, + 0x8082,0x8182,0x8282,0x8382,0x8482,0x8582,0x8682,0x8782, + 0x888a,0x898a,0x8a8a,0x8b8a,0x8c8a,0x8d8a,0x8e8a,0x8f8a, + 0x9082,0x9182,0x9282,0x9382,0x9482,0x9582,0x9682,0x9782, + 0x988a,0x998a,0x9a8a,0x9b8a,0x9c8a,0x9d8a,0x9e8a,0x9f8a, + 0xa0a2,0xa1a2,0xa2a2,0xa3a2,0xa4a2,0xa5a2,0xa6a2,0xa7a2, + 0xa8aa,0xa9aa,0xaaaa,0xabaa,0xacaa,0xadaa,0xaeaa,0xafaa, + 0xb0a2,0xb1a2,0xb2a2,0xb3a2,0xb4a2,0xb5a2,0xb6a2,0xb7a2, + 0xb8aa,0xb9aa,0xbaaa,0xbbaa,0xbcaa,0xbdaa,0xbeaa,0xbfaa, + 0xc082,0xc182,0xc282,0xc382,0xc482,0xc582,0xc682,0xc782, + 0xc88a,0xc98a,0xca8a,0xcb8a,0xcc8a,0xcd8a,0xce8a,0xcf8a, + 0xd082,0xd182,0xd282,0xd382,0xd482,0xd582,0xd682,0xd782, + 0xd88a,0xd98a,0xda8a,0xdb8a,0xdc8a,0xdd8a,0xde8a,0xdf8a, + 0xe0a2,0xe1a2,0xe2a2,0xe3a2,0xe4a2,0xe5a2,0xe6a2,0xe7a2, + 0xe8aa,0xe9aa,0xeaaa,0xebaa,0xecaa,0xedaa,0xeeaa,0xefaa, + 0xf0a2,0xf1a2,0xf2a2,0xf3a2,0xf4a2,0xf5a2,0xf6a2,0xf7a2, + 0xf8aa,0xf9aa,0xfaaa,0xfbaa,0xfcaa,0xfdaa,0xfeaa,0xffaa, +}; + +/* andTable[i] = (i << 8) | (i & 0xa8) | ((i == 0) << 6) | 0x10 | parityTable[i], i = 0..255 */ +static const uint16 andTable[256] = { + 0x0054,0x0110,0x0210,0x0314,0x0410,0x0514,0x0614,0x0710, + 0x0818,0x091c,0x0a1c,0x0b18,0x0c1c,0x0d18,0x0e18,0x0f1c, + 0x1010,0x1114,0x1214,0x1310,0x1414,0x1510,0x1610,0x1714, + 0x181c,0x1918,0x1a18,0x1b1c,0x1c18,0x1d1c,0x1e1c,0x1f18, + 0x2030,0x2134,0x2234,0x2330,0x2434,0x2530,0x2630,0x2734, + 0x283c,0x2938,0x2a38,0x2b3c,0x2c38,0x2d3c,0x2e3c,0x2f38, + 0x3034,0x3130,0x3230,0x3334,0x3430,0x3534,0x3634,0x3730, + 0x3838,0x393c,0x3a3c,0x3b38,0x3c3c,0x3d38,0x3e38,0x3f3c, + 0x4010,0x4114,0x4214,0x4310,0x4414,0x4510,0x4610,0x4714, + 0x481c,0x4918,0x4a18,0x4b1c,0x4c18,0x4d1c,0x4e1c,0x4f18, + 0x5014,0x5110,0x5210,0x5314,0x5410,0x5514,0x5614,0x5710, + 0x5818,0x591c,0x5a1c,0x5b18,0x5c1c,0x5d18,0x5e18,0x5f1c, + 0x6034,0x6130,0x6230,0x6334,0x6430,0x6534,0x6634,0x6730, + 0x6838,0x693c,0x6a3c,0x6b38,0x6c3c,0x6d38,0x6e38,0x6f3c, + 0x7030,0x7134,0x7234,0x7330,0x7434,0x7530,0x7630,0x7734, + 0x783c,0x7938,0x7a38,0x7b3c,0x7c38,0x7d3c,0x7e3c,0x7f38, + 0x8090,0x8194,0x8294,0x8390,0x8494,0x8590,0x8690,0x8794, + 0x889c,0x8998,0x8a98,0x8b9c,0x8c98,0x8d9c,0x8e9c,0x8f98, + 0x9094,0x9190,0x9290,0x9394,0x9490,0x9594,0x9694,0x9790, + 0x9898,0x999c,0x9a9c,0x9b98,0x9c9c,0x9d98,0x9e98,0x9f9c, + 0xa0b4,0xa1b0,0xa2b0,0xa3b4,0xa4b0,0xa5b4,0xa6b4,0xa7b0, + 0xa8b8,0xa9bc,0xaabc,0xabb8,0xacbc,0xadb8,0xaeb8,0xafbc, + 0xb0b0,0xb1b4,0xb2b4,0xb3b0,0xb4b4,0xb5b0,0xb6b0,0xb7b4, + 0xb8bc,0xb9b8,0xbab8,0xbbbc,0xbcb8,0xbdbc,0xbebc,0xbfb8, + 0xc094,0xc190,0xc290,0xc394,0xc490,0xc594,0xc694,0xc790, + 0xc898,0xc99c,0xca9c,0xcb98,0xcc9c,0xcd98,0xce98,0xcf9c, + 0xd090,0xd194,0xd294,0xd390,0xd494,0xd590,0xd690,0xd794, + 0xd89c,0xd998,0xda98,0xdb9c,0xdc98,0xdd9c,0xde9c,0xdf98, + 0xe0b0,0xe1b4,0xe2b4,0xe3b0,0xe4b4,0xe5b0,0xe6b0,0xe7b4, + 0xe8bc,0xe9b8,0xeab8,0xebbc,0xecb8,0xedbc,0xeebc,0xefb8, + 0xf0b4,0xf1b0,0xf2b0,0xf3b4,0xf4b0,0xf5b4,0xf6b4,0xf7b0, + 0xf8b8,0xf9bc,0xfabc,0xfbb8,0xfcbc,0xfdb8,0xfeb8,0xffbc, +}; + +/* xororTable[i] = (i << 8) | (i & 0xa8) | ((i == 0) << 6) | parityTable[i], i = 0..255 */ +static const uint16 xororTable[256] = { + 0x0044,0x0100,0x0200,0x0304,0x0400,0x0504,0x0604,0x0700, + 0x0808,0x090c,0x0a0c,0x0b08,0x0c0c,0x0d08,0x0e08,0x0f0c, + 0x1000,0x1104,0x1204,0x1300,0x1404,0x1500,0x1600,0x1704, + 0x180c,0x1908,0x1a08,0x1b0c,0x1c08,0x1d0c,0x1e0c,0x1f08, + 0x2020,0x2124,0x2224,0x2320,0x2424,0x2520,0x2620,0x2724, + 0x282c,0x2928,0x2a28,0x2b2c,0x2c28,0x2d2c,0x2e2c,0x2f28, + 0x3024,0x3120,0x3220,0x3324,0x3420,0x3524,0x3624,0x3720, + 0x3828,0x392c,0x3a2c,0x3b28,0x3c2c,0x3d28,0x3e28,0x3f2c, + 0x4000,0x4104,0x4204,0x4300,0x4404,0x4500,0x4600,0x4704, + 0x480c,0x4908,0x4a08,0x4b0c,0x4c08,0x4d0c,0x4e0c,0x4f08, + 0x5004,0x5100,0x5200,0x5304,0x5400,0x5504,0x5604,0x5700, + 0x5808,0x590c,0x5a0c,0x5b08,0x5c0c,0x5d08,0x5e08,0x5f0c, + 0x6024,0x6120,0x6220,0x6324,0x6420,0x6524,0x6624,0x6720, + 0x6828,0x692c,0x6a2c,0x6b28,0x6c2c,0x6d28,0x6e28,0x6f2c, + 0x7020,0x7124,0x7224,0x7320,0x7424,0x7520,0x7620,0x7724, + 0x782c,0x7928,0x7a28,0x7b2c,0x7c28,0x7d2c,0x7e2c,0x7f28, + 0x8080,0x8184,0x8284,0x8380,0x8484,0x8580,0x8680,0x8784, + 0x888c,0x8988,0x8a88,0x8b8c,0x8c88,0x8d8c,0x8e8c,0x8f88, + 0x9084,0x9180,0x9280,0x9384,0x9480,0x9584,0x9684,0x9780, + 0x9888,0x998c,0x9a8c,0x9b88,0x9c8c,0x9d88,0x9e88,0x9f8c, + 0xa0a4,0xa1a0,0xa2a0,0xa3a4,0xa4a0,0xa5a4,0xa6a4,0xa7a0, + 0xa8a8,0xa9ac,0xaaac,0xaba8,0xacac,0xada8,0xaea8,0xafac, + 0xb0a0,0xb1a4,0xb2a4,0xb3a0,0xb4a4,0xb5a0,0xb6a0,0xb7a4, + 0xb8ac,0xb9a8,0xbaa8,0xbbac,0xbca8,0xbdac,0xbeac,0xbfa8, + 0xc084,0xc180,0xc280,0xc384,0xc480,0xc584,0xc684,0xc780, + 0xc888,0xc98c,0xca8c,0xcb88,0xcc8c,0xcd88,0xce88,0xcf8c, + 0xd080,0xd184,0xd284,0xd380,0xd484,0xd580,0xd680,0xd784, + 0xd88c,0xd988,0xda88,0xdb8c,0xdc88,0xdd8c,0xde8c,0xdf88, + 0xe0a0,0xe1a4,0xe2a4,0xe3a0,0xe4a4,0xe5a0,0xe6a0,0xe7a4, + 0xe8ac,0xe9a8,0xeaa8,0xebac,0xeca8,0xedac,0xeeac,0xefa8, + 0xf0a4,0xf1a0,0xf2a0,0xf3a4,0xf4a0,0xf5a4,0xf6a4,0xf7a0, + 0xf8a8,0xf9ac,0xfaac,0xfba8,0xfcac,0xfda8,0xfea8,0xffac, +}; + +/* rotateShiftTable[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | parityTable[i & 0xff], i = 0..255 */ +static const uint8 rotateShiftTable[256] = { + 68, 0, 0, 4, 0, 4, 4, 0, 8, 12, 12, 8, 12, 8, 8, 12, + 0, 4, 4, 0, 4, 0, 0, 4, 12, 8, 8, 12, 8, 12, 12, 8, + 32, 36, 36, 32, 36, 32, 32, 36, 44, 40, 40, 44, 40, 44, 44, 40, + 36, 32, 32, 36, 32, 36, 36, 32, 40, 44, 44, 40, 44, 40, 40, 44, + 0, 4, 4, 0, 4, 0, 0, 4, 12, 8, 8, 12, 8, 12, 12, 8, + 4, 0, 0, 4, 0, 4, 4, 0, 8, 12, 12, 8, 12, 8, 8, 12, + 36, 32, 32, 36, 32, 36, 36, 32, 40, 44, 44, 40, 44, 40, 40, 44, + 32, 36, 36, 32, 36, 32, 32, 36, 44, 40, 40, 44, 40, 44, 44, 40, + 128,132,132,128,132,128,128,132,140,136,136,140,136,140,140,136, + 132,128,128,132,128,132,132,128,136,140,140,136,140,136,136,140, + 164,160,160,164,160,164,164,160,168,172,172,168,172,168,168,172, + 160,164,164,160,164,160,160,164,172,168,168,172,168,172,172,168, + 132,128,128,132,128,132,132,128,136,140,140,136,140,136,136,140, + 128,132,132,128,132,128,128,132,140,136,136,140,136,140,140,136, + 160,164,164,160,164,160,160,164,172,168,168,172,168,172,172,168, + 164,160,160,164,160,164,164,160,168,172,172,168,172,168,168,172, +}; + +/* incZ80Table[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | + (((i & 0xf) == 0) << 4) | ((i == 0x80) << 2), i = 0..256 */ +static const uint8 incZ80Table[257] = { + 80, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 16, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 48, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 148,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 144,128,128,128,128,128,128,128,136,136,136,136,136,136,136,136, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, + 176,160,160,160,160,160,160,160,168,168,168,168,168,168,168,168, 80, +}; + +/* decZ80Table[i] = (i & 0xa8) | (((i & 0xff) == 0) << 6) | + (((i & 0xf) == 0xf) << 4) | ((i == 0x7f) << 2) | 2, i = 0..255 */ +static const uint8 decZ80Table[256] = { + 66, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 26, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 58, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 62, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 130,130,130,130,130,130,130,130,138,138,138,138,138,138,138,154, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, + 162,162,162,162,162,162,162,162,170,170,170,170,170,170,170,186, +}; + +/* cbitsZ80Table[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1), i = 0..511 */ +static const uint8 cbitsZ80Table[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +}; + +/* cbitsZ80DupTable[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | + ((i >> 8) & 1) | (i & 0xa8), i = 0..511 */ +static const uint8 cbitsZ80DupTable[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 16, 16, 16, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24, + 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 48, 48, 48, 48, 48, 48, 48, 48, 56, 56, 56, 56, 56, 56, 56, 56, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, + 16, 16, 16, 16, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24, + 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 40, 40, 40, + 48, 48, 48, 48, 48, 48, 48, 48, 56, 56, 56, 56, 56, 56, 56, 56, + 132,132,132,132,132,132,132,132,140,140,140,140,140,140,140,140, + 148,148,148,148,148,148,148,148,156,156,156,156,156,156,156,156, + 164,164,164,164,164,164,164,164,172,172,172,172,172,172,172,172, + 180,180,180,180,180,180,180,180,188,188,188,188,188,188,188,188, + 132,132,132,132,132,132,132,132,140,140,140,140,140,140,140,140, + 148,148,148,148,148,148,148,148,156,156,156,156,156,156,156,156, + 164,164,164,164,164,164,164,164,172,172,172,172,172,172,172,172, + 180,180,180,180,180,180,180,180,188,188,188,188,188,188,188,188, + 5, 5, 5, 5, 5, 5, 5, 5, 13, 13, 13, 13, 13, 13, 13, 13, + 21, 21, 21, 21, 21, 21, 21, 21, 29, 29, 29, 29, 29, 29, 29, 29, + 37, 37, 37, 37, 37, 37, 37, 37, 45, 45, 45, 45, 45, 45, 45, 45, + 53, 53, 53, 53, 53, 53, 53, 53, 61, 61, 61, 61, 61, 61, 61, 61, + 5, 5, 5, 5, 5, 5, 5, 5, 13, 13, 13, 13, 13, 13, 13, 13, + 21, 21, 21, 21, 21, 21, 21, 21, 29, 29, 29, 29, 29, 29, 29, 29, + 37, 37, 37, 37, 37, 37, 37, 37, 45, 45, 45, 45, 45, 45, 45, 45, + 53, 53, 53, 53, 53, 53, 53, 53, 61, 61, 61, 61, 61, 61, 61, 61, + 129,129,129,129,129,129,129,129,137,137,137,137,137,137,137,137, + 145,145,145,145,145,145,145,145,153,153,153,153,153,153,153,153, + 161,161,161,161,161,161,161,161,169,169,169,169,169,169,169,169, + 177,177,177,177,177,177,177,177,185,185,185,185,185,185,185,185, + 129,129,129,129,129,129,129,129,137,137,137,137,137,137,137,137, + 145,145,145,145,145,145,145,145,153,153,153,153,153,153,153,153, + 161,161,161,161,161,161,161,161,169,169,169,169,169,169,169,169, + 177,177,177,177,177,177,177,177,185,185,185,185,185,185,185,185, +}; + +/* cbits2Z80Table[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | 2, i = 0..511 */ +static const uint8 cbits2Z80Table[512] = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, +}; + +/* cbits2Z80DupTable[i] = (i & 0x10) | (((i >> 6) ^ (i >> 5)) & 4) | ((i >> 8) & 1) | 2 | + (i & 0xa8), i = 0..511 */ +static const uint8 cbits2Z80DupTable[512] = { + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 10, + 18, 18, 18, 18, 18, 18, 18, 18, 26, 26, 26, 26, 26, 26, 26, 26, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 42, + 50, 50, 50, 50, 50, 50, 50, 50, 58, 58, 58, 58, 58, 58, 58, 58, + 2, 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 10, + 18, 18, 18, 18, 18, 18, 18, 18, 26, 26, 26, 26, 26, 26, 26, 26, + 34, 34, 34, 34, 34, 34, 34, 34, 42, 42, 42, 42, 42, 42, 42, 42, + 50, 50, 50, 50, 50, 50, 50, 50, 58, 58, 58, 58, 58, 58, 58, 58, + 134,134,134,134,134,134,134,134,142,142,142,142,142,142,142,142, + 150,150,150,150,150,150,150,150,158,158,158,158,158,158,158,158, + 166,166,166,166,166,166,166,166,174,174,174,174,174,174,174,174, + 182,182,182,182,182,182,182,182,190,190,190,190,190,190,190,190, + 134,134,134,134,134,134,134,134,142,142,142,142,142,142,142,142, + 150,150,150,150,150,150,150,150,158,158,158,158,158,158,158,158, + 166,166,166,166,166,166,166,166,174,174,174,174,174,174,174,174, + 182,182,182,182,182,182,182,182,190,190,190,190,190,190,190,190, + 7, 7, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15, 15, 15, + 23, 23, 23, 23, 23, 23, 23, 23, 31, 31, 31, 31, 31, 31, 31, 31, + 39, 39, 39, 39, 39, 39, 39, 39, 47, 47, 47, 47, 47, 47, 47, 47, + 55, 55, 55, 55, 55, 55, 55, 55, 63, 63, 63, 63, 63, 63, 63, 63, + 7, 7, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15, 15, 15, + 23, 23, 23, 23, 23, 23, 23, 23, 31, 31, 31, 31, 31, 31, 31, 31, + 39, 39, 39, 39, 39, 39, 39, 39, 47, 47, 47, 47, 47, 47, 47, 47, + 55, 55, 55, 55, 55, 55, 55, 55, 63, 63, 63, 63, 63, 63, 63, 63, + 131,131,131,131,131,131,131,131,139,139,139,139,139,139,139,139, + 147,147,147,147,147,147,147,147,155,155,155,155,155,155,155,155, + 163,163,163,163,163,163,163,163,171,171,171,171,171,171,171,171, + 179,179,179,179,179,179,179,179,187,187,187,187,187,187,187,187, + 131,131,131,131,131,131,131,131,139,139,139,139,139,139,139,139, + 147,147,147,147,147,147,147,147,155,155,155,155,155,155,155,155, + 163,163,163,163,163,163,163,163,171,171,171,171,171,171,171,171, + 179,179,179,179,179,179,179,179,187,187,187,187,187,187,187,187, +}; + +/* negTable[i] = (((i & 0x0f) != 0) << 4) | ((i == 0x80) << 2) | 2 | (i != 0), i = 0..255 */ +static const uint8 negTable[256] = { + 2,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 7,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, +}; + +/* rrdrldTable[i] = (i << 8) | (i & 0xa8) | (((i & 0xff) == 0) << 6) | parityTable[i], i = 0..255 */ +static const uint16 rrdrldTable[256] = { + 0x0044,0x0100,0x0200,0x0304,0x0400,0x0504,0x0604,0x0700, + 0x0808,0x090c,0x0a0c,0x0b08,0x0c0c,0x0d08,0x0e08,0x0f0c, + 0x1000,0x1104,0x1204,0x1300,0x1404,0x1500,0x1600,0x1704, + 0x180c,0x1908,0x1a08,0x1b0c,0x1c08,0x1d0c,0x1e0c,0x1f08, + 0x2020,0x2124,0x2224,0x2320,0x2424,0x2520,0x2620,0x2724, + 0x282c,0x2928,0x2a28,0x2b2c,0x2c28,0x2d2c,0x2e2c,0x2f28, + 0x3024,0x3120,0x3220,0x3324,0x3420,0x3524,0x3624,0x3720, + 0x3828,0x392c,0x3a2c,0x3b28,0x3c2c,0x3d28,0x3e28,0x3f2c, + 0x4000,0x4104,0x4204,0x4300,0x4404,0x4500,0x4600,0x4704, + 0x480c,0x4908,0x4a08,0x4b0c,0x4c08,0x4d0c,0x4e0c,0x4f08, + 0x5004,0x5100,0x5200,0x5304,0x5400,0x5504,0x5604,0x5700, + 0x5808,0x590c,0x5a0c,0x5b08,0x5c0c,0x5d08,0x5e08,0x5f0c, + 0x6024,0x6120,0x6220,0x6324,0x6420,0x6524,0x6624,0x6720, + 0x6828,0x692c,0x6a2c,0x6b28,0x6c2c,0x6d28,0x6e28,0x6f2c, + 0x7020,0x7124,0x7224,0x7320,0x7424,0x7520,0x7620,0x7724, + 0x782c,0x7928,0x7a28,0x7b2c,0x7c28,0x7d2c,0x7e2c,0x7f28, + 0x8080,0x8184,0x8284,0x8380,0x8484,0x8580,0x8680,0x8784, + 0x888c,0x8988,0x8a88,0x8b8c,0x8c88,0x8d8c,0x8e8c,0x8f88, + 0x9084,0x9180,0x9280,0x9384,0x9480,0x9584,0x9684,0x9780, + 0x9888,0x998c,0x9a8c,0x9b88,0x9c8c,0x9d88,0x9e88,0x9f8c, + 0xa0a4,0xa1a0,0xa2a0,0xa3a4,0xa4a0,0xa5a4,0xa6a4,0xa7a0, + 0xa8a8,0xa9ac,0xaaac,0xaba8,0xacac,0xada8,0xaea8,0xafac, + 0xb0a0,0xb1a4,0xb2a4,0xb3a0,0xb4a4,0xb5a0,0xb6a0,0xb7a4, + 0xb8ac,0xb9a8,0xbaa8,0xbbac,0xbca8,0xbdac,0xbeac,0xbfa8, + 0xc084,0xc180,0xc280,0xc384,0xc480,0xc584,0xc684,0xc780, + 0xc888,0xc98c,0xca8c,0xcb88,0xcc8c,0xcd88,0xce88,0xcf8c, + 0xd080,0xd184,0xd284,0xd380,0xd484,0xd580,0xd680,0xd784, + 0xd88c,0xd988,0xda88,0xdb8c,0xdc88,0xdd8c,0xde8c,0xdf88, + 0xe0a0,0xe1a4,0xe2a4,0xe3a0,0xe4a4,0xe5a0,0xe6a0,0xe7a4, + 0xe8ac,0xe9a8,0xeaa8,0xebac,0xeca8,0xedac,0xeeac,0xefa8, + 0xf0a4,0xf1a0,0xf2a0,0xf3a4,0xf4a0,0xf5a4,0xf6a4,0xf7a0, + 0xf8a8,0xf9ac,0xfaac,0xfba8,0xfcac,0xfda8,0xfea8,0xffac, +}; + +/* cpTable[i] = (i & 0x80) | (((i & 0xff) == 0) << 6), i = 0..255 */ +static const uint8 cpTable[256] = { + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, + 128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128, +}; + +/* remove comments to generate table contents and add a call to + altairz80_print_tables in the altairz80_init +static void altairz80_print_tables(void) { +*/ +/* parityTable */ +/* + uint32 i, v; + for (i = 0; i < 256; i++) { + v = ((i & 1) + ((i & 2) >> 1) + ((i & 4) >> 2) + ((i & 8) >> 3) + + ((i & 16) >> 4) + ((i & 32) >> 5) + ((i & 64) >> 6) + ((i & 128) >> 7)) % 2 ? 0 : 4; + sim_printf("%1d,", v); + if ( ((i+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* incTable */ +/* + uint32 temp, v; + for (temp = 0; temp <= 256; temp++) { + v = (temp & 0xa8) | (((temp & 0xff) == 0) << 6) | (((temp & 0xf) == 0) << 4); + sim_printf("%3d,", v); + if ( ((temp+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* decTable */ +/* + uint32 temp, v; + for (temp = 0; temp < 256; temp++) { + v = (temp & 0xa8) | (((temp & 0xff) == 0) << 6) | (((temp & 0xf) == 0xf) << 4) | 2; + sim_printf("%3d,", v); + if ( ((temp+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbitsTable */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (cbits & 0x10) | ((cbits >> 8) & 1); + sim_printf("%2d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbitsDup8Table */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (cbits & 0x10) | ((cbits >> 8) & 1) | ((cbits & 0xff) << 8) | (cbits & 0xa8) | (((cbits & 0xff) == 0) << 6); + sim_printf("0x%04x,", v); + if ( ((cbits+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbitsDup16Table */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (cbits & 0x10) | ((cbits >> 8) & 1) | (cbits & 0x28); + sim_printf("%2d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbits2Table */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (cbits & 0x10) | ((cbits >> 8) & 1) | 2; + sim_printf("%2d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* rrcaTable */ +/* + uint32 temp, sum, v; + for (temp = 0; temp < 256; temp++) { + sum = temp >> 1; + v = ((temp & 1) << 15) | (sum << 8) | (sum & 0x28) | (temp & 1); + sim_printf("0x%04x,", v); + if ( ((temp+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* rraTable */ +/* + uint32 temp, sum, v; + for (temp = 0; temp < 256; temp++) { + sum = temp >> 1; + v = (sum << 8) | (sum & 0x28) | (temp & 1); + sim_printf("0x%04x,", v); + if ( ((temp+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* addTable */ +/* + uint32 sum, v; + for (sum = 0; sum < 512; sum++) { + v = ((sum & 0xff) << 8) | (sum & 0xa8) | (((sum & 0xff) == 0) << 6); + sim_printf("0x%04x,", v); + if ( ((sum+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* subTable */ +/* + uint32 sum, v; + for (sum = 0; sum < 256; sum++) { + v = ((sum & 0xff) << 8) | (sum & 0xa8) | (((sum & 0xff) == 0) << 6) | 2; + sim_printf("0x%04x,", v); + if ( ((sum+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* andTable */ +/* + uint32 sum, v; + for (sum = 0; sum < 256; sum++) { + v = (sum << 8) | (sum & 0xa8) | ((sum == 0) << 6) | 0x10 | parityTable[sum]; + sim_printf("0x%04x,", v); + if ( ((sum+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* xororTable */ +/* + uint32 sum, v; + for (sum = 0; sum < 256; sum++) { + v = (sum << 8) | (sum & 0xa8) | ((sum == 0) << 6) | parityTable[sum]; + sim_printf("0x%04x,", v); + if ( ((sum+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* rotateShiftTable */ +/* + uint32 temp, v; + for (temp = 0; temp < 256; temp++) { + v = (temp & 0xa8) | (((temp & 0xff) == 0) << 6) | PARITY(temp); + sim_printf("%3d,", v); + if ( ((temp+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* incZ80Table */ +/* + uint32 temp, v; + for (temp = 0; temp < 256; temp++) { + v = (temp & 0xa8) | (((temp & 0xff) == 0) << 6) | + (((temp & 0xf) == 0) << 4) | ((temp == 0x80) << 2); + sim_printf("%3d,", v); + if ( ((temp+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* decZ80Table */ +/* + uint32 temp, v; + for (temp = 0; temp < 256; temp++) { + v = (temp & 0xa8) | (((temp & 0xff) == 0) << 6) | + (((temp & 0xf) == 0xf) << 4) | ((temp == 0x7f) << 2) | 2; + sim_printf("%3d,", v); + if ( ((temp+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbitsZ80Table */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (cbits & 0x10) | (((cbits >> 6) ^ (cbits >> 5)) & 4) | + ((cbits >> 8) & 1); + sim_printf("%2d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbitsZ80DupTable */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (cbits & 0x10) | (((cbits >> 6) ^ (cbits >> 5)) & 4) | + ((cbits >> 8) & 1) | (cbits & 0xa8); + sim_printf("%3d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbits2Z80Table */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (((cbits >> 6) ^ (cbits >> 5)) & 4) | (cbits & 0x10) | 2 | ((cbits >> 8) & 1); + sim_printf("%2d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* cbits2Z80DupTable */ +/* + uint32 cbits, v; + for (cbits = 0; cbits < 512; cbits++) { + v = (((cbits >> 6) ^ (cbits >> 5)) & 4) | (cbits & 0x10) | 2 | ((cbits >> 8) & 1) | + (cbits & 0xa8); + sim_printf("%3d,", v); + if ( ((cbits+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* negTable */ +/* + uint32 temp, v; + for (temp = 0; temp < 256; temp++) { + v = (((temp & 0x0f) != 0) << 4) | ((temp == 0x80) << 2) | 2 | (temp != 0); + sim_printf("%2d,", v); + if ( ((temp+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* rrdrldTable */ +/* + uint32 acu, v; + for (acu = 0; acu < 256; acu++) { + v = (acu << 8) | (acu & 0xa8) | (((acu & 0xff) == 0) << 6) | parityTable[acu]; + sim_printf("0x%04x,", v); + if ( ((acu+1) & 0x7) == 0) { + sim_printf("\n"); + } + } +*/ +/* cpTable */ +/* + uint32 sum, v; + for (sum = 0; sum < 256; sum++) { + v = (sum & 0x80) | (((sum & 0xff) == 0) << 6); + sim_printf("%3d,", v); + if ( ((sum+1) & 0xf) == 0) { + sim_printf("\n"); + } + } +*/ +/* remove comments to generate table contents +} +*/ + +#define RAM_PP(Addr) s100_bus_memr(Addr++) +#define RAM_MM(Addr) s100_bus_memr(Addr--) +#define GET_WORD(Addr) (s100_bus_memr(Addr) | (s100_bus_memr(Addr + 1) << 8)) +#define PUT_WORD(Addr, v) { s100_bus_memw(Addr, v); s100_bus_memw(Addr + 1, v >> 8); } +#define PUT_BYTE_PP(a,v) s100_bus_memw(a++, v) +#define PUT_BYTE_MM(a,v) s100_bus_memw(a--, v) +#define MM_PUT_BYTE(a,v) s100_bus_memw(--a, v) + +#define PUSH(x) { \ + MM_PUT_BYTE(SP, (x) >> 8); \ + MM_PUT_BYTE(SP, x); \ +} + +#define CHECK_BREAK_BYTE(a) \ + if (sim_brk_summ & SWMASK('M')) { \ + if (sim_brk_test((a) & 0xffff, SWMASK('M'))) { \ + reason = STOP_MEM; \ + prepareMemoryAccessMessage((a) & 0xffff); \ + goto end_decode; \ + } \ + } + +#define CHECK_BREAK_TWO_BYTES_EXTENDED(a1, a2, iCode) \ + if (sim_brk_summ & SWMASK('M')) { \ + if (sim_brk_test((a1) & 0xffff, SWMASK('M'))) { \ + reason = STOP_MEM; \ + prepareMemoryAccessMessage((a1) & 0xffff); \ + iCode; \ + goto end_decode; \ + } \ + if (sim_brk_test((a2) & 0xffff, SWMASK('M'))) { \ + reason = STOP_MEM; \ + prepareMemoryAccessMessage((a2) & 0xffff); \ + iCode; \ + goto end_decode; \ + } \ + } + +#define CHECK_BREAK_TWO_BYTES(a1, a2) CHECK_BREAK_TWO_BYTES_EXTENDED(a1, a2,;) + +#define CHECK_BREAK_WORD(a) CHECK_BREAK_TWO_BYTES(a, (a + 1)) + +#define HALTINSTRUCTION 0x76 + +/* Macros for the IN/OUT instructions INI/INIR/IND/INDR/OUTI/OTIR/OUTD/OTDR + + Pre condition + acu == value of transferred byte (IN or OUT) + Post condition + F is set correctly + + Use INOUTFLAGS_ZERO(x) for INIR/INDR/OTIR/OTDR where + x == (C + 1) & 0xff for INIR + x == L for OTIR and OTDR + x == (C - 1) & 0xff for INDR + Use INOUTFLAGS_NONZERO(x) for INI/IND/OUTI/OUTD where + x == (C + 1) & 0xff for INI + x == L for OUTI and OUTD + x == (C - 1) & 0xff for IND +*/ +#define INOUTFLAGS_ZERO(x) \ + AF = (AF & 0xff00) | (FLAG_Z) | /* SF, YF, XF, ZF */ \ + ((acu & 0x80) >> 6) | /* NF */ \ + ((acu + (x)) > 0xff ? (FLAG_C | FLAG_H) : 0) | /* CF, HF */ \ + parityTable[((acu + (x)) & 7)] /* PF */ + +#define INOUTFLAGS_NONZERO(x) \ + AF = (AF & 0xff00) | (HIGH_REGISTER(BC) & 0xa8) | \ + ((HIGH_REGISTER(BC) == 0) << 6) | /* SF, YF, XF, ZF */ \ + ((acu & 0x80) >> 6) | /* NF */ \ + ((acu + (x)) > 0xff ? (FLAG_C | FLAG_H) : 0) | /* CF, HF */ \ + parityTable[((acu + (x)) & 7) ^ HIGH_REGISTER(BC)] /* PF */ + +t_stat z80_instr(void) +{ + int i; + int32 reason = SCPE_OK; + register uint32 specialProcessing; + register uint32 AF; + register uint32 BC; + register uint32 DE; + register uint32 HL; + register uint32 PC; + register uint32 SP; + register uint32 IX; + register uint32 IY; + register uint32 temp = 0; + register uint32 acu = 0; + register uint32 sum; + register uint32 cbits; + register uint32 op; + register uint32 adr; + + register uint32 tStates; /* number of t-states executed in the current time-slice */ + int32 tStateModifier = FALSE; + + AF = AF_S; + BC = BC_S; + DE = DE_S; + HL = HL_S; + PC = PC_S & ADDRMASK; + SP = SP_S; + IX = IX_S; + IY = IY_S; + +// vectorInterrupt = s100_bus_get_int(); +// nmiInterrupt = s100_bus_get_nmi(); + + specialProcessing = nmiInterrupt | vectorInterrupt | sim_brk_summ; + + tStates = 0; + + /* main instruction fetch/decode loop */ + while (1) { /* loop until halted */ + + if (sim_interval <= 0) { /* check clock queue */ + if ((reason = sim_process_event())) { + break; + } + + specialProcessing = nmiInterrupt | vectorInterrupt | sim_brk_summ; + } + + if (specialProcessing) { /* quick check for special processing */ + + if (nmiInterrupt) { + s100_bus_clr_nmi(); + specialProcessing = sim_brk_summ; + IFF_S &= IFF2; /* Clear IFF1 */ + CHECK_BREAK_TWO_BYTES_EXTENDED(SP - 2, SP - 1, (nmiInterrupt = TRUE, IFF_S |= IFF1)); + if ((s100_bus_memr(PC) == HALTINSTRUCTION) && ((z80_unit.flags & UNIT_Z80_STOPONHALT) == 0)) { + PUSH(PC + 1); + } + else { + PUSH(PC); + } + tStates += 11; + PC = 0x0066; + } + + if ((IM_S == 1) && vectorInterrupt && (IFF_S & IFF1)) { /* Z80 Interrupt Mode 1 */ + uint32 tempVectorInterrupt = vectorInterrupt; + uint8 intVector = 0; + + while ((tempVectorInterrupt & 1) == 0) { + tempVectorInterrupt >>= 1; + intVector++; + } + + s100_bus_clr_int(intVector); + + specialProcessing = sim_brk_summ; + IFF_S = 0; /* disable interrupts */ + CHECK_BREAK_TWO_BYTES_EXTENDED(SP - 2, SP - 1, (vectorInterrupt |= (1 << intVector), IFF_S |= (IFF1 | IFF2))); + if ((s100_bus_memr(PC) == HALTINSTRUCTION) && ((z80_unit.flags & UNIT_Z80_STOPONHALT) == 0)) { + PUSH(PC + 1); + } + else { + PUSH(PC); + } + + PC = 0x0038; + + sim_debug(INT_MSG, &z80_dev, ADDRESS_FORMAT + " INT(mode=1 intVector=%d PC=%04X)\n", PCX, intVector, PC); + } else if ((IM_S == 2) && vectorInterrupt && (IFF_S & IFF1)) { + int32 vector; + uint32 tempVectorInterrupt = vectorInterrupt; + uint8 intVector = 0; + + while ((tempVectorInterrupt & 1) == 0) { + tempVectorInterrupt >>= 1; + intVector++; + } + + vectorInterrupt &= ~(1 << intVector); + + specialProcessing = sim_brk_summ; + IFF_S = 0; /* disable interrupts */ + CHECK_BREAK_TWO_BYTES_EXTENDED(SP - 2, SP - 1, (vectorInterrupt |= (1 << intVector), IFF_S |= (IFF1 | IFF2))); + if ((s100_bus_memr(PC) == HALTINSTRUCTION) && ((z80_unit.flags & UNIT_Z80_STOPONHALT) == 0)) { + PUSH(PC + 1); + } + else { + PUSH(PC); + } + + vector = (HIGH_REGISTER(IR_S) << 8) | s100_bus_get_int_data(intVector); + PC = ((s100_bus_memr(vector+1) << 8) | s100_bus_memr(vector)) & ADDRMASK; + + sim_debug(INT_MSG, &z80_dev, ADDRESS_FORMAT + " INT(mode=2 intVector=%d vector=%04X PC=%04X)\n", PCX, intVector, vector, PC); + } + + if (sim_brk_summ) { + if (sim_brk_test(PC, (2u << SIM_BKPT_V_SPC) | SWMASK('E'))) { /* breakpoint? */ + reason = STOP_IBKPT; /* stop simulation */ + break; + } + if (sim_brk_test(s100_bus_memr(PC), (1u << SIM_BKPT_V_SPC) | SWMASK('I'))) { /* instruction breakpoint? */ + reason = STOP_INSTR; /* stop simulation */ + prepareInstructionMessage(PC, s100_bus_memr(PC)); + break; + } + } + } + + PCX = PC; + s100_bus_set_addr(PCX); /* Put current address on the bus */ + + /* 8080 INT/Z80 Interrupt Mode 0 + Instruction to execute (ex. RST0-7) is on the data bus + NOTE: does not support multi-byte instructions such as CALL + */ + if ((IM_S == 0) && vectorInterrupt && (IFF_S & IFF1)) { /* 8080/Z80 Interrupt Mode 0 */ + uint32 tempVectorInterrupt = vectorInterrupt; + uint8 intVector = 0; + + while ((tempVectorInterrupt & 1) == 0) { + tempVectorInterrupt >>= 1; + intVector++; + } + + IFF_S = 0; /* disable interrupts */ + + s100_bus_clr_int(intVector); /* Clear interrupt */ + + op = s100_bus_get_int_data(intVector); + + sim_debug(INT_MSG, &z80_dev, ADDRESS_FORMAT + " INT(mode=0 vectorInterrupt=%X intVector=%d op=%02X)\n", PCX, vectorInterrupt, intVector, op); + } else { + INCR(1); + op = RAM_PP(PC); + } + + switch(op) { + + case 0x00: /* NOP */ + tStates += 4; /* NOP 4 */ + break; + + case 0x01: /* LD BC,nnnn */ + tStates += 10; /* LXI B,nnnn 10 */ + BC = GET_WORD(PC); + PC += 2; + break; + + case 0x02: /* LD (BC),A */ + tStates += 7; /* STAX B 7 */ + CHECK_BREAK_BYTE(BC) + s100_bus_memw(BC, HIGH_REGISTER(AF)); + break; + + case 0x03: /* INC BC */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* INX B 5 */ + ++BC; + break; + + case 0x04: /* INC B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR B 5 */ + BC += 0x100; + temp = HIGH_REGISTER(BC); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); /* SET_PV2 uses temp */ + break; + + case 0x05: /* DEC B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR B 5 */ + BC -= 0x100; + temp = HIGH_REGISTER(BC); + AF = (AF & ~0xfe) | decTable[temp] | SET_PV2(0x7f); /* SET_PV2 uses temp */ + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x06: /* LD B,nn */ + tStates += 7; /* MVI B,nn 7 */ + SET_HIGH_REGISTER(BC, RAM_PP(PC)); + break; + + case 0x07: /* RLCA */ + tStates += 4; /* RLC 4 */ + temp = ((AF >> 7) & 0x0128) | ((AF << 1) & ~0x1ff) | + (AF & 0xc4) | ((AF >> 15) & 1); + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x08: /* EX AF,AF' */ + tStates += 4; /* NOP 4 */ + CHECK_CPU_8080; + temp = AF; + AF = AF1_S; + AF1_S = temp; + break; + + case 0x09: /* ADD HL,BC */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* DAD B 10 */ + HL &= ADDRMASK; + BC &= ADDRMASK; + sum = HL + BC; + temp = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(HL ^ BC ^ sum) >> 8]; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + HL = sum; + break; + + case 0x0a: /* LD A,(BC) */ + tStates += 7; /* LDAX B 7 */ + CHECK_BREAK_BYTE(BC) + SET_HIGH_REGISTER(AF, s100_bus_memr(BC)); + break; + + case 0x0b: /* DEC BC */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* DCX B 5 */ + --BC; + break; + + case 0x0c: /* INC C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR C 5 */ + temp = LOW_REGISTER(BC) + 1; + SET_LOW_REGISTER(BC, temp); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); + break; + + case 0x0d: /* DEC C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR C 5 */ + temp = LOW_REGISTER(BC) - 1; + SET_LOW_REGISTER(BC, temp); + AF = (AF & ~0xfe) | decTable[temp & 0xff] | SET_PV2(0x7f); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x0e: /* LD C,nn */ + tStates += 7; /* MVI C,nn 7 */ + SET_LOW_REGISTER(BC, RAM_PP(PC)); + break; + + case 0x0f: /* RRCA */ + tStates += 4; /* RRC 4 */ + temp = (AF & 0xc4) | rrcaTable[HIGH_REGISTER(AF)]; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x10: /* DJNZ dd */ + if (z80_chiptype == CHIP_TYPE_8080) + tStates += 4; /* NOP 4 */ + CHECK_CPU_8080; + if ((BC -= 0x100) & 0xff00) { + PC += (int8) s100_bus_memr(PC) + 1; + tStates += 13; + } else { + PC++; + tStates += 8; + } + break; + + case 0x11: /* LD DE,nnnn */ + tStates += 10; /* LXI D,nnnn 10 */ + DE = GET_WORD(PC); + PC += 2; + break; + + case 0x12: /* LD (DE),A */ + tStates += 7; /* STAX D 7 */ + CHECK_BREAK_BYTE(DE) + s100_bus_memw(DE, HIGH_REGISTER(AF)); + break; + + case 0x13: /* INC DE */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* INX D 5 */ + ++DE; + break; + + case 0x14: /* INC D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR D 5 */ + DE += 0x100; + temp = HIGH_REGISTER(DE); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); /* SET_PV2 uses temp */ + break; + + case 0x15: /* DEC D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR D 5 */ + DE -= 0x100; + temp = HIGH_REGISTER(DE); + AF = (AF & ~0xfe) | decTable[temp] | SET_PV2(0x7f); /* SET_PV2 uses temp */ + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x16: /* LD D,nn */ + tStates += 7; /* MVI D,nn 7 */ + SET_HIGH_REGISTER(DE, RAM_PP(PC)); + break; + + case 0x17: /* RLA */ + tStates += 4; /* RAL 4 */ + temp = ((AF << 8) & 0x0100) | ((AF >> 7) & 0x28) | ((AF << 1) & ~0x01ff) | + (AF & 0xc4) | ((AF >> 15) & 1); + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x18: /* JR dd */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 4 : 12); /* NOP 4 */ + CHECK_CPU_8080; + PC += (int8) s100_bus_memr(PC) + 1; + break; + + case 0x19: /* ADD HL,DE */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* DAD D 10 */ + HL &= ADDRMASK; + DE &= ADDRMASK; + sum = HL + DE; + temp = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(HL ^ DE ^ sum) >> 8]; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + HL = sum; + break; + + case 0x1a: /* LD A,(DE) */ + tStates += 7; /* LDAX D 7 */ + CHECK_BREAK_BYTE(DE) + SET_HIGH_REGISTER(AF, s100_bus_memr(DE)); + break; + + case 0x1b: /* DEC DE */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* DCX D 5 */ + --DE; + break; + + case 0x1c: /* INC E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR E 5 */ + temp = LOW_REGISTER(DE) + 1; + SET_LOW_REGISTER(DE, temp); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); + break; + + case 0x1d: /* DEC E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR E 5 */ + temp = LOW_REGISTER(DE) - 1; + SET_LOW_REGISTER(DE, temp); + AF = (AF & ~0xfe) | decTable[temp & 0xff] | SET_PV2(0x7f); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x1e: /* LD E,nn */ + tStates += 7; /* MVI E 7 */ + SET_LOW_REGISTER(DE, RAM_PP(PC)); + break; + + case 0x1f: /* RRA */ + tStates += 4; /* RAR 4 */ + temp = ((AF & 1) << 15) | (AF & 0xc4) | rraTable[HIGH_REGISTER(AF)]; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x20: /* JR NZ,dd */ + if (z80_chiptype == CHIP_TYPE_8080) + tStates += 4; /* NOP 4 */ + CHECK_CPU_8080; + if (TSTFLAG(Z)) { + PC++; + tStates += 7; + } else { + PC += (int8) s100_bus_memr(PC) + 1; + tStates += 12; + } + break; + + case 0x21: /* LD HL,nnnn */ + tStates += 10; /* LXI H,nnnn 10 */ + HL = GET_WORD(PC); + PC += 2; + break; + + case 0x22: /* LD (nnnn),HL */ + tStates += 16; /* SHLD 16 */ + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, HL); + PC += 2; + break; + + case 0x23: /* INC HL */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* INX H 5 */ + ++HL; + break; + + case 0x24: /* INC H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR H 5 */ + HL += 0x100; + temp = HIGH_REGISTER(HL); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); /* SET_PV2 uses temp */ + break; + + case 0x25: /* DEC H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR H 5 */ + HL -= 0x100; + temp = HIGH_REGISTER(HL); + AF = (AF & ~0xfe) | decTable[temp] | SET_PV2(0x7f); /* SET_PV2 uses temp */ + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x26: /* LD H,nn */ + tStates += 7; /* MVI H,nn 7 */ + SET_HIGH_REGISTER(HL, RAM_PP(PC)); + break; + + case 0x27: /* DAA */ + tStates += 4; /* DAA 4 */ + acu = HIGH_REGISTER(AF); + temp = LOW_DIGIT(acu); + cbits = TSTFLAG(C); + if (z80_chiptype == CHIP_TYPE_Z80 && TSTFLAG(N)) { /* last operation was a subtract */ + int hd = cbits || acu > 0x99; + if (TSTFLAG(H) || (temp > 9)) { /* adjust low digit */ + if (temp > 5) { + SETFLAG(H, 0); + } + acu -= 6; + acu &= 0xff; + } + if (hd) + acu -= 0x160; /* adjust high digit */ + } else { /* last operation was an add */ + if (TSTFLAG(H) || (temp > 9)) { /* adjust low digit */ + SETFLAG(H, (temp > 9)); + acu += 6; + } + if (cbits || ((acu & 0x1f0) > 0x90)) + acu += 0x60; /* adjust high digit */ + } + AF = (AF & 0x12) | rrdrldTable[acu & 0xff] | ((acu >> 8) & 1) | cbits; + break; + + case 0x28: /* JR Z,dd */ + if (z80_chiptype == CHIP_TYPE_8080) + tStates += 4; /* NOP 4 */ + CHECK_CPU_8080; + if (TSTFLAG(Z)) { + PC += (int8) s100_bus_memr(PC) + 1; + tStates += 12; + } else { + PC++; + tStates += 7; + } + break; + + case 0x29: /* ADD HL,HL */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* DAD H 10 */ + HL &= ADDRMASK; + sum = HL + HL; + temp = (AF & ~0x3b) | cbitsDup16Table[sum >> 8]; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + HL = sum; + break; + + case 0x2a: /* LD HL,(nnnn) */ + tStates += 16; /* LHLD nnnn 16 */ + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + HL = GET_WORD(temp); + PC += 2; + break; + + case 0x2b: /* DEC HL */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* DCX H 5 */ + --HL; + break; + + case 0x2c: /* INC L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR L 5 */ + temp = LOW_REGISTER(HL) + 1; + SET_LOW_REGISTER(HL, temp); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); + break; + + case 0x2d: /* DEC L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR L 5 */ + temp = LOW_REGISTER(HL) - 1; + SET_LOW_REGISTER(HL, temp); + AF = (AF & ~0xfe) | decTable[temp & 0xff] | SET_PV2(0x7f); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x2e: /* LD L,nn */ + tStates += 7; /* MVI L,nn 7 */ + SET_LOW_REGISTER(HL, RAM_PP(PC)); + break; + + case 0x2f: /* CPL */ + tStates += 4; /* CMA 4 */ + temp = (~AF & ~0xff) | (AF & 0xc5) | ((~AF >> 8) & 0x28) | 0x12; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x30: /* JR NC,dd */ + if (z80_chiptype == CHIP_TYPE_8080) + tStates += 4; /* NOP 4 */ + CHECK_CPU_8080; + if (TSTFLAG(C)) { + PC++; + tStates += 7; + } else { + PC += (int8) s100_bus_memr(PC) + 1; + tStates += 12; + } + break; + + case 0x31: /* LD SP,nnnn */ + tStates += 10; /* LXI SP,nnnn 10 */ + SP = GET_WORD(PC); + PC += 2; + break; + + case 0x32: /* LD (nnnn),A */ + tStates += 13; /* STA nnnn 13 */ + temp = GET_WORD(PC); + CHECK_BREAK_BYTE(temp); + s100_bus_memw(temp, HIGH_REGISTER(AF)); + PC += 2; + break; + + case 0x33: /* INC SP */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* INX SP 5 */ + ++SP; + break; + + case 0x34: /* INC (HL) */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* INR M 10 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL) + 1; + s100_bus_memw(HL, temp); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); + break; + + case 0x35: /* DEC (HL) */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* DCR M 10 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL) - 1; + s100_bus_memw(HL, temp); + AF = (AF & ~0xfe) | decTable[temp & 0xff] | SET_PV2(0x7f); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x36: /* LD (HL),nn */ + tStates += 10; /* MVI M 10 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, RAM_PP(PC)); + break; + + case 0x37: /* SCF */ + tStates += 4; /* STC 4 */ + temp = (AF & ~0x3b) | ((AF >> 8) & 0x28) | 1; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x38: /* JR C,dd */ + if (z80_chiptype == CHIP_TYPE_8080) + tStates += 4; /* NOP 4 */ + CHECK_CPU_8080; + if (TSTFLAG(C)) { + PC += (int8) s100_bus_memr(PC) + 1; + tStates += 12; + } else { + PC++; + tStates += 7; + } + break; + + case 0x39: /* ADD HL,SP */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* DAD SP 10 */ + HL &= ADDRMASK; + SP &= ADDRMASK; + sum = HL + SP; + temp = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(HL ^ SP ^ sum) >> 8]; + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + HL = sum; + break; + + case 0x3a: /* LD A,(nnnn) */ + tStates += 13; /* LDA nnnn 13 */ + temp = GET_WORD(PC); + CHECK_BREAK_BYTE(temp); + SET_HIGH_REGISTER(AF, s100_bus_memr(temp)); + PC += 2; + break; + + case 0x3b: /* DEC SP */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* DCX SP 5 */ + --SP; + break; + + case 0x3c: /* INC A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* INR A 5 */ + AF += 0x100; + temp = HIGH_REGISTER(AF); + AF = (AF & ~0xfe) | incTable[temp] | SET_PV2(0x80); /* SET_PV2 uses temp */ + break; + + case 0x3d: /* DEC A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* DCR A 5 */ + AF -= 0x100; + temp = HIGH_REGISTER(AF); + AF = (AF & ~0xfe) | decTable[temp] | SET_PV2(0x7f); /* SET_PV2 uses temp */ + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x3e: /* LD A,nn */ + tStates += 7; /* MVI A,nn 7 */ + SET_HIGH_REGISTER(AF, RAM_PP(PC)); + break; + + case 0x3f: /* CCF */ + tStates += 4; /* CMC 4 */ + temp = (AF & ~0x3b) | ((AF >> 8) & 0x28) | ((AF & 1) << 4) | (~AF & 1); + if (z80_chiptype == CHIP_TYPE_8080) + temp = (temp & ~0x10) | (AF & 0x10); + AF = temp; + break; + + case 0x40: /* LD B,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,B 5 */ + break; + + case 0x41: /* LD B,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,C 5 */ + BC = (BC & 0xff) | ((BC & 0xff) << 8); + break; + + case 0x42: /* LD B,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,D 5 */ + BC = (BC & 0xff) | (DE & ~0xff); + break; + + case 0x43: /* LD B,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,E 5 */ + BC = (BC & 0xff) | ((DE & 0xff) << 8); + break; + + case 0x44: /* LD B,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,H 5 */ + BC = (BC & 0xff) | (HL & ~0xff); + break; + + case 0x45: /* LD B,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,L 5 */ + BC = (BC & 0xff) | ((HL & 0xff) << 8); + break; + + case 0x46: /* LD B,(HL) */ + tStates += 7; /* MOV B,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_HIGH_REGISTER(BC, s100_bus_memr(HL)); + break; + + case 0x47: /* LD B,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV B,A 5 */ + BC = (BC & 0xff) | (AF & ~0xff); + break; + + case 0x48: /* LD C,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,B 5 */ + BC = (BC & ~0xff) | ((BC >> 8) & 0xff); + break; + + case 0x49: /* LD C,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,C 5 */ + break; + + case 0x4a: /* LD C,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,D 5 */ + BC = (BC & ~0xff) | ((DE >> 8) & 0xff); + break; + + case 0x4b: /* LD C,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,E 5 */ + BC = (BC & ~0xff) | (DE & 0xff); + break; + + case 0x4c: /* LD C,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,H 5 */ + BC = (BC & ~0xff) | ((HL >> 8) & 0xff); + break; + + case 0x4d: /* LD C,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,L 5 */ + BC = (BC & ~0xff) | (HL & 0xff); + break; + + case 0x4e: /* LD C,(HL) */ + tStates += 7; /* MOV C,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_LOW_REGISTER(BC, s100_bus_memr(HL)); + break; + + case 0x4f: /* LD C,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV C,A 5 */ + BC = (BC & ~0xff) | ((AF >> 8) & 0xff); + break; + + case 0x50: /* LD D,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,B 5 */ + DE = (DE & 0xff) | (BC & ~0xff); + break; + + case 0x51: /* LD D,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,C 5 */ + DE = (DE & 0xff) | ((BC & 0xff) << 8); + break; + + case 0x52: /* LD D,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,D 5 */ + break; + + case 0x53: /* LD D,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,E 5 */ + DE = (DE & 0xff) | ((DE & 0xff) << 8); + break; + + case 0x54: /* LD D,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,H 5 */ + DE = (DE & 0xff) | (HL & ~0xff); + break; + + case 0x55: /* LD D,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,L 5 */ + DE = (DE & 0xff) | ((HL & 0xff) << 8); + break; + + case 0x56: /* LD D,(HL) */ + tStates += 7; /* MOV D,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_HIGH_REGISTER(DE, s100_bus_memr(HL)); + break; + + case 0x57: /* LD D,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV D,A 5 */ + DE = (DE & 0xff) | (AF & ~0xff); + break; + + case 0x58: /* LD E,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,B 5 */ + DE = (DE & ~0xff) | ((BC >> 8) & 0xff); + break; + + case 0x59: /* LD E,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,C 5 */ + DE = (DE & ~0xff) | (BC & 0xff); + break; + + case 0x5a: /* LD E,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,D 5 */ + DE = (DE & ~0xff) | ((DE >> 8) & 0xff); + break; + + case 0x5b: /* LD E,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,E 5 */ + break; + + case 0x5c: /* LD E,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,H 5 */ + DE = (DE & ~0xff) | ((HL >> 8) & 0xff); + break; + + case 0x5d: /* LD E,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,L 5 */ + DE = (DE & ~0xff) | (HL & 0xff); + break; + + case 0x5e: /* LD E,(HL) */ + tStates += 7; /* MOV E,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_LOW_REGISTER(DE, s100_bus_memr(HL)); + break; + + case 0x5f: /* LD E,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV E,A 5 */ + DE = (DE & ~0xff) | ((AF >> 8) & 0xff); + break; + + case 0x60: /* LD H,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,B 5 */ + HL = (HL & 0xff) | (BC & ~0xff); + break; + + case 0x61: /* LD H,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,C 5 */ + HL = (HL & 0xff) | ((BC & 0xff) << 8); + break; + + case 0x62: /* LD H,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,D 5 */ + HL = (HL & 0xff) | (DE & ~0xff); + break; + + case 0x63: /* LD H,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,E 5 */ + HL = (HL & 0xff) | ((DE & 0xff) << 8); + break; + + case 0x64: /* LD H,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,H 5 */ + break; + + case 0x65: /* LD H,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,L 5 */ + HL = (HL & 0xff) | ((HL & 0xff) << 8); + break; + + case 0x66: /* LD H,(HL) */ + tStates += 7; /* MOV H,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_HIGH_REGISTER(HL, s100_bus_memr(HL)); + break; + + case 0x67: /* LD H,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV H,A 5 */ + HL = (HL & 0xff) | (AF & ~0xff); + break; + + case 0x68: /* LD L,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,B 5 */ + HL = (HL & ~0xff) | ((BC >> 8) & 0xff); + break; + + case 0x69: /* LD L,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,C 5 */ + HL = (HL & ~0xff) | (BC & 0xff); + break; + + case 0x6a: /* LD L,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,D 5 */ + HL = (HL & ~0xff) | ((DE >> 8) & 0xff); + break; + + case 0x6b: /* LD L,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,E 5 */ + HL = (HL & ~0xff) | (DE & 0xff); + break; + + case 0x6c: /* LD L,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,H 5 */ + HL = (HL & ~0xff) | ((HL >> 8) & 0xff); + break; + + case 0x6d: /* LD L,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,L 5 */ + break; + + case 0x6e: /* LD L,(HL) */ + tStates += 7; /* MOV L,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_LOW_REGISTER(HL, s100_bus_memr(HL)); + break; + + case 0x6f: /* LD L,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV L,A 5 */ + HL = (HL & ~0xff) | ((AF >> 8) & 0xff); + break; + + case 0x70: /* LD (HL),B */ + tStates += 7; /* MOV M,B 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, HIGH_REGISTER(BC)); + break; + + case 0x71: /* LD (HL),C */ + tStates += 7; /* MOV M,C 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, LOW_REGISTER(BC)); + break; + + case 0x72: /* LD (HL),D */ + tStates += 7; /* MOV M,D 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, HIGH_REGISTER(DE)); + break; + + case 0x73: /* LD (HL),E */ + tStates += 7; /* MOV M,E 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, LOW_REGISTER(DE)); + break; + + case 0x74: /* LD (HL),H */ + tStates += 7; /* MOV M,H 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, HIGH_REGISTER(HL)); + break; + + case 0x75: /* LD (HL),L */ + tStates += 7; /* MOV M,L 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, LOW_REGISTER(HL)); + break; + + case HALTINSTRUCTION: /* HALT */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 7 : 4); /* HLT 7 */ + PC--; + if (z80_unit.flags & UNIT_Z80_STOPONHALT) { + reason = STOP_HALT; + goto end_decode; + } + sim_interval = 0; + break; + + case 0x77: /* LD (HL),A */ + tStates += 7; /* MOV M,A 7 */ + CHECK_BREAK_BYTE(HL); + s100_bus_memw(HL, HIGH_REGISTER(AF)); + break; + + case 0x78: /* LD A,B */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,B 5 */ + AF = (AF & 0xff) | (BC & ~0xff); + break; + + case 0x79: /* LD A,C */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,C 5 */ + AF = (AF & 0xff) | ((BC & 0xff) << 8); + break; + + case 0x7a: /* LD A,D */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,D 5 */ + AF = (AF & 0xff) | (DE & ~0xff); + break; + + case 0x7b: /* LD A,E */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,E 5 */ + AF = (AF & 0xff) | ((DE & 0xff) << 8); + break; + + case 0x7c: /* LD A,H */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,H 5 */ + AF = (AF & 0xff) | (HL & ~0xff); + break; + + case 0x7d: /* LD A,L */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,L 5 */ + AF = (AF & 0xff) | ((HL & 0xff) << 8); + break; + + case 0x7e: /* LD A,(HL) */ + tStates += 7; /* MOV A,M 7 */ + CHECK_BREAK_BYTE(HL); + SET_HIGH_REGISTER(AF, s100_bus_memr(HL)); + break; + + case 0x7f: /* LD A,A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* MOV A,A 5 */ + break; + + case 0x80: /* ADD A,B */ + tStates += 4; /* ADD B 4 */ + temp = HIGH_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x81: /* ADD A,C */ + tStates += 4; /* ADD C 4 */ + temp = LOW_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x82: /* ADD A,D */ + tStates += 4; /* ADD D 4 */ + temp = HIGH_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x83: /* ADD A,E */ + tStates += 4; /* ADD E 4 */ + temp = LOW_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x84: /* ADD A,H */ + tStates += 4; /* ADD H 4 */ + temp = HIGH_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x85: /* ADD A,L */ + tStates += 4; /* ADD L 4 */ + temp = LOW_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x86: /* ADD A,(HL) */ + tStates += 7; /* ADD M 7 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x87: /* ADD A,A */ + tStates += 4; /* ADD A 4 */ + cbits = 2 * HIGH_REGISTER(AF); + AF = cbitsDup8Table[cbits] | (SET_PVS(cbits)); + break; + + case 0x88: /* ADC A,B */ + tStates += 4; /* ADC B 4 */ + temp = HIGH_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x89: /* ADC A,C */ + tStates += 4; /* ADC C 4 */ + temp = LOW_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x8a: /* ADC A,D */ + tStates += 4; /* ADC D 4 */ + temp = HIGH_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x8b: /* ADC A,E */ + tStates += 4; /* ADC E 4 */ + temp = LOW_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x8c: /* ADC A,H */ + tStates += 4; /* ADC H 4 */ + temp = HIGH_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x8d: /* ADC A,L */ + tStates += 4; /* ADC L 4 */ + temp = LOW_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x8e: /* ADC A,(HL) */ + tStates += 7; /* ADC M 7 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0x8f: /* ADC A,A */ + tStates += 4; /* ADC A 4 */ + cbits = 2 * HIGH_REGISTER(AF) + TSTFLAG(C); + AF = cbitsDup8Table[cbits] | (SET_PVS(cbits)); + break; + + case 0x90: /* SUB B */ + tStates += 4; /* SUB B 4 */ + temp = HIGH_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x91: /* SUB C */ + tStates += 4; /* SUB C 4 */ + temp = LOW_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x92: /* SUB D */ + tStates += 4; /* SUB D 4 */ + temp = HIGH_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x93: /* SUB E */ + tStates += 4; /* SUB E 4 */ + temp = LOW_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x94: /* SUB H */ + tStates += 4; /* SUB H 4 */ + temp = HIGH_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x95: /* SUB L */ + tStates += 4; /* SUB L 4 */ + temp = LOW_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x96: /* SUB (HL) */ + tStates += 7; /* SUB M 7 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x97: /* SUB A */ + tStates += 4; /* SUB A 4 */ + AF = (z80_chiptype == CHIP_TYPE_Z80) ? 0x42 : 0x56; + break; + + case 0x98: /* SBC A,B */ + tStates += 4; /* SBB B 4 */ + temp = HIGH_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x99: /* SBC A,C */ + tStates += 4; /* SBB C 4 */ + temp = LOW_REGISTER(BC); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x9a: /* SBC A,D */ + tStates += 4; /* SBB D 4 */ + temp = HIGH_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x9b: /* SBC A,E */ + tStates += 4; /* SBB E 4 */ + temp = LOW_REGISTER(DE); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x9c: /* SBC A,H */ + tStates += 4; /* SBB H 4 */ + temp = HIGH_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x9d: /* SBC A,L */ + tStates += 4; /* SBB L 4 */ + temp = LOW_REGISTER(HL); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x9e: /* SBC A,(HL) */ + tStates += 7; /* SBB M 7 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0x9f: /* SBC A,A */ + tStates += 4; /* SBB A 4 */ + cbits = -TSTFLAG(C); + AF = subTable[cbits & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PVS(cbits)); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xa0: /* AND B */ + tStates += 4; /* ANA B 4 */ + acu = andTable[((AF & BC) >> 8) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && ((AF | BC) & 0x0800) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa1: /* AND C */ + tStates += 4; /* ANA C 4 */ + acu = andTable[((AF >> 8) & BC) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && (((AF >> 8) | BC) & 0x08) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa2: /* AND D */ + tStates += 4; /* ANA D 4 */ + acu = andTable[((AF & DE) >> 8) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && ((AF | DE) & 0x0800) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa3: /* AND E */ + tStates += 4; /* ANA E 4 */ + acu = andTable[((AF >> 8) & DE) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && (((AF >> 8) | DE) & 0x08) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa4: /* AND H */ + tStates += 4; /* ANA H 4 */ + acu = andTable[((AF & HL) >> 8) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && ((AF | HL) & 0x0800) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa5: /* AND L */ + tStates += 4; /* ANA L 4 */ + acu = andTable[((AF >> 8) & HL) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && (((AF >> 8) | HL) & 0x08) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa6: /* AND (HL) */ + tStates += 7; /* ANA M 7 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL); + acu = andTable[((AF >> 8) & temp) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && (((AF >> 8) | temp) & 0x08) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa7: /* AND A */ + tStates += 4; /* ANA A 4 */ + acu = andTable[(AF >> 8) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && (AF & 0x0800) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xa8: /* XOR B */ + tStates += 4; /* XRA B 4 */ + AF = xororTable[((AF ^ BC) >> 8) & 0xff]; + break; + + case 0xa9: /* XOR C */ + tStates += 4; /* XRA C 4 */ + AF = xororTable[((AF >> 8) ^ BC) & 0xff]; + break; + + case 0xaa: /* XOR D */ + tStates += 4; /* XRA D 4 */ + AF = xororTable[((AF ^ DE) >> 8) & 0xff]; + break; + + case 0xab: /* XOR E */ + tStates += 4; /* XRA E 4 */ + AF = xororTable[((AF >> 8) ^ DE) & 0xff]; + break; + + case 0xac: /* XOR H */ + tStates += 4; /* XRA H 4 */ + AF = xororTable[((AF ^ HL) >> 8) & 0xff]; + break; + + case 0xad: /* XOR L */ + tStates += 4; /* XRA L 4 */ + AF = xororTable[((AF >> 8) ^ HL) & 0xff]; + break; + + case 0xae: /* XOR (HL) */ + tStates += 7; /* XRA M 7 */ + CHECK_BREAK_BYTE(HL); + AF = xororTable[((AF >> 8) ^ s100_bus_memr(HL)) & 0xff]; + break; + + case 0xaf: /* XOR A */ + tStates += 4; /* XRA A 4 */ + AF = 0x44; + break; + + case 0xb0: /* OR B */ + tStates += 4; /* ORA B 4 */ + AF = xororTable[((AF | BC) >> 8) & 0xff]; + break; + + case 0xb1: /* OR C */ + tStates += 4; /* ORA C 4 */ + AF = xororTable[((AF >> 8) | BC) & 0xff]; + break; + + case 0xb2: /* OR D */ + tStates += 4; /* ORA D 4 */ + AF = xororTable[((AF | DE) >> 8) & 0xff]; + break; + + case 0xb3: /* OR E */ + tStates += 4; /* ORA E 4 */ + AF = xororTable[((AF >> 8) | DE) & 0xff]; + break; + + case 0xb4: /* OR H */ + tStates += 4; /* ORA H 4 */ + AF = xororTable[((AF | HL) >> 8) & 0xff]; + break; + + case 0xb5: /* OR L */ + tStates += 4; /* ORA L 4 */ + AF = xororTable[((AF >> 8) | HL) & 0xff]; + break; + + case 0xb6: /* OR (HL) */ + tStates += 7; /* ORA M 7 */ + CHECK_BREAK_BYTE(HL); + AF = xororTable[((AF >> 8) | s100_bus_memr(HL)) & 0xff]; + break; + + case 0xb7: /* OR A */ + tStates += 4; /* ORA A 4 */ + AF = xororTable[(AF >> 8) & 0xff]; + break; + + case 0xb8: /* CP B */ + tStates += 4; /* CMP B 4 */ + temp = HIGH_REGISTER(BC); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xb9: /* CP C */ + tStates += 4; /* CMP C 4 */ + temp = LOW_REGISTER(BC); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xba: /* CP D */ + tStates += 4; /* CMP D 4 */ + temp = HIGH_REGISTER(DE); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xbb: /* CP E */ + tStates += 4; /* CMP E 4 */ + temp = LOW_REGISTER(DE); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xbc: /* CP H */ + tStates += 4; /* CMP H 4 */ + temp = HIGH_REGISTER(HL); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xbd: /* CP L */ + tStates += 4; /* CMP L 4 */ + temp = LOW_REGISTER(HL); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xbe: /* CP (HL) */ + tStates += 7; /* CMP M 7 */ + CHECK_BREAK_BYTE(HL); + temp = s100_bus_memr(HL); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xbf: /* CP A */ + tStates += 4; /* CMP A 4 */ + SET_LOW_REGISTER(AF, (HIGH_REGISTER(AF) & 0x28) | (z80_chiptype == CHIP_TYPE_Z80 ? 0x42 : 0x56)); + break; + + case 0xc0: /* RET NZ */ + if (TSTFLAG(Z)) { + tStates += 5; /* RNZ 5 */ + } else { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RNZ 11 */ + } + break; + + case 0xc1: /* POP BC */ + tStates += 10; /* POP B 10 */ + CHECK_BREAK_WORD(SP); + POP(BC); + break; + + case 0xc2: /* JP NZ,nnnn */ + JPC(!TSTFLAG(Z)); /* also updates tStates, Z80 and 8080 are equal */ + break; + + case 0xc3: /* JP nnnn */ + JPC(1); /* also updates tStates, Z80 and 8080 are equal */ + break; + + case 0xc4: /* CALL NZ,nnnn */ + CALLC(!TSTFLAG(Z)); /* also updates tStates */ + break; + + case 0xc5: /* PUSH BC */ + tStates += 11; /* PUSH B 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(BC); + break; + + case 0xc6: /* ADD A,nn */ + tStates += 7; /* ADI nn 7 */ + temp = RAM_PP(PC); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0xc7: /* RST 0 */ + tStates += 11; /* RST 0 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0; + break; + + case 0xc8: /* RET Z */ + if (TSTFLAG(Z)) { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RZ 11 */ + } else { + tStates += 5; /* RZ 5 */ + } + break; + + case 0xc9: /* RET */ + tStates += 10; /* RET 10 */ + CHECK_BREAK_WORD(SP); + POP(PC); + break; + + case 0xca: /* JP Z,nnnn */ + JPC(TSTFLAG(Z)); /* also updates tStates */ + break; + + case 0xcb: /* CB prefix */ + if (z80_chiptype == CHIP_TYPE_8080) { + if (z80_unit.flags & UNIT_Z80_OPSTOP) { + reason = STOP_OPCODE; + goto end_decode; + } else { + JPC(1); + break; + } + } + + INCR(1); + adr = HL; + switch ((op = s100_bus_memr(PC)) & 7) { + + case 0: + tStateModifier = FALSE; + ++PC; + acu = HIGH_REGISTER(BC); + tStates += 8; + break; + + case 1: + tStateModifier = FALSE; + ++PC; + acu = LOW_REGISTER(BC); + tStates += 8; + break; + + case 2: + tStateModifier = FALSE; + ++PC; + acu = HIGH_REGISTER(DE); + tStates += 8; + break; + + case 3: + tStateModifier = FALSE; + ++PC; + acu = LOW_REGISTER(DE); + tStates += 8; + break; + + case 4: + tStateModifier = FALSE; + ++PC; + acu = HIGH_REGISTER(HL); + tStates += 8; + break; + + case 5: + tStateModifier = FALSE; + ++PC; + acu = LOW_REGISTER(HL); + tStates += 8; + break; + + case 6: + CHECK_BREAK_BYTE(adr); + ++PC; + acu = s100_bus_memr(adr); + tStateModifier = TRUE; + tStates += 15; + break; + + case 7: + tStateModifier = FALSE; + ++PC; + acu = HIGH_REGISTER(AF); + tStates += 8; + break; + } + switch (op & 0xc0) { + + case 0x00: /* shift/rotate */ + switch (op & 0x38) { + + case 0x00: /* RLC */ + temp = (acu << 1) | (acu >> 7); + cbits = temp & 1; + goto cbshflg1; + + case 0x08: /* RRC */ + temp = (acu >> 1) | (acu << 7); + cbits = temp & 0x80; + goto cbshflg1; + + case 0x10: /* RL */ + temp = (acu << 1) | TSTFLAG(C); + cbits = acu & 0x80; + goto cbshflg1; + + case 0x18: /* RR */ + temp = (acu >> 1) | (TSTFLAG(C) << 7); + cbits = acu & 1; + goto cbshflg1; + + case 0x20: /* SLA */ + temp = acu << 1; + cbits = acu & 0x80; + goto cbshflg1; + + case 0x28: /* SRA */ + temp = (acu >> 1) | (acu & 0x80); + cbits = acu & 1; + goto cbshflg1; + + case 0x30: /* SLIA */ + temp = (acu << 1) | 1; + cbits = acu & 0x80; + goto cbshflg1; + + case 0x38: /* SRL */ + temp = acu >> 1; + cbits = acu & 1; + cbshflg1: + AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; + /* !!cbits == 0 if cbits == 0 !!cbits == 1 if cbits > 0 */ + } + break; + + case 0x40: /* BIT */ + if (tStateModifier) + tStates -= 3; + if (acu & (1 << ((op >> 3) & 7))) + AF = (AF & ~0xfe) | 0x10 | (((op & 0x38) == 0x38) << 7); + else + AF = (AF & ~0xfe) | 0x54; + if ((op & 7) != 6) + AF |= (acu & 0x28); + temp = acu; + break; + + case 0x80: /* RES */ + temp = acu & ~(1 << ((op >> 3) & 7)); + break; + + case 0xc0: /* SET */ + temp = acu | (1 << ((op >> 3) & 7)); + break; + } + + switch (op & 7) { + + case 0: + SET_HIGH_REGISTER(BC, temp); + break; + + case 1: + SET_LOW_REGISTER(BC, temp); + break; + + case 2: + SET_HIGH_REGISTER(DE, temp); + break; + + case 3: + SET_LOW_REGISTER(DE, temp); + break; + + case 4: + SET_HIGH_REGISTER(HL, temp); + break; + + case 5: + SET_LOW_REGISTER(HL, temp); + break; + + case 6: + s100_bus_memw(adr, temp); + break; + + case 7: + SET_HIGH_REGISTER(AF, temp); + break; + } + break; + + case 0xcc: /* CALL Z,nnnn */ + CALLC(TSTFLAG(Z)); /* also updates tStates */ + break; + + case 0xcd: /* CALL nnnn */ + CALLC(1); /* also updates tStates */ + break; + + case 0xce: /* ADC A,nn */ + tStates += 7; /* ACI nn 7 */ + temp = RAM_PP(PC); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = addTable[sum] | cbitsTable[cbits] | (SET_PV); + break; + + case 0xcf: /* RST 8 */ + tStates += 11; /* RST 1 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 8; + break; + + case 0xd0: /* RET NC */ + if (TSTFLAG(C)) { + tStates += 5; /* RNC 5 */ + } else { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RNC 11 */ + } + break; + + case 0xd1: /* POP DE */ + tStates += 10; /* POP D 10 */ + CHECK_BREAK_WORD(SP); + POP(DE); + break; + + case 0xd2: /* JP NC,nnnn */ + JPC(!TSTFLAG(C)); /* also updates tStates */ + break; + + case 0xd3: /* OUT (nn),A */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 :11); /* OUT nn 10 */ + s100_bus_out(RAM_PP(PC), HIGH_REGISTER(AF)); + break; + + case 0xd4: /* CALL NC,nnnn */ + CALLC(!TSTFLAG(C)); /* also updates tStates */ + break; + + case 0xd5: /* PUSH DE */ + tStates += 11; /* PUSH D 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(DE); + break; + + case 0xd6: /* SUB nn */ + tStates += 7; /* SUI nn 7 */ + temp = RAM_PP(PC); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xd7: /* RST 10H */ + tStates += 11; /* RST 2 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0x10; + break; + + case 0xd8: /* RET C */ + if (TSTFLAG(C)) { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RC 11 */ + } else { + tStates += 5; /* RC 5 */ + } + break; + + case 0xd9: /* EXX */ + if (z80_chiptype == CHIP_TYPE_8080) { + if (z80_unit.flags & UNIT_Z80_OPSTOP) { + reason = STOP_OPCODE; + goto end_decode; + } + else { + tStates += 10; /* RET 10 */ + CHECK_BREAK_WORD(SP); + POP(PC); + break; + } + } + tStates += 4; + temp = BC; + BC = BC1_S; + BC1_S = temp; + temp = DE; + DE = DE1_S; + DE1_S = temp; + temp = HL; + HL = HL1_S; + HL1_S = temp; + break; + + case 0xda: /* JP C,nnnn */ + JPC(TSTFLAG(C)); /* also updates tStates */ + break; + + case 0xdb: /* IN A,(nn) */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 10 : 11); /* IN nn 10 */ + SET_HIGH_REGISTER(AF, s100_bus_in(RAM_PP(PC))); + break; + + case 0xdc: /* CALL C,nnnn */ + CALLC(TSTFLAG(C)); /* also updates tStates */ + break; + + case 0xdd: /* DD prefix */ + if (z80_chiptype == CHIP_TYPE_8080) { + if (z80_unit.flags & UNIT_Z80_OPSTOP) { + reason = STOP_OPCODE; + goto end_decode; + } else { + CALLC(1); /* also updates tStates */ + break; + } + } + + INCR(1); + switch (RAM_PP(PC)) { + + case 0x09: /* ADD IX,BC */ + tStates += 15; + IX &= ADDRMASK; + BC &= ADDRMASK; + sum = IX + BC; + AF = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(IX ^ BC ^ sum) >> 8]; + IX = sum; + break; + + case 0x19: /* ADD IX,DE */ + tStates += 15; + IX &= ADDRMASK; + DE &= ADDRMASK; + sum = IX + DE; + AF = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(IX ^ DE ^ sum) >> 8]; + IX = sum; + break; + + case 0x21: /* LD IX,nnnn */ + tStates += 14; + IX = GET_WORD(PC); + PC += 2; + break; + + case 0x22: /* LD (nnnn),IX */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, IX); + PC += 2; + break; + + case 0x23: /* INC IX */ + tStates += 10; + ++IX; + break; + + case 0x24: /* INC IXH */ + tStates += 9; + IX += 0x100; + AF = (AF & ~0xfe) | incZ80Table[HIGH_REGISTER(IX)]; + break; + + case 0x25: /* DEC IXH */ + tStates += 9; + IX -= 0x100; + AF = (AF & ~0xfe) | decZ80Table[HIGH_REGISTER(IX)]; + break; + + case 0x26: /* LD IXH,nn */ + tStates += 9; + SET_HIGH_REGISTER(IX, RAM_PP(PC)); + break; + + case 0x29: /* ADD IX,IX */ + tStates += 15; + IX &= ADDRMASK; + sum = IX + IX; + AF = (AF & ~0x3b) | cbitsDup16Table[sum >> 8]; + IX = sum; + break; + + case 0x2a: /* LD IX,(nnnn) */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + IX = GET_WORD(temp); + PC += 2; + break; + + case 0x2b: /* DEC IX */ + tStates += 10; + --IX; + break; + + case 0x2c: /* INC IXL */ + tStates += 9; + temp = LOW_REGISTER(IX) + 1; + SET_LOW_REGISTER(IX, temp); + AF = (AF & ~0xfe) | incZ80Table[temp]; + break; + + case 0x2d: /* DEC IXL */ + tStates += 9; + temp = LOW_REGISTER(IX) - 1; + SET_LOW_REGISTER(IX, temp); + AF = (AF & ~0xfe) | decZ80Table[temp & 0xff]; + break; + + case 0x2e: /* LD IXL,nn */ + tStates += 9; + SET_LOW_REGISTER(IX, RAM_PP(PC)); + break; + + case 0x34: /* INC (IX+dd) */ + tStates += 23; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr) + 1; + s100_bus_memw(adr, temp); + AF = (AF & ~0xfe) | incZ80Table[temp]; + break; + + case 0x35: /* DEC (IX+dd) */ + tStates += 23; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr) - 1; + s100_bus_memw(adr, temp); + AF = (AF & ~0xfe) | decZ80Table[temp & 0xff]; + break; + + case 0x36: /* LD (IX+dd),nn */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, RAM_PP(PC)); + break; + + case 0x39: /* ADD IX,SP */ + tStates += 15; + IX &= ADDRMASK; + SP &= ADDRMASK; + sum = IX + SP; + AF = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(IX ^ SP ^ sum) >> 8]; + IX = sum; + break; + + case 0x44: /* LD B,IXH */ + tStates += 9; + SET_HIGH_REGISTER(BC, HIGH_REGISTER(IX)); + break; + + case 0x45: /* LD B,IXL */ + tStates += 9; + SET_HIGH_REGISTER(BC, LOW_REGISTER(IX)); + break; + + case 0x46: /* LD B,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(BC, s100_bus_memr(adr)); + break; + + case 0x4c: /* LD C,IXH */ + tStates += 9; + SET_LOW_REGISTER(BC, HIGH_REGISTER(IX)); + break; + + case 0x4d: /* LD C,IXL */ + tStates += 9; + SET_LOW_REGISTER(BC, LOW_REGISTER(IX)); + break; + + case 0x4e: /* LD C,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_LOW_REGISTER(BC, s100_bus_memr(adr)); + break; + + case 0x54: /* LD D,IXH */ + tStates += 9; + SET_HIGH_REGISTER(DE, HIGH_REGISTER(IX)); + break; + + case 0x55: /* LD D,IXL */ + tStates += 9; + SET_HIGH_REGISTER(DE, LOW_REGISTER(IX)); + break; + + case 0x56: /* LD D,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(DE, s100_bus_memr(adr)); + break; + + case 0x5c: /* LD E,IXH */ + tStates += 9; + SET_LOW_REGISTER(DE, HIGH_REGISTER(IX)); + break; + + case 0x5d: /* LD E,IXL */ + tStates += 9; + SET_LOW_REGISTER(DE, LOW_REGISTER(IX)); + break; + + case 0x5e: /* LD E,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_LOW_REGISTER(DE, s100_bus_memr(adr)); + break; + + case 0x60: /* LD IXH,B */ + tStates += 9; + SET_HIGH_REGISTER(IX, HIGH_REGISTER(BC)); + break; + + case 0x61: /* LD IXH,C */ + tStates += 9; + SET_HIGH_REGISTER(IX, LOW_REGISTER(BC)); + break; + + case 0x62: /* LD IXH,D */ + tStates += 9; + SET_HIGH_REGISTER(IX, HIGH_REGISTER(DE)); + break; + + case 0x63: /* LD IXH,E */ + tStates += 9; + SET_HIGH_REGISTER(IX, LOW_REGISTER(DE)); + break; + + case 0x64: /* LD IXH,IXH */ + tStates += 9; + break; + + case 0x65: /* LD IXH,IXL */ + tStates += 9; + SET_HIGH_REGISTER(IX, LOW_REGISTER(IX)); + break; + + case 0x66: /* LD H,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(HL, s100_bus_memr(adr)); + break; + + case 0x67: /* LD IXH,A */ + tStates += 9; + SET_HIGH_REGISTER(IX, HIGH_REGISTER(AF)); + break; + + case 0x68: /* LD IXL,B */ + tStates += 9; + SET_LOW_REGISTER(IX, HIGH_REGISTER(BC)); + break; + + case 0x69: /* LD IXL,C */ + tStates += 9; + SET_LOW_REGISTER(IX, LOW_REGISTER(BC)); + break; + + case 0x6a: /* LD IXL,D */ + tStates += 9; + SET_LOW_REGISTER(IX, HIGH_REGISTER(DE)); + break; + + case 0x6b: /* LD IXL,E */ + tStates += 9; + SET_LOW_REGISTER(IX, LOW_REGISTER(DE)); + break; + + case 0x6c: /* LD IXL,IXH */ + tStates += 9; + SET_LOW_REGISTER(IX, HIGH_REGISTER(IX)); + break; + + case 0x6d: /* LD IXL,IXL */ + tStates += 9; + break; + + case 0x6e: /* LD L,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_LOW_REGISTER(HL, s100_bus_memr(adr)); + break; + + case 0x6f: /* LD IXL,A */ + tStates += 9; + SET_LOW_REGISTER(IX, HIGH_REGISTER(AF)); + break; + + case 0x70: /* LD (IX+dd),B */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(BC)); + break; + + case 0x71: /* LD (IX+dd),C */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, LOW_REGISTER(BC)); + break; + + case 0x72: /* LD (IX+dd),D */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(DE)); + break; + + case 0x73: /* LD (IX+dd),E */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, LOW_REGISTER(DE)); + break; + + case 0x74: /* LD (IX+dd),H */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(HL)); + break; + + case 0x75: /* LD (IX+dd),L */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, LOW_REGISTER(HL)); + break; + + case 0x77: /* LD (IX+dd),A */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(AF)); + break; + + case 0x7c: /* LD A,IXH */ + tStates += 9; + SET_HIGH_REGISTER(AF, HIGH_REGISTER(IX)); + break; + + case 0x7d: /* LD A,IXL */ + tStates += 9; + SET_HIGH_REGISTER(AF, LOW_REGISTER(IX)); + break; + + case 0x7e: /* LD A,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(AF, s100_bus_memr(adr)); + break; + + case 0x84: /* ADD A,IXH */ + tStates += 9; + temp = HIGH_REGISTER(IX); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x85: /* ADD A,IXL */ + tStates += 9; + temp = LOW_REGISTER(IX); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x86: /* ADD A,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x8c: /* ADC A,IXH */ + tStates += 9; + temp = HIGH_REGISTER(IX); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x8d: /* ADC A,IXL */ + tStates += 9; + temp = LOW_REGISTER(IX); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x8e: /* ADC A,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x96: /* SUB (IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0x94: /* SUB IXH */ + SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ + + case 0x9c: /* SBC A,IXH */ + tStates += 9; + temp = HIGH_REGISTER(IX); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0x95: /* SUB IXL */ + SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ + + case 0x9d: /* SBC A,IXL */ + tStates += 9; + temp = LOW_REGISTER(IX); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0x9e: /* SBC A,(IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xa4: /* AND IXH */ + tStates += 9; + AF = andTable[((AF & IX) >> 8) & 0xff]; + break; + + case 0xa5: /* AND IXL */ + tStates += 9; + AF = andTable[((AF >> 8) & IX) & 0xff]; + break; + + case 0xa6: /* AND (IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + AF = andTable[((AF >> 8) & s100_bus_memr(adr)) & 0xff]; + break; + + case 0xac: /* XOR IXH */ + tStates += 9; + AF = xororTable[((AF ^ IX) >> 8) & 0xff]; + break; + + case 0xad: /* XOR IXL */ + tStates += 9; + AF = xororTable[((AF >> 8) ^ IX) & 0xff]; + break; + + case 0xae: /* XOR (IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + AF = xororTable[((AF >> 8) ^ s100_bus_memr(adr)) & 0xff]; + break; + + case 0xb4: /* OR IXH */ + tStates += 9; + AF = xororTable[((AF | IX) >> 8) & 0xff]; + break; + + case 0xb5: /* OR IXL */ + tStates += 9; + AF = xororTable[((AF >> 8) | IX) & 0xff]; + break; + + case 0xb6: /* OR (IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + AF = xororTable[((AF >> 8) | s100_bus_memr(adr)) & 0xff]; + break; + + case 0xbc: /* CP IXH */ + tStates += 9; + temp = HIGH_REGISTER(IX); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xbd: /* CP IXL */ + tStates += 9; + temp = LOW_REGISTER(IX); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xbe: /* CP (IX+dd) */ + tStates += 19; + adr = IX + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xcb: /* CB prefix */ + adr = IX + (int8) RAM_PP(PC); + switch ((op = s100_bus_memr(PC)) & 7) { + + case 0: + ++PC; + acu = HIGH_REGISTER(BC); + break; + + case 1: + ++PC; + acu = LOW_REGISTER(BC); + break; + + case 2: + ++PC; + acu = HIGH_REGISTER(DE); + break; + + case 3: + ++PC; + acu = LOW_REGISTER(DE); + break; + + case 4: + ++PC; + acu = HIGH_REGISTER(HL); + break; + + case 5: + ++PC; + acu = LOW_REGISTER(HL); + break; + + case 6: + CHECK_BREAK_BYTE(adr); + ++PC; + acu = s100_bus_memr(adr); + break; + + case 7: + ++PC; + acu = HIGH_REGISTER(AF); + break; + } + switch (op & 0xc0) { + + case 0x00: /* shift/rotate */ + tStates += 23; + switch (op & 0x38) { + + case 0x00: /* RLC */ + temp = (acu << 1) | (acu >> 7); + cbits = temp & 1; + goto cbshflg2; + + case 0x08: /* RRC */ + temp = (acu >> 1) | (acu << 7); + cbits = temp & 0x80; + goto cbshflg2; + + case 0x10: /* RL */ + temp = (acu << 1) | TSTFLAG(C); + cbits = acu & 0x80; + goto cbshflg2; + + case 0x18: /* RR */ + temp = (acu >> 1) | (TSTFLAG(C) << 7); + cbits = acu & 1; + goto cbshflg2; + + case 0x20: /* SLA */ + temp = acu << 1; + cbits = acu & 0x80; + goto cbshflg2; + + case 0x28: /* SRA */ + temp = (acu >> 1) | (acu & 0x80); + cbits = acu & 1; + goto cbshflg2; + + case 0x30: /* SLIA */ + temp = (acu << 1) | 1; + cbits = acu & 0x80; + goto cbshflg2; + + case 0x38: /* SRL */ + temp = acu >> 1; + cbits = acu & 1; + cbshflg2: + AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; + /* !!cbits == 0 if cbits == 0 !!cbits == 1 if cbits > 0 */ + } + break; + + case 0x40: /* BIT */ + tStates += 20; + if (acu & (1 << ((op >> 3) & 7))) + AF = (AF & ~0xfe) | 0x10 | (((op & 0x38) == 0x38) << 7); + else + AF = (AF & ~0xfe) | 0x54; + if ((op & 7) != 6) + AF |= (acu & 0x28); + temp = acu; + break; + + case 0x80: /* RES */ + tStates += 23; + temp = acu & ~(1 << ((op >> 3) & 7)); + break; + + case 0xc0: /* SET */ + tStates += 23; + temp = acu | (1 << ((op >> 3) & 7)); + break; + } + switch (op & 7) { + + case 0: + SET_HIGH_REGISTER(BC, temp); + break; + + case 1: + SET_LOW_REGISTER(BC, temp); + break; + + case 2: + SET_HIGH_REGISTER(DE, temp); + break; + + case 3: + SET_LOW_REGISTER(DE, temp); + break; + + case 4: + SET_HIGH_REGISTER(HL, temp); + break; + + case 5: + SET_LOW_REGISTER(HL, temp); + break; + + case 6: + s100_bus_memw(adr, temp); + break; + + case 7: + SET_HIGH_REGISTER(AF, temp); + break; + } + break; + + case 0xe1: /* POP IX */ + tStates += 14; + CHECK_BREAK_WORD(SP); + POP(IX); + break; + + case 0xe3: /* EX (SP),IX */ + tStates += 23; + CHECK_BREAK_WORD(SP); + temp = IX; + POP(IX); + PUSH(temp); + break; + + case 0xe5: /* PUSH IX */ + tStates += 15; + CHECK_BREAK_WORD(SP - 2); + PUSH(IX); + break; + + case 0xe9: /* JP (IX) */ + tStates += 8; + PC = IX; + break; + + case 0xf9: /* LD SP,IX */ + tStates += 10; + SP = IX; + break; + + default: /* ignore DD */ + CHECK_CPU_Z80; + PC--; + } + break; + + case 0xde: /* SBC A,nn */ + tStates += 7; /* SBI nn 7 */ + temp = RAM_PP(PC); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + cbits = acu ^ temp ^ sum; + AF = subTable[sum & 0xff] | cbitsTable[cbits & 0x1ff] | (SET_PV); + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xdf: /* RST 18H */ + tStates += 11; /* RST 3 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0x18; + break; + + case 0xe0: /* RET PO */ + if (TSTFLAG(P)) { + tStates += 5; /* RPO 5 */ + } else { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RPO 11 */ + } + break; + + case 0xe1: /* POP HL */ + tStates += 10; /* POP H 10 */ + CHECK_BREAK_WORD(SP); + POP(HL); + break; + + case 0xe2: /* JP PO,nnnn */ + JPC(!TSTFLAG(P)); /* also updates tStates */ + break; + + case 0xe3: /* EX (SP),HL */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 18 : 19); /* XTHL 18 */ + CHECK_BREAK_WORD(SP); + temp = HL; + POP(HL); + PUSH(temp); + break; + + case 0xe4: /* CALL PO,nnnn */ + CALLC(!TSTFLAG(P)); /* also updates tStates */ + break; + + case 0xe5: /* PUSH HL */ + tStates += 11; /* PUSH H 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(HL); + break; + + case 0xe6: /* AND nn */ + tStates += 7; /* ANI nn 7 */ + temp = RAM_PP(PC); + acu = andTable[((AF >> 8) & temp) & 0xff]; + if (z80_chiptype == CHIP_TYPE_8080 && (((AF >> 8) | temp) & 0x08) == 0) + acu &= ~0x10; + AF = acu; + break; + + case 0xe7: /* RST 20H */ + tStates += 11; /* RST 4 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0x20; + break; + + case 0xe8: /* RET PE */ + if (TSTFLAG(P)) { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RPE 11 */ + } else { + tStates += 5; /* RPE 5 */ + } + break; + + case 0xe9: /* JP (HL) */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* PCHL 5 */ + PC = HL; + break; + + case 0xea: /* JP PE,nnnn */ + JPC(TSTFLAG(P)); /* also updates tStates */ + break; + + case 0xeb: /* EX DE,HL */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 4); /* XCHG 5 */ + temp = HL; + HL = DE; + DE = temp; + break; + + case 0xec: /* CALL PE,nnnn */ + CALLC(TSTFLAG(P)); /* also updates tStates */ + break; + + case 0xed: /* ED prefix */ + if (z80_chiptype == CHIP_TYPE_8080) { + if (z80_unit.flags & UNIT_Z80_OPSTOP) { + reason = STOP_OPCODE; + goto end_decode; + } else { + CALLC(1); /* also updates tStates */ + break; + } + } + + INCR(1); + switch (RAM_PP(PC)) { + + case 0x40: /* IN B,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_HIGH_REGISTER(BC, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x41: /* OUT (C),B */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), HIGH_REGISTER(BC)); + break; + + case 0x42: /* SBC HL,BC */ + tStates += 15; + HL &= ADDRMASK; + BC &= ADDRMASK; + sum = HL - BC - TSTFLAG(C); + AF = (AF & ~0xff) | ((sum >> 8) & 0xa8) | (((sum & ADDRMASK) == 0) << 6) | + cbits2Z80Table[((HL ^ BC ^ sum) >> 8) & 0x1ff]; + HL = sum; + break; + + case 0x43: /* LD (nnnn),BC */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, BC); + PC += 2; + break; + + case 0x44: /* NEG */ + + case 0x4C: /* NEG, unofficial */ + + case 0x54: /* NEG, unofficial */ + + case 0x5C: /* NEG, unofficial */ + + case 0x64: /* NEG, unofficial */ + + case 0x6C: /* NEG, unofficial */ + + case 0x74: /* NEG, unofficial */ + + case 0x7C: /* NEG, unofficial */ + tStates += 8; + temp = HIGH_REGISTER(AF); + AF = ((~(AF & 0xff00) + 1) & 0xff00); /* AF = (-(AF & 0xff00) & 0xff00); */ + AF |= ((AF >> 8) & 0xa8) | (((AF & 0xff00) == 0) << 6) | negTable[temp]; + break; + + case 0x45: /* RETN */ + + case 0x55: /* RETN, unofficial */ + + case 0x5D: /* RETN, unofficial */ + + case 0x65: /* RETN, unofficial */ + + case 0x6D: /* RETN, unofficial */ + + case 0x75: /* RETN, unofficial */ + + case 0x7D: /* RETN, unofficial */ + tStates += 14; + IFF_S |= IFF_S >> 1; + CHECK_BREAK_WORD(SP); + POP(PC); + break; + + case 0x46: /* IM 0 */ + IM_S = 0; + tStates += 8; /* interrupt mode 0 */ + break; + + case 0x47: /* LD I,A */ + tStates += 9; + IR_S = (IR_S & 0xff) | (AF & ~0xff); + break; + + case 0x48: /* IN C,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_LOW_REGISTER(BC, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x49: /* OUT (C),C */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), LOW_REGISTER(BC)); + break; + + case 0x4a: /* ADC HL,BC */ + tStates += 15; + HL &= ADDRMASK; + BC &= ADDRMASK; + sum = HL + BC + TSTFLAG(C); + AF = (AF & ~0xff) | ((sum >> 8) & 0xa8) | (((sum & ADDRMASK) == 0) << 6) | + cbitsZ80Table[(HL ^ BC ^ sum) >> 8]; + HL = sum; + break; + + case 0x4b: /* LD BC,(nnnn) */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + BC = GET_WORD(temp); + PC += 2; + break; + + case 0x4d: /* RETI */ + tStates += 14; + IFF_S |= IFF_S >> 1; + CHECK_BREAK_WORD(SP); + POP(PC); + break; + + case 0x4f: /* LD R,A */ + tStates += 9; + IR_S = (IR_S & ~0xff) | ((AF >> 8) & 0xff); + break; + + case 0x50: /* IN D,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_HIGH_REGISTER(DE, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x51: /* OUT (C),D */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), HIGH_REGISTER(DE)); + break; + + case 0x52: /* SBC HL,DE */ + tStates += 15; + HL &= ADDRMASK; + DE &= ADDRMASK; + sum = HL - DE - TSTFLAG(C); + AF = (AF & ~0xff) | ((sum >> 8) & 0xa8) | (((sum & ADDRMASK) == 0) << 6) | + cbits2Z80Table[((HL ^ DE ^ sum) >> 8) & 0x1ff]; + HL = sum; + break; + + case 0x53: /* LD (nnnn),DE */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, DE); + PC += 2; + break; + + case 0x56: /* IM 1 */ + IM_S = 1; + tStates += 8; /* interrupt mode 1 */ + break; + + case 0x57: /* LD A,I */ + tStates += 9; + AF = (AF & 0x29) | (IR_S & ~0xff) | ((IR_S >> 8) & 0x80) | (((IR_S & ~0xff) == 0) << 6) | ((IFF_S & IFF2) << 1); + break; + + case 0x58: /* IN E,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_LOW_REGISTER(DE, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x59: /* OUT (C),E */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), LOW_REGISTER(DE)); + break; + + case 0x5a: /* ADC HL,DE */ + tStates += 15; + HL &= ADDRMASK; + DE &= ADDRMASK; + sum = HL + DE + TSTFLAG(C); + AF = (AF & ~0xff) | ((sum >> 8) & 0xa8) | (((sum & ADDRMASK) == 0) << 6) | + cbitsZ80Table[(HL ^ DE ^ sum) >> 8]; + HL = sum; + break; + + case 0x5b: /* LD DE,(nnnn) */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + DE = GET_WORD(temp); + PC += 2; + break; + + case 0x5e: /* IM 2 */ + IM_S = 2; + tStates += 8; /* interrupt mode 2 */ + break; + + case 0x5f: /* LD A,R */ + tStates += 9; + AF = (AF & 0x29) | ((IR_S & 0xff) << 8) | (IR_S & 0x80) | + (((IR_S & 0xff) == 0) << 6) | ((IFF_S & IFF2) << 1); + break; + + case 0x60: /* IN H,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_HIGH_REGISTER(HL, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x61: /* OUT (C),H */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), HIGH_REGISTER(HL)); + break; + + case 0x62: /* SBC HL,HL */ + tStates += 15; + HL &= ADDRMASK; + sum = HL - HL - TSTFLAG(C); + AF = (AF & ~0xff) | (((sum & ADDRMASK) == 0) << 6) | + cbits2Z80DupTable[(sum >> 8) & 0x1ff]; + HL = sum; + break; + + case 0x63: /* LD (nnnn),HL */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, HL); + PC += 2; + break; + + case 0x67: /* RRD */ + tStates += 18; + temp = s100_bus_memr(HL); + acu = HIGH_REGISTER(AF); + s100_bus_memw(HL, HIGH_DIGIT(temp) | (LOW_DIGIT(acu) << 4)); + AF = rrdrldTable[(acu & 0xf0) | LOW_DIGIT(temp)] | (AF & 1); + break; + + case 0x68: /* IN L,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_LOW_REGISTER(HL, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x69: /* OUT (C),L */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), LOW_REGISTER(HL)); + break; + + case 0x6a: /* ADC HL,HL */ + tStates += 15; + HL &= ADDRMASK; + sum = HL + HL + TSTFLAG(C); + AF = (AF & ~0xff) | (((sum & ADDRMASK) == 0) << 6) | + cbitsZ80DupTable[sum >> 8]; + HL = sum; + break; + + case 0x6b: /* LD HL,(nnnn) */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + HL = GET_WORD(temp); + PC += 2; + break; + + case 0x6f: /* RLD */ + tStates += 18; + temp = s100_bus_memr(HL); + acu = HIGH_REGISTER(AF); + s100_bus_memw(HL, (LOW_DIGIT(temp) << 4) | LOW_DIGIT(acu)); + AF = rrdrldTable[(acu & 0xf0) | HIGH_DIGIT(temp)] | (AF & 1); + break; + + case 0x70: /* IN (C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_LOW_REGISTER(temp, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x71: /* OUT (C),0 */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), 0); + break; + + case 0x72: /* SBC HL,SP */ + tStates += 15; + HL &= ADDRMASK; + SP &= ADDRMASK; + sum = HL - SP - TSTFLAG(C); + AF = (AF & ~0xff) | ((sum >> 8) & 0xa8) | (((sum & ADDRMASK) == 0) << 6) | + cbits2Z80Table[((HL ^ SP ^ sum) >> 8) & 0x1ff]; + HL = sum; + break; + + case 0x73: /* LD (nnnn),SP */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, SP); + PC += 2; + break; + + case 0x78: /* IN A,(C) */ + tStates += 12; + temp = s100_bus_in(LOW_REGISTER(BC)); + SET_HIGH_REGISTER(AF, temp); + AF = (AF & ~0xfe) | rotateShiftTable[temp & 0xff]; + break; + + case 0x79: /* OUT (C),A */ + tStates += 12; + s100_bus_out(LOW_REGISTER(BC), HIGH_REGISTER(AF)); + break; + + case 0x7a: /* ADC HL,SP */ + tStates += 15; + HL &= ADDRMASK; + SP &= ADDRMASK; + sum = HL + SP + TSTFLAG(C); + AF = (AF & ~0xff) | ((sum >> 8) & 0xa8) | (((sum & ADDRMASK) == 0) << 6) | + cbitsZ80Table[(HL ^ SP ^ sum) >> 8]; + HL = sum; + break; + + case 0x7b: /* LD SP,(nnnn) */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + SP = GET_WORD(temp); + PC += 2; + break; + + case 0xa0: /* LDI */ + tStates += 16; + CHECK_BREAK_TWO_BYTES(HL, DE); + acu = RAM_PP(HL); + PUT_BYTE_PP(DE, acu); + acu += HIGH_REGISTER(AF); + AF = (AF & ~0x3e) | (acu & 8) | ((acu & 2) << 4) | + (((--BC & ADDRMASK) != 0) << 2); + break; + + case 0xa1: /* CPI */ + tStates += 16; + CHECK_BREAK_BYTE(HL); + acu = HIGH_REGISTER(AF); + temp = RAM_PP(HL); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xfe) | (sum & 0x80) | (!(sum & 0xff) << 6) | + (((sum - ((cbits & 16) >> 4)) & 2) << 4) | (cbits & 16) | + ((sum - ((cbits >> 4) & 1)) & 8) | + ((--BC & ADDRMASK) != 0) << 2 | 2; + if ((sum & 15) == 8 && (cbits & 16) != 0) + AF &= ~8; + break; + +/* SF, ZF, YF, XF flags are affected by decreasing register B, as in DEC B. + NF flag A is copy of bit 7 of the value read from or written to an I/O port. + INI/INIR/IND/INDR use the C flag in stead of the L register. There is a + catch though, because not the value of C is used, but C + 1 if it's INI/INIR or + C - 1 if it's IND/INDR. So, first of all INI/INIR: + HF and CF Both set if ((HL) + ((C + 1) & 255) > 255) + PF The parity of (((HL) + ((C + 1) & 255)) & 7) xor B) */ + case 0xa2: /* INI */ + tStates += 16; + CHECK_BREAK_BYTE(HL); + acu = s100_bus_in(LOW_REGISTER(BC)); + s100_bus_memw(HL, acu); + ++HL; + BC -= 0x100; + INOUTFLAGS_NONZERO((LOW_REGISTER(BC) + 1) & 0xff); + break; + +/* SF, ZF, YF, XF flags are affected by decreasing register B, as in DEC B. + NF flag A is copy of bit 7 of the value read from or written to an I/O port. + And now the for OUTI/OTIR/OUTD/OTDR instructions. Take state of the L + after the increment or decrement of HL; add the value written to the I/O port + to; call that k for now. If k > 255, then the CF and HF flags are set. The PF + flags is set like the parity of k bitwise and'ed with 7, bitwise xor'ed with B. + HF and CF Both set if ((HL) + L > 255) + PF The parity of ((((HL) + L) & 7) xor B) */ + case 0xa3: /* OUTI */ + tStates += 16; + CHECK_BREAK_BYTE(HL); + acu = s100_bus_memr(HL); + s100_bus_out(LOW_REGISTER(BC), acu); + ++HL; + BC -= 0x100; + INOUTFLAGS_NONZERO(LOW_REGISTER(HL)); + break; + + case 0xa8: /* LDD */ + tStates += 16; + CHECK_BREAK_TWO_BYTES(HL, DE); + acu = RAM_MM(HL); + PUT_BYTE_MM(DE, acu); + acu += HIGH_REGISTER(AF); + AF = (AF & ~0x3e) | (acu & 8) | ((acu & 2) << 4) | + (((--BC & ADDRMASK) != 0) << 2); + break; + + case 0xa9: /* CPD */ + tStates += 16; + CHECK_BREAK_BYTE(HL); + acu = HIGH_REGISTER(AF); + temp = RAM_MM(HL); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xfe) | (sum & 0x80) | (!(sum & 0xff) << 6) | + (((sum - ((cbits & 16) >> 4)) & 2) << 4) | (cbits & 16) | + ((sum - ((cbits >> 4) & 1)) & 8) | + ((--BC & ADDRMASK) != 0) << 2 | 2; + if ((sum & 15) == 8 && (cbits & 16) != 0) + AF &= ~8; + break; + +/* SF, ZF, YF, XF flags are affected by decreasing register B, as in DEC B. + NF flag A is copy of bit 7 of the value read from or written to an I/O port. + INI/INIR/IND/INDR use the C flag in stead of the L register. There is a + catch though, because not the value of C is used, but C + 1 if it's INI/INIR or + C - 1 if it's IND/INDR. And last IND/INDR: + HF and CF Both set if ((HL) + ((C - 1) & 255) > 255) + PF The parity of (((HL) + ((C - 1) & 255)) & 7) xor B) */ + case 0xaa: /* IND */ + tStates += 16; + CHECK_BREAK_BYTE(HL); + acu = s100_bus_in(LOW_REGISTER(BC)); + s100_bus_memw(HL, acu); + --HL; + BC -= 0x100; + INOUTFLAGS_NONZERO((LOW_REGISTER(BC) - 1) & 0xff); + break; + + case 0xab: /* OUTD */ + tStates += 16; + CHECK_BREAK_BYTE(HL); + acu = s100_bus_memr(HL); + s100_bus_out(LOW_REGISTER(BC), acu); + --HL; + BC -= 0x100; + INOUTFLAGS_NONZERO(LOW_REGISTER(HL)); + break; + + case 0xb0: /* LDIR */ + tStates -= 5; + BC &= ADDRMASK; + if (BC == 0) + BC = 0x10000; + do { + tStates += 21; + INCR(2); + CHECK_BREAK_TWO_BYTES(HL, DE); + acu = RAM_PP(HL); + PUT_BYTE_PP(DE, acu); + } while (--BC); + acu += HIGH_REGISTER(AF); + AF = (AF & ~0x3e) | (acu & 8) | ((acu & 2) << 4); + break; + + case 0xb1: /* CPIR */ + tStates -= 5; + acu = HIGH_REGISTER(AF); + BC &= ADDRMASK; + if (BC == 0) + BC = 0x10000; + do { + tStates += 21; + INCR(1); + CHECK_BREAK_BYTE(HL); + temp = RAM_PP(HL); + op = --BC != 0; + sum = acu - temp; + } while (op && sum != 0); + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xfe) | (sum & 0x80) | (!(sum & 0xff) << 6) | + (((sum - ((cbits & 16) >> 4)) & 2) << 4) | + (cbits & 16) | ((sum - ((cbits >> 4) & 1)) & 8) | + op << 2 | 2; + if ((sum & 15) == 8 && (cbits & 16) != 0) + AF &= ~8; + break; + + case 0xb2: /* INIR */ + tStates -= 5; + temp = HIGH_REGISTER(BC); + if (temp == 0) + temp = 0x100; + do { + tStates += 21; + INCR(1); + CHECK_BREAK_BYTE(HL); + acu = s100_bus_in(LOW_REGISTER(BC)); + s100_bus_memw(HL, acu); + ++HL; + } while (--temp); + SET_HIGH_REGISTER(BC, 0); + INOUTFLAGS_ZERO((LOW_REGISTER(BC) + 1) & 0xff); + break; + + case 0xb3: /* OTIR */ + tStates -= 5; + temp = HIGH_REGISTER(BC); + if (temp == 0) + temp = 0x100; + do { + tStates += 21; + INCR(1); + CHECK_BREAK_BYTE(HL); + acu = s100_bus_memr(HL); + s100_bus_out(LOW_REGISTER(BC), acu); + ++HL; + } while (--temp); + SET_HIGH_REGISTER(BC, 0); + INOUTFLAGS_ZERO(LOW_REGISTER(HL)); + break; + + case 0xb8: /* LDDR */ + tStates -= 5; + BC &= ADDRMASK; + if (BC == 0) + BC = 0x10000; + do { + tStates += 21; + INCR(2); + CHECK_BREAK_TWO_BYTES(HL, DE); + acu = RAM_MM(HL); + PUT_BYTE_MM(DE, acu); + } while (--BC); + acu += HIGH_REGISTER(AF); + AF = (AF & ~0x3e) | (acu & 8) | ((acu & 2) << 4); + break; + + case 0xb9: /* CPDR */ + tStates -= 5; + acu = HIGH_REGISTER(AF); + BC &= ADDRMASK; + if (BC == 0) + BC = 0x10000; + do { + tStates += 21; + INCR(1); + CHECK_BREAK_BYTE(HL); + temp = RAM_MM(HL); + op = --BC != 0; + sum = acu - temp; + } while (op && sum != 0); + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xfe) | (sum & 0x80) | (!(sum & 0xff) << 6) | + (((sum - ((cbits & 16) >> 4)) & 2) << 4) | + (cbits & 16) | ((sum - ((cbits >> 4) & 1)) & 8) | + op << 2 | 2; + if ((sum & 15) == 8 && (cbits & 16) != 0) + AF &= ~8; + break; + + case 0xba: /* INDR */ + tStates -= 5; + temp = HIGH_REGISTER(BC); + if (temp == 0) + temp = 0x100; + do { + tStates += 21; + INCR(1); + CHECK_BREAK_BYTE(HL); + acu = s100_bus_in(LOW_REGISTER(BC)); + s100_bus_memw(HL, acu); + --HL; + } while (--temp); + SET_HIGH_REGISTER(BC, 0); + INOUTFLAGS_ZERO((LOW_REGISTER(BC) - 1) & 0xff); + break; + + case 0xbb: /* OTDR */ + tStates -= 5; + temp = HIGH_REGISTER(BC); + if (temp == 0) + temp = 0x100; + do { + tStates += 21; + INCR(1); + CHECK_BREAK_BYTE(HL); + acu = s100_bus_memr(HL); + s100_bus_out(LOW_REGISTER(BC), acu); + --HL; + } while (--temp); + SET_HIGH_REGISTER(BC, 0); + INOUTFLAGS_ZERO(LOW_REGISTER(HL)); + break; + + default: /* ignore ED and following byte */ + CHECK_CPU_Z80; + } + break; + + case 0xee: /* XOR nn */ + tStates += 7; /* XRI nn 7 */ + AF = xororTable[((AF >> 8) ^ RAM_PP(PC)) & 0xff]; + break; + + case 0xef: /* RST 28H */ + tStates += 11; /* RST 5 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0x28; + break; + + case 0xf0: /* RET P */ + if (TSTFLAG(S)) { + tStates += 5; /* RP 5 */ + } else { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RP 11 */ + } + break; + + case 0xf1: /* POP AF */ + tStates += 10; /* POP PSW 10 */ + CHECK_BREAK_WORD(SP); + POP(AF); + break; + + case 0xf2: /* JP P,nnnn */ + JPC(!TSTFLAG(S)); /* also updates tStates */ + break; + + case 0xf3: /* DI */ + tStates += 4; /* DI 4 */ + IFF_S = 0; + break; + + case 0xf4: /* CALL P,nnnn */ + CALLC(!TSTFLAG(S)); /* also updates tStates */ + break; + + case 0xf5: /* PUSH AF */ + tStates += 11; /* PUSH PSW 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(z80_chiptype == CHIP_TYPE_8080 ? (AF & ~0x28) | 0x02 : AF); + break; + + case 0xf6: /* OR nn */ + tStates += 7; /* ORI nn 7 */ + AF = xororTable[((AF >> 8) | RAM_PP(PC)) & 0xff]; + break; + + case 0xf7: /* RST 30H */ + tStates += 11; /* RST 6 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0x30; + break; + + case 0xf8: /* RET M */ + if (TSTFLAG(S)) { + CHECK_BREAK_WORD(SP); + POP(PC); + tStates += 11; /* RM 11 */ + } else { + tStates += 5; /* RM 5 */ + } + break; + + case 0xf9: /* LD SP,HL */ + tStates += (z80_chiptype == CHIP_TYPE_8080 ? 5 : 6); /* SPHL 5 */ + SP = HL; + break; + + case 0xfa: /* JP M,nnnn */ + JPC(TSTFLAG(S)); /* also updates tStates */ + break; + + case 0xfb: /* EI */ + tStates += 4; /* EI 4 */ + IFF_S = (IFF1 | IFF2); + break; + + case 0xfc: /* CALL M,nnnn */ + CALLC(TSTFLAG(S)); /* also updates tStates */ + break; + + case 0xfd: /* FD prefix */ + if (z80_chiptype == CHIP_TYPE_8080) { + if (z80_unit.flags & UNIT_Z80_OPSTOP) { + reason = STOP_OPCODE; + goto end_decode; + } else { + CALLC(1); /* also updates tStates */ + break; + } + } + + INCR(1); + switch (RAM_PP(PC)) { + + case 0x09: /* ADD IY,BC */ + tStates += 15; + IY &= ADDRMASK; + BC &= ADDRMASK; + sum = IY + BC; + AF = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(IY ^ BC ^ sum) >> 8]; + IY = sum; + break; + + case 0x19: /* ADD IY,DE */ + tStates += 15; + IY &= ADDRMASK; + DE &= ADDRMASK; + sum = IY + DE; + AF = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(IY ^ DE ^ sum) >> 8]; + IY = sum; + break; + + case 0x21: /* LD IY,nnnn */ + tStates += 14; + IY = GET_WORD(PC); + PC += 2; + break; + + case 0x22: /* LD (nnnn),IY */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + PUT_WORD(temp, IY); + PC += 2; + break; + + case 0x23: /* INC IY */ + tStates += 10; + ++IY; + break; + + case 0x24: /* INC IYH */ + tStates += 9; + IY += 0x100; + AF = (AF & ~0xfe) | incZ80Table[HIGH_REGISTER(IY)]; + break; + + case 0x25: /* DEC IYH */ + tStates += 9; + IY -= 0x100; + AF = (AF & ~0xfe) | decZ80Table[HIGH_REGISTER(IY)]; + break; + + case 0x26: /* LD IYH,nn */ + tStates += 9; + SET_HIGH_REGISTER(IY, RAM_PP(PC)); + break; + + case 0x29: /* ADD IY,IY */ + tStates += 15; + IY &= ADDRMASK; + sum = IY + IY; + AF = (AF & ~0x3b) | cbitsDup16Table[sum >> 8]; + IY = sum; + break; + + case 0x2a: /* LD IY,(nnnn) */ + tStates += 20; + temp = GET_WORD(PC); + CHECK_BREAK_WORD(temp); + IY = GET_WORD(temp); + PC += 2; + break; + + case 0x2b: /* DEC IY */ + tStates += 10; + --IY; + break; + + case 0x2c: /* INC IYL */ + tStates += 9; + temp = LOW_REGISTER(IY) + 1; + SET_LOW_REGISTER(IY, temp); + AF = (AF & ~0xfe) | incZ80Table[temp]; + break; + + case 0x2d: /* DEC IYL */ + tStates += 9; + temp = LOW_REGISTER(IY) - 1; + SET_LOW_REGISTER(IY, temp); + AF = (AF & ~0xfe) | decZ80Table[temp & 0xff]; + break; + + case 0x2e: /* LD IYL,nn */ + tStates += 9; + SET_LOW_REGISTER(IY, RAM_PP(PC)); + break; + + case 0x34: /* INC (IY+dd) */ + tStates += 23; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr) + 1; + s100_bus_memw(adr, temp); + AF = (AF & ~0xfe) | incZ80Table[temp]; + break; + + case 0x35: /* DEC (IY+dd) */ + tStates += 23; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr) - 1; + s100_bus_memw(adr, temp); + AF = (AF & ~0xfe) | decZ80Table[temp & 0xff]; + break; + + case 0x36: /* LD (IY+dd),nn */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, RAM_PP(PC)); + break; + + case 0x39: /* ADD IY,SP */ + tStates += 15; + IY &= ADDRMASK; + SP &= ADDRMASK; + sum = IY + SP; + AF = (AF & ~0x3b) | ((sum >> 8) & 0x28) | cbitsTable[(IY ^ SP ^ sum) >> 8]; + IY = sum; + break; + + case 0x44: /* LD B,IYH */ + tStates += 9; + SET_HIGH_REGISTER(BC, HIGH_REGISTER(IY)); + break; + + case 0x45: /* LD B,IYL */ + tStates += 9; + SET_HIGH_REGISTER(BC, LOW_REGISTER(IY)); + break; + + case 0x46: /* LD B,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(BC, s100_bus_memr(adr)); + break; + + case 0x4c: /* LD C,IYH */ + tStates += 9; + SET_LOW_REGISTER(BC, HIGH_REGISTER(IY)); + break; + + case 0x4d: /* LD C,IYL */ + tStates += 9; + SET_LOW_REGISTER(BC, LOW_REGISTER(IY)); + break; + + case 0x4e: /* LD C,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_LOW_REGISTER(BC, s100_bus_memr(adr)); + break; + + case 0x54: /* LD D,IYH */ + tStates += 9; + SET_HIGH_REGISTER(DE, HIGH_REGISTER(IY)); + break; + + case 0x55: /* LD D,IYL */ + tStates += 9; + SET_HIGH_REGISTER(DE, LOW_REGISTER(IY)); + break; + + case 0x56: /* LD D,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(DE, s100_bus_memr(adr)); + break; + + case 0x5c: /* LD E,IYH */ + tStates += 9; + SET_LOW_REGISTER(DE, HIGH_REGISTER(IY)); + break; + + case 0x5d: /* LD E,IYL */ + tStates += 9; + SET_LOW_REGISTER(DE, LOW_REGISTER(IY)); + break; + + case 0x5e: /* LD E,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_LOW_REGISTER(DE, s100_bus_memr(adr)); + break; + + case 0x60: /* LD IYH,B */ + tStates += 9; + SET_HIGH_REGISTER(IY, HIGH_REGISTER(BC)); + break; + + case 0x61: /* LD IYH,C */ + tStates += 9; + SET_HIGH_REGISTER(IY, LOW_REGISTER(BC)); + break; + + case 0x62: /* LD IYH,D */ + tStates += 9; + SET_HIGH_REGISTER(IY, HIGH_REGISTER(DE)); + break; + + case 0x63: /* LD IYH,E */ + tStates += 9; + SET_HIGH_REGISTER(IY, LOW_REGISTER(DE)); + break; + + case 0x64: /* LD IYH,IYH */ + tStates += 9; + break; + + case 0x65: /* LD IYH,IYL */ + tStates += 9; + SET_HIGH_REGISTER(IY, LOW_REGISTER(IY)); + break; + + case 0x66: /* LD H,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(HL, s100_bus_memr(adr)); + break; + + case 0x67: /* LD IYH,A */ + tStates += 9; + SET_HIGH_REGISTER(IY, HIGH_REGISTER(AF)); + break; + + case 0x68: /* LD IYL,B */ + tStates += 9; + SET_LOW_REGISTER(IY, HIGH_REGISTER(BC)); + break; + + case 0x69: /* LD IYL,C */ + tStates += 9; + SET_LOW_REGISTER(IY, LOW_REGISTER(BC)); + break; + + case 0x6a: /* LD IYL,D */ + tStates += 9; + SET_LOW_REGISTER(IY, HIGH_REGISTER(DE)); + break; + + case 0x6b: /* LD IYL,E */ + tStates += 9; + SET_LOW_REGISTER(IY, LOW_REGISTER(DE)); + break; + + case 0x6c: /* LD IYL,IYH */ + tStates += 9; + SET_LOW_REGISTER(IY, HIGH_REGISTER(IY)); + break; + + case 0x6d: /* LD IYL,IYL */ + tStates += 9; + break; + + case 0x6e: /* LD L,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_LOW_REGISTER(HL, s100_bus_memr(adr)); + break; + + case 0x6f: /* LD IYL,A */ + tStates += 9; + SET_LOW_REGISTER(IY, HIGH_REGISTER(AF)); + break; + + case 0x70: /* LD (IY+dd),B */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(BC)); + break; + + case 0x71: /* LD (IY+dd),C */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, LOW_REGISTER(BC)); + break; + + case 0x72: /* LD (IY+dd),D */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(DE)); + break; + + case 0x73: /* LD (IY+dd),E */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, LOW_REGISTER(DE)); + break; + + case 0x74: /* LD (IY+dd),H */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(HL)); + break; + + case 0x75: /* LD (IY+dd),L */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, LOW_REGISTER(HL)); + break; + + case 0x77: /* LD (IY+dd),A */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + s100_bus_memw(adr, HIGH_REGISTER(AF)); + break; + + case 0x7c: /* LD A,IYH */ + tStates += 9; + SET_HIGH_REGISTER(AF, HIGH_REGISTER(IY)); + break; + + case 0x7d: /* LD A,IYL */ + tStates += 9; + SET_HIGH_REGISTER(AF, LOW_REGISTER(IY)); + break; + + case 0x7e: /* LD A,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + SET_HIGH_REGISTER(AF, s100_bus_memr(adr)); + break; + + case 0x84: /* ADD A,IYH */ + tStates += 9; + temp = HIGH_REGISTER(IY); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x85: /* ADD A,IYL */ + tStates += 9; + temp = LOW_REGISTER(IY); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x86: /* ADD A,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu + temp; + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x8c: /* ADC A,IYH */ + tStates += 9; + temp = HIGH_REGISTER(IY); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x8d: /* ADC A,IYL */ + tStates += 9; + temp = LOW_REGISTER(IY); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x8e: /* ADC A,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu + temp + TSTFLAG(C); + AF = addTable[sum] | cbitsZ80Table[acu ^ temp ^ sum]; + break; + + case 0x96: /* SUB (IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0x94: /* SUB IYH */ + SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ + + case 0x9c: /* SBC A,IYH */ + tStates += 9; + temp = HIGH_REGISTER(IY); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0x95: /* SUB IYL */ + SETFLAG(C, 0);/* fall through, a bit less efficient but smaller code */ + + case 0x9d: /* SBC A,IYL */ + tStates += 9; + temp = LOW_REGISTER(IY); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0x9e: /* SBC A,(IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + acu = HIGH_REGISTER(AF); + sum = acu - temp - TSTFLAG(C); + AF = addTable[sum & 0xff] | cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xa4: /* AND IYH */ + tStates += 9; + AF = andTable[((AF & IY) >> 8) & 0xff]; + break; + + case 0xa5: /* AND IYL */ + tStates += 9; + AF = andTable[((AF >> 8) & IY) & 0xff]; + break; + + case 0xa6: /* AND (IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + AF = andTable[((AF >> 8) & s100_bus_memr(adr)) & 0xff]; + break; + + case 0xac: /* XOR IYH */ + tStates += 9; + AF = xororTable[((AF ^ IY) >> 8) & 0xff]; + break; + + case 0xad: /* XOR IYL */ + tStates += 9; + AF = xororTable[((AF >> 8) ^ IY) & 0xff]; + break; + + case 0xae: /* XOR (IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + AF = xororTable[((AF >> 8) ^ s100_bus_memr(adr)) & 0xff]; + break; + + case 0xb4: /* OR IYH */ + tStates += 9; + AF = xororTable[((AF | IY) >> 8) & 0xff]; + break; + + case 0xb5: /* OR IYL */ + tStates += 9; + AF = xororTable[((AF >> 8) | IY) & 0xff]; + break; + + case 0xb6: /* OR (IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + AF = xororTable[((AF >> 8) | s100_bus_memr(adr)) & 0xff]; + break; + + case 0xbc: /* CP IYH */ + tStates += 9; + temp = HIGH_REGISTER(IY); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xbd: /* CP IYL */ + tStates += 9; + temp = LOW_REGISTER(IY); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xbe: /* CP (IY+dd) */ + tStates += 19; + adr = IY + (int8) RAM_PP(PC); + CHECK_BREAK_BYTE(adr); + temp = s100_bus_memr(adr); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + cbits2Z80Table[(acu ^ temp ^ sum) & 0x1ff]; + break; + + case 0xcb: /* CB prefix */ + adr = IY + (int8) RAM_PP(PC); + switch ((op = s100_bus_memr(PC)) & 7) { + + case 0: + ++PC; + acu = HIGH_REGISTER(BC); + break; + + case 1: + ++PC; + acu = LOW_REGISTER(BC); + break; + + case 2: + ++PC; + acu = HIGH_REGISTER(DE); + break; + + case 3: + ++PC; + acu = LOW_REGISTER(DE); + break; + + case 4: + ++PC; + acu = HIGH_REGISTER(HL); + break; + + case 5: + ++PC; + acu = LOW_REGISTER(HL); + break; + + case 6: + CHECK_BREAK_BYTE(adr); + ++PC; + acu = s100_bus_memr(adr); + break; + + case 7: + ++PC; + acu = HIGH_REGISTER(AF); + break; + } + switch (op & 0xc0) { + + case 0x00: /* shift/rotate */ + tStates += 23; + switch (op & 0x38) { + + case 0x00: /* RLC */ + temp = (acu << 1) | (acu >> 7); + cbits = temp & 1; + goto cbshflg3; + + case 0x08: /* RRC */ + temp = (acu >> 1) | (acu << 7); + cbits = temp & 0x80; + goto cbshflg3; + + case 0x10: /* RL */ + temp = (acu << 1) | TSTFLAG(C); + cbits = acu & 0x80; + goto cbshflg3; + + case 0x18: /* RR */ + temp = (acu >> 1) | (TSTFLAG(C) << 7); + cbits = acu & 1; + goto cbshflg3; + + case 0x20: /* SLA */ + temp = acu << 1; + cbits = acu & 0x80; + goto cbshflg3; + + case 0x28: /* SRA */ + temp = (acu >> 1) | (acu & 0x80); + cbits = acu & 1; + goto cbshflg3; + + case 0x30: /* SLIA */ + temp = (acu << 1) | 1; + cbits = acu & 0x80; + goto cbshflg3; + + case 0x38: /* SRL */ + temp = acu >> 1; + cbits = acu & 1; + cbshflg3: + AF = (AF & ~0xff) | rotateShiftTable[temp & 0xff] | !!cbits; + /* !!cbits == 0 if cbits == 0 !!cbits == 1 if cbits > 0 */ + } + break; + + case 0x40: /* BIT */ + tStates += 20; + if (acu & (1 << ((op >> 3) & 7))) + AF = (AF & ~0xfe) | 0x10 | (((op & 0x38) == 0x38) << 7); + else + AF = (AF & ~0xfe) | 0x54; + if ((op & 7) != 6) + AF |= (acu & 0x28); + temp = acu; + break; + + case 0x80: /* RES */ + tStates += 23; + temp = acu & ~(1 << ((op >> 3) & 7)); + break; + + case 0xc0: /* SET */ + tStates += 23; + temp = acu | (1 << ((op >> 3) & 7)); + break; + } + switch (op & 7) { + + case 0: + SET_HIGH_REGISTER(BC, temp); + break; + + case 1: + SET_LOW_REGISTER(BC, temp); + break; + + case 2: + SET_HIGH_REGISTER(DE, temp); + break; + + case 3: + SET_LOW_REGISTER(DE, temp); + break; + + case 4: + SET_HIGH_REGISTER(HL, temp); + break; + + case 5: + SET_LOW_REGISTER(HL, temp); + break; + + case 6: + s100_bus_memw(adr, temp); + break; + + case 7: + SET_HIGH_REGISTER(AF, temp); + break; + } + break; + + case 0xe1: /* POP IY */ + tStates += 14; + CHECK_BREAK_WORD(SP); + POP(IY); + break; + + case 0xe3: /* EX (SP),IY */ + tStates += 23; + CHECK_BREAK_WORD(SP); + temp = IY; + POP(IY); + PUSH(temp); + break; + + case 0xe5: /* PUSH IY */ + tStates += 15; + CHECK_BREAK_WORD(SP - 2); + PUSH(IY); + break; + + case 0xe9: /* JP (IY) */ + tStates += 8; + PC = IY; + break; + + case 0xf9: /* LD SP,IY */ + tStates += 10; + SP = IY; + break; + + default: /* ignore FD */ + CHECK_CPU_Z80; + PC--; + } + break; + + case 0xfe: /* CP nn */ + tStates += 7; /* CPI nn 7 */ + temp = RAM_PP(PC); + AF = (AF & ~0x28) | (temp & 0x28); + acu = HIGH_REGISTER(AF); + sum = acu - temp; + cbits = acu ^ temp ^ sum; + AF = (AF & ~0xff) | cpTable[sum & 0xff] | (temp & 0x28) | + (SET_PV) | cbits2Table[cbits & 0x1ff]; + if (z80_chiptype == CHIP_TYPE_8080) + AF ^= 0x10; + break; + + case 0xff: /* RST 38H */ + tStates += 11; /* RST 7 11 */ + CHECK_BREAK_WORD(SP - 2); + PUSH(PC); + PC = 0x38; + } + + /* + ** Save in instruction history ring buffer + */ + if (hst_lnt && ((z80_chiptype == CHIP_TYPE_8080) || (z80_chiptype == CHIP_TYPE_Z80))) { + hst[hst_p].valid = 1; + hst[hst_p].pc = PCX; + hst[hst_p].sp = SP; + hst[hst_p].af = AF; + hst[hst_p].bc = BC; + hst[hst_p].de = DE; + hst[hst_p].hl = HL; + hst[hst_p].af1 = AF1_S; + hst[hst_p].bc1 = BC1_S; + hst[hst_p].de1 = DE1_S; + hst[hst_p].hl1 = HL1_S; + hst[hst_p].ix = IX; + hst[hst_p].iy = IY; + + for (i = 0; i < INST_MAX_BYTES; i++) { + hst[hst_p].op[i] = s100_bus_memr(PCX + i); + } + + if (++hst_p == hst_lnt) { + hst_p = 0; + } + } + + PC &= ADDRMASK; /* reestablish invariant */ + sim_interval--; + } + + end_decode: + + /* simulation halted */ + PC_S = ((reason == STOP_OPCODE) || (reason == STOP_MEM)) ? PCX : (PC & ADDRMASK); + AF_S = AF; + BC_S = BC; + DE_S = DE; + HL_S = HL; + IX_S = IX; + IY_S = IY; + SP_S = SP; + + executedTStates = tStates; + + return reason; +} + +/* + * This sequence of instructions is a mix that mimics + * a reasonable instruction set that is a close estimate + * to the calibrated result. + */ + +static const char *z80_clock_precalibrate_commands[] = { + "-m 100 LXI H,200H", + "-m 103 MVI B,0", + "-m 105 DCR B", + "-m 106 MOV M,B", + "-m 107 INX H", + "-m 108 JNZ 0105H", + "-m 10B JMP 0100H", + "PC 100", + NULL}; + +/* reset routine */ + +static t_stat z80_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { + poc = TRUE; + } + else { + if (poc) { /* First time reset? */ + poc = FALSE; + } + } + + AF_S = AF1_S = 0; + BC_S = DE_S = HL_S = 0; + BC1_S = DE1_S = HL1_S = 0; + IR_S = IX_S = IY_S = SP_S = PC_S = 0; + IM_S = IFF_S = 0; /* Set IM0, reset IFF1 and IFF2 */ + sim_clock_precalibrate_commands = z80_clock_precalibrate_commands; + sim_brk_types = (SWMASK('E') | SWMASK('I') | SWMASK('M')); + sim_brk_dflt = SWMASK('E'); + + return SCPE_OK; +} + +t_bool z80_is_pc_a_subroutine_call (t_addr **ret_addrs) +{ + static t_addr returns[2] = {0, 0}; + + switch (z80_chiptype) { + case CHIP_TYPE_8080: + case CHIP_TYPE_Z80: + switch (s100_bus_memr(PC_S)) { + case 0xc4: /* CALL NZ,nnnn */ + case 0xcc: /* CALL Z,nnnn */ + case 0xcd: /* CALL nnnn */ + case 0xd4: /* CALL NC,nnnn */ + case 0xdc: /* CALL C,nnnn */ + case 0xe4: /* CALL PO,nnnn */ + case 0xec: /* CALL PE,nnnn */ + case 0xf4: /* CALL P,nnnn */ + case 0xfc: /* CALL M,nnnn */ + returns[0] = PC_S + 3; + *ret_addrs = returns; + return TRUE; + + default: + return FALSE; + } + break; + + default: + break; + } + + return FALSE; +} + +static t_stat chip_show(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + fprintf(st, z80_unit.flags & UNIT_Z80_OPSTOP ? "ITRAP, " : "NOITRAP, "); + if ((z80_chiptype >= 0) && (z80_chiptype < NUM_CHIP_TYPE)) { + fprintf(st, "%s", z80_mod[z80_chiptype].mstring); + } + + return SCPE_OK; +} + +static t_stat z80_show(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (z80_unit.flags & UNIT_CPU_VERBOSE) { + fprintf(st, "VERBOSE"); + } + + return SCPE_OK; +} + +static t_stat z80_set_chiptype(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + if (z80_chiptype != value) { + if (z80_unit.flags & UNIT_CPU_VERBOSE) { + sim_printf("CPU changed from %s to %s\n", cpu_get_chipname(z80_chiptype), cpu_get_chipname(value)); + } + + cpu_set_chiptype(value); + } + + return SCPE_OK; +} + +static t_stat z80_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint32 i, lnt; + t_stat r; + + if ((z80_chiptype >= 0) && (z80_chiptype != CHIP_TYPE_8080) && (z80_chiptype != CHIP_TYPE_Z80)) { + sim_printf("History not supported for chiptype: %s\n", + (z80_chiptype < NUM_CHIP_TYPE) ? z80_mod[z80_chiptype].mstring : "????"); + return SCPE_NOFNC; + } + + /* + ** If cptr is NULL, reset ring buffer ("SET HISTORY") + */ + if (cptr == NULL) { + if (hst == NULL) { + sim_printf("History buffer not enabled.\n"); + return SCPE_NOFNC; + } + + for (i = 0; i < hst_lnt; i++) { + hst[i].valid = 0; + } + + hst_p = 0; + + return SCPE_OK; + } + + /* + ** Enable/Resize ring buffer ("SET HISTORY=") + */ + lnt = (uint32) get_uint (cptr, 10, HIST_MAX, &r); + + if ((r != SCPE_OK) || (lnt && (lnt < HIST_MIN))) { + sim_printf("History buffer minimum/maximum size: %d/%d\n", HIST_MIN, HIST_MAX); + return SCPE_ARG; + } + + /* + ** Delete old history buffer + */ + if (hst!=NULL) { + free (hst); + hst_lnt = 0; + hst = NULL; + } + + hst_p = 0; + + /* + ** If a length was specified, allocate new buffer ("SET HISTORY=" where n>0) + */ + if (lnt) { + hst = (insthist_t *) calloc (lnt, sizeof (insthist_t)); + if (hst == NULL) { + return SCPE_MEM; + } + hst_lnt = lnt; + } + + return SCPE_OK; +} + +t_stat z80_show_hist (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + int32 k, di, lnt; + CONST char *cptr = (CONST char *) desc; + t_stat r; + insthist_t *h; + + if ((z80_chiptype != CHIP_TYPE_8080) && (z80_chiptype != CHIP_TYPE_Z80)) { + sim_printf("History not supported for chiptype: %s\n", + (0 <= z80_chiptype) && (z80_chiptype < NUM_CHIP_TYPE) ? + z80_mod[z80_chiptype].mstring : "????"); + return SCPE_NOFNC; + } + + if (hst_lnt == 0) { + return SCPE_NOFNC; /* enabled? */ + } + + if (cptr) { + lnt = (int32) get_uint (cptr, 10, hst_lnt, &r); + + if ((r != SCPE_OK) || (lnt == 0)) { + return SCPE_ARG; + } + } else { + lnt = hst_lnt; + } + + di = hst_p - lnt; + + if (di < 0) di = di + hst_lnt; + + for (k = 0; k < lnt; k++) { + if (stop_cpu) { /* Control-C (SIGINT) */ + stop_cpu = FALSE; + break; /* abandon remaining output */ + } + h = &hst[(di++) % hst_lnt]; + + if (h->valid) { /* valid entry? */ + if (z80_chiptype == CHIP_TYPE_8080) { + /* + ** Use DDT output: + ** CfZfMfEfIf A=bb B=dddd D=dddd H=dddd S=dddd P=dddd inst + */ + fprintf(st, "Z80: C%dZ%dM%dE%dI%d A=%02X B=%04X D=%04X H=%04X S=%04X P=%04X ", + TSTFLAG2(h->af, C), + TSTFLAG2(h->af, Z), + TSTFLAG2(h->af, S), + TSTFLAG2(h->af, P), + TSTFLAG2(h->af, H), + HIGH_REGISTER(h->af), h->bc, h->de, h->hl, h->sp, h->pc); + fprint_sym (st, h->pc, h->op, &z80_unit, SWMASK ('M')); + fprintf(st, "\n"); + } else { /* Z80 */ + /* + ** Use DDT/Z output: + */ + fprintf(st, "Z80: C%dZ%dS%dV%dH%dN%d A =%02X BC =%04X DE =%04X HL =%04X S =%04X P =%04X ", + TSTFLAG2(h->af, C), + TSTFLAG2(h->af, Z), + TSTFLAG2(h->af, S), + TSTFLAG2(h->af, P), + TSTFLAG2(h->af, H), + TSTFLAG2(h->af, N), + HIGH_REGISTER(h->af), h->bc, h->de, h->hl, h->sp, h->pc); + fprint_sym (st, h->pc, h->op, &z80_unit, SWMASK ('M')); + fprintf(st, "\n"); + fprintf(st, " A'=%02X BC'=%04X DE'=%04X HL'=%04X IX=%04X IY=%04X ", + HIGH_REGISTER(h->af1), h->bc1, h->de1, h->hl1, h->ix, h->iy); + fprintf(st, "\n"); + } + } + } + + return SCPE_OK; +} + +t_value z80_pc_value (void) { + return (t_value) PCX; +} + +t_stat z80_cmd_reg(int32 flag, CONST char *cptr) +{ + t_value op[INST_MAX_BYTES]; + int i; + + if (z80_chiptype != CHIP_TYPE_8080 && z80_chiptype != CHIP_TYPE_Z80) { + sim_printf("REG requires 8080 or Z80 CPU\n"); + return SCPE_NOFNC; + } + + for (i = 0; i < INST_MAX_BYTES; i++) { + op[i] = s100_bus_memr(PC_S + i); + } + + if (z80_chiptype == CHIP_TYPE_8080) { + /* + ** Use DDT output: + ** CfZfMfEfIf A=bb B=dddd D=dddd H=dddd S=dddd P=dddd inst + */ + sim_printf("C%dZ%dM%dE%dI%d A=%02X B=%04X D=%04X H=%04X S=%04X P=%04X ", + TSTFLAG2(AF_S, C), + TSTFLAG2(AF_S, Z), + TSTFLAG2(AF_S, S), + TSTFLAG2(AF_S, P), + TSTFLAG2(AF_S, H), + HIGH_REGISTER(AF_S), (uint16) BC_S, (uint16) DE_S, (uint16) HL_S, (uint16) SP_S, (uint16) PC_S); + fprint_sym (stdout, PC_S, op, &z80_unit, SWMASK ('M')); + } else { /* Z80 */ + /* + ** Use DDT/Z output: + */ + sim_printf("C%dZ%dS%dV%dH%dN%d A =%02X BC =%04X DE =%04X HL =%04X S =%04X P =%04X ", + TSTFLAG2(AF_S, C), + TSTFLAG2(AF_S, Z), + TSTFLAG2(AF_S, S), + TSTFLAG2(AF_S, P), + TSTFLAG2(AF_S, H), + TSTFLAG2(AF_S, N), + HIGH_REGISTER(AF_S), (uint16) BC_S, (uint16) DE_S, (uint16) HL_S, (uint16) SP_S, (uint16) PC_S); + fprint_sym (stdout, PC_S, op, &z80_unit, SWMASK ('M')); + sim_printf("\n"); + sim_printf(" A'=%02X BC'=%04X DE'=%04X HL'=%04X IX=%04X IY=%04X", + HIGH_REGISTER(AF1_S), (uint16) BC1_S, (uint16) DE1_S, (uint16) HL1_S, (uint16) IX_S, (uint16) IY_S); + } + + sim_printf("\n"); + + return SCPE_OK | SCPE_NOMESSAGE; +} + +static const char *const Mnemonics8080[] = { +/* 0/8 1/9 2/A 3/B 4/C 5/D 6/E 7/F */ + "NOP", "LXI B,#h", "STAX B", "INX B", "INR B", "DCR B", "MVI B,*h", "RLC", /* 00-07 */ + "_NOP", "DAD B", "LDAX B", "DCX B", "INR C", "DCR C", "MVI C,*h", "RRC", /* 08-0f */ + "_NOP", "LXI D,#h", "STAX D", "INX D", "INR D", "DCR D", "MVI D,*h", "RAL", /* 10-17 */ + "_NOP", "DAD D", "LDAX D", "DCX D", "INR E", "DCR E", "MVI E,*h", "RAR", /* 18-1f */ + "_NOP", "LXI H,#h", "SHLD #h", "INX H", "INR H", "DCR H", "MVI H,*h", "DAA", /* 20-27 */ + "_NOP", "DAD H", "LHLD #h", "DCX H", "INR L", "DCR L", "MVI L,*h", "CMA", /* 28-2f */ + "_NOP", "LXI SP,#h", "STA #h", "INX SP", "INR M", "DCR M", "MVI M,*h", "STC", /* 30-37 */ + "_NOP", "DAD SP", "LDA #h", "DCX SP", "INR A", "DCR A", "MVI A,*h", "CMC", /* 38-3f */ + "MOV B,B", "MOV B,C", "MOV B,D", "MOV B,E", "MOV B,H", "MOV B,L", "MOV B,M", "MOV B,A", /* 40-47 */ + "MOV C,B", "MOV C,C", "MOV C,D", "MOV C,E", "MOV C,H", "MOV C,L", "MOV C,M", "MOV C,A", /* 48-4f */ + "MOV D,B", "MOV D,C", "MOV D,D", "MOV D,E", "MOV D,H", "MOV D,L", "MOV D,M", "MOV D,A", /* 50-57 */ + "MOV E,B", "MOV E,C", "MOV E,D", "MOV E,E", "MOV E,H", "MOV E,L", "MOV E,M", "MOV E,A", /* 58-5f */ + "MOV H,B", "MOV H,C", "MOV H,D", "MOV H,E", "MOV H,H", "MOV H,L", "MOV H,M", "MOV H,A", /* 60-67 */ + "MOV L,B", "MOV L,C", "MOV L,D", "MOV L,E", "MOV L,H", "MOV L,L", "MOV L,M", "MOV L,A", /* 68-6f */ + "MOV M,B", "MOV M,C", "MOV M,D", "MOV M,E", "MOV M,H", "MOV M,L", "HLT", "MOV M,A", /* 70-77 */ + "MOV A,B", "MOV A,C", "MOV A,D", "MOV A,E", "MOV A,H", "MOV A,L", "MOV A,M", "MOV A,A", /* 78-7f */ + "ADD B", "ADD C", "ADD D", "ADD E", "ADD H", "ADD L", "ADD M", "ADD A", /* 80-87 */ + "ADC B", "ADC C", "ADC D", "ADC E", "ADC H", "ADC L", "ADC M", "ADC A", /* 88-8f */ + "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB M", "SUB A", /* 90-97 */ + "SBB B", "SBB C", "SBB D", "SBB E", "SBB H", "SBB L", "SBB M", "SBB A", /* 98-9f */ + "ANA B", "ANA C", "ANA D", "ANA E", "ANA H", "ANA L", "ANA M", "ANA A", /* a0-a7 */ + "XRA B", "XRA C", "XRA D", "XRA E", "XRA H", "XRA L", "XRA M", "XRA A", /* a8-af */ + "ORA B", "ORA C", "ORA D", "ORA E", "ORA H", "ORA L", "ORA M", "ORA A", /* b0-b7 */ + "CMP B", "CMP C", "CMP D", "CMP E", "CMP H", "CMP L", "CMP M", "CMP A", /* b8-bf */ + "RNZ", "POP B", "JNZ #h", "JMP #h", "CNZ #h", "PUSH B", "ADI *h", "RST 0", /* c0-c7 */ + "RZ", "RET", "JZ #h", "_JMP #h", "CZ #h", "CALL #h", "ACI *h", "RST 1", /* c8-cf */ + "RNC", "POP D", "JNC #h", "OUT *h", "CNC #h", "PUSH D", "SUI *h", "RST 2", /* d0-d7 */ + "RC", "_RET", "JC #h", "IN *h", "CC #h", "_CALL #h", "SBI *h", "RST 3", /* d8-df */ + "RPO", "POP H", "JPO #h", "XTHL", "CPO #h", "PUSH H", "ANI *h", "RST 4", /* e0-e7 */ + "RPE", "PCHL", "JPE #h", "XCHG", "CPE #h", "_CALL #h", "XRI *h", "RST 5", /* e8-ef */ + "RP", "POP PSW", "JP #h", "DI", "CP #h", "PUSH PSW", "ORI *h", "RST 6", /* f0-f7 */ + "RM", "SPHL", "JM #h", "EI", "CM #h", "_CALL #h", "CPI *h", "RST 7" /* f8-ff */ +}; + +static const char *const MnemonicsZ80[256] = { +/* 0/8 1/9 2/A 3/B 4/C 5/D 6/E 7/F */ + "NOP", "LD BC,#h", "LD (BC),A", "INC BC", "INC B", "DEC B", "LD B,*h", "RLCA", /* 00-07 */ + "EX AF,AF'", "ADD HL,BC", "LD A,(BC)", "DEC BC", "INC C", "DEC C", "LD C,*h", "RRCA", /* 08-0f */ + "DJNZ $h", "LD DE,#h", "LD (DE),A", "INC DE", "INC D", "DEC D", "LD D,*h", "RLA", /* 10-17 */ + "JR $h", "ADD HL,DE", "LD A,(DE)", "DEC DE", "INC E", "DEC E", "LD E,*h", "RRA", /* 18-1f */ + "JR NZ,$h", "LD HL,#h", "LD (#h),HL", "INC HL", "INC H", "DEC H", "LD H,*h", "DAA", /* 20-27 */ + "JR Z,$h", "ADD HL,HL", "LD HL,(#h)", "DEC HL", "INC L", "DEC L", "LD L,*h", "CPL", /* 28-2f */ + "JR NC,$h", "LD SP,#h", "LD (#h),A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL),*h", "SCF", /* 30-37 */ + "JR C,$h", "ADD HL,SP", "LD A,(#h)", "DEC SP", "INC A", "DEC A", "LD A,*h", "CCF", /* 38-3f */ + "LD B,B", "LD B,C", "LD B,D", "LD B,E", "LD B,H", "LD B,L", "LD B,(HL)", "LD B,A", /* 40-47 */ + "LD C,B", "LD C,C", "LD C,D", "LD C,E", "LD C,H", "LD C,L", "LD C,(HL)", "LD C,A", /* 48-4f */ + "LD D,B", "LD D,C", "LD D,D", "LD D,E", "LD D,H", "LD D,L", "LD D,(HL)", "LD D,A", /* 50-57 */ + "LD E,B", "LD E,C", "LD E,D", "LD E,E", "LD E,H", "LD E,L", "LD E,(HL)", "LD E,A", /* 58-5f */ + "LD H,B", "LD H,C", "LD H,D", "LD H,E", "LD H,H", "LD H,L", "LD H,(HL)", "LD H,A", /* 60-67 */ + "LD L,B", "LD L,C", "LD L,D", "LD L,E", "LD L,H", "LD L,L", "LD L,(HL)", "LD L,A", /* 68-6f */ + "LD (HL),B", "LD (HL),C", "LD (HL),D", "LD (HL),E", "LD (HL),H", "LD (HL),L", "HALT", "LD (HL),A", /* 70-77 */ + "LD A,B", "LD A,C", "LD A,D", "LD A,E", "LD A,H", "LD A,L", "LD A,(HL)", "LD A,A", /* 78-7f */ + "ADD A,B", "ADD A,C", "ADD A,D", "ADD A,E", "ADD A,H", "ADD A,L", "ADD A,(HL)", "ADD A,A", /* 80-87 */ + "ADC A,B", "ADC A,C", "ADC A,D", "ADC A,E", "ADC A,H", "ADC A,L", "ADC A,(HL)", "ADC A,A", /* 88-8f */ + "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", /* 90-97 */ + "SBC A,B", "SBC A,C", "SBC A,D", "SBC A,E", "SBC A,H", "SBC A,L", "SBC A,(HL)", "SBC A,A", /* 98-9f */ + "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", /* a0-a7 */ + "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", /* a8-af */ + "OR B", "OR C", "OR D", "OR E", "OR H", "OR L", "OR (HL)", "OR A", /* b0-b7 */ + "CP B", "CP C", "CP D", "CP E", "CP H", "CP L", "CP (HL)", "CP A", /* b8-bf */ + "RET NZ", "POP BC", "JP NZ,#h", "JP #h", "CALL NZ,#h", "PUSH BC", "ADD A,*h", "RST 00h", /* c0-c7 */ + "RET Z", "RET", "JP Z,#h", "PFX_CB", "CALL Z,#h", "CALL #h", "ADC A,*h", "RST 08h", /* c8-cf */ + "RET NC", "POP DE", "JP NC,#h", "OUT (*h),A", "CALL NC,#h", "PUSH DE", "SUB *h", "RST 10h", /* d0-d7 */ + "RET C", "EXX", "JP C,#h", "IN A,(*h)", "CALL C,#h", "PFX_DD", "SBC A,*h", "RST 18h", /* d8-df */ + "RET PO", "POP HL", "JP PO,#h", "EX (SP),HL", "CALL PO,#h", "PUSH HL", "AND *h", "RST 20h", /* e0-e7 */ + "RET PE", "LD PC,HL", "JP PE,#h", "EX DE,HL", "CALL PE,#h", "PFX_ED", "XOR *h", "RST 28h", /* e8-ef */ + "RET P", "POP AF", "JP P,#h", "DI", "CALL P,#h", "PUSH AF", "OR *h", "RST 30h", /* f0-f7 */ + "RET M", "LD SP,HL", "JP M,#h", "EI", "CALL M,#h", "PFX_FD", "CP *h", "RST 38h" /* f8-ff */ +}; + +static const char *const MnemonicsCB[256] = { +/* 0/8 1/9 2/A 3/B 4/C 5/D 6/E 7/F */ + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (HL)", "RLC A", /* 00-07 */ + "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (HL)", "RRC A", /* 08-0f */ + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (HL)", "RL A", /* 10-17 */ + "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (HL)", "RR A", /* 18-1f */ + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (HL)", "SLA A", /* 20-27 */ + "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (HL)", "SRA A", /* 28-2f */ + "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (HL)", "SLL A", /* 30-37 */ + "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (HL)", "SRL A", /* 38-3f */ + "BIT 0,B", "BIT 0,C", "BIT 0,D", "BIT 0,E", "BIT 0,H", "BIT 0,L", "BIT 0,(HL)", "BIT 0,A", /* 40-47 */ + "BIT 1,B", "BIT 1,C", "BIT 1,D", "BIT 1,E", "BIT 1,H", "BIT 1,L", "BIT 1,(HL)", "BIT 1,A", /* 48-4f */ + "BIT 2,B", "BIT 2,C", "BIT 2,D", "BIT 2,E", "BIT 2,H", "BIT 2,L", "BIT 2,(HL)", "BIT 2,A", /* 50-57 */ + "BIT 3,B", "BIT 3,C", "BIT 3,D", "BIT 3,E", "BIT 3,H", "BIT 3,L", "BIT 3,(HL)", "BIT 3,A", /* 58-5f */ + "BIT 4,B", "BIT 4,C", "BIT 4,D", "BIT 4,E", "BIT 4,H", "BIT 4,L", "BIT 4,(HL)", "BIT 4,A", /* 60-67 */ + "BIT 5,B", "BIT 5,C", "BIT 5,D", "BIT 5,E", "BIT 5,H", "BIT 5,L", "BIT 5,(HL)", "BIT 5,A", /* 68-6f */ + "BIT 6,B", "BIT 6,C", "BIT 6,D", "BIT 6,E", "BIT 6,H", "BIT 6,L", "BIT 6,(HL)", "BIT 6,A", /* 70-77 */ + "BIT 7,B", "BIT 7,C", "BIT 7,D", "BIT 7,E", "BIT 7,H", "BIT 7,L", "BIT 7,(HL)", "BIT 7,A", /* 78-7f */ + "RES 0,B", "RES 0,C", "RES 0,D", "RES 0,E", "RES 0,H", "RES 0,L", "RES 0,(HL)", "RES 0,A", /* 80-87 */ + "RES 1,B", "RES 1,C", "RES 1,D", "RES 1,E", "RES 1,H", "RES 1,L", "RES 1,(HL)", "RES 1,A", /* 88-8f */ + "RES 2,B", "RES 2,C", "RES 2,D", "RES 2,E", "RES 2,H", "RES 2,L", "RES 2,(HL)", "RES 2,A", /* 90-97 */ + "RES 3,B", "RES 3,C", "RES 3,D", "RES 3,E", "RES 3,H", "RES 3,L", "RES 3,(HL)", "RES 3,A", /* 98-9f */ + "RES 4,B", "RES 4,C", "RES 4,D", "RES 4,E", "RES 4,H", "RES 4,L", "RES 4,(HL)", "RES 4,A", /* a0-a7 */ + "RES 5,B", "RES 5,C", "RES 5,D", "RES 5,E", "RES 5,H", "RES 5,L", "RES 5,(HL)", "RES 5,A", /* a8-af */ + "RES 6,B", "RES 6,C", "RES 6,D", "RES 6,E", "RES 6,H", "RES 6,L", "RES 6,(HL)", "RES 6,A", /* b0-b7 */ + "RES 7,B", "RES 7,C", "RES 7,D", "RES 7,E", "RES 7,H", "RES 7,L", "RES 7,(HL)", "RES 7,A", /* b8-bf */ + "SET 0,B", "SET 0,C", "SET 0,D", "SET 0,E", "SET 0,H", "SET 0,L", "SET 0,(HL)", "SET 0,A", /* c0-c7 */ + "SET 1,B", "SET 1,C", "SET 1,D", "SET 1,E", "SET 1,H", "SET 1,L", "SET 1,(HL)", "SET 1,A", /* c8-cf */ + "SET 2,B", "SET 2,C", "SET 2,D", "SET 2,E", "SET 2,H", "SET 2,L", "SET 2,(HL)", "SET 2,A", /* d0-d7 */ + "SET 3,B", "SET 3,C", "SET 3,D", "SET 3,E", "SET 3,H", "SET 3,L", "SET 3,(HL)", "SET 3,A", /* d8-df */ + "SET 4,B", "SET 4,C", "SET 4,D", "SET 4,E", "SET 4,H", "SET 4,L", "SET 4,(HL)", "SET 4,A", /* e0-e7 */ + "SET 5,B", "SET 5,C", "SET 5,D", "SET 5,E", "SET 5,H", "SET 5,L", "SET 5,(HL)", "SET 5,A", /* e8-ef */ + "SET 6,B", "SET 6,C", "SET 6,D", "SET 6,E", "SET 6,H", "SET 6,L", "SET 6,(HL)", "SET 6,A", /* f0-f7 */ + "SET 7,B", "SET 7,C", "SET 7,D", "SET 7,E", "SET 7,H", "SET 7,L", "SET 7,(HL)", "SET 7,A" /* f8-ff */ +}; + +static const char *const MnemonicsED[256] = { +/* 0/8 1/9 2/A 3/B 4/C 5/D 6/E 7/F */ + "DB EDh,00h", "DB EDh,01h", "DB EDh,02h", "DB EDh,03h", "DB EDh,04h", "DB EDh,05h", "DB EDh,06h", "DB EDh,07h", /* 00-07 */ + "DB EDh,08h", "DB EDh,09h", "DB EDh,0Ah", "DB EDh,0Bh", "DB EDh,0Ch", "DB EDh,0Dh", "DB EDh,0Eh", "DB EDh,0Fh", /* 08-0f */ + "DB EDh,10h", "DB EDh,11h", "DB EDh,12h", "DB EDh,13h", "DB EDh,14h", "DB EDh,15h", "DB EDh,16h", "DB EDh,17h", /* 10-17 */ + "DB EDh,18h", "DB EDh,19h", "DB EDh,1Ah", "DB EDh,1Bh", "DB EDh,1Ch", "DB EDh,1Dh", "DB EDh,1Eh", "DB EDh,1Fh", /* 18-1f */ + "DB EDh,20h", "DB EDh,21h", "DB EDh,22h", "DB EDh,23h", "DB EDh,24h", "DB EDh,25h", "DB EDh,26h", "DB EDh,27h", /* 20-27 */ + "DB EDh,28h", "DB EDh,29h", "DB EDh,2Ah", "DB EDh,2Bh", "DB EDh,2Ch", "DB EDh,2Dh", "DB EDh,2Eh", "DB EDh,2Fh", /* 28-2f */ + "DB EDh,30h", "DB EDh,31h", "DB EDh,32h", "DB EDh,33h", "DB EDh,34h", "DB EDh,35h", "DB EDh,36h", "DB EDh,37h", /* 30-37 */ + "DB EDh,38h", "DB EDh,39h", "DB EDh,3Ah", "DB EDh,3Bh", "DB EDh,3Ch", "DB EDh,3Dh", "DB EDh,3Eh", "DB EDh,3Fh", /* 38-3f */ + "IN B,(C)", "OUT (C),B", "SBC HL,BC", "LD (#h),BC", "NEG", "RETN", "IM 0", "LD I,A", /* 40-47 */ + "IN C,(C)", "OUT (C),C", "ADC HL,BC", "LD BC,(#h)", "DB EDh,4Ch", "RETI", "DB EDh,4Eh", "LD R,A", /* 48-4f */ + "IN D,(C)", "OUT (C),D", "SBC HL,DE", "LD (#h),DE", "DB EDh,54h", "DB EDh,55h", "IM 1", "LD A,I", /* 50-57 */ + "IN E,(C)", "OUT (C),E", "ADC HL,DE", "LD DE,(#h)", "DB EDh,5Ch", "DB EDh,5Dh", "IM 2", "LD A,R", /* 58-5f */ + "IN H,(C)", "OUT (C),H", "SBC HL,HL", "LD (#h),HL", "DB EDh,64h", "DB EDh,65h", "DB EDh,66h", "RRD", /* 60-67 */ + "IN L,(C)", "OUT (C),L", "ADC HL,HL", "LD HL,(#h)", "DB EDh,6Ch", "DB EDh,6Dh", "DB EDh,6Eh", "RLD", /* 68-6f */ + "IN F,(C)", "DB EDh,71h", "SBC HL,SP", "LD (#h),SP", "DB EDh,74h", "DB EDh,75h", "DB EDh,76h", "DB EDh,77h", /* 70-77 */ + "IN A,(C)", "OUT (C),A", "ADC HL,SP", "LD SP,(#h)", "DB EDh,7Ch", "DB EDh,7Dh", "DB EDh,7Eh", "DB EDh,7Fh", /* 78-7f */ + "DB EDh,80h", "DB EDh,81h", "DB EDh,82h", "DB EDh,83h", "DB EDh,84h", "DB EDh,85h", "DB EDh,86h", "DB EDh,87h", /* 80-87 */ + "DB EDh,88h", "DB EDh,89h", "DB EDh,8Ah", "DB EDh,8Bh", "DB EDh,8Ch", "DB EDh,8Dh", "DB EDh,8Eh", "DB EDh,8Fh", /* 88-8f */ + "DB EDh,90h", "DB EDh,91h", "DB EDh,92h", "DB EDh,93h", "DB EDh,94h", "DB EDh,95h", "DB EDh,96h", "DB EDh,97h", /* 90-97 */ + "DB EDh,98h", "DB EDh,99h", "DB EDh,9Ah", "DB EDh,9Bh", "DB EDh,9Ch", "DB EDh,9Dh", "DB EDh,9Eh", "DB EDh,9Fh", /* 98-9f */ + "LDI", "CPI", "INI", "OUTI", "DB EDh,A4h", "DB EDh,A5h", "DB EDh,A6h", "DB EDh,A7h", /* a0-a7 */ + "LDD", "CPD", "IND", "OUTD", "DB EDh,ACh", "DB EDh,ADh", "DB EDh,AEh", "DB EDh,AFh", /* a8-af */ + "LDIR", "CPIR", "INIR", "OTIR", "DB EDh,B4h", "DB EDh,B5h", "DB EDh,B6h", "DB EDh,B7h", /* b0-b7 */ + "LDDR", "CPDR", "INDR", "OTDR", "DB EDh,BCh", "DB EDh,BDh", "DB EDh,BEh", "DB EDh,BFh", /* b8-bf */ + "DB EDh,C0h", "DB EDh,C1h", "DB EDh,C2h", "DB EDh,C3h", "DB EDh,C4h", "DB EDh,C5h", "DB EDh,C6h", "DB EDh,C7h", /* c0-c7 */ + "DB EDh,C8h", "DB EDh,C9h", "DB EDh,CAh", "DB EDh,CBh", "DB EDh,CCh", "DB EDh,CDh", "DB EDh,CEh", "DB EDh,CFh", /* c8-cf */ + "DB EDh,D0h", "DB EDh,D1h", "DB EDh,D2h", "DB EDh,D3h", "DB EDh,D4h", "DB EDh,D5h", "DB EDh,D6h", "DB EDh,D7h", /* d0-d7 */ + "DB EDh,D8h", "DB EDh,D9h", "DB EDh,DAh", "DB EDh,DBh", "DB EDh,DCh", "DB EDh,DDh", "DB EDh,DEh", "DB EDh,DFh", /* d8-df */ + "DB EDh,E0h", "DB EDh,E1h", "DB EDh,E2h", "DB EDh,E3h", "DB EDh,E4h", "DB EDh,E5h", "DB EDh,E6h", "DB EDh,E7h", /* e0-e7 */ + "DB EDh,E8h", "DB EDh,E9h", "DB EDh,EAh", "DB EDh,EBh", "DB EDh,ECh", "DB EDh,EDh", "DB EDh,EEh", "DB EDh,EFh", /* e8-ef */ + "DB EDh,F0h", "DB EDh,F1h", "DB EDh,F2h", "DB EDh,F3h", "DB EDh,F4h", "DB EDh,F5h", "DB EDh,F6h", "DB EDh,F7h", /* f0-f7 */ + "DB EDh,F8h", "DB EDh,F9h", "DB EDh,FAh", "DB EDh,FBh", "DB EDh,FCh", "DB EDh,FDh", "DB EDh,FEh", "DB EDh,FFh" /* f8-ff */ +}; + +static const char *const MnemonicsXX[256] = { +/* 0/8 1/9 2/A 3/B 4/C 5/D 6/E 7/F */ + "NOP", "LD BC,#h", "LD (BC),A", "INC BC", "INC B", "DEC B", "LD B,*h", "RLCA", /* 00-07 */ + "EX AF,AF'", "ADD I%,BC", "LD A,(BC)", "DEC BC", "INC C", "DEC C", "LD C,*h", "RRCA", /* 08-0f */ + "DJNZ $h", "LD DE,#h", "LD (DE),A", "INC DE", "INC D", "DEC D", "LD D,*h", "RLA", /* 10-17 */ + "JR $h", "ADD I%,DE", "LD A,(DE)", "DEC DE", "INC E", "DEC E", "LD E,*h", "RRA", /* 18-1f */ + "JR NZ,$h", "LD I%,#h", "LD (#h),I%", "INC I%", "INC I%H", "DEC I%H", "LD I%H,*h", "DAA", /* 20-27 */ + "JR Z,$h", "ADD I%,I%", "LD I%,(#h)", "DEC I%", "INC I%L", "DEC I%L", "LD I%L,*h", "CPL", /* 28-2f */ + "JR NC,$h", "LD SP,#h", "LD (#h),A", "INC SP", "INC (I%+^h)", "DEC (I%+^h)", "LD (I%+^h),*h","SCF", /* 30-37 */ + "JR C,$h", "ADD I%,SP", "LD A,(#h)", "DEC SP", "INC A", "DEC A", "LD A,*h", "CCF", /* 38-3f */ + "LD B,B", "LD B,C", "LD B,D", "LD B,E", "LD B,I%H", "LD B,I%L", "LD B,(I%+^h)", "LD B,A", /* 40-47 */ + "LD C,B", "LD C,C", "LD C,D", "LD C,E", "LD C,I%H", "LD C,I%L", "LD C,(I%+^h)", "LD C,A", /* 48-4f */ + "LD D,B", "LD D,C", "LD D,D", "LD D,E", "LD D,I%H", "LD D,I%L", "LD D,(I%+^h)", "LD D,A", /* 50-57 */ + "LD E,B", "LD E,C", "LD E,D", "LD E,E", "LD E,I%H", "LD E,I%L", "LD E,(I%+^h)", "LD E,A", /* 58-5f */ + "LD I%H,B", "LD I%H,C", "LD I%H,D", "LD I%H,E", "LD I%H,I%H", "LD I%H,I%L", "LD H,(I%+^h)", "LD I%H,A", /* 60-67 */ + "LD I%L,B", "LD I%L,C", "LD I%L,D", "LD I%L,E", "LD I%L,I%H", "LD I%L,I%L", "LD L,(I%+^h)", "LD I%L,A", /* 68-6f */ + "LD (I%+^h),B", "LD (I%+^h),C", "LD (I%+^h),D", "LD (I%+^h),E", "LD (I%+^h),H", "LD (I%+^h),L", "HALT", "LD (I%+^h),A", /* 70-77 */ + "LD A,B", "LD A,C", "LD A,D", "LD A,E", "LD A,I%H", "LD A,I%L", "LD A,(I%+^h)", "LD A,A", /* 78-7f */ + "ADD A,B", "ADD A,C", "ADD A,D", "ADD A,E", "ADD A,I%H", "ADD A,I%L", "ADD A,(I%+^h)","ADD A,A", /* 80-87 */ + "ADC A,B", "ADC A,C", "ADC A,D", "ADC A,E", "ADC A,I%H", "ADC A,I%L", "ADC A,(I%+^h)","ADC A,A", /* 88-8f */ + "SUB B", "SUB C", "SUB D", "SUB E", "SUB I%H", "SUB I%L", "SUB (I%+^h)", "SUB A", /* 90-97 */ + "SBC A,B", "SBC A,C", "SBC A,D", "SBC A,E", "SBC A,I%H", "SBC A,I%L", "SBC A,(I%+^h)","SBC A,A", /* 98-9f */ + "AND B", "AND C", "AND D", "AND E", "AND I%H", "AND I%L", "AND (I%+^h)", "AND A", /* a0-a7 */ + "XOR B", "XOR C", "XOR D", "XOR E", "XOR I%H", "XOR I%L", "XOR (I%+^h)", "XOR A", /* a8-af */ + "OR B", "OR C", "OR D", "OR E", "OR I%H", "OR I%L", "OR (I%+^h)", "OR A", /* b0-b7 */ + "CP B", "CP C", "CP D", "CP E", "CP I%H", "CP I%L", "CP (I%+^h)", "CP A", /* b8-bf */ + "RET NZ", "POP BC", "JP NZ,#h", "JP #h", "CALL NZ,#h", "PUSH BC", "ADD A,*h", "RST 00h", /* c8-cf */ + "RET Z", "RET", "JP Z,#h", "PFX_CB", "CALL Z,#h", "CALL #h", "ADC A,*h", "RST 08h", /* c8-cf */ + "RET NC", "POP DE", "JP NC,#h", "OUT (*h),A", "CALL NC,#h", "PUSH DE", "SUB *h", "RST 10h", /* d0-d7 */ + "RET C", "EXX", "JP C,#h", "IN A,(*h)", "CALL C,#h", "PFX_DD", "SBC A,*h", "RST 18h", /* d8-df */ + "RET PO", "POP I%", "JP PO,#h", "EX (SP),I%", "CALL PO,#h", "PUSH I%", "AND *h", "RST 20h", /* e0-e7 */ + "RET PE", "LD PC,I%", "JP PE,#h", "EX DE,I%", "CALL PE,#h", "PFX_ED", "XOR *h", "RST 28h", /* e8-ef */ + "RET P", "POP AF", "JP P,#h", "DI", "CALL P,#h", "PUSH AF", "OR *h", "RST 30h", /* f0-f7 */ + "RET M", "LD SP,I%", "JP M,#h", "EI", "CALL M,#h", "PFX_FD", "CP *h", "RST 38h" /* f8-ff */ +}; + +static const char *const MnemonicsXCB[256] = { +/*0/8 1/9 2/A 3/B 4/C 5/D 6/E 7/F */ + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (I%@h)", "RLC A", /* 00-07 */ + "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (I%@h)", "RRC A", /* 08-0f */ + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (I%@h)", "RL A", /* 10-17 */ + "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (I%@h)", "RR A", /* 18-1f */ + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (I%@h)", "SLA A", /* 20-27 */ + "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (I%@h)", "SRA A", /* 28-2f */ + "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (I%@h)", "SLL A", /* 30-37 */ + "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (I%@h)", "SRL A", /* 38-3f */ + "BIT 0,B", "BIT 0,C", "BIT 0,D", "BIT 0,E", "BIT 0,H", "BIT 0,L", "BIT 0,(I%@h)", "BIT 0,A", /* 40-47 */ + "BIT 1,B", "BIT 1,C", "BIT 1,D", "BIT 1,E", "BIT 1,H", "BIT 1,L", "BIT 1,(I%@h)", "BIT 1,A", /* 48-4f */ + "BIT 2,B", "BIT 2,C", "BIT 2,D", "BIT 2,E", "BIT 2,H", "BIT 2,L", "BIT 2,(I%@h)", "BIT 2,A", /* 50-57 */ + "BIT 3,B", "BIT 3,C", "BIT 3,D", "BIT 3,E", "BIT 3,H", "BIT 3,L", "BIT 3,(I%@h)", "BIT 3,A", /* 58-5f */ + "BIT 4,B", "BIT 4,C", "BIT 4,D", "BIT 4,E", "BIT 4,H", "BIT 4,L", "BIT 4,(I%@h)", "BIT 4,A", /* 60-67 */ + "BIT 5,B", "BIT 5,C", "BIT 5,D", "BIT 5,E", "BIT 5,H", "BIT 5,L", "BIT 5,(I%@h)", "BIT 5,A", /* 68-6f */ + "BIT 6,B", "BIT 6,C", "BIT 6,D", "BIT 6,E", "BIT 6,H", "BIT 6,L", "BIT 6,(I%@h)", "BIT 6,A", /* 70-77 */ + "BIT 7,B", "BIT 7,C", "BIT 7,D", "BIT 7,E", "BIT 7,H", "BIT 7,L", "BIT 7,(I%@h)", "BIT 7,A", /* 78-7f */ + "RES 0,B", "RES 0,C", "RES 0,D", "RES 0,E", "RES 0,H", "RES 0,L", "RES 0,(I%@h)", "RES 0,A", /* 80-87 */ + "RES 1,B", "RES 1,C", "RES 1,D", "RES 1,E", "RES 1,H", "RES 1,L", "RES 1,(I%@h)", "RES 1,A", /* 88-8f */ + "RES 2,B", "RES 2,C", "RES 2,D", "RES 2,E", "RES 2,H", "RES 2,L", "RES 2,(I%@h)", "RES 2,A", /* 90-97 */ + "RES 3,B", "RES 3,C", "RES 3,D", "RES 3,E", "RES 3,H", "RES 3,L", "RES 3,(I%@h)", "RES 3,A", /* 98-9f */ + "RES 4,B", "RES 4,C", "RES 4,D", "RES 4,E", "RES 4,H", "RES 4,L", "RES 4,(I%@h)", "RES 4,A", /* a0-a7 */ + "RES 5,B", "RES 5,C", "RES 5,D", "RES 5,E", "RES 5,H", "RES 5,L", "RES 5,(I%@h)", "RES 5,A", /* a8-af */ + "RES 6,B", "RES 6,C", "RES 6,D", "RES 6,E", "RES 6,H", "RES 6,L", "RES 6,(I%@h)", "RES 6,A", /* b0-b7 */ + "RES 7,B", "RES 7,C", "RES 7,D", "RES 7,E", "RES 7,H", "RES 7,L", "RES 7,(I%@h)", "RES 7,A", /* b8-bf */ + "SET 0,B", "SET 0,C", "SET 0,D", "SET 0,E", "SET 0,H", "SET 0,L", "SET 0,(I%@h)", "SET 0,A", /* c0-c7 */ + "SET 1,B", "SET 1,C", "SET 1,D", "SET 1,E", "SET 1,H", "SET 1,L", "SET 1,(I%@h)", "SET 1,A", /* c8-cf */ + "SET 2,B", "SET 2,C", "SET 2,D", "SET 2,E", "SET 2,H", "SET 2,L", "SET 2,(I%@h)", "SET 2,A", /* d0-d7 */ + "SET 3,B", "SET 3,C", "SET 3,D", "SET 3,E", "SET 3,H", "SET 3,L", "SET 3,(I%@h)", "SET 3,A", /* d8-df */ + "SET 4,B", "SET 4,C", "SET 4,D", "SET 4,E", "SET 4,H", "SET 4,L", "SET 4,(I%@h)", "SET 4,A", /* e0-e7 */ + "SET 5,B", "SET 5,C", "SET 5,D", "SET 5,E", "SET 5,H", "SET 5,L", "SET 5,(I%@h)", "SET 5,A", /* e8-ef */ + "SET 6,B", "SET 6,C", "SET 6,D", "SET 6,E", "SET 6,H", "SET 6,L", "SET 6,(I%@h)", "SET 6,A", /* f0-f7 */ + "SET 7,B", "SET 7,C", "SET 7,D", "SET 7,E", "SET 7,H", "SET 7,L", "SET 7,(I%@h)", "SET 7,A" /* f8-ff */ +}; + +static void prepareMemoryAccessMessage(const t_addr loc) { + sprintf(memoryAccessMessage, "Memory access breakpoint [%05xh]", loc); +} + +static void prepareInstructionMessage(const t_addr loc, const uint32 op) { + sprintf(instructionMessage, "Instruction \"%s\" breakpoint [%05xh]", z80_chiptype == CHIP_TYPE_8080 ? Mnemonics8080[op & 0xff] : + (z80_chiptype == CHIP_TYPE_Z80 ? MnemonicsZ80[op & 0xff] : "???"), loc); +} + +/* Ensure that hex number starts with a digit when printed */ +static void printHex2(char* string, const uint32 value) { + sprintf(string, (value <= 0x9f ? "%02X" : "%03X"), value); +} + +static void printHex4(char* string, const uint32 value) { + sprintf(string, (value <= 0x9fff ? "%04X" : "%05X"), value); +} + +/* Symbolic disassembler + + Inputs: + *val = instructions to disassemble + addr = current PC + Outputs: + *S = output text + return = length of instruction in bytes + + DAsm is Copyright (C) Marat Fayzullin 1995,1996,1997 + You are not allowed to distribute this software + commercially. + +*/ + +int32 z80_dasm(char *S, const uint32 *val, const int32 addr) { + char R[128], H[10], C = '\0', *P; + const char *T, *T1; + uint8 J = 0, Offset = 0; + uint16 B = 0; + + if (z80_chiptype == CHIP_TYPE_Z80) + switch(val[B]) { + + case 0xcb: + B++; + T = MnemonicsCB[val[B++]]; + break; + + case 0xed: + B++; + T = MnemonicsED[val[B++]]; + break; + + case 0xdd: + + case 0xfd: + C = (val[B++] == 0xdd) ? 'X' : 'Y'; + if (val[B] == 0xcb) { + B++; + Offset = val[B++]; + J = 1; + T = MnemonicsXCB[val[B++]]; + } else + T = MnemonicsXX[val[B++]]; + break; + + default: + T = MnemonicsZ80[val[B++]]; + } + else + T = Mnemonics8080[val[B++]]; + + if ( (T1 = strchr(T, '^')) ) { + strncpy(R, T, T1 - T); + R[T1 - T] = '\0'; + printHex2(H, val[B++]); + strlcat(R, H, sizeof (R)); + strlcat(R, T1 + 1, sizeof (R)); /* ok, since T1 is a short sub-string coming from one of the tables */ + } else + strlcpy(R, T, sizeof (R)); /* ok, since T is a short string coming from one of the tables */ + if ( (P = strchr(R, '%')) ) { + *P = C; + if ( (P = strchr(P + 1, '%')) ) + *P = C; + } + + if ( (P = strchr(R, '*')) ) { + strncpy(S, R, P - R); + S[P - R] = '\0'; + printHex2(H, val[B++]); + strcat(S, H); + strcat(S, P + 1); + } else if ( (P = strchr(R, '@')) ) { + strncpy(S, R, P - R); + S[P - R] = '\0'; + if (!J) + Offset = val[B++]; + strcat(S, Offset & 0x80 ? "-" : "+"); + J = Offset & 0x80 ? 256 - Offset : Offset; + printHex2(H, J); + strcat(S, H); + strcat(S, P + 1); + } else if ( (P = strchr(R, '$')) ) { + strncpy(S, R, P - R); + S[P - R] = '\0'; + Offset = val[B++]; + printHex4(H, (addr + 2 + (Offset & 0x80 ? (Offset - 256) : Offset)) & 0xFFFF); + strcat(S, H); + strcat(S, P + 1); + } else if ( (P = strchr(R, '#')) ) { + strncpy(S, R, P - R); + S[P - R] = '\0'; + printHex4(H, val[B] + 256 * val[B + 1]); + strcat(S, H); + strcat(S, P + 1); + B += 2; + } else + strcpy(S, R); + return(B); +} + +/* checkbase determines the base of the number (ch, *numString) + and returns FALSE if the number is bad */ +static int32 checkbase(char ch, const char *numString) { + int32 decimal = (ch <= '9'); + if (toupper(ch) == 'H') + return FALSE; + while (isxdigit(ch = *numString++)) + if (ch > '9') + decimal = FALSE; + return toupper(ch) == 'H' ? 16 : (decimal ? 10 : FALSE); +} + +static int32 numok(char ch, const char **numString, const int32 minvalue, + const int32 maxvalue, const int32 requireSign, int32 *result) { + int32 sign = 1, value = 0, base; + if (requireSign) { + if (ch == '+') + ch = *(*numString)++; + else if (ch == '-') { + sign = -1; + ch = *(*numString)++; + } else + return FALSE; + } + if (!(base = checkbase(ch, *numString))) + return FALSE; + while (isxdigit(ch)) { + value = base * value + ((ch <= '9') ? (ch - '0') : (toupper(ch) - 'A' + 10)); + ch = *(*numString)++; + } + if (toupper(ch) != 'H') + (*numString)--; + *result = value * sign; + return (minvalue <= value) && (value <= maxvalue); +} + +static int32 match(const char *pattern, const char *input, char *xyFirst, char *xy, int32 *number, int32 *star, + int32 *at, int32 *hat, int32 *dollar) { + char pat = *pattern++; + char inp = *input++; + while ((pat) && (inp)) { + switch(pat) { + + case '_': /* patterns containing '_' should never match */ + return FALSE; + + case ',': + if (inp == ' ') { + inp = *input++; + continue; + } /* otherwise fall through */ + + case ' ': + if (inp != pat) + return FALSE; + pat = *pattern++; + inp = *input++; + while (inp == ' ') + inp = *input++; + continue; + + case '%': + inp = toupper(inp); + if ((inp == 'X') || (inp == 'Y')) + if (*xyFirst) /* make sure that second '%' corresponds to first */ + if (*xyFirst == inp) + *xy = inp; + else + return FALSE; + else { /* take note of first '%' for later */ + *xyFirst = inp; + *xy = inp; + } + else + return FALSE; + break; + + case '#': + if (numok(inp, &input, 0, 65535, FALSE, number)) + pattern++; /* skip h */ + else + return FALSE; + break; + + case '*': + if (numok(inp, &input, 0, 255, FALSE, star)) + pattern++; /* skip h */ + else + return FALSE; + break; + + case '@': + if (numok(inp, &input, -128, 65535, TRUE, at)) + pattern++; /* skip h */ + else + return FALSE; + break; + + case '$': + if (numok(inp, &input, 0, 65535, FALSE, dollar)) + pattern++; /* skip h */ + else + return FALSE; + break; + + case '^': + if (numok(inp, &input, 0, 255, FALSE, hat)) + pattern++; /* skip h */ + else + return FALSE; + break; + + default: + if (toupper(pat) != toupper(inp)) + return FALSE; + } + + pat = *pattern++; + inp = *input++; + } + + while (inp == ' ') + inp = *input++; + + return (pat == 0) && (inp == 0); +} + +static int32 checkXY(const char xy) { + return xy == 'X' ? 0xdd : 0xfd; /* else is 'Y' */ +} + + +static int32 parse_X80(const char *cptr, const int32 addr, uint32 *val, const char *const Mnemonics[]) { + char xyFirst = 0, xy; + int32 op, number, star, at, hat, dollar; + for (op = 0; op < 256; op++) { + number = star = at = dollar = -129; + if (match(Mnemonics[op], cptr, &xyFirst, &xy, &number, &star, &at, &hat, &dollar)) { + val[0] = op; + if (number >= 0) { + val[1] = (0xff) & number; + val[2] = (0xff) & (number >> 8); + return -2; /* two additional bytes returned */ + } else if (star >= 0) { + val[1] = (0xff) & star; + return -1; /* one additional byte returned */ + } else if (at > -129) + if ((-128 <= at) && (at <= 127)) { + val[1] = (int8)(at); + return -1; /* one additional byte returned */ + } else + return SCPE_ARG; + else if (dollar >= 0) { + dollar -= addr + 2; /* relative translation */ + if ((-128 <= dollar) && (dollar <= 127)) { + val[1] = (int8)(dollar); + return -1; /* one additional byte returned */ + } else + return SCPE_ARG; + } else + return SCPE_OK; + } + } + if (Mnemonics == Mnemonics8080) + return SCPE_ARG; + + for (op = 0; op < 256; op++) + if (match(MnemonicsCB[op], cptr, &xyFirst, &xy, &number, &star, &at, &hat, &dollar)) { + val[0] = 0xcb; + val[1] = op; + return -1; /* one additional byte returned */ + } + + for (op = 0; op < 256; op++) { + number = -1; + if (match(MnemonicsED[op], cptr, &xyFirst, &xy, &number, &star, &at, &hat, &dollar)) { + val[0] = 0xed; + val[1] = op; + if (number >= 0) { + val[2] = (0xff) & number; + val[3] = (0xff) & (number >> 8); + return -3; /* three additional bytes returned */ + } else + return -1; /* one additional byte returned */ + } + } + + for (op = 0; op < 256; op++) { + number = star = hat = -1; + xy = 0; + if (match(MnemonicsXX[op], cptr, &xyFirst, &xy, &number, &star, &at, &hat, &dollar)) { + /* all matches must have contained a '%' character */ + if (!(val[0] = checkXY(xy))) + return SCPE_ARG; + val[1] = op; + if (number >= 0) { + val[2] = (0xff) & number; + val[3] = (0xff) & (number >> 8); + return -3; /* three additional bytes returned */ + } else if ((star >= 0) && (hat >= 0)) { + val[2] = (0xff) & hat; + val[3] = (0xff) & star; + return -3; /* three additional bytes returned */ + } else if (star >= 0) { + val[2] = (0xff) & star; + return -2; /* two additional bytes returned */ + } else if (hat >= 0) { + val[2] = (0xff) & hat; + return -2; /* two additional bytes returned */ + } else + return -1; /* one additional byte returned */ + } + } + + for (op = 0; op < 256; op++) { + at = -129; + xy = 0; + if (match(MnemonicsXCB[op], cptr, &xyFirst, &xy, &number, &star, &at, &hat, &dollar)) { + /* all matches must have contained a '%' character */ + if (!(val[0] = checkXY(xy))) + return SCPE_ARG; + val[1] = 0xcb; + if (at > -129) + val[2] = (int8) (at); + else { + sim_printf("Offset expected.\n"); + return SCPE_ARG; + } + val[3] = op; + return -3; /* three additional bytes returned */ + } + } + return SCPE_ARG; +} + +t_stat z80_parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) +{ + return (parse_X80(cptr, addr, val, z80_chiptype == CHIP_TYPE_Z80 ? MnemonicsZ80 : Mnemonics8080)); +} + +t_stat z80_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nIntel 8080 / Zilog Z80 CPU (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_z80.h b/Altair8800/s100_z80.h new file mode 100644 index 00000000..a52b3902 --- /dev/null +++ b/Altair8800/s100_z80.h @@ -0,0 +1,70 @@ +/* s100_z80.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/07/25 Initial version + +*/ + +#ifndef S100_Z80_H_ +#define S100_Z80_H_ + +#include "s100_cpu.h" + +#define LDA_INSTRUCTION 0x3e /* op-code for LD A,<8-bit value> instruction */ +#define UNIT_NO_OFFSET_1 0x37 /* LD A, */ +#define UNIT_NO_OFFSET_2 0xb4 /* LD a,80h | */ +#define LDB_INSTRUCTION 0x06 /* op-code for LD B,<8-bit value> instruction */ +#define START_SECTOR_OFFSET 0x57 /* LD B, */ + +#define CPU_INDEX_8080 4 /* index of default PC register */ + +/* simulator stop codes */ +#define STOP_IBKPT 1 /* breakpoint (program counter) */ +#define STOP_MEM 2 /* breakpoint (memory access) */ +#define STOP_INSTR 3 /* breakpoint (instruction access) */ +#define STOP_OPCODE 4 /* invalid operation encountered (8080, Z80, 8086) */ +#define STOP_HALT 5 /* HALT */ + +#define UNIT_Z80_V_OPSTOP (UNIT_V_UF+0) /* stop on invalid operation */ +#define UNIT_Z80_OPSTOP (1 << UNIT_Z80_V_OPSTOP) +#define UNIT_Z80_V_STOPONHALT (UNIT_V_UF+1) /* stop simulation on HALT */ +#define UNIT_Z80_STOPONHALT (1 << UNIT_Z80_V_STOPONHALT) + +#define PLURAL(x) (x), (x) == 1 ? "" : "s" + +extern ChipType z80_chiptype; +extern REG *z80_pc_reg; + +extern t_stat z80_instr(void); +extern t_value z80_pc_value(void); +extern t_stat z80_parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw); +extern t_bool z80_is_pc_a_subroutine_call (t_addr **ret_addrs); +extern int32 z80_dasm(char *S, const uint32 *val, const int32 addr); +extern t_stat z80_cmd_reg(int32 flag, CONST char *cptr); +extern t_bool z80_is_pc_a_subroutine_call(t_addr **ret_addrs); +extern t_stat z80_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +#endif diff --git a/Altair8800/sds_sbc200.c b/Altair8800/sds_sbc200.c new file mode 100644 index 00000000..d42dd371 --- /dev/null +++ b/Altair8800/sds_sbc200.c @@ -0,0 +1,1294 @@ +/* sds_sbc200.c: SD Systems SBC-200 + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 07-Nov-2025 Initial version + +*/ + +#include "sim_tmxr.h" +#include "s100_bus.h" +#include "s100_cpu.h" + +#define SBC200_NAME "SD SYSTEMS SBC-200" +#define SBC200_SNAME "SBC200" + +#define SBC200_WAIT 250 /* Service Wait Interval */ + +#define SBC200_IOBASE 0x78 +#define SBC200_IOSIZE 8 + +#define SBC200_MON_BASE 0xe000 +#define SBC200_MON_SIZE 2048 +#define SBC200_MON_MASK (SBC200_MON_SIZE-1) + +#define SBC200_DDB_BASE 0xf000 +#define SBC200_DDB_SIZE 2048 +#define SBC200_DDB_MASK (SBC200_DDB_SIZE-1) + +RES sbc200_iores = { SBC200_IOBASE, SBC200_IOSIZE, 0x0000, 0 }; +RES sbc200_monres = { 0x00, 0, SBC200_MON_BASE, SBC200_MON_SIZE }; +RES sbc200_ddbres = { 0x00, 0, SBC200_DDB_BASE, SBC200_DDB_SIZE }; + +#define SBC200_TRDY 0x01 /* Transmit Data Ready */ +#define SBC200_RDRF 0x02 /* Receive Data Register Full */ +#define SBC200_TDRE 0x04 /* Transmit Data Register Empty */ +#define SBC200_PE 0x08 /* Parity Error */ +#define SBC200_OVRN 0x10 /* Overrun */ +#define SBC200_FE 0x20 /* Framing Error */ +#define SBC200_DSR 0x80 /* Data Set Ready */ +#define SBC200_IR 0x40 /* Internal Reset */ +#define SBC200_CLK1 0x01 /* Divide Clock by 1 */ +#define SBC200_CLK16 0x02 /* Divide Clock by 16 */ +#define SBC200_CLK64 0x03 /* Divide Clock by 64 */ +#define SBC200_72E 0xf8 /* 7-2-E */ +#define SBC200_72O 0xd8 /* 7-2-O */ +#define SBC200_72N 0xc8 /* 7-2-O */ +#define SBC200_71E 0x78 /* 7-1-E */ +#define SBC200_71O 0x58 /* 7-1-O */ +#define SBC200_71N 0x48 /* 7-1-O */ +#define SBC200_82E 0xfc /* 8-2-E */ +#define SBC200_82O 0xdc /* 8-2-O */ +#define SBC200_82N 0xcc /* 8-2-O */ +#define SBC200_81E 0x7c /* 8-1-E */ +#define SBC200_81O 0xbc /* 8-1-O */ +#define SBC200_81N 0x4c /* 8-1-N */ +#define SBC200_FMTMSK 0xfc /* Length, Parity, Stop Mask */ +#define SBC200_DTR 0x02 /* RTS Bit Mask */ +#define SBC200_RTS 0x20 /* RTS Bit Mask */ +#define SBC200_RIE 0x80 /* Receive Int Enabled */ + +#define SBC200_BAUD 9600 /* Default baud rate */ + +/* SBC-200 Monitor 2.1 @ E000 */ +static uint8 sbc200_mon_rom[SBC200_MON_SIZE] = { + 0xc3, 0x0f, 0xe0, 0xc3, 0x79, 0xe0, 0xc3, 0x0f, + 0xe7, 0xc3, 0x17, 0xe7, 0xc3, 0x25, 0xe7, 0xdb, + 0x7f, 0x21, 0x60, 0xff, 0x22, 0xe6, 0xff, 0xaf, + 0x32, 0xc0, 0xff, 0x3e, 0x4e, 0xd3, 0x7d, 0x3e, + 0x37, 0xd3, 0x7d, 0x3e, 0x45, 0xd3, 0x78, 0x0e, + 0x7d, 0x11, 0x01, 0x00, 0xed, 0x78, 0xf2, 0x2c, + 0xe0, 0x13, 0xed, 0x78, 0xfa, 0x31, 0xe0, 0x31, + 0x59, 0xe0, 0xc1, 0xe1, 0x37, 0xed, 0x52, 0x38, + 0xf9, 0xc1, 0x3e, 0x40, 0xd3, 0x7d, 0x78, 0xd3, + 0x7d, 0x3e, 0x37, 0xd3, 0x7d, 0x79, 0xd3, 0x78, + 0x3e, 0x64, 0x06, 0xc8, 0x10, 0xfe, 0x3d, 0x20, + 0xf9, 0x18, 0x1c, 0x16, 0x00, 0x0d, 0x4e, 0x2c, + 0x00, 0x1a, 0x4e, 0x57, 0x00, 0x34, 0x4e, 0xae, + 0x00, 0x68, 0x4e, 0x5c, 0x01, 0xd0, 0x4e, 0xb8, + 0x02, 0x68, 0x4f, 0xff, 0x7f, 0xd0, 0x4f, 0xdb, + 0x7c, 0xaf, 0x32, 0xfa, 0xff, 0x3c, 0x32, 0xc6, + 0xff, 0x31, 0xc0, 0xff, 0xcd, 0x2f, 0xe7, 0x0e, + 0x2e, 0xcd, 0x0c, 0xe0, 0xcd, 0x21, 0xe7, 0xfe, + 0x2e, 0x28, 0xee, 0xc5, 0xcd, 0x39, 0xe7, 0xcd, + 0xb6, 0xe7, 0xc1, 0x79, 0x21, 0x81, 0xe0, 0xe5, + 0x2a, 0xd2, 0xff, 0xed, 0x5b, 0xd0, 0xff, 0xfd, + 0x21, 0xc0, 0xff, 0xfe, 0x42, 0x20, 0x3c, 0xd5, + 0x3a, 0xc0, 0xff, 0xcb, 0x47, 0x28, 0x10, 0xed, + 0x5b, 0xc4, 0xff, 0x21, 0xc1, 0xff, 0x01, 0x03, + 0x00, 0xed, 0xb0, 0xaf, 0x32, 0xc0, 0xff, 0x3a, + 0xc7, 0xff, 0xa7, 0xe1, 0x28, 0x19, 0x22, 0xc4, + 0xff, 0xe5, 0x11, 0xc1, 0xff, 0x01, 0x03, 0x00, + 0xc5, 0xed, 0xb0, 0x21, 0xe8, 0xe0, 0xc1, 0xd1, + 0xed, 0xb0, 0x3e, 0x01, 0x32, 0xc0, 0xff, 0xc9, + 0xc3, 0xd3, 0xe5, 0xfe, 0x47, 0x20, 0x13, 0x3a, + 0xc7, 0xff, 0xa7, 0x28, 0x05, 0xed, 0x53, 0xfe, + 0xff, 0xf1, 0x3e, 0x01, 0x32, 0xc9, 0xff, 0xc3, + 0x14, 0xe6, 0xfe, 0x52, 0x20, 0x06, 0xcd, 0x16, + 0xe1, 0xc3, 0x2d, 0xf0, 0xfe, 0x57, 0x20, 0x27, + 0xcd, 0x16, 0xe1, 0xc3, 0x30, 0xf0, 0x3a, 0xc7, + 0xff, 0xfe, 0x05, 0xda, 0x49, 0xe4, 0xed, 0x53, + 0x40, 0x00, 0x7d, 0x21, 0x42, 0x00, 0x77, 0x23, + 0x3a, 0xd6, 0xff, 0x77, 0x23, 0x3a, 0xd4, 0xff, + 0x77, 0x23, 0x3a, 0xd8, 0xff, 0x77, 0xc9, 0xfe, + 0x5a, 0x20, 0x45, 0x3a, 0xc7, 0xff, 0x3d, 0xc2, + 0x49, 0xe4, 0x7b, 0x32, 0x42, 0x00, 0xaf, 0x32, + 0x44, 0x00, 0x3c, 0x32, 0x43, 0x00, 0xcd, 0x18, + 0xf0, 0xdb, 0x63, 0xf6, 0x50, 0xd3, 0x63, 0xcd, + 0x33, 0xf0, 0xcd, 0x6e, 0xe1, 0x28, 0x07, 0xcb, + 0xa7, 0xd3, 0x63, 0xcd, 0x33, 0xf0, 0xcd, 0x73, + 0xe1, 0x20, 0xe6, 0xc3, 0x18, 0xf0, 0xdb, 0x63, + 0xcb, 0x6f, 0xc9, 0x3a, 0x44, 0x00, 0x3c, 0x32, + 0x44, 0x00, 0x47, 0x3a, 0x40, 0xf0, 0xb8, 0xc9, + 0xfe, 0x51, 0x20, 0x34, 0x7b, 0x32, 0x42, 0x00, + 0xaf, 0x32, 0x44, 0x00, 0x3e, 0x01, 0x32, 0x43, + 0x00, 0x3a, 0x3f, 0xf0, 0x47, 0xcd, 0x6e, 0xe1, + 0x28, 0x02, 0xcb, 0x20, 0x78, 0x32, 0x45, 0x00, + 0x21, 0x00, 0x01, 0x22, 0x40, 0x00, 0xcd, 0x2d, + 0xf0, 0xcd, 0xbe, 0xe5, 0xcd, 0x73, 0xe1, 0x20, + 0xdb, 0x0e, 0x50, 0xcd, 0x0c, 0xe0, 0x18, 0xd0, + 0xfe, 0x4d, 0x20, 0x23, 0xcd, 0xcb, 0xe5, 0x44, + 0x4d, 0xeb, 0xed, 0x5b, 0xd4, 0xff, 0xb7, 0xed, + 0x52, 0x30, 0x0e, 0x2a, 0xd4, 0xff, 0x09, 0x2b, + 0x54, 0x5d, 0x2a, 0xd2, 0xff, 0xed, 0xb8, 0x18, + 0x05, 0x2a, 0xd0, 0xff, 0xed, 0xb0, 0xc9, 0xfe, + 0x43, 0xca, 0x03, 0xf0, 0xfe, 0x48, 0x20, 0x1d, + 0xe5, 0x19, 0x0e, 0x2b, 0xcd, 0x0c, 0xe0, 0xcd, + 0xf0, 0xe7, 0xcd, 0x39, 0xe7, 0xe1, 0xeb, 0xb7, + 0xed, 0x52, 0x0e, 0x2d, 0xcd, 0x0c, 0xe0, 0xcd, + 0xf0, 0xe7, 0xc3, 0x81, 0xe0, 0xfe, 0x58, 0x20, + 0x15, 0xaf, 0xbb, 0xc4, 0x3e, 0xe7, 0x3a, 0xc7, + 0xff, 0xfe, 0x02, 0x20, 0x06, 0x3a, 0xd2, 0xff, + 0x32, 0xc6, 0xff, 0xc3, 0x88, 0xe6, 0xfe, 0x49, + 0x20, 0x36, 0xfd, 0xcb, 0x0a, 0x86, 0x3a, 0xd0, + 0xff, 0x4f, 0x06, 0x01, 0x3a, 0xc7, 0xff, 0xfe, + 0x02, 0x38, 0x0b, 0x3a, 0xd2, 0xff, 0x47, 0xb7, + 0x20, 0x04, 0xfd, 0xcb, 0x0a, 0xc6, 0x79, 0xcd, + 0xf5, 0xe7, 0xed, 0x78, 0xcd, 0xf5, 0xe7, 0xcd, + 0xbe, 0xe5, 0xc5, 0xcd, 0x2f, 0xe7, 0xc1, 0xfd, + 0xcb, 0x0a, 0x46, 0x20, 0xe9, 0x10, 0xe7, 0xc9, + 0xfe, 0x4f, 0x20, 0x2e, 0xfd, 0xcb, 0x0a, 0x86, + 0x3a, 0xd0, 0xff, 0x4f, 0x3a, 0xd2, 0xff, 0x57, + 0x06, 0x01, 0x3a, 0xc7, 0xff, 0xfe, 0x03, 0x38, + 0x0b, 0x3a, 0xd4, 0xff, 0x47, 0xb7, 0x20, 0x04, + 0xfd, 0xcb, 0x0a, 0xc6, 0xed, 0x51, 0xcd, 0xbe, + 0xe5, 0xfd, 0xcb, 0x0a, 0x46, 0x20, 0xf5, 0x10, + 0xf3, 0xc9, 0xfe, 0x46, 0x20, 0x12, 0x3a, 0xd4, + 0xff, 0xe5, 0xcd, 0xcb, 0xe5, 0xe1, 0x12, 0xe5, + 0xb7, 0xed, 0x52, 0xe1, 0x13, 0x20, 0xf7, 0xc9, + 0xfe, 0x4c, 0x20, 0x4d, 0xcd, 0xcb, 0xe5, 0x3a, + 0xc7, 0xff, 0xd6, 0x03, 0xda, 0x49, 0xe4, 0x47, + 0x3c, 0x32, 0xc7, 0xff, 0x21, 0xd5, 0xff, 0x11, + 0xd6, 0xff, 0x1a, 0x77, 0x23, 0x13, 0x13, 0x10, + 0xf9, 0x3a, 0xc7, 0xff, 0x47, 0x2a, 0xd0, 0xff, + 0x11, 0xd4, 0xff, 0x1a, 0xbe, 0x20, 0x10, 0x23, + 0x13, 0x10, 0xf8, 0x2a, 0xd0, 0xff, 0xcd, 0xf0, + 0xe7, 0xcd, 0x2f, 0xe7, 0xcd, 0xbe, 0xe5, 0x2a, + 0xd2, 0xff, 0xed, 0x5b, 0xd0, 0xff, 0xb7, 0xed, + 0x52, 0xc8, 0x13, 0xed, 0x53, 0xd0, 0xff, 0x18, + 0xd0, 0xfe, 0x56, 0x20, 0x38, 0xcd, 0xcb, 0xe5, + 0xe5, 0xc1, 0xeb, 0xed, 0x5b, 0xd4, 0xff, 0x1a, + 0xed, 0xa1, 0x13, 0x20, 0x03, 0xe0, 0x18, 0xf7, + 0xf5, 0xc5, 0xd5, 0x2b, 0xcd, 0xf0, 0xe7, 0x7e, + 0x23, 0xcd, 0xf5, 0xe7, 0xd1, 0xd5, 0xe5, 0xeb, + 0x2b, 0xcd, 0xf0, 0xe7, 0x7e, 0xcd, 0x4d, 0xe7, + 0xcd, 0x2f, 0xe7, 0xe1, 0xd1, 0xc1, 0xf1, 0xe0, + 0xcd, 0xbe, 0xe5, 0x18, 0xd2, 0xfe, 0x54, 0x20, + 0x3f, 0xeb, 0x13, 0x06, 0x00, 0x2a, 0xd0, 0xff, + 0x7d, 0xac, 0xa8, 0x77, 0x23, 0xe5, 0xb7, 0xed, + 0x52, 0xe1, 0x20, 0xf4, 0x2a, 0xd0, 0xff, 0x7d, + 0xac, 0xa8, 0xbe, 0xc4, 0x61, 0xe3, 0x23, 0xe5, + 0xb7, 0xed, 0x52, 0xe1, 0x20, 0xf1, 0x04, 0xcd, + 0xbe, 0xe5, 0x0e, 0x50, 0xcd, 0x0c, 0xe0, 0x18, + 0xd4, 0xf5, 0xcd, 0xf0, 0xe7, 0xf1, 0xcd, 0xf5, + 0xe7, 0x7e, 0xcd, 0xf5, 0xe7, 0xc3, 0x2f, 0xe7, + 0xfe, 0x44, 0x20, 0x5d, 0xfd, 0xcb, 0x0a, 0x86, + 0xeb, 0x3a, 0xc7, 0xff, 0xfe, 0x02, 0x30, 0x09, + 0x11, 0xff, 0x00, 0xe5, 0x19, 0x22, 0xd2, 0xff, + 0xe1, 0xcd, 0x2f, 0xe7, 0xe5, 0xc1, 0xe5, 0x2a, + 0xd2, 0xff, 0xb7, 0xed, 0x42, 0xda, 0x49, 0xe4, + 0x01, 0x0f, 0x00, 0xb7, 0xed, 0x42, 0x06, 0x10, + 0x28, 0x06, 0xd2, 0xac, 0xe3, 0x7d, 0x80, 0x47, + 0xfd, 0xcb, 0x0a, 0xc6, 0xe1, 0xc5, 0xcd, 0xf0, + 0xe7, 0xc1, 0xcd, 0xa8, 0xe6, 0xcd, 0xbe, 0xe5, + 0xfd, 0xcb, 0x0a, 0x46, 0x28, 0xcb, 0xcd, 0x09, + 0xe0, 0xfe, 0x2e, 0xc8, 0xfe, 0x20, 0x20, 0xf6, + 0xfd, 0xcb, 0x0a, 0x86, 0xcd, 0x2f, 0xe7, 0x18, + 0xaf, 0xfe, 0x45, 0x20, 0x38, 0xeb, 0xcd, 0xf0, + 0xe7, 0x7e, 0xcd, 0x4d, 0xe7, 0x0e, 0x2d, 0xcd, + 0x0c, 0xe0, 0xe5, 0xcd, 0xb6, 0xe7, 0xe1, 0x3a, + 0xc8, 0xff, 0xfe, 0x2e, 0xc8, 0x3a, 0xc7, 0xff, + 0xe6, 0x03, 0x28, 0x0f, 0x3a, 0xd0, 0xff, 0x77, + 0x23, 0x3a, 0xc8, 0xff, 0xfe, 0x0d, 0x28, 0xd6, + 0x2b, 0x18, 0xd3, 0x3a, 0xc8, 0xff, 0xfe, 0x5e, + 0x28, 0xf6, 0x23, 0x18, 0xc9, 0xfe, 0x50, 0x20, + 0x34, 0x3a, 0xd0, 0xff, 0x4f, 0x79, 0xcd, 0xf5, + 0xe7, 0xed, 0x78, 0xcd, 0xf5, 0xe7, 0xc5, 0xcd, + 0xb6, 0xe7, 0xc1, 0x3a, 0xc8, 0xff, 0xfe, 0x2e, + 0xc8, 0x67, 0x3a, 0xc7, 0xff, 0xa7, 0x28, 0x0d, + 0x3a, 0xd0, 0xff, 0xed, 0x79, 0x3e, 0x5e, 0xbc, + 0x28, 0xdb, 0x0c, 0x18, 0xd8, 0x3e, 0x5e, 0xbc, + 0x20, 0xf8, 0x0d, 0x18, 0xd0, 0xfe, 0x53, 0x28, + 0x08, 0x0e, 0x3f, 0xcd, 0x0c, 0xe0, 0xc3, 0x81, + 0xe0, 0xf1, 0xcd, 0x3e, 0xe7, 0x3a, 0xc7, 0xff, + 0xa7, 0x28, 0x04, 0xeb, 0x22, 0xfe, 0xff, 0x3a, + 0xd2, 0xff, 0xa7, 0x20, 0x01, 0x3c, 0x32, 0xc9, + 0xff, 0xaf, 0x32, 0xc7, 0xff, 0xcd, 0xaf, 0xe0, + 0xed, 0x5b, 0xfe, 0xff, 0x1a, 0xfe, 0x40, 0x38, + 0x05, 0xfe, 0xc0, 0xda, 0x53, 0xe5, 0xe6, 0x03, + 0x47, 0x1a, 0x1f, 0x1f, 0xe6, 0x1f, 0xc5, 0x01, + 0x9a, 0xe5, 0x81, 0x6f, 0x60, 0xc1, 0x7e, 0x04, + 0x10, 0x29, 0xe6, 0x03, 0xca, 0x3e, 0xe5, 0xf5, + 0x1a, 0x2a, 0xf4, 0xff, 0xfe, 0xe9, 0xca, 0x07, + 0xe6, 0xfe, 0xc3, 0x28, 0x70, 0xfe, 0xcd, 0x28, + 0x6c, 0xfe, 0xc9, 0x28, 0x5f, 0xfe, 0x10, 0x20, + 0x10, 0x21, 0xf9, 0xff, 0x35, 0x20, 0x47, 0x34, + 0xc3, 0x74, 0xe5, 0xcb, 0x3f, 0xcb, 0x3f, 0x18, + 0xcf, 0xfe, 0x18, 0x28, 0x39, 0xfe, 0x80, 0x30, + 0x0a, 0xee, 0x20, 0x47, 0xe6, 0x67, 0x20, 0xe8, + 0x78, 0x18, 0x0d, 0xe6, 0xc7, 0xfe, 0xc2, 0x28, + 0x06, 0xe6, 0xc3, 0xfe, 0xc0, 0x20, 0x43, 0x1a, + 0xe6, 0x30, 0x21, 0xb9, 0xe5, 0x23, 0xd6, 0x10, + 0x30, 0xfb, 0x3a, 0xfc, 0xff, 0xa6, 0x1a, 0x28, + 0x01, 0x2f, 0xcb, 0x5f, 0x20, 0xc2, 0xf1, 0xf5, + 0xfe, 0x02, 0x38, 0x10, 0x20, 0x17, 0x13, 0x1a, + 0x13, 0x6f, 0x17, 0x26, 0x00, 0x30, 0x01, 0x25, + 0x19, 0x18, 0x30, 0x1b, 0x2a, 0xe6, 0xff, 0x7e, + 0x23, 0x66, 0x6f, 0x18, 0x64, 0x1a, 0xcb, 0x57, + 0x13, 0x1a, 0x6f, 0x13, 0x1a, 0x67, 0x20, 0x59, + 0x18, 0x19, 0x1a, 0xe6, 0xc7, 0xfe, 0xc7, 0x1a, + 0x20, 0x07, 0xe6, 0x38, 0x6f, 0x26, 0x00, 0x18, + 0x48, 0xfe, 0xfb, 0x20, 0x3f, 0x2f, 0x32, 0xfa, + 0xff, 0xeb, 0x23, 0xc3, 0x07, 0xe6, 0x1a, 0x13, + 0xfe, 0xed, 0x20, 0x13, 0x06, 0x03, 0x1a, 0xe6, + 0xf7, 0xfe, 0x45, 0x28, 0xbe, 0xe6, 0xc7, 0xfe, + 0x43, 0x28, 0x22, 0x06, 0x01, 0x18, 0x1e, 0xfe, + 0xdd, 0x2a, 0xea, 0xff, 0x28, 0x03, 0x2a, 0xe8, + 0xff, 0x1a, 0xfe, 0xe9, 0x28, 0xd5, 0x21, 0x95, + 0xe5, 0x01, 0x05, 0x00, 0xed, 0xb1, 0x20, 0x0f, + 0x04, 0x04, 0x04, 0xc5, 0xc1, 0xeb, 0x23, 0x10, + 0xfd, 0xcd, 0xce, 0xe0, 0xc3, 0x14, 0xe6, 0xe6, + 0xfe, 0xfe, 0x34, 0x28, 0xec, 0x1a, 0xe6, 0x07, + 0xfe, 0x06, 0x28, 0xe5, 0x1a, 0xe6, 0xf8, 0xfe, + 0x70, 0x28, 0xde, 0x18, 0xdd, 0x21, 0x22, 0x2a, + 0x36, 0xcb, 0x5d, 0x65, 0x55, 0x65, 0x5e, 0x65, + 0x56, 0x65, 0x7e, 0x65, 0x76, 0x65, 0x7e, 0x65, + 0x76, 0x65, 0xf5, 0x67, 0xb5, 0x6f, 0xb5, 0x67, + 0xb5, 0x63, 0x75, 0x67, 0x75, 0x63, 0x75, 0x67, + 0x75, 0x63, 0x40, 0x01, 0x04, 0x80, 0xcd, 0x06, + 0xe0, 0xc8, 0xcd, 0x09, 0xe0, 0xfe, 0x2e, 0xc0, + 0xc3, 0x81, 0xe0, 0xb7, 0xed, 0x52, 0xda, 0x49, + 0xe4, 0x23, 0xc9, 0xed, 0x73, 0xe6, 0xff, 0x31, + 0x00, 0x00, 0xf5, 0xf5, 0xb7, 0xed, 0x57, 0xf5, + 0xf3, 0xc5, 0xd5, 0xe5, 0xd9, 0x08, 0xf5, 0xc5, + 0xd5, 0xe5, 0x3a, 0xfa, 0xff, 0xe6, 0x04, 0x32, + 0xfa, 0xff, 0xd9, 0x08, 0xdd, 0xe5, 0xfd, 0xe5, + 0x21, 0xc1, 0xff, 0xed, 0x5b, 0xc4, 0xff, 0x01, + 0x03, 0x00, 0xed, 0xb0, 0x2a, 0xc4, 0xff, 0x3e, + 0x80, 0x32, 0xc0, 0xff, 0x22, 0xfe, 0xff, 0x31, + 0xc0, 0xff, 0x18, 0x32, 0xed, 0x7b, 0xe6, 0xff, + 0x2a, 0xfe, 0xff, 0xe5, 0x2a, 0xfc, 0xff, 0xe5, + 0xed, 0x73, 0xe4, 0xff, 0x31, 0xe8, 0xff, 0xfd, + 0xe1, 0xdd, 0xe1, 0xd9, 0x08, 0xe1, 0xd1, 0xc1, + 0xf1, 0xd9, 0x08, 0xe1, 0xd1, 0xc1, 0xf1, 0xed, + 0x47, 0xed, 0x7b, 0xe4, 0xff, 0xea, 0x43, 0xe6, + 0xf1, 0xf3, 0xc9, 0xf1, 0xfb, 0xc9, 0xcd, 0x88, + 0xe6, 0x3a, 0xc0, 0xff, 0x47, 0xaf, 0x32, 0xc0, + 0xff, 0x21, 0xc9, 0xff, 0x35, 0x28, 0x07, 0xcd, + 0x06, 0xe0, 0x20, 0x02, 0x18, 0x23, 0xcd, 0x21, + 0xe7, 0xfe, 0x2e, 0xca, 0x81, 0xe0, 0xfe, 0x0d, + 0x28, 0x15, 0xfe, 0x20, 0x20, 0xf0, 0xaf, 0xbe, + 0x36, 0x00, 0x20, 0xea, 0xcd, 0x2f, 0xe7, 0xcd, + 0x3e, 0xe7, 0x3e, 0x0b, 0xc3, 0x66, 0xe4, 0x36, + 0x01, 0xcd, 0x2f, 0xe7, 0x7e, 0xc3, 0x66, 0xe4, + 0x2a, 0xfe, 0xff, 0xcd, 0xf0, 0xe7, 0x3a, 0xc6, + 0xff, 0x1f, 0x06, 0x01, 0x30, 0x02, 0x06, 0x0c, + 0x21, 0xfd, 0xff, 0x7e, 0xcd, 0x4d, 0xe7, 0x2b, + 0x7e, 0xcd, 0xf5, 0xe7, 0x2b, 0x10, 0xf4, 0xc9, + 0xc5, 0xe5, 0x7e, 0xcd, 0xf5, 0xe7, 0x23, 0x10, + 0xf9, 0xcd, 0x39, 0xe7, 0xcd, 0x39, 0xe7, 0xe1, + 0xc1, 0x7e, 0xe6, 0x7f, 0x4f, 0xfe, 0x20, 0x38, + 0x04, 0xfe, 0x7b, 0x38, 0x02, 0x0e, 0x2e, 0xcd, + 0x0c, 0xe0, 0x23, 0x10, 0xec, 0xc9, 0x50, 0x43, + 0x20, 0x20, 0x20, 0x41, 0x46, 0x20, 0x20, 0x49, + 0x20, 0x49, 0x46, 0x20, 0x20, 0x42, 0x43, 0x20, + 0x20, 0x20, 0x44, 0x45, 0x20, 0x20, 0x20, 0x48, + 0x4c, 0x20, 0x20, 0x41, 0x27, 0x46, 0x27, 0x20, + 0x42, 0x27, 0x43, 0x27, 0x20, 0x44, 0x27, 0x45, + 0x27, 0x20, 0x48, 0x27, 0x4c, 0x27, 0x20, 0x20, + 0x49, 0x58, 0x20, 0x20, 0x20, 0x49, 0x59, 0x20, + 0x20, 0x20, 0x53, 0x50, 0x0d, 0x0a, 0x03, 0xdb, + 0x7d, 0xe6, 0x02, 0xc8, 0x3e, 0xff, 0xc9, 0xcd, + 0x0f, 0xe7, 0x28, 0xfb, 0xdb, 0x7c, 0xe6, 0x7f, + 0xc9, 0xcd, 0x09, 0xe0, 0x4f, 0xdb, 0x7d, 0xe6, + 0x01, 0x28, 0xfa, 0x79, 0xd3, 0x7c, 0xc9, 0x0e, + 0x0d, 0xcd, 0x0c, 0xe0, 0x0e, 0x0a, 0xc3, 0x0c, + 0xe0, 0x0e, 0x20, 0xc3, 0x0c, 0xe0, 0x21, 0xce, + 0xe6, 0x7e, 0xfe, 0x03, 0xc8, 0x4f, 0xcd, 0x0c, + 0xe0, 0x23, 0x18, 0xf5, 0x00, 0xf5, 0x0f, 0x0f, + 0x0f, 0x0f, 0xcd, 0x56, 0xe7, 0xf1, 0xe6, 0x0f, + 0xc6, 0x90, 0x27, 0xce, 0x40, 0x27, 0x4f, 0xc3, + 0x0c, 0xe0, 0xd6, 0x30, 0xfe, 0x0a, 0xf8, 0xd6, + 0x07, 0xc9, 0xfe, 0x30, 0x38, 0x0e, 0xfe, 0x3a, + 0x38, 0x08, 0xfe, 0x40, 0x38, 0x06, 0xfe, 0x47, + 0x30, 0x02, 0xaf, 0xc9, 0xaf, 0x3c, 0xc9, 0xfe, + 0x20, 0xc8, 0xfe, 0x5e, 0x28, 0x08, 0xfe, 0x2e, + 0xca, 0x81, 0xe0, 0xfe, 0x0d, 0xc0, 0xc5, 0xcd, + 0x2f, 0xe7, 0xc1, 0xaf, 0xc9, 0xe5, 0xe5, 0xe5, + 0xe5, 0xe5, 0x21, 0x00, 0x00, 0x45, 0xcd, 0x21, + 0xe7, 0x04, 0xcd, 0x7f, 0xe7, 0xc8, 0xcd, 0x6a, + 0xe7, 0xc0, 0x79, 0xcd, 0x62, 0xe7, 0x29, 0x29, + 0x29, 0x29, 0x85, 0x6f, 0x18, 0xe8, 0xaf, 0x21, + 0xd0, 0xff, 0xe5, 0xdd, 0xe1, 0x77, 0x01, 0x09, + 0x00, 0x11, 0xd1, 0xff, 0xed, 0xb0, 0x32, 0xc7, + 0xff, 0xcd, 0x9a, 0xe7, 0xc2, 0x49, 0xe4, 0x79, + 0x32, 0xc8, 0xff, 0xfe, 0x20, 0x28, 0x02, 0x05, + 0xc8, 0xdd, 0x75, 0x00, 0xdd, 0x74, 0x01, 0x3a, + 0xc7, 0xff, 0x3c, 0x32, 0xc7, 0xff, 0xdd, 0x23, + 0xdd, 0x23, 0x79, 0xfe, 0x20, 0x28, 0xda, 0xc9, + 0x7c, 0xcd, 0x4d, 0xe7, 0x7d, 0xc5, 0xcd, 0x4d, + 0xe7, 0xcd, 0x39, 0xe7, 0xc1, 0xc9, 0xff, 0xff, + }; + +/* SBC-200 DDBIOS 3.3 @ F000 */ +static uint8 sbc200_ddb_rom[SBC200_DDB_SIZE] = { + 0x00, 0x1e, 0x00, 0xc3, 0x6c, 0xf0, 0xc3, 0x06, + 0xe0, 0xc3, 0x09, 0xe0, 0xc3, 0x0c, 0xe0, 0xc3, + 0x1b, 0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc3, 0x6a, 0xf1, 0xc3, 0x79, 0xf1, 0xc3, 0x7e, + 0xf1, 0xc3, 0x83, 0xf1, 0xc3, 0x88, 0xf1, 0xc3, + 0x8d, 0xf1, 0xc3, 0xa9, 0xf1, 0xc3, 0xc3, 0xf3, + 0xc3, 0xd0, 0xf3, 0xc3, 0x14, 0xf4, 0x00, 0x3a, + 0x80, 0x00, 0x5f, 0x16, 0x00, 0x21, 0x80, 0x1a, + 0x4d, 0x49, 0x06, 0x0b, 0x1b, 0x08, 0x18, 0x1c, + 0x32, 0x4d, 0x54, 0x08, 0x16, 0x10, 0x08, 0x18, + 0x1c, 0x1a, 0x4d, 0x2e, 0x08, 0x16, 0x36, 0x08, + 0x18, 0x1c, 0x12, 0x23, 0x0c, 0x06, 0x0b, 0x08, + 0x0b, 0x1b, 0x1f, 0x1d, 0x23, 0x54, 0x08, 0x16, + 0x10, 0x0b, 0x1b, 0x1f, 0x31, 0x80, 0x00, 0xdb, + 0x7f, 0x3e, 0x00, 0x32, 0x42, 0x00, 0x7b, 0xfe, + 0x04, 0xd2, 0x03, 0xe8, 0xf5, 0x3e, 0xef, 0xe5, + 0xed, 0x73, 0x4d, 0x00, 0xe1, 0x0f, 0x30, 0x1a, + 0xd3, 0x63, 0xcd, 0xb7, 0xf3, 0xe6, 0x04, 0x28, + 0x0d, 0xaf, 0xd3, 0x65, 0x3e, 0x02, 0x32, 0x44, + 0x00, 0x3e, 0x1a, 0xcd, 0x0a, 0xf3, 0xdb, 0x63, + 0x18, 0xe3, 0xf1, 0xf5, 0xcd, 0x6a, 0xf1, 0xf1, + 0xcd, 0x1b, 0xf1, 0x20, 0x19, 0xcd, 0xd0, 0xf0, + 0x21, 0x80, 0x00, 0x7e, 0xe6, 0xce, 0x20, 0x05, + 0xcb, 0x46, 0xc2, 0x80, 0x00, 0x3e, 0x31, 0x21, + 0x00, 0xe8, 0xbe, 0xca, 0x00, 0xe8, 0x3e, 0xe0, + 0x21, 0x02, 0xe0, 0xbe, 0xca, 0x03, 0xe0, 0x76, + 0x21, 0x80, 0x00, 0x22, 0x40, 0x00, 0xaf, 0x32, + 0x44, 0x00, 0x3c, 0x32, 0x43, 0x00, 0xcd, 0x8d, + 0xf1, 0xc8, 0x18, 0xe2, 0x3a, 0x42, 0x00, 0x5f, + 0x3e, 0x04, 0xb8, 0x28, 0x19, 0x3d, 0xb8, 0x28, + 0x0c, 0x3d, 0xb8, 0x28, 0x18, 0x3d, 0xb8, 0x28, + 0x1b, 0xf6, 0x01, 0xe1, 0xc9, 0x7b, 0xcb, 0xb7, + 0xcb, 0xbf, 0x32, 0x42, 0x00, 0xc9, 0x7b, 0xcb, + 0xff, 0x32, 0x42, 0x00, 0xc9, 0x7b, 0xcb, 0xef, + 0x32, 0x42, 0x00, 0xc9, 0x7b, 0xcb, 0xf7, 0x32, + 0x42, 0x00, 0xc9, 0x06, 0x04, 0xe6, 0x0f, 0xf6, + 0x40, 0x32, 0x42, 0x00, 0x2a, 0x40, 0x00, 0x22, + 0x51, 0x00, 0xcd, 0x4d, 0xf1, 0x2a, 0x51, 0x00, + 0x22, 0x40, 0x00, 0x3a, 0x42, 0x00, 0xc0, 0x30, + 0x02, 0xcb, 0xff, 0x5f, 0x3a, 0x49, 0x00, 0xb7, + 0x7b, 0xcb, 0xa7, 0xf2, 0x48, 0xf1, 0xcb, 0xe7, + 0x32, 0x42, 0x00, 0xaf, 0xc9, 0xc5, 0xdd, 0xe5, + 0xe5, 0xed, 0x73, 0x4d, 0x00, 0xe1, 0xcd, 0xe4, + 0xf1, 0xcd, 0x37, 0xf2, 0xdd, 0xe1, 0xc1, 0x3a, + 0x4b, 0x00, 0x0f, 0xc8, 0x05, 0xcd, 0xe4, 0xf0, + 0x18, 0xe3, 0xed, 0x73, 0x4d, 0x00, 0xdd, 0xe5, + 0xcd, 0xe4, 0xf1, 0xcd, 0xd8, 0xf1, 0xdd, 0xe1, + 0xc9, 0x79, 0x32, 0x42, 0x00, 0xc9, 0x79, 0x32, + 0x44, 0x00, 0xc9, 0x79, 0x32, 0x43, 0x00, 0xc9, + 0xed, 0x43, 0x40, 0x00, 0xc9, 0x01, 0x01, 0x03, + 0xed, 0x43, 0x56, 0x00, 0xdd, 0xe5, 0xdd, 0x2a, + 0x53, 0x00, 0xc5, 0xcd, 0x6c, 0xf2, 0xc1, 0x28, + 0x05, 0xcd, 0xc2, 0xf1, 0x18, 0xf4, 0xdd, 0xe1, + 0xc9, 0x01, 0x01, 0x03, 0xed, 0x43, 0x56, 0x00, + 0xdd, 0xe5, 0xdd, 0x2a, 0x53, 0x00, 0xc5, 0xcd, + 0xbd, 0xf2, 0xc1, 0x28, 0xe9, 0xcd, 0xc2, 0xf1, + 0x18, 0xf4, 0x10, 0x13, 0x3a, 0x57, 0x00, 0x47, + 0x0d, 0xf2, 0xd2, 0xf1, 0xf1, 0xdd, 0xe1, 0xaf, + 0x3c, 0xc9, 0xc5, 0xcd, 0xd8, 0xf1, 0xc1, 0xc9, + 0xed, 0x73, 0x4d, 0x00, 0xdd, 0x7e, 0x06, 0xcd, + 0x0a, 0xf3, 0xaf, 0xc9, 0x11, 0x42, 0x00, 0x1a, + 0xe6, 0xe0, 0x4f, 0x1a, 0xe6, 0x03, 0x47, 0x3e, + 0x01, 0x28, 0x03, 0x07, 0x10, 0xfd, 0xb1, 0xe6, + 0x7f, 0x47, 0x79, 0xdd, 0x21, 0x3f, 0xf0, 0xfe, + 0x00, 0x28, 0x1c, 0xdd, 0x21, 0x48, 0xf0, 0xfe, + 0x40, 0x28, 0x14, 0xdd, 0x21, 0x51, 0xf0, 0xfe, + 0xc0, 0x28, 0x0c, 0xdd, 0x21, 0x5a, 0xf0, 0xfe, + 0x20, 0x28, 0x04, 0xdd, 0x21, 0x63, 0xf0, 0xdd, + 0x22, 0x53, 0x00, 0xc5, 0xf1, 0x2f, 0xd3, 0x63, + 0x1a, 0x32, 0x55, 0x00, 0xcd, 0x59, 0xf2, 0xdb, + 0x64, 0xe6, 0x80, 0xc2, 0xe1, 0xf2, 0xc9, 0xcd, + 0x42, 0xf3, 0x21, 0x48, 0x00, 0x01, 0x67, 0x06, + 0x3e, 0xf8, 0x32, 0x46, 0x00, 0xcd, 0x75, 0xf3, + 0x3e, 0xc0, 0xcd, 0x77, 0xf2, 0x3a, 0x48, 0x00, + 0xfe, 0x4d, 0xd2, 0xfe, 0xf2, 0xd3, 0x65, 0xaf, + 0xc9, 0x3a, 0x42, 0x00, 0xcb, 0x6f, 0x3e, 0x27, + 0x28, 0x02, 0x3e, 0x3c, 0x06, 0x00, 0x10, 0xfe, + 0x3d, 0x20, 0xf9, 0xc9, 0xcd, 0x9c, 0xf2, 0x3e, + 0x88, 0xcd, 0x90, 0xf2, 0x2a, 0x40, 0x00, 0x32, + 0x4c, 0x00, 0xed, 0x5b, 0x4c, 0x00, 0xd5, 0xf3, + 0xd3, 0x64, 0x18, 0x00, 0x18, 0x00, 0xed, 0xb2, + 0xd1, 0xed, 0x53, 0x4c, 0x00, 0xfb, 0x18, 0x45, + 0x67, 0x3a, 0x49, 0x00, 0x6f, 0xe6, 0x01, 0x7c, + 0xc8, 0xcb, 0xcf, 0xc9, 0xe1, 0xed, 0x73, 0x4d, + 0x00, 0xe5, 0x3a, 0x42, 0x00, 0x57, 0x3a, 0x55, + 0x00, 0xba, 0x28, 0x06, 0xcd, 0xe4, 0xf1, 0xcd, + 0x37, 0xf2, 0xcd, 0xf1, 0xf2, 0x3e, 0xfe, 0x32, + 0x46, 0x00, 0xc3, 0x7c, 0xf3, 0xcd, 0x9c, 0xf2, + 0x3e, 0xa8, 0xcd, 0x90, 0xf2, 0x2a, 0x40, 0x00, + 0x32, 0x4c, 0x00, 0xf3, 0xd3, 0x64, 0x18, 0x00, + 0x18, 0x00, 0xed, 0xb3, 0xfb, 0xcd, 0x42, 0xf3, + 0xdb, 0x64, 0x57, 0x3a, 0x46, 0x00, 0xa2, 0xc8, + 0x7a, 0x32, 0x47, 0x00, 0xcd, 0x59, 0xf2, 0xf6, + 0x01, 0xed, 0x7b, 0x4d, 0x00, 0xcd, 0xf4, 0xf7, + 0xc9, 0xcd, 0x2f, 0xf2, 0xdd, 0x7e, 0x01, 0x4f, + 0x3a, 0x44, 0x00, 0xb9, 0x38, 0x04, 0x3e, 0x0f, + 0x18, 0xdf, 0x4f, 0xdb, 0x65, 0xb9, 0xc8, 0xdd, + 0x7e, 0x08, 0x32, 0x4c, 0x00, 0x06, 0xd2, 0x10, + 0xfe, 0xcd, 0x42, 0xf3, 0x3a, 0x44, 0x00, 0xd3, + 0x67, 0x3e, 0x80, 0x32, 0x46, 0x00, 0x3a, 0x4c, + 0x00, 0xd3, 0x64, 0x06, 0x0a, 0x10, 0xfe, 0xcd, + 0xd5, 0xf2, 0xcd, 0x59, 0xf2, 0x3a, 0x4c, 0x00, + 0xdd, 0xbe, 0x06, 0xc8, 0xdb, 0x64, 0xe6, 0x10, + 0x20, 0x04, 0xdb, 0x65, 0xb9, 0xc8, 0x3e, 0x20, + 0x18, 0x9f, 0x1e, 0x00, 0xc5, 0x0e, 0x02, 0xdb, + 0x64, 0xe6, 0x01, 0x28, 0x20, 0x10, 0xf8, 0x1d, + 0x20, 0xf5, 0x0d, 0x20, 0xf2, 0xc1, 0xdb, 0x63, + 0xf6, 0x80, 0xd3, 0x60, 0x10, 0xfe, 0xdb, 0x60, + 0xcd, 0xb7, 0xf3, 0xdd, 0x7e, 0x06, 0xcd, 0x0a, + 0xf3, 0x3e, 0xfe, 0x18, 0xd3, 0xc1, 0xdb, 0x63, + 0xf6, 0x80, 0xd3, 0x63, 0xc9, 0xdb, 0x63, 0xe6, + 0x7f, 0xd3, 0x63, 0xc9, 0x06, 0x00, 0xdd, 0x7e, + 0x00, 0x3c, 0x4f, 0x3a, 0x43, 0x00, 0xb9, 0x38, + 0x04, 0x06, 0x10, 0x0d, 0x91, 0xf5, 0xcd, 0xa1, + 0xf3, 0xf1, 0xd3, 0x66, 0x01, 0x67, 0x80, 0x3a, + 0x42, 0x00, 0x07, 0x30, 0xd8, 0x06, 0x00, 0x18, + 0xd4, 0xdb, 0x63, 0x2f, 0x5f, 0xe6, 0x10, 0xb8, + 0xc8, 0x7b, 0xe6, 0x6f, 0xb0, 0x2f, 0xd3, 0x63, + 0x06, 0xd2, 0x10, 0xfe, 0xc3, 0x37, 0xf2, 0x3e, + 0xd0, 0xd3, 0x64, 0x3e, 0x0a, 0x3d, 0x20, 0xfd, + 0xdb, 0x64, 0xc9, 0xcd, 0xf4, 0xf7, 0xcd, 0x8d, + 0xf1, 0xc0, 0xcd, 0xdd, 0xf3, 0x20, 0xf7, 0xc9, + 0xcd, 0xf4, 0xf7, 0xcd, 0xa9, 0xf1, 0xc0, 0xcd, + 0xdd, 0xf3, 0x20, 0xf7, 0xc9, 0x2a, 0x40, 0x00, + 0x11, 0x80, 0x00, 0x3a, 0x42, 0x00, 0x07, 0x30, + 0x03, 0x11, 0x00, 0x01, 0x19, 0x22, 0x40, 0x00, + 0x21, 0x45, 0x00, 0x35, 0xc8, 0x2b, 0x2b, 0x34, + 0x3a, 0x49, 0x00, 0xb7, 0xdd, 0xe5, 0xdd, 0x2a, + 0x53, 0x00, 0xdd, 0x7e, 0x00, 0xdd, 0xe1, 0xf2, + 0x0b, 0xf4, 0x07, 0x3c, 0xbe, 0xc0, 0x36, 0x01, + 0x23, 0x34, 0xb7, 0xc9, 0xcd, 0xf4, 0xf7, 0xed, + 0x73, 0x51, 0x00, 0xfd, 0xe5, 0xdd, 0xe5, 0xe5, + 0x21, 0x00, 0x08, 0x22, 0x40, 0x00, 0xed, 0x73, + 0x4d, 0x00, 0xe1, 0xcd, 0xe4, 0xf1, 0xcc, 0xd8, + 0xf1, 0x20, 0x30, 0xcd, 0x6a, 0xf4, 0xcd, 0xd9, + 0xf4, 0xaf, 0xcd, 0x0d, 0xf5, 0xc5, 0xcd, 0x61, + 0xf5, 0xcd, 0xaa, 0xf5, 0xc1, 0x20, 0x05, 0xdd, + 0xbe, 0x01, 0x20, 0xee, 0x3e, 0x4c, 0x32, 0x44, + 0x00, 0xdb, 0x63, 0xcb, 0xaf, 0xd3, 0x63, 0x11, + 0x55, 0x00, 0x1a, 0xcb, 0xef, 0x12, 0xdd, 0xe1, + 0xfd, 0xe1, 0xc9, 0xdd, 0xe1, 0xed, 0x7b, 0x51, + 0x00, 0xc9, 0x2a, 0x40, 0x00, 0xdd, 0x7e, 0x04, + 0x0f, 0x3e, 0x4e, 0xd2, 0x78, 0xf4, 0x3e, 0xff, + 0x5f, 0xdd, 0x46, 0x02, 0xcd, 0xd4, 0xf4, 0xdd, + 0x46, 0x05, 0xcd, 0xd4, 0xf4, 0xaf, 0xdd, 0x46, + 0x03, 0xcd, 0xd4, 0xf4, 0xcd, 0xcd, 0xf4, 0x3e, + 0xfe, 0x77, 0x23, 0xe5, 0xd9, 0xd1, 0xd9, 0xaf, + 0x06, 0x04, 0xcd, 0xd4, 0xf4, 0x3e, 0xf7, 0x77, + 0x23, 0xdd, 0x46, 0x04, 0x78, 0xcb, 0x3f, 0x3c, + 0x4f, 0x7b, 0xcd, 0xd4, 0xf4, 0x41, 0xaf, 0xcd, + 0xd4, 0xf4, 0xcd, 0xcd, 0xf4, 0x3e, 0xfb, 0x77, + 0x23, 0x06, 0x80, 0x3a, 0x42, 0x00, 0x07, 0x30, + 0x02, 0x06, 0x00, 0x3e, 0xe5, 0xcd, 0xd4, 0xf4, + 0x3e, 0xf7, 0x77, 0x23, 0xc9, 0x7b, 0xb7, 0xf8, + 0x3e, 0xf5, 0x06, 0x03, 0x77, 0x23, 0x10, 0xfc, + 0xc9, 0xd9, 0xdd, 0x46, 0x00, 0x05, 0xd9, 0xed, + 0x5b, 0x40, 0x00, 0xe5, 0xb7, 0xdd, 0x4e, 0x02, + 0x06, 0x00, 0xeb, 0x09, 0xeb, 0xed, 0x52, 0xe5, + 0xc1, 0xe1, 0xc5, 0xeb, 0xd9, 0xd9, 0xed, 0xb0, + 0xc1, 0xc5, 0xd9, 0x10, 0xf8, 0xd9, 0xc1, 0xeb, + 0x3a, 0x42, 0x00, 0xcb, 0x67, 0x3e, 0x80, 0x20, + 0x01, 0xaf, 0x6f, 0x65, 0xc9, 0xe5, 0xc5, 0xd9, + 0xc1, 0xe1, 0xd5, 0xfd, 0xe1, 0xd5, 0x08, 0x16, + 0x01, 0xdd, 0x7e, 0x00, 0xfe, 0x1a, 0x3e, 0x01, + 0x28, 0x08, 0xdd, 0x7e, 0x00, 0xcb, 0x3f, 0x3c, + 0x30, 0x26, 0x08, 0xfd, 0x77, 0x00, 0xb5, 0xf2, + 0x37, 0xf5, 0xfd, 0x74, 0x01, 0xcb, 0xbf, 0x08, + 0xfd, 0x77, 0x02, 0xf5, 0x3a, 0x42, 0x00, 0x07, + 0x30, 0x05, 0x3e, 0x01, 0xfd, 0x77, 0x03, 0xf1, + 0xdd, 0xbe, 0x00, 0x28, 0x10, 0xfd, 0x09, 0x3c, + 0x5f, 0x3e, 0x1a, 0xdd, 0xbe, 0x00, 0x7b, 0x28, + 0xd1, 0x7a, 0x53, 0x18, 0xcd, 0x08, 0xd1, 0xd9, + 0xc9, 0xd5, 0xc5, 0xd9, 0x08, 0xc1, 0xd5, 0x5f, + 0x51, 0xdd, 0x46, 0x02, 0x0e, 0x67, 0x2a, 0x40, + 0x00, 0x3e, 0x80, 0x32, 0x46, 0x00, 0xcd, 0x75, + 0xf3, 0x3a, 0x42, 0x00, 0x07, 0x3e, 0x00, 0x30, + 0x02, 0x3e, 0x01, 0x32, 0x50, 0x00, 0x3e, 0xf4, + 0x32, 0x4c, 0x00, 0xf3, 0xd3, 0x64, 0x3a, 0x50, + 0x00, 0xed, 0xb3, 0x42, 0xed, 0xb3, 0xb7, 0x28, + 0x02, 0xed, 0xb3, 0x42, 0x1d, 0x20, 0xf5, 0xd1, + 0xe1, 0x7e, 0xed, 0x79, 0x10, 0xfc, 0xfb, 0xd9, + 0x08, 0xc9, 0x08, 0x3e, 0x2f, 0x10, 0xfe, 0x3d, + 0x20, 0xfb, 0xdb, 0x63, 0xf6, 0x80, 0xcb, 0x7d, + 0x28, 0x10, 0xcb, 0x44, 0xcb, 0x84, 0x20, 0x0a, + 0x24, 0xcb, 0xa7, 0xd3, 0x63, 0x10, 0xfe, 0x08, + 0xbf, 0xc9, 0xcb, 0xe7, 0xd3, 0x63, 0x08, 0x3c, + 0xdd, 0xbe, 0x01, 0xc8, 0x32, 0x44, 0x00, 0xe5, + 0xed, 0x73, 0x4d, 0x00, 0xe1, 0x10, 0xfe, 0xd9, + 0xeb, 0xdd, 0x7e, 0x07, 0xcd, 0x0a, 0xf3, 0xeb, + 0xd9, 0xdb, 0x65, 0x47, 0x3a, 0x44, 0x00, 0xb8, + 0x06, 0x00, 0xc9, 0x05, 0x58, 0xff, 0xff, 0x05, + 0x26, 0x16, 0x71, 0x00, 0x79, 0xff, 0xff, 0x05, + 0x31, 0x80, 0x00, 0x21, 0x5b, 0xf6, 0xcd, 0x38, + 0xf6, 0xcd, 0x41, 0xf6, 0x20, 0x20, 0x7d, 0x32, + 0x42, 0x00, 0x7c, 0x32, 0x39, 0x00, 0xcd, 0x18, + 0xf0, 0xdd, 0x2a, 0x53, 0x00, 0x3a, 0x39, 0x00, + 0xcd, 0x7c, 0xf6, 0xcd, 0x18, 0xf0, 0x21, 0x6e, + 0xf6, 0xcd, 0x38, 0xf6, 0x18, 0xd2, 0x0e, 0x3f, + 0xcd, 0x0c, 0xf0, 0xcd, 0xc6, 0xf6, 0x18, 0xc8, + 0xcd, 0x53, 0xf6, 0xc2, 0x41, 0xe7, 0xc3, 0x07, + 0xe7, 0xcd, 0x53, 0xf6, 0xc2, 0x9a, 0xe7, 0xc3, + 0x60, 0xe7, 0xcd, 0x53, 0xf6, 0xc2, 0x4d, 0xe7, + 0xc3, 0x13, 0xe7, 0x47, 0x3a, 0x28, 0xe0, 0xfe, + 0x01, 0x78, 0xc9, 0x54, 0x45, 0x53, 0x54, 0x23, + 0x44, 0x52, 0x56, 0x23, 0x20, 0x28, 0x54, 0x54, + 0x44, 0x44, 0x29, 0x3a, 0x20, 0x03, 0x0a, 0x0d, + 0x54, 0x41, 0x53, 0x4b, 0x20, 0x44, 0x4f, 0x4e, + 0x45, 0x0d, 0x0a, 0x03, 0xfe, 0x00, 0xca, 0xd1, + 0xf7, 0xfe, 0x05, 0x28, 0x19, 0x38, 0x60, 0xfe, + 0xff, 0xc0, 0x21, 0x94, 0xf6, 0xcd, 0x38, 0xf6, + 0xcd, 0x41, 0xf6, 0xe9, 0x41, 0x44, 0x44, 0x52, + 0x45, 0x53, 0x53, 0x3a, 0x20, 0x03, 0xcd, 0x33, + 0xf0, 0xc9, 0x21, 0xcf, 0xf6, 0xcd, 0x38, 0xf6, + 0x3a, 0x4c, 0x00, 0xcd, 0xec, 0xf7, 0x3a, 0x47, + 0x00, 0xcd, 0xec, 0xf7, 0x3a, 0x42, 0x00, 0xcd, + 0xec, 0xf7, 0x3a, 0x44, 0x00, 0xcd, 0xec, 0xf7, + 0x3a, 0x43, 0x00, 0xcd, 0x4a, 0xf6, 0xcd, 0x53, + 0xf6, 0xc2, 0x2f, 0xe7, 0xc3, 0xf5, 0xe6, 0x43, + 0x4d, 0x44, 0x20, 0x53, 0x54, 0x41, 0x54, 0x20, + 0x44, 0x52, 0x56, 0x20, 0x54, 0x52, 0x4b, 0x20, + 0x53, 0x43, 0x54, 0x52, 0x3e, 0x20, 0x03, 0xaf, + 0x32, 0x44, 0x00, 0x3c, 0x32, 0x43, 0x00, 0x18, + 0x54, 0x21, 0x00, 0x08, 0x22, 0x40, 0x00, 0xcd, + 0x2a, 0xf0, 0xc4, 0xa2, 0xf6, 0x21, 0x00, 0x09, + 0x22, 0x40, 0x00, 0xcd, 0x27, 0xf0, 0xc4, 0xa2, + 0xf6, 0x3a, 0x39, 0x00, 0xfe, 0x02, 0x28, 0x0f, + 0xf5, 0xcd, 0x59, 0xf7, 0xf1, 0xfe, 0x03, 0xca, + 0xae, 0xf7, 0xfe, 0x04, 0xca, 0x9e, 0xf7, 0x21, + 0x43, 0x00, 0x34, 0xdd, 0x7e, 0x00, 0xe5, 0x21, + 0x42, 0x00, 0xcb, 0x66, 0xe1, 0x28, 0x01, 0x07, + 0x3c, 0xbe, 0x20, 0x11, 0x36, 0x01, 0x23, 0x34, + 0xdd, 0x7e, 0x01, 0xbe, 0x20, 0x07, 0x36, 0x00, + 0x0e, 0x50, 0xcd, 0x0c, 0xf0, 0xcd, 0x06, 0xf0, + 0x28, 0x06, 0xcd, 0x09, 0xf0, 0xfe, 0x2e, 0xc8, + 0x3a, 0x39, 0x00, 0xfe, 0x02, 0x28, 0xa6, 0x18, + 0x98, 0x21, 0x00, 0x09, 0xeb, 0x21, 0x00, 0x08, + 0x3a, 0x42, 0x00, 0x07, 0x06, 0x80, 0x30, 0x02, + 0x06, 0x00, 0x1a, 0xbe, 0x3e, 0xff, 0x32, 0x47, + 0x00, 0xc2, 0xa2, 0xf6, 0x23, 0x13, 0x10, 0xf2, + 0x21, 0x38, 0x00, 0x11, 0x00, 0x08, 0x06, 0x40, + 0x3a, 0x42, 0x00, 0x07, 0x38, 0x02, 0xcb, 0x20, + 0x7e, 0x0f, 0x38, 0x01, 0x13, 0xed, 0x5f, 0x12, + 0x13, 0x13, 0x10, 0xf9, 0x7e, 0x0f, 0x38, 0x03, + 0x3c, 0x77, 0xc9, 0xaf, 0x77, 0xc9, 0xe5, 0x21, + 0x42, 0x00, 0x7e, 0xe6, 0xf0, 0x77, 0xed, 0x5f, + 0x00, 0xe6, 0x01, 0xb6, 0x77, 0xe1, 0xdd, 0x7e, + 0x00, 0x3c, 0x47, 0xed, 0x5f, 0xe6, 0x1f, 0xb7, + 0x28, 0xf4, 0x32, 0x43, 0x00, 0xb8, 0x30, 0xee, + 0xdd, 0x7e, 0x01, 0x47, 0xed, 0x5f, 0xe6, 0x7f, + 0x32, 0x44, 0x00, 0xb8, 0x30, 0xf2, 0xc3, 0x45, + 0xf7, 0xcd, 0x18, 0xf0, 0xdd, 0x7e, 0x01, 0x3d, + 0xd3, 0x67, 0xdd, 0x7e, 0x07, 0xd3, 0x64, 0xcd, + 0x06, 0xf0, 0x28, 0xed, 0xcd, 0x09, 0xf0, 0xfe, + 0x2e, 0xc8, 0x18, 0xe5, 0xcd, 0x4a, 0xf6, 0x0e, + 0x20, 0xc3, 0x0c, 0xf0, 0x3e, 0xff, 0x32, 0x55, + 0x00, 0xc9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + +/* Provide RAM if ROMs are disabled */ +static uint8 sbc200_mon_ram[SBC200_MON_SIZE] = {0}; +static uint8 sbc200_ddb_ram[SBC200_DDB_SIZE] = {0}; + +static int32 poc = TRUE; /* Power On Clear */ + +/* Debug flags */ +#define STATUS_MSG (1 << 0) +#define IRQ_MSG (1 << 1) +#define VERBOSE_MSG (1 << 3) + +typedef struct { + t_bool conn; /* Connected Status */ + TMLN *tmln; /* TMLN pointer */ + TMXR *tmxr; /* TMXR pointer */ + t_bool mif; /* Mode Instruction */ + int32 mode; /* Mode */ + int32 baud; /* Baud rate */ + int32 dtr; /* DTR Status */ + int32 rts; /* RTS Status */ + int32 cts; /* CTS Status */ + int32 rxb; /* Receive Buffer */ + int32 txb; /* Transmit Buffer */ + t_bool txp; /* Transmit Pending */ + int32 stb; /* Status Buffer */ + int32 ctb; /* Control Buffer */ + uint8 rxintenable; /* Interrupt Enable */ + uint8 txintenable; /* Interrupt Enable */ + uint8 rxintvector; /* Interrupt Vector */ + uint8 txintvector; /* Interrupt Vector */ + uint8 rxdatabus; /* Data Bus Value */ + uint8 txdatabus; /* Data Bus Value */ +} SBC200_INFO; + +static const char* sbc200_description(DEVICE *dptr); +static t_stat sbc200_svc(UNIT *uptr); +static t_stat sbc200_reset(DEVICE *dptr); +static t_stat sbc200_attach(UNIT *uptr, CONST char *cptr); +static t_stat sbc200_detach(UNIT *uptr); +static t_stat sbc200_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat sbc200_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat sbc200_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc); +static t_stat sbc200_config_line(UNIT *uptr); +static t_stat sbc200_config_dtr(DEVICE *dptr, char rts); +static t_stat sbc200_config_rts(DEVICE *dptr, char rts); +static int32 sbc200_io(int32 addr, int32 io, int32 data); +static int32 sbc200_stat(DEVICE *dptr, int32 io, int32 data); +static int32 sbc200_data(DEVICE *dptr, int32 io, int32 data); +static int32 sbc200_rom(DEVICE *dptr, int32 io, int32 data); +static void sbc200_int(UNIT *uptr, int32 vector, int32 databus); +static int32 sbc200_mon(int32 Addr, int32 rw, int32 Data); +static int32 sbc200_ddb(int32 Addr, int32 rw, int32 Data); +static t_stat sbc200_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +extern uint32 vectorInterrupt; /* Vector Interrupt bits */ +extern uint8 dataBus[MAX_INT_VECTORS]; /* Data bus value */ + +/* Debug Flags */ +static DEBTAB sbc200_dt[] = { + { "STATUS", STATUS_MSG, "Status messages" }, + { "IRQ", IRQ_MSG, "Interrupt messages" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +/* Terminal multiplexer library descriptors */ + +static TMLN sbc200_tmln[] = { /* line descriptors */ + { 0 } +}; + +static TMXR sbc200_tmxr = { /* multiplexer descriptor */ + 1, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + sbc200_tmln, /* line descriptor array */ + NULL, /* line connection order */ + NULL /* multiplexer device (derived internally) */ +}; + +#define UNIT_V_SBC200_CONSOLE (UNIT_V_UF + 0) /* Port checks console for input */ +#define UNIT_SBC200_CONSOLE (1 << UNIT_V_SBC200_CONSOLE) +#define UNIT_V_SBC200_MONITOR (UNIT_V_UF + 1) /* Monitor ROM */ +#define UNIT_SBC200_MONITOR (1 << UNIT_V_SBC200_MONITOR) +#define UNIT_V_SBC200_DDBIOS (UNIT_V_UF + 2) /* DDBIOS ROM */ +#define UNIT_SBC200_DDBIOS (1 << UNIT_V_SBC200_DDBIOS) + +static MTAB sbc200_mod[] = { + { UNIT_SBC200_MONITOR, UNIT_SBC200_MONITOR, "MONITOR", "MONITOR", NULL, NULL, NULL, + "Enable ROM monitor at E000" }, + { UNIT_SBC200_MONITOR, 0, "NOMONITOR", "NOMONITOR", NULL, NULL, NULL, + "Disable ROM monitor at E000" }, + { UNIT_SBC200_DDBIOS, UNIT_SBC200_DDBIOS, "DDBIOS", "DDBIOS", NULL, NULL, NULL, + "Enable ROM DDBIOS at F000" }, + { UNIT_SBC200_DDBIOS, 0, "NODDBIOS", "NODDBIOS", NULL, NULL, NULL, + "Disable ROM DDBIOS at F000" }, + + { MTAB_XTD | MTAB_VUN, UNIT_SBC200_CONSOLE, NULL, "CONSOLE", &sbc200_set_console, NULL, NULL, "Set as CONSOLE" }, + { MTAB_XTD | MTAB_VUN, 0, NULL, "NOCONSOLE", &sbc200_set_console, NULL, NULL, "Remove as CONSOLE" }, + + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "BAUD", "BAUD", &sbc200_set_baud, &sbc200_show_baud, + NULL, "Set baud rate (default=9600)" }, + { 0 } +}; + +static SBC200_INFO sbc200_info = { 0, sbc200_tmln, &sbc200_tmxr, SBC200_BAUD, 1 }; + +static UNIT sbc200_unit[] = { + { UDATA (&sbc200_svc, UNIT_ATTABLE | UNIT_DISABLE | + UNIT_SBC200_CONSOLE | UNIT_SBC200_MONITOR | UNIT_SBC200_DDBIOS, 0), SBC200_WAIT }, +}; + +static REG sbc200_reg[] = { + { HRDATAD (SBCSTA, sbc200_info.stb, 8, "Status register"), }, + { HRDATAD (SBCCTL, sbc200_info.ctb, 8, "Control register"), }, + { HRDATAD (SBCRXD, sbc200_info.rxb, 8, "RX data buffer"), }, + { HRDATAD (SBCTXD, sbc200_info.txb, 8, "TX data buffer"), }, + { FLDATAD (SBCTXP, sbc200_info.txp, 0, "TX data pending"), }, + { FLDATAD (SBCCON, sbc200_info.conn, 0, "Connection status"), }, + { FLDATAD (SBCRTS, sbc200_info.rts, 0, "RTS status (active low)"), }, + { FLDATAD (SBCDTR, sbc200_info.dtr, 0, "DTR status (active low)"), }, + { FLDATAD (SBCRDRF, sbc200_info.stb, 0, "RDRF status"), }, + { FLDATAD (SBCTRDY, sbc200_info.stb, 1, "TRDY status"), }, + { FLDATAD (SBCTDRE, sbc200_info.stb, 2, "TDRE status"), }, + { FLDATAD (SBCOVRN, sbc200_info.stb, 4, "OVRN status"), }, + { FLDATAD (SBCCTS, sbc200_info.cts, 0, "CTS status (active low)"), }, + { FLDATAD (SBCDSR, sbc200_info.stb, 7, "DSR status (active low)"), }, + { DRDATAD (SBCWAIT, sbc200_unit[0].wait, 32, "Wait cycles"), }, + { FLDATAD (SBCRXINTEN, sbc200_info.rxintenable, 1, "Global vectored interrupt enable"), }, + { FLDATAD (SBCTXINTEN, sbc200_info.txintenable, 1, "Global vectored interrupt enable"), }, + { DRDATAD (SBCRXVEC, sbc200_info.rxintvector, 8, "RX interrupt vector"), }, + { DRDATAD (SBCTXVEC, sbc200_info.txintvector, 8, "TX interrupt vector"), }, + { HRDATAD (SBCRXDBVAL, sbc200_info.rxdatabus, 8, "RX data bus value"), }, + { HRDATAD (SBCTXDBVAL, sbc200_info.txdatabus, 8, "TX data bus value"), }, + { NULL } +}; + +DEVICE sbc200_dev = { + SBC200_SNAME, /* name */ + sbc200_unit, /* unit */ + sbc200_reg, /* registers */ + sbc200_mod, /* modifiers */ + 1, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* address increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &sbc200_reset, /* reset routine */ + NULL, /* boot routine */ + &sbc200_attach, /* attach routine */ + &sbc200_detach, /* detach routine */ + &sbc200_iores, /* context */ + (DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_MUX), /* flags */ + 0, /* debug control */ + sbc200_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &sbc200_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &sbc200_description /* description */ +}; + +static const char* sbc200_description(DEVICE *dptr) +{ + return SBC200_NAME; +} + +static t_stat sbc200_reset(DEVICE *dptr) +{ + sim_debug(STATUS_MSG, dptr, "reset adapter.\n"); + + /* Connect/Disconnect I/O Ports at base address */ + if (dptr->flags & DEV_DIS) { + s100_bus_remio(sbc200_iores.io_base, sbc200_iores.io_size, &sbc200_io); + s100_bus_remmem(sbc200_monres.mem_base, sbc200_monres.mem_size, &sbc200_mon); + s100_bus_remmem(sbc200_ddbres.mem_base, sbc200_ddbres.mem_size, &sbc200_ddb); + + s100_bus_noconsole(&dptr->units[0]); + + poc = TRUE; + + return SCPE_OK; + } + + if (poc == TRUE) { + s100_bus_addio(sbc200_iores.io_base, sbc200_iores.io_size, &sbc200_io, dptr->name); + + cpu_set_chiptype(CHIP_TYPE_Z80); /* Set to Z80 */ + + poc = FALSE; + } + + /* Enable ROMs on Reset */ + s100_bus_addmem(sbc200_monres.mem_base, sbc200_monres.mem_size, &sbc200_mon, dptr->name); + s100_bus_addmem(sbc200_ddbres.mem_base, sbc200_ddbres.mem_size, &sbc200_ddb, dptr->name); + + /* Set as CONSOLE unit */ + if (dptr->units[0].flags & UNIT_SBC200_CONSOLE) { + s100_bus_console(&dptr->units[0]); + } + + /* Set DEVICE for this UNIT */ + dptr->units[0].dptr = dptr; + dptr->units[0].wait = SBC200_WAIT; + + /* Enable TMXR modem control passthrough */ + tmxr_set_modem_control_passthru(sbc200_info.tmxr); + + /* Reset status registers */ + dptr->units[0].flags |= (UNIT_SBC200_MONITOR | UNIT_SBC200_DDBIOS); + sbc200_info.mif = TRUE; + sbc200_info.stb = 0x00; + sbc200_info.txp = FALSE; + sbc200_info.cts = 0; /* Force CTS active (low) */ + sbc200_info.rxintenable = FALSE; + sbc200_info.txintenable = FALSE; + if (dptr->units[0].flags & UNIT_ATT) { + sbc200_config_dtr(dptr, 1); /* disable DTR */ + sbc200_config_rts(dptr, 1); /* disable RTS */ + } + + /* Activate Service Routine */ + sim_activate(&dptr->units[0], dptr->units[0].wait); + + return SCPE_OK; +} + +static t_stat sbc200_svc(UNIT *uptr) +{ + int32 c,s,stb,cts; + t_stat r = SCPE_OK; + + /* Check for new incoming connection */ + if (uptr->flags & UNIT_ATT) { + if (tmxr_poll_conn(sbc200_info.tmxr) >= 0) { /* poll connection */ + + sbc200_info.conn = TRUE; /* set connected */ + + sim_debug(STATUS_MSG, uptr->dptr, "new connection.\n"); + } + } + + /* Update incoming modem status bits */ + if (uptr->flags & UNIT_ATT) { + tmxr_set_get_modem_bits(sbc200_info.tmln, 0, 0, &s); + cts = sbc200_info.cts; + stb = sbc200_info.stb; + + sbc200_info.stb &= ~SBC200_DSR; + sbc200_info.stb |= (s & TMXR_MDM_DSR) ? 0 : SBC200_DSR; /* Active Low */ + if ((stb ^ sbc200_info.stb) & SBC200_DSR) { + sim_debug(STATUS_MSG, uptr->dptr, "DSR state changed to %s.\n", (sbc200_info.stb & SBC200_DSR) ? "LOW" : "HIGH"); + } + sbc200_info.cts = 0; + sbc200_info.cts = (s & TMXR_MDM_CTS) ? 0 : 1; /* Active Low */ + if (cts != sbc200_info.cts) { + sim_debug(STATUS_MSG, uptr->dptr, "CTS state changed to %s.\n", (sbc200_info.cts) ? "LOW" : "HIGH"); + } + } else { + /* + * The SBC-200 connects RXD to DSR to determine + * the baud rate. Simulate by toggling DSR. + */ + sbc200_info.stb ^= SBC200_DSR; + } + + /* TX data */ + if (sbc200_info.txp) { + if (uptr->flags & UNIT_ATT) { + if (!(sbc200_info.cts)) { /* Active low */ + r = tmxr_putc_ln(sbc200_info.tmln, sbc200_info.txb); + sbc200_info.txp = FALSE; /* Reset TX Pending */ + } else { + r = SCPE_STALL; + } + } else { + r = sim_putchar(sbc200_info.txb); + sbc200_info.txp = FALSE; /* Reset TX Pending */ + } + + if (r == SCPE_LOST) { + sbc200_info.conn = FALSE; /* Connection was lost */ + sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n"); + } + + /* If TX buffer now empty, send interrupt */ + if (!sbc200_info.txp && sbc200_info.txintenable) { + sim_debug(IRQ_MSG, uptr->dptr, "%s: TxRDY Vector=%d\n", sim_uname(uptr), sbc200_info.txintvector); + sbc200_int(uptr, sbc200_info.txintvector, sbc200_info.txdatabus); + } + + } + + /* Update TDRE if not set and no character pending */ + if (!sbc200_info.txp && !(sbc200_info.stb & SBC200_TDRE)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_tx(sbc200_info.tmxr); + sbc200_info.stb |= (tmxr_txdone_ln(sbc200_info.tmln) && sbc200_info.conn) ? SBC200_TRDY | SBC200_TDRE : 0; + } else { + sbc200_info.stb |= (SBC200_TRDY | SBC200_TDRE); + } + } + + /* Check for Data if RX buffer empty */ + if (!(sbc200_info.stb & SBC200_RDRF)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_rx(sbc200_info.tmxr); + + c = tmxr_getc_ln(sbc200_info.tmln); + } + else { + c = s100_bus_poll_kbd(uptr); + } + + if (c & (TMXR_VALID | SCPE_KFLAG)) { + sbc200_info.rxb = c & 0xff; + sbc200_info.stb |= SBC200_RDRF; + sbc200_info.stb &= ~(SBC200_FE | SBC200_OVRN | SBC200_PE); + sim_debug(IRQ_MSG, uptr->dptr, "%s: RxRDY Vector=%d\n", sim_uname(uptr), sbc200_info.rxintvector); + if (sbc200_info.rxintenable) { + sbc200_int(uptr, sbc200_info.rxintvector, sbc200_info.rxdatabus); + } + } + } + + /* Don't let TMXR clobber our wait time */ + uptr->wait = SBC200_WAIT; + + sim_activate_abs(uptr, uptr->wait); + + return r; +} + + +/* Attach routine */ +static t_stat sbc200_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + + sim_debug(VERBOSE_MSG, uptr->dptr, "attach (%s).\n", cptr); + + if ((r = tmxr_attach(sbc200_info.tmxr, uptr, cptr)) == SCPE_OK) { + + if (sbc200_info.tmln->serport) { + sbc200_config_dtr(uptr->dptr, sbc200_info.dtr); /* update DTR */ + sbc200_config_rts(uptr->dptr, sbc200_info.rts); /* update RTS */ + } + + sbc200_info.tmln->rcve = 1; + } + + return r; +} + + +/* Detach routine */ +static t_stat sbc200_detach(UNIT *uptr) +{ + if (uptr->dptr == NULL) { + return SCPE_IERR; + } + + sim_debug(VERBOSE_MSG, uptr->dptr, "detach.\n"); + + if (uptr->flags & UNIT_ATT) { + sim_cancel(uptr); + + return (tmxr_detach(sbc200_info.tmxr, uptr)); + } + + return SCPE_UNATT; +} + +static t_stat sbc200_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + if (value == UNIT_SBC200_CONSOLE) { + s100_bus_console(uptr); + } + else { + s100_bus_noconsole(uptr); + } + + return SCPE_OK; +} + +static t_stat sbc200_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + int32 baud; + t_stat r = SCPE_ARG; + + if (!(uptr->flags & UNIT_ATT)) { + return SCPE_UNATT; + } + + if (cptr != NULL) { + if (sscanf(cptr, "%d", &baud)) { + switch (baud) { + case 110: + case 150: + case 300: + case 1200: + case 1800: + case 2400: + case 4800: + case 9600: + case 19200: + sbc200_info.baud = baud; + r = sbc200_config_line(uptr); + + return r; + + default: + break; + } + } + } + + return r; +} + +static t_stat sbc200_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc) +{ + if (uptr->flags & UNIT_ATT) { + fprintf(st, "Baud rate: %d", sbc200_info.baud); + } + + return SCPE_OK; +} + +static t_stat sbc200_config_line(UNIT *uptr) +{ + char config[20]; + const char *fmt; + t_stat r = SCPE_IERR; + + switch (sbc200_info.mode & SBC200_FMTMSK) { + case SBC200_72E: + fmt = "7E2"; + break; + case SBC200_72O: + fmt = "7O2"; + break; + case SBC200_71E: + fmt = "7E1"; + break; + case SBC200_71O: + fmt = "7O1"; + break; + case SBC200_82N: + fmt = "8N2"; + break; + case SBC200_81E: + fmt = "8E1"; + break; + case SBC200_81O: + fmt = "8O1"; + break; + case SBC200_81N: + default: + fmt = "8N1"; + break; + } + + sprintf(config, "%d-%s", sbc200_info.baud, fmt); + + r = tmxr_set_config_line(sbc200_info.tmln, config); + + sim_debug(STATUS_MSG, uptr->dptr, "port configuration set to '%s'.\n", config); + + return r; +} + +/* +** DTR is active low +** 0 = DTR active +** 1 = DTR inactive +*/ +static t_stat sbc200_config_dtr(DEVICE *dptr, char dtr) +{ + t_stat r = SCPE_OK; + int32 s; + + if (dptr->units[0].flags & UNIT_ATT) { + /* DTR Control */ + s = TMXR_MDM_DTR; + + if (!dtr) { + r = tmxr_set_get_modem_bits(sbc200_info.tmln, s, 0, NULL); + if (sbc200_info.dtr) { + sim_debug(STATUS_MSG, dptr, "DTR state changed to HIGH.\n"); + } + } else { + r = tmxr_set_get_modem_bits(sbc200_info.tmln, 0, s, NULL); + if (!sbc200_info.dtr) { + sim_debug(STATUS_MSG, dptr, "DTR state changed to LOW.\n"); + } + } + } + + sbc200_info.dtr = dtr; /* Active low */ + + return r; +} + +/* +** RTS is active low +** 0 = RTS active +** 1 = RTS inactive +*/ +static t_stat sbc200_config_rts(DEVICE *dptr, char rts) +{ + t_stat r = SCPE_OK; + int32 s; + + if (dptr->units[0].flags & UNIT_ATT) { + /* RTS Control */ + s = TMXR_MDM_RTS; + + if (!rts) { + r = tmxr_set_get_modem_bits(sbc200_info.tmln, s, 0, NULL); + if (sbc200_info.rts) { + sim_debug(STATUS_MSG, dptr, "RTS state changed to HIGH.\n"); + } + } else { + r = tmxr_set_get_modem_bits(sbc200_info.tmln, 0, s, NULL); + if (!sbc200_info.rts) { + sim_debug(STATUS_MSG, dptr, "RTS state changed to LOW.\n"); + } + } + } + + sbc200_info.rts = rts; /* Active low */ + + return r; +} + +static int32 sbc200_io(int32 addr, int32 io, int32 data) +{ + int32 r = 0xff; + + switch (addr - sbc200_iores.io_base) { + case 0x04: + r = sbc200_data(&sbc200_dev, io, data); + break; + + case 0x05: + r = sbc200_stat(&sbc200_dev, io, data); + break; + + case 0x07: + r = sbc200_rom(&sbc200_dev, io, data); + break; + + default: + break; + + } + + return(r); +} + +static int32 sbc200_stat(DEVICE *dptr, int32 io, int32 data) +{ + int32 r; + + if (io == S100_IO_READ) { + r = sbc200_info.stb; + } else { + if (sbc200_info.mif) { + sbc200_info.mif = FALSE; + sbc200_info.mode = data & 0xff; + + /* Set data bits, parity and stop bits format */ + sbc200_config_line(&dptr->units[0]); + } else { /* Command Mode */ + /* Internal Reset */ + if (data & SBC200_IR) { + sim_debug(STATUS_MSG, dptr, "8251 internal reset.\n"); + + sbc200_info.ctb = data & 0xff; /* save control byte */ + sbc200_info.stb = SBC200_DSR; /* Reset status register */ + sbc200_info.mif = TRUE; + sbc200_info.rxb = 0x00; + sbc200_info.txp = FALSE; + + sbc200_config_dtr(dptr, 1); /* disable DTR */ + sbc200_config_rts(dptr, 1); /* disable RTS */ + } else { + sbc200_config_dtr(dptr, (data & SBC200_DTR)); /* enable DTR */ + sbc200_config_rts(dptr, (data & SBC200_RTS)); /* enable RTS */ + } + } + + r = 0x00; + } + + return(r); +} + +static int32 sbc200_data(DEVICE *dptr, int32 io, int32 data) +{ + int32 r; + + if (io == S100_IO_READ) { + r = sbc200_info.rxb; + sbc200_info.stb &= ~(SBC200_RDRF | SBC200_FE | SBC200_OVRN | SBC200_PE); + } else { + sbc200_info.txb = data; + sbc200_info.stb &= ~(SBC200_TRDY | SBC200_TDRE); + sbc200_info.txp = TRUE; + + sbc200_svc(dptr->units); /* Process TX right now */ + + r = 0x00; + } + + return r; +} + +static int32 sbc200_rom(DEVICE *dptr, int32 io, int32 data) +{ + sim_debug(VERBOSE_MSG, dptr, "Switch %s ROMs\n", data & 0x02 ? "OUT" : "IN"); + + if (data & 0x02) { /* Disable ROMs */ + s100_bus_remmem(sbc200_monres.mem_base, sbc200_monres.mem_size, &sbc200_mon); + s100_bus_remmem(sbc200_ddbres.mem_base, sbc200_ddbres.mem_size, &sbc200_ddb); + } + else { + s100_bus_addmem(sbc200_monres.mem_base, sbc200_monres.mem_size, &sbc200_mon, dptr->name); + s100_bus_addmem(sbc200_ddbres.mem_base, sbc200_ddbres.mem_size, &sbc200_ddb, dptr->name); + } + + return data; +} + +static void sbc200_int(UNIT *uptr, int32 vector, int32 databus) +{ + vectorInterrupt |= (1 << vector); + dataBus[vector] = databus; + + sim_debug(IRQ_MSG, uptr->dptr, "%s: IRQ Vector=%d dataBus=%02X Status=%02X\n", sim_uname(uptr), vector, databus, sbc200_info.stb); +} + + +static int32 sbc200_mon(int32 Addr, int32 rw, int32 Data) +{ + /* If ROM enabled, return ROM byte */ + if (sbc200_dev.units->flags & UNIT_SBC200_MONITOR) { + return(sbc200_mon_rom[Addr & SBC200_MON_MASK]); + } else if (rw == S100_IO_WRITE) { + sbc200_mon_ram[Addr & SBC200_MON_MASK] = Data; + } + + return sbc200_mon_ram[Addr & SBC200_MON_MASK]; +} + +static int32 sbc200_ddb(int32 Addr, int32 rw, int32 Data) +{ + /* If ROM has been disabled, unmap and return byte from RAM */ + if (sbc200_dev.units->flags & UNIT_SBC200_DDBIOS) { + return(sbc200_ddb_rom[Addr & SBC200_DDB_MASK]); + } else if (rw == S100_IO_WRITE) { + sbc200_ddb_ram[Addr & SBC200_DDB_MASK] = Data; + } + + return sbc200_ddb_ram[Addr & SBC200_DDB_MASK]; +} + +static t_stat sbc200_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nSD Systems SBC-200 (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + fprintf(st, "\n\n"); + tmxr_attach_help(st, dptr, uptr, flag, cptr); + + fprintf(st, "----- NOTES -----\n\n"); + fprintf(st, "Only one device may poll the host keyboard for CONSOLE input.\n"); + fprintf(st, "Use SET %s CONSOLE to select this UNIT as the CONSOLE device.\n", sim_dname(dptr)); + fprintf(st, "\nUse SHOW BUS CONSOLE to display the current CONSOLE device.\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/sds_vfii.c b/Altair8800/sds_vfii.c new file mode 100644 index 00000000..7e7cbcdd --- /dev/null +++ b/Altair8800/sds_vfii.c @@ -0,0 +1,340 @@ +/* vfii_fdc.c: SD Systems VersaFloppy II + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 13-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "altair8800_dsk.h" +#include "s100_bus.h" +#include "sds_vfii.h" +#include "wd_17xx.h" + +#define DEV_NAME "VFII" + +static WD17XX_INFO *wd17xx = NULL; + +#define VFII_WD17XX_OFFSET 1 + +static int32 poc = TRUE; /* Power On Clear */ + +static uint8 drv_sel = 0; +static uint8 vfii_creg = 0; + +static RES vfii_res = { VFII_IO_BASE, VFII_IO_SIZE, 0x0000, 0x0000 }; +static DSK_INFO dsk_info[VFII_NUM_DRIVES]; + +static t_stat vfii_reset(DEVICE *vfii_dev); +static t_stat vfii_attach(UNIT *uptr, CONST char *cptr); +static t_stat vfii_detach(UNIT *uptr); +static t_stat vfii_boot(int32 unitno, DEVICE *dptr); +static int32 vfii_io(const int32 port, const int32 io, const int32 data); +static int32 vfii_io_in(const int32 port); +static void vfii_io_out(const int32 port, const int32 data); +static t_stat vfii_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* vfii_description(DEVICE *dptr); + +static UNIT vfii_unit[VFII_NUM_DRIVES] = { + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, VFII_SD_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, VFII_SD_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, VFII_SD_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, VFII_SD_CAPACITY) } +}; + +static REG vfii_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { DRDATAD (DRVSEL, drv_sel, 8, "Drive select"), }, + { NULL } +}; + +#define VFII_NAME "SD Systems VersaFloppy II" + +static const char* vfii_description(DEVICE *dptr) { + if (dptr == NULL) { + return NULL; + } + return VFII_NAME; +} + +static MTAB vfii_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "Sets disk controller I/O base address" }, + { 0 } +}; + +/* Debug flags */ +#define VERBOSE_MSG (1 << 0) +#define ERROR_MSG (1 << 1) +#define STATUS_MSG (1 << 2) +#define DRIVE_MSG (1 << 3) +#define IRQ_MSG (1 << 4) +#define READ_MSG (1 << 5) +#define WRITE_MSG (1 << 6) +#define COMMAND_MSG (1 << 7) +#define FORMAT_MSG (1 << 8) + +/* Debug Flags */ +static DEBTAB vfii_dt[] = { + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { "ERROR", ERROR_MSG, "Error messages" }, + { "STATUS", STATUS_MSG, "Status messages" }, + { "DRIVE", DRIVE_MSG, "Drive messages" }, + { "IRQ", IRQ_MSG, "IRQ messages" }, + { "READ", READ_MSG, "Read messages" }, + { "WRITE", WRITE_MSG, "Write messages" }, + { "COMMAND", COMMAND_MSG, "Command messages" }, + { "FORMAT", FORMAT_MSG, "Format messages" }, + { NULL, 0 } +}; + +DEVICE vfii_dev = { + DEV_NAME, vfii_unit, vfii_reg, vfii_mod, VFII_NUM_DRIVES, + ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &vfii_reset, + &vfii_boot, &vfii_attach, &vfii_detach, + &vfii_res, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0, + vfii_dt, NULL, NULL, &vfii_show_help, NULL, NULL, &vfii_description +}; + +/* Reset routine */ +static t_stat vfii_reset(DEVICE *dptr) +{ + RES *res; + int i; + + if ((res = (RES *) dptr->ctxt) == NULL) { + sim_printf("CTX is NULL!\n"); + return SCPE_IERR; + } + + if(dptr->flags & DEV_DIS) { /* Unmap I/O Ports */ + wd17xx = wd17xx_release(wd17xx); + + s100_bus_remio(res->io_base, res->io_size, &vfii_io); + + poc = TRUE; + } else { + if (poc) { + for (i = 0; i < VFII_NUM_DRIVES; i++) { + vfii_unit[i].dptr = dptr; + dsk_init(&dsk_info[i], &vfii_unit[i], 77, 1, 0); + dsk_set_verbose_flag(&dsk_info[i], VERBOSE_MSG); + } + + if (wd17xx == NULL) { + if ((wd17xx = wd17xx_init(dptr)) == NULL) { + sim_printf("Could not init WD17XX\n"); + } + else { + wd17xx_set_fdctype(wd17xx, WD17XX_FDCTYPE_1795); /* Set to 1795 */ + wd17xx_set_verbose_flag(wd17xx, VERBOSE_MSG); + wd17xx_set_error_flag(wd17xx, ERROR_MSG); + wd17xx_set_read_flag(wd17xx, READ_MSG); + wd17xx_set_write_flag(wd17xx, WRITE_MSG); + wd17xx_set_command_flag(wd17xx, COMMAND_MSG); + wd17xx_set_format_flag(wd17xx, FORMAT_MSG); + } + } + + s100_bus_addio(res->io_base, res->io_size, &vfii_io, DEV_NAME); + + poc = FALSE; + } + + drv_sel = 0; + + if (wd17xx != NULL) { + wd17xx_reset(wd17xx); + wd17xx_set_dsk(wd17xx, &dsk_info[drv_sel]); + } + } + + return SCPE_OK; +} + +static t_stat vfii_boot(int32 unitno, DEVICE *dptr) +{ + + sim_debug(STATUS_MSG, &vfii_dev, DEV_NAME ": Booting Controller at 0x%04x\n", 0xE000); + + s100_bus_set_addr(0xE000); + + return SCPE_OK; +} + +/* Attach routine */ +static t_stat vfii_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + int d; + + /* Determine drive number */ + d = uptr - &vfii_unit[0]; + + if (d < 0 || d >= VFII_NUM_DRIVES) { + return SCPE_IERR; + } + + sim_switches |= SWMASK ('E'); /* File must exist */ + + if ((r = attach_unit(uptr, cptr)) != SCPE_OK) { /* attach unit */ + sim_printf(DEV_NAME ": ATTACH error=%d\n", r); + return r; + } + + /* Determine length of this disk */ + uptr->capac = sim_fsize(uptr->fileref); + + /* init format based on file size */ + switch (uptr->capac) { + case VFII_DD_CAPACITY: + dsk_init_format(&dsk_info[d], 0, 76, 0, 0, DSK_DENSITY_DD, 26, 256, 1); + break; + + default: + uptr->capac = VFII_SD_CAPACITY; + dsk_init_format(&dsk_info[d], 0, 76, 0, 0, DSK_DENSITY_DD, 26, 256, 1); + break; + } + +// dsk_show(&dsk_info[d]); + + return r; +} + +/* Detach routine */ +static t_stat vfii_detach(UNIT *uptr) +{ + t_stat r; + + r = detach_unit(uptr); /* detach unit */ + + return r; +} + +static int32 vfii_io(const int32 port, const int32 io, const int32 data) +{ + int32 result = 0xff; + + if (io == S100_IO_READ) { /* I/O Write */ + result = vfii_io_in(port); + } else { /* I/O Write */ + vfii_io_out(port, data); + } + + return result; +} + +static int32 vfii_io_in(const int32 port) +{ + int32 result = 0xff; + int32 offset = port - vfii_res.io_base; + + switch (offset) { + case VFII_REG_STATUS: + result = vfii_creg; + + sim_debug(STATUS_MSG, &vfii_dev, DEV_NAME ": " ADDRESS_FORMAT + " Read WAIT, Port 0x%02x Result 0x%02x\n", s100_bus_get_addr(), port, result); + break; + + case WD17XX_REG_STATUS + VFII_WD17XX_OFFSET: + case WD17XX_REG_TRACK + VFII_WD17XX_OFFSET: + case WD17XX_REG_SECTOR + VFII_WD17XX_OFFSET: + case WD17XX_REG_DATA + VFII_WD17XX_OFFSET: + result = wd17xx_inp(wd17xx, offset - VFII_WD17XX_OFFSET); + + sim_debug(STATUS_MSG, &vfii_dev, DEV_NAME ": " ADDRESS_FORMAT + " Read WD17XX, Port 0x%02x (0x%02x) Result 0x%02x\n", s100_bus_get_addr(), port, offset - VFII_WD17XX_OFFSET, result); + break; + + default: + break; + } + + return result; +} + +/* VersaFloppy II Control/Status + * + * BIT 0-3 Drive Select + * BIT 4 Side Select (1 = Side 0) + * BIT 5 5"/8" Drive (1 = 8") + * BIT 6 Double/Single Density (1 = SD) + * BIT 7 Wait Enable (Not used in simulator) + * + * All bits are inverted on the VFII + * + */ + +static void vfii_io_out(const int32 port, const int32 data) +{ + int32 offset = port - vfii_res.io_base; + + switch (offset) { + case VFII_REG_CONTROL: + vfii_creg = data & 0xff; + + drv_sel = sys_floorlog2((~data) & VFII_DSEL_MASK); + wd17xx_sel_side(wd17xx, (data & VFII_SIDE_MASK) ? 0 : 1); + wd17xx_sel_dden(wd17xx, (data & VFII_DDEN_MASK) ? FALSE : TRUE); + wd17xx_sel_drive_type(wd17xx, (data & VFII_SIZE_MASK) ? 8 : 5); + + sim_debug(DRIVE_MSG, &vfii_dev, DEV_NAME ": " ADDRESS_FORMAT " WR DRVSEL (0x%02x) = 0x%02x: Drive: %d\n", + s100_bus_get_addr(), port, data & DATAMASK, drv_sel); + + /* Tell WD17XX which drive is selected */ + wd17xx_set_dsk(wd17xx, &dsk_info[drv_sel]); + break; + + case WD17XX_REG_COMMAND + VFII_WD17XX_OFFSET: + case WD17XX_REG_TRACK + VFII_WD17XX_OFFSET: + case WD17XX_REG_SECTOR + VFII_WD17XX_OFFSET: + case WD17XX_REG_DATA + VFII_WD17XX_OFFSET: + wd17xx_outp(wd17xx, offset - VFII_WD17XX_OFFSET, data & DATAMASK); + + sim_debug(STATUS_MSG, &vfii_dev, DEV_NAME ": " ADDRESS_FORMAT + " Write WD17XX, Port 0x%02x (0x%02x) Data 0x%02x\n", s100_bus_get_addr(), port, offset - VFII_WD17XX_OFFSET, data & DATAMASK); + break; + + default: + break; + } +} + +static t_stat vfii_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nSD Systems VersaFlopyy II (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/sds_vfii.h b/Altair8800/sds_vfii.h new file mode 100644 index 00000000..b7d16b51 --- /dev/null +++ b/Altair8800/sds_vfii.h @@ -0,0 +1,60 @@ +/* sds_vfii.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/16/25 Initial version + +*/ + +#ifndef _VFII_FDC_H +#define _VFII_FDC_H + +#define VFII_NUM_DRIVES 4 + +#define VFII_IO_BASE 0x63 +#define VFII_IO_SIZE 5 + +#define VFII_PROM_BASE 0x0000 +#define VFII_PROM_SIZE 32 +#define VFII_PROM_MASK (VFII_PROM_SIZE - 1) + +/* VFII Register Offsets */ +#define VFII_REG_STATUS 0x00 /* Status Port */ +#define VFII_REG_CONTROL 0x00 /* Control Port */ + +#define VFII_DSEL_MASK 0x0f +#define VFII_SIDE_MASK 0x10 +#define VFII_SIZE_MASK 0x20 +#define VFII_DDEN_MASK 0x40 +#define VFII_WAIT_MASK 0x80 + + +#define VFII_FLAG_DRQ 0x80 /* End of Job (DRQ) */ + +#define VFII_SD_CAPACITY (77*26*128) /* SSSD 8" (IBM 3740) Disk Capacity */ +#define VFII_DD_CAPACITY (77*26*256) /* SSDD 8" DD Disk Capacity */ + +#endif + diff --git a/Altair8800/tarbell_fdc.c b/Altair8800/tarbell_fdc.c new file mode 100644 index 00000000..4cf8968a --- /dev/null +++ b/Altair8800/tarbell_fdc.c @@ -0,0 +1,471 @@ +/* tarbell_fdc.c: Tarbell 1011/2022 Floppy Disk Controller + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 13-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "s100_bus.h" +#include "altair8800_dsk.h" +#include "tarbell_fdc.h" +#include "wd_17xx.h" + +#define DEV_NAME "TARBELL" + +static WD17XX_INFO *wd17xx = NULL; + +/* Debug flags */ +#define VERBOSE_MSG (1 << 0) +#define ERROR_MSG (1 << 1) +#define STATUS_MSG (1 << 2) +#define DRIVE_MSG (1 << 3) +#define IRQ_MSG (1 << 4) +#define READ_MSG (1 << 5) +#define WRITE_MSG (1 << 6) +#define COMMAND_MSG (1 << 7) +#define FORMAT_MSG (1 << 8) + +static int32 poc = TRUE; /* Power On Clear */ + +static uint8 drv_sel = 0; +static int32 ddfdc_enabled = FALSE; +static int32 prom_enabled = TRUE; +static int32 prom_active = FALSE; + +static RES tarbell_res = { TARBELL_IO_BASE, TARBELL_IO_SIZE, TARBELL_PROM_BASE, TARBELL_PROM_SIZE }; +static MDEV mdev = { NULL, NULL }; +static DSK_INFO dsk_info[TARBELL_NUM_DRIVES]; + +/* Tarbell PROM is 32 bytes */ +static uint8 tarbell_prom[TARBELL_PROM_SIZE] = { + 0xdb, 0xfc, 0xaf, 0x6f, 0x67, 0x3c, 0xd3, 0xfa, + 0x3e, 0x8c, 0xd3, 0xf8, 0xdb, 0xfc, 0xb7, 0xf2, + 0x19, 0x00, 0xdb, 0xfb, 0x77, 0x23, 0xc3, 0x0c, + 0x00, 0xdb, 0xf8, 0xb7, 0xca, 0x7d, 0x00, 0x76 +}; + +static t_stat tarbell_reset(DEVICE *tarbell_dev); +static t_stat tarbell_attach(UNIT *uptr, CONST char *cptr); +static t_stat tarbell_detach(UNIT *uptr); +static t_stat tarbell_boot(int32 unitno, DEVICE *dptr); +static t_stat tarbell_set_model(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat tarbell_show_model(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat tarbell_set_prom(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat tarbell_show_prom(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static void tarbell_enable_prom(void); +static void tarbell_disable_prom(void); +static int32 tarbell_io(const int32 port, const int32 io, const int32 data); +static int32 tarbell_memio(const int32 addr, const int32 rw, const int32 data); +static t_stat tarbell_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* tarbell_description(DEVICE *dptr); + +static UNIT tarbell_unit[TARBELL_NUM_DRIVES] = { + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, TARBELL_SD_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, TARBELL_SD_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, TARBELL_SD_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, TARBELL_SD_CAPACITY) } +}; + +static REG tarbell_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { DRDATAD (DRVSEL, drv_sel, 8, "Drive select"), }, + { NULL } +}; + +#define TARBELL_NAME "Tarbell 2022 Double-Density FDC" + +static const char* tarbell_description(DEVICE *dptr) { + if (dptr == NULL) { + return NULL; + } + return TARBELL_NAME; +} + +static MTAB tarbell_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "Sets disk controller I/O base address" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "PROM", "PROM={ENABLE|DISABLE}", + &tarbell_set_prom, &tarbell_show_prom, NULL, "ROM enabled/disabled status"}, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MODEL", "MODEL={SD|DD}", + &tarbell_set_model, &tarbell_show_model, NULL, "Set/Show the current controller model" }, + + { 0 } +}; + +/* Debug Flags */ +static DEBTAB tarbell_dt[] = { + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { "ERROR", ERROR_MSG, "Error messages" }, + { "STATUS", STATUS_MSG, "Status messages" }, + { "DRIVE", DRIVE_MSG, "Drive messages" }, + { "IRQ", IRQ_MSG, "IRQ messages" }, + { "READ", READ_MSG, "Read messages" }, + { "WRITE", WRITE_MSG, "Write messages" }, + { "COMMAND", COMMAND_MSG, "Command messages" }, + { "FORMAT", FORMAT_MSG, "Format messages" }, + { NULL, 0 } +}; + +DEVICE tarbell_dev = { + DEV_NAME, tarbell_unit, tarbell_reg, tarbell_mod, TARBELL_NUM_DRIVES, + ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &tarbell_reset, + &tarbell_boot, &tarbell_attach, &tarbell_detach, + &tarbell_res, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0, + tarbell_dt, NULL, NULL, &tarbell_show_help, NULL, NULL, &tarbell_description +}; + +/* Reset routine */ +static t_stat tarbell_reset(DEVICE *dptr) +{ + RES *res; + int i; + + if ((res = (RES *) dptr->ctxt) == NULL) { + sim_printf("CTX is NULL!\n"); + return SCPE_IERR; + } + + if(dptr->flags & DEV_DIS) { /* Unmap I/O Ports */ + wd17xx = wd17xx_release(wd17xx); + + s100_bus_remio(res->io_base, res->io_size, &tarbell_io); + + poc = TRUE; + } else { + if (poc) { + ddfdc_enabled = FALSE; + + for (i = 0; i < TARBELL_NUM_DRIVES; i++) { + tarbell_unit[i].dptr = dptr; + dsk_init(&dsk_info[i], &tarbell_unit[i], 77, 1, 0); + dsk_set_verbose_flag(&dsk_info[i], VERBOSE_MSG); + } + + if (wd17xx == NULL) { + if ((wd17xx = wd17xx_init(dptr)) == NULL) { + sim_printf("Could not init WD17XX\n"); + } + else { + wd17xx_set_fdctype(wd17xx, WD17XX_FDCTYPE_1771); /* Set to 1771 */ + wd17xx_set_verbose_flag(wd17xx, VERBOSE_MSG); + wd17xx_set_error_flag(wd17xx, ERROR_MSG); + wd17xx_set_read_flag(wd17xx, READ_MSG); + wd17xx_set_write_flag(wd17xx, WRITE_MSG); + wd17xx_set_command_flag(wd17xx, COMMAND_MSG); + wd17xx_set_format_flag(wd17xx, FORMAT_MSG); + } + } + + s100_bus_addio(res->io_base, res->io_size, &tarbell_io, DEV_NAME); + + if (prom_enabled) { + tarbell_enable_prom(); + } + + poc = FALSE; + } + + if (prom_enabled) { + prom_active = TRUE; + } + + drv_sel = 0; + + if (wd17xx != NULL) { + wd17xx_reset(wd17xx); + wd17xx_set_dsk(wd17xx, &dsk_info[drv_sel]); + } + } + + return SCPE_OK; +} + +static t_stat tarbell_boot(int32 unitno, DEVICE *dptr) +{ + + sim_debug(STATUS_MSG, &tarbell_dev, DEV_NAME ": Booting Controller at 0x%04x\n", tarbell_res.mem_base); + + s100_bus_set_addr(tarbell_res.mem_base); + + return SCPE_OK; +} + +/* Attach routine */ +static t_stat tarbell_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + int d; + + /* Determine drive number */ + d = uptr - &tarbell_unit[0]; + + if (d < 0 || d >= TARBELL_NUM_DRIVES) { + return SCPE_IERR; + } + + sim_switches |= SWMASK ('E'); /* File must exist */ + + if ((r = attach_unit(uptr, cptr)) != SCPE_OK) { /* attach unit */ + sim_printf(DEV_NAME ": ATTACH error=%d\n", r); + return r; + } + + /* Determine length of this disk */ + uptr->capac = sim_fsize(uptr->fileref); + + /* init format based on file size */ + switch (uptr->capac) { + case TARBELL_DD_CAPACITY: + dsk_init_format(&dsk_info[d], 0, 0, 0, 0, DSK_DENSITY_SD, 26, 128, 1); + dsk_init_format(&dsk_info[d], 1, 76, 0, 0, DSK_DENSITY_DD, 51, 128, 1); + break; + + default: + uptr->capac = TARBELL_SD_CAPACITY; + dsk_init_format(&dsk_info[d], 0, 76, 0, 0, DSK_DENSITY_SD, 26, 128, 1); + break; + } + +// dsk_show(&dsk_info[d]); + + return r; +} + +/* Detach routine */ +static t_stat tarbell_detach(UNIT *uptr) +{ + t_stat r; + + r = detach_unit(uptr); /* detach unit */ + + return r; +} + +static int32 tarbell_io(const int32 port, const int32 io, const int32 data) +{ + int32 result = 0xff; + + if (io == S100_IO_WRITE) { /* I/O Write */ + + switch (port & TARBELL_IO_MASK) { + case WD17XX_REG_COMMAND: + case WD17XX_REG_TRACK: + case WD17XX_REG_SECTOR: + case WD17XX_REG_DATA: + wd17xx_outp(wd17xx, port & TARBELL_IO_MASK, data & DATAMASK); + sim_debug(STATUS_MSG, &tarbell_dev, DEV_NAME ": " ADDRESS_FORMAT + " Write WD17XX, Port 0x%02x Data 0x%02x\n", s100_bus_get_addr(), port, data & DATAMASK); + break; + + case TARBELL_REG_DRVSEL: + if (ddfdc_enabled) { + drv_sel = (data & TARBELL_DSEL_MASK) >> 4; /* 2022 not inverted */ + wd17xx_sel_side(wd17xx, (data & TARBELL_SIDE_MASK) >> 6); + wd17xx_sel_dden(wd17xx, (data & TARBELL_DENS_MASK) ? TRUE : FALSE); + + sim_debug(DRIVE_MSG, &tarbell_dev, DEV_NAME ": " ADDRESS_FORMAT " WR DRVSEL (0x%02x) = 0x%02x: Drive: %d, Side: %d, %s-Density.\n", + s100_bus_get_addr(), port, data & DATAMASK, drv_sel, + (data & TARBELL_SIDE_MASK) >> 6, (data & TARBELL_DENS_MASK) ? "Double" : "Single"); + } + else { + drv_sel = (~data & TARBELL_DSEL_MASK) >> 4; /* 1011 inverted */ + wd17xx_sel_side(wd17xx, 0); + wd17xx_sel_dden(wd17xx, FALSE); + + sim_debug(DRIVE_MSG, &tarbell_dev, DEV_NAME ": " ADDRESS_FORMAT " WR DRVSEL (0x%02x) = 0x%02x: Drive: %d\n", + s100_bus_get_addr(), port, data & DATAMASK, drv_sel); + } + + /* Tell WD17XX which drive is selected */ + wd17xx_set_dsk(wd17xx, &dsk_info[drv_sel]); + + break; + + case TARBELL_REG_EXTADDR: + sim_debug(STATUS_MSG, &tarbell_dev, DEV_NAME ": " ADDRESS_FORMAT + " Write Extended Address, Port 0x%02x=0x%02x\n", s100_bus_get_addr(), port, data & DATAMASK); + break; + + default: + break; + } + } else { /* I/O Read */ + switch (port & TARBELL_IO_MASK) { + case WD17XX_REG_STATUS: + case WD17XX_REG_TRACK: + case WD17XX_REG_SECTOR: + case WD17XX_REG_DATA: + result = wd17xx_inp(wd17xx, port & TARBELL_IO_MASK); + sim_debug(STATUS_MSG, &tarbell_dev, DEV_NAME ": " ADDRESS_FORMAT + " Read WD17XX, Port 0x%02x Result 0x%02x\n", s100_bus_get_addr(), port, result); + break; + + case TARBELL_REG_WAIT: + result = (wd17xx_intrq(wd17xx)) ? 0 : TARBELL_FLAG_DRQ; + sim_debug(STATUS_MSG, &tarbell_dev, DEV_NAME ": " ADDRESS_FORMAT + " Read WAIT, Port 0x%02x Result 0x%02x\n", s100_bus_get_addr(), port, result); + break; + + case TARBELL_REG_DMASTAT: + result = 0x00; + break; + } + } + + return result; +} + +/* + * The Tarbell Floppy Disk Constroller has a 32-byte PROM + * located at 0x0000. The PROM loads the first sector of + * track 0 from drive 0 into 0x0000. Since the PROM is + * active at 0x0000, the Tarbell asserts /PHANTOM. While + * /PHANTOM is asserted, memory reads from 0x0000-0x001f + * will be provided by the Tarbell PROM, while memory + * writes to those locations will be handled by the RAM + * board. /PHANTOM is simulated below by passing requests + * to the RAM board configured on the BUS for the first + * page of RAM. The PROM is disabled and /PHANTOM is + * deasserted when A5 is active. + */ +static int32 tarbell_memio(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_READ) { + if (prom_active && ((addr & TARBELL_PROM_MASK) == addr)) { + return tarbell_prom[addr]; + } + else if (mdev.routine != NULL) { + if (addr & 0x0020) { + prom_active = FALSE; + } + + return (mdev.routine)(addr, rw, data); + } + } + else { + /* If writing to RAM, call memory device routine */ + if (mdev.routine != NULL) { + return (mdev.routine)(addr, rw, data); + } + } + + return 0xff; +} + +static t_stat tarbell_set_model(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + + /* Remove IO mapping */ + s100_bus_remio(tarbell_res.io_base, tarbell_res.io_size, &tarbell_io); + + /* this assumes that the parameter has already been upcased */ + if (!strcmp(cptr, "DD")) { + ddfdc_enabled = TRUE; + tarbell_res.io_size = TARBELL_IO_SIZE; + wd17xx_set_fdctype(wd17xx, WD17XX_FDCTYPE_1791); /* Set to 1791 */ + } else { + ddfdc_enabled = FALSE; + tarbell_res.io_size = TARBELL_IO_SIZE - 1; + wd17xx_set_fdctype(wd17xx, WD17XX_FDCTYPE_1771); /* Set to 1771 */ + } + + /* Map new IO */ + s100_bus_addio(tarbell_res.io_base, tarbell_res.io_size, &tarbell_io, DEV_NAME); + + return SCPE_OK; +} + +static t_stat tarbell_show_model(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + fprintf(st, "MODEL=%s", (ddfdc_enabled) ? "2022DD" : "1011SD"); + + return SCPE_OK; +} + +static t_stat tarbell_set_prom(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "ENABLE", strlen(cptr)) && prom_enabled == FALSE) { + tarbell_enable_prom(); + } else if (!strncmp(cptr, "DISABLE", strlen(cptr)) && prom_enabled == TRUE) { + tarbell_disable_prom(); + } else { + return SCPE_ARG; + } + + return SCPE_OK; +} + +static void tarbell_enable_prom(void) +{ + /* Save existing memory device */ + s100_bus_get_mdev(tarbell_res.mem_base, &mdev); + + /* Add PROM to bus */ + s100_bus_addmem(tarbell_res.mem_base, tarbell_res.mem_size, &tarbell_memio, DEV_NAME); + + prom_enabled = TRUE; +} + +static void tarbell_disable_prom(void) +{ + /* Restore memory device */ + if (mdev.routine != NULL) { + s100_bus_addmem(tarbell_res.mem_base, tarbell_res.mem_size, mdev.routine, mdev.name); + mdev.routine = NULL; + mdev.name = NULL; + } + else { + s100_bus_remmem(tarbell_res.mem_base, tarbell_res.mem_size, &tarbell_memio); + } + + prom_enabled = FALSE; +} + +static t_stat tarbell_show_prom(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + fprintf(st, "%s (%sactive)", (prom_enabled) ? "PROM" : "NOPROM", (prom_active) ? "" : "in"); + + return SCPE_OK; +} + +static t_stat tarbell_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nTarbell Model 1011/2022 Disk Controller (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/tarbell_fdc.h b/Altair8800/tarbell_fdc.h new file mode 100644 index 00000000..b5bbf112 --- /dev/null +++ b/Altair8800/tarbell_fdc.h @@ -0,0 +1,60 @@ +/* tarbell_fdc.h + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 11/10/25 Initial version + +*/ + +#ifndef _TARBELL_FDC_H +#define _TARBELL_FDC_H + +#define TARBELL_NUM_DRIVES 4 + +#define TARBELL_IO_BASE 0xF8 +#define TARBELL_IO_SIZE 6 +#define TARBELL_IO_MASK 0x07 + +#define TARBELL_PROM_BASE 0x0000 +#define TARBELL_PROM_SIZE 32 +#define TARBELL_PROM_MASK (TARBELL_PROM_SIZE - 1) + +/* Tarbell Register Offsets */ +#define TARBELL_REG_WAIT 0x04 /* Wait Port */ +#define TARBELL_REG_DRVSEL 0x04 /* Drive Select */ +#define TARBELL_REG_DMASTAT 0x05 /* DMA INTRQ Status */ +#define TARBELL_REG_EXTADDR 0x05 /* Extended Address */ + +#define TARBELL_DENS_MASK 0x08 +#define TARBELL_DSEL_MASK 0x30 +#define TARBELL_SIDE_MASK 0x40 + +#define TARBELL_FLAG_DRQ 0x80 /* End of Job (DRQ) */ + +#define TARBELL_SD_CAPACITY (77*26*128) /* SSSD 8" (IBM 3740) Disk Capacity */ +#define TARBELL_DD_CAPACITY ((26*128) + (76*51*128)) /* SSDD 8" Tarbell DD Disk Capacity */ + +#endif + diff --git a/Altair8800/wd_17xx.c b/Altair8800/wd_17xx.c new file mode 100644 index 00000000..ba795de5 --- /dev/null +++ b/Altair8800/wd_17xx.c @@ -0,0 +1,860 @@ +/* wd_17xx.c: Western Digital FD17XX Floppy Disk Controller/Formatter + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + Based on work by Howard M. Harte 2007-2022 + + History: + 13-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "s100_bus.h" +#include "wd_17xx.h" + +#define WD17XX_NAME "WD17XX" + +static uint8 sbuf[WD17XX_MAX_SECTOR_SIZE]; + +static void wd17xx_command(WD17XX_INFO *wd, uint8 data); +static int32 wd17xx_valid(WD17XX_INFO *wd); +static uint8 wd17xx_sec_len(WD17XX_INFO *wd); +static t_stat wd17xx_read_sector(WD17XX_INFO *wd, uint8 *sbuf, int32 *bytesread); +static t_stat wd17xx_write_sector(WD17XX_INFO *wd, uint8 *sbuf, int32 *byteswritten); +static void wd17xx_set_intrq(WD17XX_INFO *wd, int32 value); + +WD17XX_INFO * wd17xx_init(DEVICE *dptr) +{ + WD17XX_INFO *wd; + + if ((dptr == NULL) || (wd = malloc(sizeof(WD17XX_INFO))) == NULL) { + return NULL; + } + + memset(wd, 0x00, sizeof(WD17XX_INFO)); + + /* Save device */ + wd->dptr = dptr; + + return wd; +} + +WD17XX_INFO * wd17xx_release(WD17XX_INFO *wd) +{ + if (wd != NULL) { + free(wd); + } + + return NULL; +} + +void wd17xx_reset(WD17XX_INFO *wd) +{ + if (wd != NULL) { + wd->intrq = TRUE; + wd->drq = FALSE; + wd->status = 0; + wd->track = 0; + wd->fdc_write = FALSE; + wd->fdc_read = FALSE; + wd->fdc_write_track = FALSE; + wd->fdc_readadr = FALSE; + wd->fdc_datacount = 0; + wd->fdc_dataindex = 0; + wd->fdc_sec_len = wd17xx_sec_len(wd); + } +} + +void wd17xx_set_intena(WD17XX_INFO *wd, int32 ena) { + if (wd != NULL) { + wd->intenable = ena; + } +} + +void wd17xx_set_intvec(WD17XX_INFO *wd, int32 vec) { + if (wd != NULL) { + wd->intvector = vec; + } +} + +void wd17xx_set_verbose_flag(WD17XX_INFO *wd, uint32 flag) +{ + if (wd != NULL) { + wd->dbg_verbose = flag; + } +} + +void wd17xx_set_error_flag(WD17XX_INFO *wd, uint32 flag) +{ + if (wd != NULL) { + wd->dbg_error = flag; + } +} + +void wd17xx_set_command_flag(WD17XX_INFO *wd, uint32 flag) +{ + if (wd != NULL) { + wd->dbg_command = flag; + } +} + +void wd17xx_set_read_flag(WD17XX_INFO *wd, uint32 flag) +{ + if (wd != NULL) { + wd->dbg_read = flag; + } +} + +void wd17xx_set_write_flag(WD17XX_INFO *wd, uint32 flag) +{ + if (wd != NULL) { + wd->dbg_write = flag; + } +} + +void wd17xx_set_format_flag(WD17XX_INFO *wd, uint32 flag) +{ + if (wd != NULL) { + wd->dbg_format = flag; + } +} + +void wd17xx_sel_dden(WD17XX_INFO *wd, uint8 dden) +{ + if (wd != NULL) { + wd->dden = dden; + } +} + +void wd17xx_sel_side(WD17XX_INFO *wd, uint8 side) +{ + if (wd != NULL) { + wd->side = side; + wd->fdc_sec_len = wd17xx_sec_len(wd); + } +} + +void wd17xx_sel_drive_type(WD17XX_INFO *wd, uint8 type) +{ + if (wd != NULL) { + wd->drivetype = type; + } +} + +uint8 wd17xx_intrq(WD17XX_INFO *wd) +{ + if (wd != NULL) { + return wd->intrq; + } + + return 0xff; +} + +void wd17xx_set_fdctype(WD17XX_INFO *wd, int fdctype) +{ + if (wd != NULL) { + wd->fdctype = fdctype; + } +} + +void wd17xx_set_dsk(WD17XX_INFO *wd, DSK_INFO *dsk) +{ + if (wd != NULL) { + wd->dsk = dsk; + } +} + +uint8 wd17xx_inp(WD17XX_INFO *wd, uint8 port) +{ + uint8 r = 0xff; + int32 bytesread; + + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME " INP %02X\n", port); + + if (wd == NULL || wd->dsk == NULL) { + return r; + } + + switch (port) { + case WD17XX_REG_STATUS: + /* Fix up status based on Command Type */ + if ((wd->cmdtype == 0) || (wd->cmdtype == 1) || (wd->cmdtype == 4)) { + wd->status ^= WD17XX_STAT_IDX; /* Generate Index pulses */ + wd->status &= ~WD17XX_STAT_TRK0; + wd->status |= (wd->track == 0) ? WD17XX_STAT_TRK0 : 0; + } + else { /* Command Type 3 */ + wd->status &= ~WD17XX_STAT_IDX; /* Mask index pulses */ + wd->status |= (wd->drq) ? WD17XX_STAT_DRQ : 0; + } + + wd->status &= ~WD17XX_STAT_NRDY; + wd->status |= (wd->dsk->unit == NULL || wd->dsk->unit->fileref == NULL) ? WD17XX_STAT_NRDY : 0; + + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " RD STATUS = 0x%02x, CMDTYPE=%x\n", s100_bus_get_addr(), wd->status, wd->cmdtype); + + r = wd->status; + break; + + case WD17XX_REG_TRACK: + r = wd->track; + break; + + case WD17XX_REG_SECTOR: + r = wd->sector; + break; + + case WD17XX_REG_DATA: + r = 0xFF; /* Return High-Z data */ + if (wd->fdc_read == TRUE) { + if (wd->fdc_dataindex < wd->fdc_datacount) { + wd->status &= ~(WD17XX_STAT_BUSY); /* Clear BUSY */ + wd->data = sbuf[wd->fdc_dataindex]; + r = wd->data; + + if (wd->fdc_readadr == TRUE) { + sim_debug(wd->dbg_read, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " READ_ADDR[%d/%d] = 0x%02x\n", s100_bus_get_addr(), wd->fdc_dataindex, wd->fdc_datacount, wd->data); + } + + wd->fdc_dataindex++; + if (wd->fdc_dataindex == wd->fdc_datacount) { + if (wd->fdc_multi == FALSE) { + wd->status &= ~(WD17XX_STAT_DRQ | WD17XX_STAT_BUSY); /* Clear DRQ, BUSY */ + wd17xx_set_intrq(wd, TRUE); + wd->fdc_read = FALSE; + wd->fdc_readadr = FALSE; + } else { + wd->sector++; + + sim_debug(wd->dbg_read, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " MULTI_READ_REC, T:%2d/H:%d/S:%2d, %s, len=%d\n", + s100_bus_get_addr(), wd->track, wd->side, wd->sector, wd->dden ? "DD" : "SD", + dsk_sector_size(wd->dsk, wd->track, wd->side)); + + if (wd->dsk->unit->fileref == NULL) { + sim_debug(wd->dbg_error, wd->dptr, ".fileref is NULL!\n"); + } else { + dsk_read_sector(wd->dsk, wd->track, wd->side, wd->sector, sbuf, &bytesread); + } + } + } + } + } + break; + + default: + break; + } + + return r; +} + +void wd17xx_outp(WD17XX_INFO *wd, uint8 port, uint8 data) +{ + int32 byteswritten; + + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME " OUTP %02X %02X\n", port, data); + + if (wd == NULL || wd->dsk == NULL) { + return; + } + + switch (port) { + case WD17XX_REG_COMMAND: + wd->fdc_read = FALSE; + wd->fdc_write = FALSE; + wd->fdc_write_track = FALSE; + wd->fdc_datacount = 0; + wd->fdc_dataindex = 0; + if (wd->intenable) { + s100_bus_int(1 << wd->intvector, wd->intvector * 2); + } + + wd17xx_command(wd, data); + break; + + case WD17XX_REG_TRACK: + wd->track = data; + wd->fdc_sec_len = wd17xx_sec_len(wd); + break; + + case WD17XX_REG_SECTOR: + wd->sector = data; + break; + + case WD17XX_REG_DATA: + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " WR DATA = 0x%02x\n", s100_bus_get_addr(), data); + if (wd->fdc_write == TRUE) { + if (wd->fdc_dataindex < wd->fdc_datacount) { + sbuf[wd->fdc_dataindex] = data; + + wd->fdc_dataindex++; + if (wd->fdc_dataindex == wd->fdc_datacount) { + wd->status &= ~(WD17XX_STAT_DRQ | WD17XX_STAT_BUSY); /* Clear DRQ, BUSY */ + wd17xx_set_intrq(wd, TRUE); + if (wd->intenable) { + s100_bus_int(1 << wd->intvector, wd->intvector * 2); + } + + sim_debug(wd->dbg_write, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " Writing sector, T:%2d/S:%d/H:%2d, Len=%d\n", + s100_bus_get_addr(), wd->track, wd->side, wd->sector, + dsk_sector_size(wd->dsk, wd->track, wd->side)); + + wd17xx_write_sector(wd, sbuf, &byteswritten); + + wd->fdc_write = FALSE; + } + } + } + + if (wd->fdc_write_track == TRUE) { + if (wd->fdc_fmt_state == WD17XX_FMT_GAP1) { + if (data != 0xFC && (data != 0x00 && wd->fdc_gap[0] < 32)) { + wd->fdc_gap[0]++; + } + else { + sim_debug(wd->dbg_format, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " FMT GAP1 Length = %d\n", s100_bus_get_addr(), wd->fdc_gap[0]); + wd->fdc_gap[1] = 0; + wd->fdc_fmt_state = WD17XX_FMT_GAP2; + } + } else if (wd->fdc_fmt_state == WD17XX_FMT_GAP2) { + if (data != 0xFE) { + wd->fdc_gap[1]++; + } + else { + sim_debug(wd->dbg_format, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " FMT GAP2 Length = %d\n", s100_bus_get_addr(), wd->fdc_gap[1]); + wd->fdc_gap[2] = 0; + wd->fdc_fmt_state = WD17XX_FMT_HEADER; + wd->fdc_header_index = 0; + } + } else if (wd->fdc_fmt_state == WD17XX_FMT_HEADER) { + if (wd->fdc_header_index == 5) { + wd->fdc_gap[2] = 0; + wd->fdc_fmt_state = WD17XX_FMT_GAP3; + } else { + sim_debug(wd->dbg_format, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " HEADER[%d]=%02x\n", s100_bus_get_addr(), wd->fdc_header_index, data); + + switch (wd->fdc_header_index) { + case 0: + wd->fdc_fmt_track = data; + break; + case 1: + wd->fdc_fmt_side = data; + break; + case 2: + wd->fdc_fmt_sector = data; + break; + case 3: + case 4: + break; + } + + wd->fdc_header_index++; + } + } else if (wd->fdc_fmt_state == WD17XX_FMT_GAP3) { + if (data != 0xFB) { + wd->fdc_gap[2]++; + } + else { + sim_debug(wd->dbg_format, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " FMT GAP3 Length = %d\n", s100_bus_get_addr(), wd->fdc_gap[2]); + wd->fdc_fmt_state = WD17XX_FMT_DATA; + wd->fdc_dataindex = 0; + } + } else if (wd->fdc_fmt_state == WD17XX_FMT_DATA) { /* data bytes */ + if (data != 0xF7) { + sbuf[wd->fdc_dataindex] = data; + wd->fdc_dataindex++; + } + else { + wd->fdc_sec_len = sys_floorlog2(wd->fdc_dataindex) - 7; + + if (wd->fdc_sec_len > wd17xx_sec_len(wd)) { /* Error calculating N or N too large */ + sim_debug(wd->dbg_error, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " Invalid sector size!\n", s100_bus_get_addr()); + wd->fdc_sec_len = 0; + } + if (wd->fdc_fmt_sector_count >= dsk_sectors(wd->dsk, wd->track, wd->side)) { + sim_debug(wd->dbg_error, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " Illegal sector count\n", s100_bus_get_addr()); + + wd->fdc_fmt_sector_count = 0; + } + + wd->fdc_fmt_sector_count++; + + /* Write the sector to disk */ + dsk_write_sector(wd->dsk, wd->track, wd->side, wd->fdc_fmt_sector_count, sbuf, NULL); + + sim_debug(wd->dbg_format, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " FMT Data Length = %d\n", s100_bus_get_addr(), wd->fdc_dataindex); + + sim_debug(wd->dbg_format, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " FORMAT T:%2d (%02d)/H:%d (%02d)/S:%2d (%02d)/L=%d (%02X)\n", s100_bus_get_addr(), + wd->track, wd->fdc_fmt_track, wd->side, wd->fdc_fmt_side, + wd->fdc_fmt_sector_count, wd->fdc_fmt_sector, + wd->fdc_dataindex, wd->fdc_sec_len); + + wd->fdc_gap[1] = 0; + wd->fdc_fmt_state = WD17XX_FMT_GAP2; + + if (wd->fdc_fmt_sector_count == dsk_sectors(wd->dsk, wd->track, wd->side)) { + wd->status &= ~(WD17XX_STAT_BUSY | WD17XX_STAT_LOSTD); /* Clear BUSY, LOST_DATA */ + wd17xx_set_intrq(wd, TRUE); + if (wd->intenable) { + s100_bus_int(1 << wd->intvector, wd->intvector * 2); + } + + /* Recalculate disk size */ + wd->dsk->unit->capac = sim_fsize(wd->dsk->unit->fileref); + } + } + } + } + + wd->data = data; + break; + + default: + break; + } +} + +static void wd17xx_command(WD17XX_INFO *wd, uint8 cmd) +{ + int32 bytesread; + + if (wd->status & WD17XX_STAT_BUSY) { + if (((cmd & WD17XX_CMD_MASK) != WD17XX_CMD_FI)) { + sim_debug(wd->dbg_error, wd->dptr, WD17XX_NAME " " ADDRESS_FORMAT + " ERROR: Command 0x%02x ignored because controller is BUSY\n\n", s100_bus_get_addr(), cmd); + } + return; + } + + switch(cmd & WD17XX_CMD_MASK) { + /* Type I Commands */ + case WD17XX_CMD_RESTORE: + case WD17XX_CMD_SEEK: + case WD17XX_CMD_STEP: + case WD17XX_CMD_STEPU: + case WD17XX_CMD_STEPIN: + case WD17XX_CMD_STEPINU: + case WD17XX_CMD_STEPOUT: + case WD17XX_CMD_STEPOUTU: + wd->cmdtype = 1; + wd->status |= WD17XX_STAT_BUSY; /* Set BUSY */ + wd->status &= ~(WD17XX_STAT_CRC | WD17XX_STAT_SEEK | WD17XX_STAT_DRQ); + wd17xx_set_intrq(wd, FALSE); + wd->hld = cmd & WD17XX_FLG_H; + wd->verify = cmd & WD17XX_FLG_V; + if (wd->fdctype == WD17XX_FDCTYPE_1795) { + /* WD1795 and WD1797 have a side select output. */ + wd->side = (cmd & WD17XX_FLG_F1) >> 1; + } + break; + + /* Type II Commands */ + case WD17XX_CMD_RD: + case WD17XX_CMD_RDM: + case WD17XX_CMD_WR: + case WD17XX_CMD_WRM: + wd->cmdtype = 2; + wd->status = WD17XX_STAT_BUSY; /* Set BUSY, clear all others */ + wd17xx_set_intrq(wd, FALSE); + wd->hld = 1; /* Load the head immediately, E Flag not checked. */ + if (wd->fdctype != WD17XX_FDCTYPE_1771) { + /* WD1795 and WD1797 have a side select output. */ + wd->side = (cmd & WD17XX_FLG_F1) >> 1; + } + break; + + /* Type III Commands */ + case WD17XX_CMD_RDADR: + case WD17XX_CMD_RDTRK: + case WD17XX_CMD_WRTRK: + wd->cmdtype = 3; + break; + + /* Type IV Commands */ + case WD17XX_CMD_FI: + wd->cmdtype = 4; + break; + + default: + wd->cmdtype = 0; + sim_debug(wd->dbg_error, wd->dptr, WD17XX_NAME " Invalid command %02X\n", cmd); + break; + } + + + switch(cmd & WD17XX_CMD_MASK) { + + /* Type I Commands */ + case WD17XX_CMD_RESTORE: + wd->track = 0; + wd17xx_set_intrq(wd, TRUE); + + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=RESTORE %s\n", s100_bus_get_addr(), wd->verify ? "[VERIFY]" : ""); + break; + + case WD17XX_CMD_SEEK: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT + " CMD=SEEK, track=%d, new=%d %s\n", s100_bus_get_addr(), wd->track, wd->data, wd->verify ? "[VERIFY]" : ""); + + wd->track = wd->data; + break; + + case WD17XX_CMD_STEP: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=STEP %s\n", s100_bus_get_addr(), wd->verify ? "[VERIFY]" : ""); + break; + + case WD17XX_CMD_STEPU: + if (wd->fdc_step_dir == 1) { + if (wd->track < wd->dsk->fmt.tracks - 1) { + wd->track++; + } + } else if (wd->fdc_step_dir == -1) { + if (wd->track > 0) { + wd->track--; + } + } + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=STEP_U dir=%d track=%d %s\n", s100_bus_get_addr(), wd->fdc_step_dir, wd->track, wd->verify ? "[VERIFY]" : ""); + break; + + case WD17XX_CMD_STEPIN: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=STEP_IN %s\n", s100_bus_get_addr(), wd->verify ? "[VERIFY]" : ""); + break; + + case WD17XX_CMD_STEPINU: + if (wd->track < wd->dsk->fmt.tracks - 1) { + wd->track++; + } + + wd->fdc_step_dir = 1; + + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=STEP_IN_U, track=%d %s\n", s100_bus_get_addr(), wd->track, wd->verify ? "[VERIFY]" : ""); + break; + + case WD17XX_CMD_STEPOUT: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=STEP_OUT %s\n", s100_bus_get_addr(), wd->verify ? "[VERIFY]" : ""); + break; + + case WD17XX_CMD_STEPOUTU: + if (wd->track > 0) { + wd->track--; + } + + wd->fdc_step_dir = -1; + + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=STEP_OUT_U, track=%d %s\n", s100_bus_get_addr(), wd->track, wd->verify ? "[VERIFY]" : ""); + break; + + /* Type II Commands */ + case WD17XX_CMD_RD: + case WD17XX_CMD_RDM: + wd->fdc_multi = (cmd & WD17XX_FLG_M) ? TRUE : FALSE; + + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=READ_REC, T:%2d/H:%d/S:%2d, %s, %s len=%d\n", s100_bus_get_addr(), wd->track, + wd->side, wd->sector, + wd->fdc_multi ? "Multiple" : "Single", + wd->dden ? "DD" : "SD", wd->dsk->fmt.track[wd->track][wd->side].sectorsize); + + if (wd17xx_valid(wd) == FALSE) { + wd->status |= WD17XX_STAT_RNF; /* Record not found */ + wd->status &= ~WD17XX_STAT_BUSY; + wd17xx_set_intrq(wd, TRUE); + } else { + wd17xx_read_sector(wd, sbuf, &bytesread); + } + break; + + case WD17XX_CMD_WR: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=WRITE_REC, T:%2d/H:%d/S:%2d, %s.\n", s100_bus_get_addr(), wd->track, wd->side, wd->sector, (cmd & WD17XX_FLG_M) ? "Multiple" : "Single"); + + wd->status |= (WD17XX_STAT_DRQ); /* Set DRQ */ + wd->status |= (wd->dsk->unit->flags & UNIT_RO) ? WD17XX_STAT_WP : 0; /* Set WP */ + wd->drq = 1; + wd->fdc_datacount = dsk_sector_size(wd->dsk, wd->track, wd->side); + wd->fdc_dataindex = 0; + wd->fdc_write = TRUE; + wd->fdc_write_track = FALSE; + wd->fdc_read = FALSE; + wd->fdc_readadr = FALSE; + + sbuf[wd->fdc_dataindex] = wd->data; + break; + + case WD17XX_CMD_WRM: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " Error: WRITE_RECS not implemented.\n", s100_bus_get_addr()); + break; + + /* Type III Commands */ + case WD17XX_CMD_RDADR: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=READ_ADDR, T:%d/S:%d, %s\n", + s100_bus_get_addr(), wd->track, wd->side, wd->dden ? "DD" : "SD"); + + if (wd17xx_valid(wd) == FALSE) { + wd->status = WD17XX_STAT_RNF; /* Record not found */ + wd17xx_set_intrq(wd, TRUE); + } else { + wd->status = (WD17XX_STAT_DRQ | WD17XX_STAT_BUSY); /* Set DRQ, BUSY */ + wd->drq = 1; + wd->fdc_datacount = 6; + wd->fdc_dataindex = 0; + wd->fdc_read = TRUE; + wd->fdc_readadr = TRUE; + + sbuf[0] = wd->track; + sbuf[1] = wd->side; + sbuf[2] = (wd->sector < dsk_start_sector(wd->dsk, wd->track, wd->side)) ? wd->sector : dsk_start_sector(wd->dsk, wd->track, wd->side); + sbuf[3] = wd->fdc_sec_len; + sbuf[4] = 0xAA; /* CRC1 */ + sbuf[5] = 0x55; /* CRC2 */ + + wd->sector = wd->track; + + wd->status &= ~(WD17XX_STAT_BUSY); /* Clear BUSY */ + + wd17xx_set_intrq(wd, TRUE); + } + break; + + case WD17XX_CMD_RDTRK: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=READ_TRACK\n", s100_bus_get_addr()); + sim_debug(wd->dbg_error, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " Error: READ_TRACK not implemented.\n", s100_bus_get_addr()); + break; + + case WD17XX_CMD_WRTRK: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=WRITE_TRACK, T:%2d/H:%d/S:%d.\n", + s100_bus_get_addr(), wd->track, wd->side, + wd->dsk->fmt.track[wd->track][wd->side].sectorsize); + + wd->status |= (WD17XX_STAT_DRQ); /* Set DRQ */ + wd->status |= (wd->dsk->unit->flags & UNIT_RO) ? WD17XX_STAT_WP : 0; /* Set WP */ + wd17xx_set_intrq(wd, FALSE); + wd->fdc_datacount = dsk_sector_size(wd->dsk, wd->track, wd->side); + wd->fdc_dataindex = 0; + wd->fdc_write = FALSE; + wd->fdc_write_track = TRUE; + wd->fdc_read = FALSE; + wd->fdc_readadr = FALSE; + wd->fdc_fmt_state = WD17XX_FMT_GAP1; /* TRUE when writing an entire track */ + wd->fdc_fmt_sector_count = 0; + + break; + + /* Type IV Commands */ + case WD17XX_CMD_FI: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " CMD=FORCE_INTR\n", s100_bus_get_addr()); + if ((cmd & WD17XX_CMD_MASK) == 0) { /* I0-I3 == 0, no intr, but clear BUSY and terminate command */ + wd->status &= ~(WD17XX_STAT_DRQ | WD17XX_STAT_BUSY); /* Clear DRQ, BUSY */ + wd->drq = 0; + wd->fdc_write = FALSE; + wd->fdc_read = FALSE; + wd->fdc_write_track = FALSE; + wd->fdc_readadr = FALSE; + wd->fdc_datacount = 0; + wd->fdc_dataindex = 0; + } + else if (cmd & 0x08) { /* Immediate Interrupt */ + wd17xx_set_intrq(wd, TRUE); + + if (wd->intenable) { + s100_bus_int(1 << wd->intvector, wd->intvector * 2); + } + wd->status &= ~(WD17XX_STAT_BUSY); /* Clear BUSY */ + } + else { /* Other interrupts not implemented yet */ + wd->status &= ~(WD17XX_STAT_BUSY); /* Clear BUSY */ + } + break; + + default: + sim_debug(wd->dbg_command, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " ERROR: Unknown command 0x%02x.\n\n", s100_bus_get_addr(), cmd); + break; + } + + /* Post processing of Type-specific command */ + switch(cmd & WD17XX_CMD_MASK) { + + /* Type I Commands */ + case WD17XX_CMD_RESTORE: + case WD17XX_CMD_SEEK: + case WD17XX_CMD_STEP: + case WD17XX_CMD_STEPU: + case WD17XX_CMD_STEPIN: + case WD17XX_CMD_STEPINU: + case WD17XX_CMD_STEPOUT: + case WD17XX_CMD_STEPOUTU: + if (wd->verify) { /* Verify the selected track/side is ok. */ + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME ADDRESS_FORMAT " Verify ", s100_bus_get_addr()); + if (dsk_validate(wd->dsk, wd->track, 0, 1) != SCPE_OK) { /* Not validating side or sector */ + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME "FAILED\n"); + wd->status |= WD17XX_STAT_SEEK; /* Seek error */ + } else { + sim_debug(wd->dbg_verbose, wd->dptr, WD17XX_NAME "Ok\n"); + } + } + + wd->status &= ~(WD17XX_STAT_TRK0); + if (wd->track == 0) { + wd->status |= WD17XX_STAT_TRK0; + } + + wd->fdc_sec_len = wd17xx_sec_len(wd); + + wd->status &= ~(WD17XX_STAT_BUSY); /* Clear BUSY */ + wd17xx_set_intrq(wd, TRUE); + + if (wd->intenable) { + s100_bus_int(1 << wd->intvector, wd->intvector * 2); + } + + break; + + /* Type II Commands */ + case WD17XX_CMD_RD: + case WD17XX_CMD_RDM: + case WD17XX_CMD_WR: + case WD17XX_CMD_WRM: + + /* Type III Commands */ + case WD17XX_CMD_RDADR: + case WD17XX_CMD_RDTRK: + case WD17XX_CMD_WRTRK: + wd->status &= ~(WD17XX_STAT_BUSY); /* Clear BUSY */ + + if (wd->intenable) { + wd17xx_set_intrq(wd, TRUE); + s100_bus_int(1 << wd->intvector, wd->intvector * 2); + } + + wd->drq = 1; + break; + + /* Type IV Commands */ + case WD17XX_CMD_FI: + default: + break; + } +} + +static t_stat wd17xx_read_sector(WD17XX_INFO *wd, uint8 *sbuf, int32 *bytesread) +{ + t_stat r; + + if (wd == NULL || wd->dsk == NULL) { + return SCPE_ARG; + } + + if ((r = dsk_read_sector(wd->dsk, wd->track, wd->side, wd->sector, sbuf, bytesread)) == SCPE_OK) { + wd->status |= (WD17XX_STAT_DRQ | WD17XX_STAT_BUSY); /* Set DRQ, BUSY */ + wd17xx_set_intrq(wd, FALSE); + wd->fdc_datacount = dsk_sector_size(wd->dsk, wd->track, wd->side); + wd->fdc_dataindex = 0; + wd->fdc_read = TRUE; + wd->fdc_readadr = FALSE; + } + else { + wd->status &= ~WD17XX_STAT_BUSY; /* Clear DRQ, BUSY */ + wd->status |= WD17XX_STAT_RNF; + wd17xx_set_intrq(wd, TRUE); + wd->fdc_read = FALSE; + wd->fdc_readadr = FALSE; + } + + return r; +} + +static t_stat wd17xx_write_sector(WD17XX_INFO *wd, uint8 *sbuf, int32 *byteswritten) +{ + t_stat r; + + if (wd == NULL || wd->dsk == NULL) { + return SCPE_ARG; + } + + r = dsk_write_sector(wd->dsk, wd->track, wd->side, wd->sector, sbuf, byteswritten); + + return r; +} + +static int32 wd17xx_valid(WD17XX_INFO *wd) +{ + return TRUE; +} + +/* Convert sector size to sector length field */ +static uint8 wd17xx_sec_len(WD17XX_INFO *wd) +{ + uint8 i; + int32 secsize; + + if (wd == NULL || wd->dsk == NULL) { + return 0; + } + + secsize = dsk_sector_size(wd->dsk, wd->track, wd->side); + + for (i = 0; i <= 4; i++) { + if ( (0x80 << i) == secsize ) { + sim_debug(wd->dbg_verbose | wd->dbg_write, wd->dptr, "%d sector size = %02X sector len field\n", secsize, i); + return i; + } + } + + return 0; /* default to 128-byte sectors */ +} + +static void wd17xx_set_intrq(WD17XX_INFO *wd, int32 value) +{ + wd->intrq = (value) ? TRUE : FALSE; /* INTRQ and DRQ are mutually exclusive */ + wd->drq = !wd->intrq; +} + +void wd17xx_show(WD17XX_INFO *wd) +{ + sim_debug(wd->dbg_verbose, wd->dptr, "fdctype: %02X\n", wd->fdctype); + sim_debug(wd->dbg_verbose, wd->dptr, "intenable: %02X\n", wd->intenable); + sim_debug(wd->dbg_verbose, wd->dptr, "intvector: %02X\n", wd->intvector); + sim_debug(wd->dbg_verbose, wd->dptr, "drq: %02X\n", wd->drq); + sim_debug(wd->dbg_verbose, wd->dptr, "intrq: %02X\n", wd->intrq); + sim_debug(wd->dbg_verbose, wd->dptr, "hld: %02X\n", wd->hld); + sim_debug(wd->dbg_verbose, wd->dptr, "dden: %02X\n", wd->dden); + sim_debug(wd->dbg_verbose, wd->dptr, "side: %02X\n", wd->side); + sim_debug(wd->dbg_verbose, wd->dptr, "drivetype: %02X\n", wd->drivetype); + sim_debug(wd->dbg_verbose, wd->dptr, "status: %02X\n", wd->status); + sim_debug(wd->dbg_verbose, wd->dptr, "command: %02X\n", wd->command); + sim_debug(wd->dbg_verbose, wd->dptr, "track: %02X\n", wd->track); + sim_debug(wd->dbg_verbose, wd->dptr, "sector: %02X\n", wd->sector); + sim_debug(wd->dbg_verbose, wd->dptr, "data: %02X\n", wd->data); +} + diff --git a/Altair8800/wd_17xx.h b/Altair8800/wd_17xx.h new file mode 100644 index 00000000..4efb3047 --- /dev/null +++ b/Altair8800/wd_17xx.h @@ -0,0 +1,163 @@ +/* wd_17xx.h: Western Digital FD17XX Floppy Disk Controller/Formatter + + Copyright (c) 2025 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 13-Nov-2025 Initial version + +*/ + +#ifndef _WD_17XX_H +#define _WD_17XX_H + +#include "altair8800_dsk.h" + +#define WD17XX_MAX_SECTOR_SIZE 4096 + +typedef struct { + uint16 fdctype; /* Default is 1771 */ + uint8 intenable; /* Interrupt Enable */ + uint8 intvector; /* Interrupt Vector */ + uint8 drq; /* WD17XX DMA Request Output */ + uint8 intrq; /* WD17XX Interrupt Request Output (EOJ) */ + uint8 hld; /* WD17XX Head Load Output */ + uint8 dden; /* WD17XX Double-Density Input */ + uint8 verify; /* Verify */ + uint8 drivetype; /* 8 or 5 depending on disk type. */ + uint8 status; /* Status Register */ + uint8 command; /* Command Register */ + uint8 track; /* Track Register */ + uint8 side; /* Side select */ + uint8 sector; /* Sector Register */ + uint8 data; /* Data Register */ + DSK_INFO *dsk; /* Current DSK information */ + uint8 cmdtype; /* Command type */ + uint8 fdc_read; /* TRUE when reading */ + uint8 fdc_readadr; /* TRUE when reading address */ + uint8 fdc_write; /* TRUE when writing */ + uint8 fdc_write_track; /* TRUE when writing an entire track */ + uint8 fdc_multi; /* Multiple records */ + uint8 fdc_sec_len; /* Sector length field */ + uint16 fdc_datacount; /* Read or Write data remaining transfer length */ + uint16 fdc_dataindex; /* index of current byte in sector data */ + uint8 fdc_fmt_state; /* Format track statemachine state */ + uint8 fdc_fmt_track; + uint8 fdc_fmt_side; + uint8 fdc_fmt_sector; + uint8 fdc_gap[4]; /* Gap I - Gap IV lengths */ + uint8 fdc_fmt_sector_count; /* sector count for format track */ + uint8 fdc_header_index; /* Index into header */ + int8 fdc_step_dir; + DEVICE *dptr; /* Device Owner */ + uint32 dbg_verbose; /* Verbose debug flag */ + uint32 dbg_error; /* Error debug flag */ + uint32 dbg_read; /* read debug flag */ + uint32 dbg_write; /* write debug flag */ + uint32 dbg_command; /* command debug flag */ + uint32 dbg_format; /* format debug flag */ +} WD17XX_INFO; + +#define WD17XX_FDCTYPE_1771 0x01 +#define WD17XX_FDCTYPE_1791 0x02 +#define WD17XX_FDCTYPE_1793 0x02 +#define WD17XX_FDCTYPE_1795 0x04 +#define WD17XX_FDCTYPE_1797 0x04 + +#define WD17XX_REG_STATUS 0x00 +#define WD17XX_REG_COMMAND 0x00 +#define WD17XX_REG_TRACK 0x01 +#define WD17XX_REG_SECTOR 0x02 +#define WD17XX_REG_DATA 0x03 + +#define WD17XX_CMD_MASK 0xf0 /* Command Mask */ +#define WD17XX_CMD_RESTORE 0x00 +#define WD17XX_CMD_SEEK 0x10 +#define WD17XX_CMD_STEP 0x20 +#define WD17XX_CMD_STEPU 0x30 +#define WD17XX_CMD_STEPIN 0x40 +#define WD17XX_CMD_STEPINU 0x50 +#define WD17XX_CMD_STEPOUT 0x60 +#define WD17XX_CMD_STEPOUTU 0x70 +#define WD17XX_CMD_RD 0x80 +#define WD17XX_CMD_RDM 0x90 +#define WD17XX_CMD_WR 0xA0 +#define WD17XX_CMD_WRM 0xB0 +#define WD17XX_CMD_RDADR 0xC0 +#define WD17XX_CMD_RDTRK 0xE0 +#define WD17XX_CMD_WRTRK 0xF0 +#define WD17XX_CMD_FI 0xD0 + +#define WD17XX_FLG_F1 0x02 +#define WD17XX_FLG_V 0x04 +#define WD17XX_FLG_F2 0x08 +#define WD17XX_FLG_H 0x08 +#define WD17XX_FLG_B 0x08 /* Block Length Flag (1771) */ +#define WD17XX_FLG_U 0x10 /* Update track register */ +#define WD17XX_FLG_M 0x10 /* Multiple record flag */ + +#define WD17XX_STAT_BUSY 0x01 /* S0 - Busy */ +#define WD17XX_STAT_IDX 0x02 /* S1 - Index */ +#define WD17XX_STAT_DRQ 0x02 /* S1 - Data Request */ +#define WD17XX_STAT_TRK0 0x04 /* S2 - Track 0 */ +#define WD17XX_STAT_LOSTD 0x04 /* S2 - Lost Data */ +#define WD17XX_STAT_CRC 0x08 /* S3 - CRC Error */ +#define WD17XX_STAT_SEEK 0x10 /* S4 - Seek Error */ +#define WD17XX_STAT_RNF 0x10 /* S4 - Record Not Found */ +#define WD17XX_STAT_HDLD 0x20 /* S5 - Head Loaded */ +#define WD17XX_STAT_RT 0x20 /* S5 - Record Type */ +#define WD17XX_STAT_WF 0x20 /* S5 - Write Fault */ +#define WD17XX_STAT_WP 0x40 /* S6 - Write Protect */ +#define WD17XX_STAT_NRDY 0x80 /* S7 - Not Ready */ + +/* Write Track (format) Statemachine states */ +#define WD17XX_FMT_GAP1 1 +#define WD17XX_FMT_GAP2 2 +#define WD17XX_FMT_GAP3 3 +#define WD17XX_FMT_GAP4 4 +#define WD17XX_FMT_HEADER 5 +#define WD17XX_FMT_DATA 6 + +extern WD17XX_INFO * wd17xx_init(DEVICE *dptr); +extern WD17XX_INFO * wd17xx_release(WD17XX_INFO *wd); +extern void wd17xx_reset(WD17XX_INFO *wd); +extern void wd17xx_sel_dden(WD17XX_INFO *wd, uint8 dden); +extern void wd17xx_sel_side(WD17XX_INFO *wd, uint8 side); +extern void wd17xx_sel_drive_type(WD17XX_INFO *wd, uint8 type); +extern void wd17xx_set_fdctype(WD17XX_INFO *wd, int fdctype); +extern void wd17xx_set_dsk(WD17XX_INFO *wd, DSK_INFO *dsk); +extern void wd17xx_set_intena(WD17XX_INFO *wd, int32 ena); +extern void wd17xx_set_intvec(WD17XX_INFO *wd, int32 vec); +extern void wd17xx_set_verbose_flag(WD17XX_INFO *wd, uint32 flag); +extern void wd17xx_set_error_flag(WD17XX_INFO *wd, uint32 flag); +extern void wd17xx_set_read_flag(WD17XX_INFO *wd, uint32 flag); +extern void wd17xx_set_write_flag(WD17XX_INFO *wd, uint32 flag); +extern void wd17xx_set_command_flag(WD17XX_INFO *wd, uint32 flag); +extern void wd17xx_set_format_flag(WD17XX_INFO *wd, uint32 flag); +extern uint8 wd17xx_intrq(WD17XX_INFO *wd); +extern uint8 wd17xx_inp(WD17XX_INFO *wd, uint8 port); +extern void wd17xx_outp(WD17XX_INFO *wd, uint8 port, uint8 data); +extern void wd17xx_show(WD17XX_INFO *wd); + +#endif + diff --git a/Visual Studio Projects/Altair8800.vcproj b/Visual Studio Projects/Altair8800.vcproj new file mode 100644 index 00000000..99815a9e --- /dev/null +++ b/Visual Studio Projects/Altair8800.vcproj @@ -0,0 +1,464 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Visual Studio Projects/Simh.sln b/Visual Studio Projects/Simh.sln index 6c815f31..7913d532 100755 --- a/Visual Studio Projects/Simh.sln +++ b/Visual Studio Projects/Simh.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 10.00 # Visual C++ Express 2008 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VAX", "VAX.vcproj", "{D5D873F7-D286-43E7-958A-3D838FAA0856}" @@ -398,6 +398,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "linc", "linc.vcproj", "{58E {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Altair8800", "Altair8800.vcproj", "{473F61F7-FCE3-4157-B1A8-A8BC054C789F}" + ProjectSection(ProjectDependencies) = postProject + {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -724,6 +729,10 @@ Global {58E9E172-1CC9-4BA6-8176-F832B11DB1EC}.Debug|Win32.Build.0 = Debug|Win32 {58E9E172-1CC9-4BA6-8176-F832B11DB1EC}.Release|Win32.ActiveCfg = Release|Win32 {58E9E172-1CC9-4BA6-8176-F832B11DB1EC}.Release|Win32.Build.0 = Release|Win32 + {473F61F7-FCE3-4157-B1A8-A8BC054C789F}.Debug|Win32.ActiveCfg = Debug|Win32 + {473F61F7-FCE3-4157-B1A8-A8BC054C789F}.Debug|Win32.Build.0 = Debug|Win32 + {473F61F7-FCE3-4157-B1A8-A8BC054C789F}.Release|Win32.ActiveCfg = Release|Win32 + {473F61F7-FCE3-4157-B1A8-A8BC054C789F}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/doc/altair8800_doc.docx b/doc/altair8800_doc.docx new file mode 100644 index 0000000000000000000000000000000000000000..69ed43276a490070cd6a64a89f20488f2d04b0f2 GIT binary patch literal 59116 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