1
0
mirror of https://github.com/simh/simh.git synced 2026-02-27 17:13:44 +00:00

Altair8800: New Simulator

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
This commit is contained in:
Patrick Linstruth
2025-11-21 17:01:40 -05:00
parent 3503e7b794
commit aad5351080
41 changed files with 17258 additions and 3 deletions

View File

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

435
Altair8800/altair8800_dsk.c Normal file
View File

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

View File

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

167
Altair8800/altair8800_sys.c Normal file
View File

@@ -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 */
}

View File

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

931
Altair8800/mits_2sio.c Normal file
View File

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

67
Altair8800/mits_2sio.h Normal file
View File

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

631
Altair8800/mits_dsk.c Normal file
View File

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

57
Altair8800/mits_dsk.h Normal file
View File

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

484
Altair8800/s100_bram.c Normal file
View File

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

57
Altair8800/s100_bram.h Normal file
View File

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

988
Altair8800/s100_bus.c Normal file
View File

@@ -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 <address> Dump a block of memory\n" },
{ "HEXLOAD", &bus_hexload_command, 0, "HEXLOAD [fname] <bias> 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; i<byteCount; i++) {
fprintf(outFile,"%02X", inBuf[i]);
checkSum += inBuf[i];
}
fprintf(outFile, "%02X\n", -checkSum & 0xff);
dataAddr += byteCount;
}
} while (dataAddr <= end);
/* Finish output file and display results */
if (sRecords) {
fprintf(outFile, "S9\n");
}
else {
fprintf(outFile, ":00000001FF\n");
}
sim_printf("Start address = %04X\n", start);
sim_printf("High address = %04X\n", dataAddr-1);
return SCPE_OK;
}
/*
* set_membase, show_membase, set_iobase, and show_iobase
*
* Generic functions for change a device's base memory and io
* addresses.
*
* DEVICE *ctxt = must point to the address of a RES resource structure.
*/
/* Set Memory Base Address routine */
t_stat set_membase(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, 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;
}

156
Altair8800/s100_bus.h Normal file
View File

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

251
Altair8800/s100_cpu.c Normal file
View File

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

62
Altair8800/s100_cpu.h Normal file
View File

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

122
Altair8800/s100_po.c Normal file
View File

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

37
Altair8800/s100_po.h Normal file
View File

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

325
Altair8800/s100_ram.c Normal file
View File

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

42
Altair8800/s100_ram.h Normal file
View File

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

252
Altair8800/s100_rom.c Normal file
View File

@@ -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 <name>. To disable a ROM,\n");
fprintf(st, "enter SET ROM NO<name>. Enabled ROMs can be seen with the SHOW BUS CONFIG command.\n\n");
return SCPE_OK;
}

59
Altair8800/s100_rom.h Normal file
View File

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

373
Altair8800/s100_roms.h Normal file
View File

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

505
Altair8800/s100_simh.c Normal file
View File

@@ -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,<cmd>
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,<cmd>
out (0feh),a
ld a,<p1>
out (0feh),a
ld a,<p2>
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,<cmd>
out (0feh),a
in a,(0feh) ; <A> contains first byte of result
in a,(0feh) ; <A> 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,<cmd>
out (0feh),a
ld a,<p1>
out (0feh),a
ld a,<p2>
out (0feh),a
... ; send all parameters
in a,(0feh) ; <A> contains first byte of result
in a,(0feh) ; <A> 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;
}

37
Altair8800/s100_simh.h Normal file
View File

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

432
Altair8800/s100_sio.c Normal file
View File

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

60
Altair8800/s100_sio.h Normal file
View File

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

116
Altair8800/s100_ssw.c Normal file
View File

@@ -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 <val> to set the value returned by an IN 0FFH instruction.\n\n");
return SCPE_OK;
}

37
Altair8800/s100_ssw.h Normal file
View File

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

6549
Altair8800/s100_z80.c Normal file

File diff suppressed because it is too large Load Diff

70
Altair8800/s100_z80.h Normal file
View File

@@ -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,<unitno> */
#define UNIT_NO_OFFSET_2 0xb4 /* LD a,80h | <unitno> */
#define LDB_INSTRUCTION 0x06 /* op-code for LD B,<8-bit value> instruction */
#define START_SECTOR_OFFSET 0x57 /* LD B,<start_sector_offset> */
#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

1294
Altair8800/sds_sbc200.c Normal file

File diff suppressed because it is too large Load Diff

340
Altair8800/sds_vfii.c Normal file
View File

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

60
Altair8800/sds_vfii.h Normal file
View File

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

471
Altair8800/tarbell_fdc.c Normal file
View File

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

60
Altair8800/tarbell_fdc.h Normal file
View File

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

860
Altair8800/wd_17xx.c Normal file
View File

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

163
Altair8800/wd_17xx.h Normal file
View File

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

View File

@@ -0,0 +1,464 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9.00"
Name="Altair8800"
ProjectGUID="{473F61F7-FCE3-4157-B1A8-A8BC054C789F}"
RootNamespace="Altair8800"
Keyword="Win32Proj"
TargetFrameworkVersion="131072"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="..\BIN\NT\$(PlatformName)-$(ConfigurationName)"
IntermediateDirectory="..\BIN\NT\Project\simh\$(ProjectName)\$(PlatformName)-$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="0"
>
<Tool
Name="VCPreBuildEventTool"
Description="Check for required build dependencies &amp; git commit id..."
CommandLine="Pre-Build-Event.cmd &quot;$(TargetDir)$(TargetName).exe&quot; LIBPCRE LIBSDL"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="../Altair8800/;../Altair8800/AltairZ80/;./;../;../slirp;../slirp_glue;../slirp_glue/qemu;../slirp_glue/qemu/win32/include;../../windows-build/include;;../../windows-build/include/SDL2"
PreprocessorDefinitions="inline=__inline;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;HAVE_LIBEDIT;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG"
KeepComments="false"
BasicRuntimeChecks="0"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
WarningLevel="3"
WarnAsError="true"
DebugInformationFormat="3"
CompileAs="1"
ShowIncludes="false"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="libcmtd.lib wsock32.lib winmm.lib Iphlpapi.lib pcrestaticd.lib SDL2-StaticD.lib SDL2_ttf-StaticD.lib freetype2412MT_D.lib libpng16.lib zlib.lib dxguid.lib Imm32.lib Version.lib Setupapi.lib wineditlined.lib"
LinkIncremental="1"
AdditionalLibraryDirectories="../../windows-build/lib/Debug/"
GenerateDebugInformation="true"
SubSystem="1"
StackReserveSize="10485760"
StackCommitSize="10485760"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
UseUnicodeResponseFiles="false"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
Description="Running Available Tests..."
CommandLine="Post-Build-Event.cmd Altair8800 &quot;$(TargetDir)$(TargetName).exe&quot;"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="..\BIN\NT\$(PlatformName)-$(ConfigurationName)"
IntermediateDirectory="..\BIN\NT\Project\simh\$(ProjectName)\$(PlatformName)-$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="0"
>
<Tool
Name="VCPreBuildEventTool"
Description="Check for required build dependencies &amp; git commit id..."
CommandLine="Pre-Build-Event.cmd &quot;$(TargetDir)$(TargetName).exe&quot; LIBPCRE LIBSDL"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
InlineFunctionExpansion="1"
OmitFramePointers="true"
WholeProgramOptimization="true"
AdditionalIncludeDirectories="../Altair8800/;../Altair8800/AltairZ80/;./;../;../slirp;../slirp_glue;../slirp_glue/qemu;../slirp_glue/qemu/win32/include;../../windows-build/include;;../../windows-build/include/SDL2"
PreprocessorDefinitions="inline=__inline;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;HAVE_LIBEDIT;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG"
StringPooling="true"
RuntimeLibrary="0"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
WarningLevel="3"
WarnAsError="true"
DebugInformationFormat="3"
CompileAs="1"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="libcmt.lib wsock32.lib winmm.lib Iphlpapi.lib pcrestatic.lib SDL2-Static.lib SDL2_ttf-Static.lib freetype2412MT.lib libpng16.lib zlib.lib dxguid.lib Imm32.lib Version.lib Setupapi.lib wineditline.lib"
LinkIncremental="1"
AdditionalLibraryDirectories="../../windows-build/lib/Release/"
GenerateDebugInformation="false"
SubSystem="1"
StackReserveSize="10485760"
StackCommitSize="10485760"
OptimizeReferences="2"
EnableCOMDATFolding="2"
LinkTimeCodeGeneration="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
UseUnicodeResponseFiles="false"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
Description="Running Available Tests..."
CommandLine="Post-Build-Event.cmd Altair8800 &quot;$(TargetDir)$(TargetName).exe&quot;"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm"
>
<File
RelativePath="..\Altair8800\altair8800_dsk.c"
>
</File>
<File
RelativePath="..\Altair8800\altair8800_sys.c"
>
</File>
<File
RelativePath="..\Altair8800\mits_2sio.c"
>
</File>
<File
RelativePath="..\Altair8800\mits_dsk.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_bram.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_bus.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_cpu.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_po.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_ram.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_rom.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_simh.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_sio.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_ssw.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_z80.c"
>
</File>
<File
RelativePath="..\Altair8800\sds_sbc200.c"
>
</File>
<File
RelativePath="..\Altair8800\sds_vfii.c"
>
</File>
<File
RelativePath="..\Altair8800\tarbell_fdc.c"
>
</File>
<File
RelativePath="..\Altair8800\wd_17xx.c"
>
</File>
<File
RelativePath="..\scp.c"
>
</File>
<File
RelativePath="..\sim_console.c"
>
</File>
<File
RelativePath="..\sim_disk.c"
>
</File>
<File
RelativePath="..\sim_ether.c"
>
</File>
<File
RelativePath="..\sim_fio.c"
>
</File>
<File
RelativePath="..\sim_imd.c"
>
</File>
<File
RelativePath="..\sim_serial.c"
>
</File>
<File
RelativePath="..\sim_sock.c"
>
</File>
<File
RelativePath="..\sim_tape.c"
>
</File>
<File
RelativePath="..\sim_timer.c"
>
</File>
<File
RelativePath="..\sim_tmxr.c"
>
</File>
<File
RelativePath="..\sim_video.c"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc"
>
<File
RelativePath="..\Altair8800\altair8800_defs.h"
>
</File>
<File
RelativePath="..\Altair8800\altair8800_dsk.h"
>
</File>
<File
RelativePath="..\Altair8800\altair8800_sys.h"
>
</File>
<File
RelativePath="..\Altair8800\mits_2sio.h"
>
</File>
<File
RelativePath="..\Altair8800\mits_dsk.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_bram.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_bus.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_cpu.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_po.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_ram.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_rom.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_roms.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_simh.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_sio.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_ssw.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_z80.h"
>
</File>
<File
RelativePath="..\Altair8800\sds_vfii.h"
>
</File>
<File
RelativePath="..\Altair8800\tarbell_fdc.h"
>
</File>
<File
RelativePath="..\Altair8800\wd_17xx.h"
>
</File>
<File
RelativePath="..\scp.h"
>
</File>
<File
RelativePath="..\sim_console.h"
>
</File>
<File
RelativePath="..\sim_defs.h"
>
</File>
<File
RelativePath="..\sim_disk.h"
>
</File>
<File
RelativePath="..\sim_ether.h"
>
</File>
<File
RelativePath="..\sim_fio.h"
>
</File>
<File
RelativePath="..\sim_imd.h"
>
</File>
<File
RelativePath="..\sim_rev.h"
>
</File>
<File
RelativePath="..\sim_serial.h"
>
</File>
<File
RelativePath="..\sim_sock.h"
>
</File>
<File
RelativePath="..\sim_tape.h"
>
</File>
<File
RelativePath="..\sim_timer.h"
>
</File>
<File
RelativePath="..\sim_tmxr.h"
>
</File>
<File
RelativePath="..\sim_video.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

View File

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

BIN
doc/altair8800_doc.docx Normal file

Binary file not shown.

View File

@@ -216,6 +216,10 @@ ifneq (3,${SIM_MAJOR})
ifneq (,$(or $(findstring pdp6,${MAKECMDGOALS}),$(findstring pdp10-ka,${MAKECMDGOALS}),$(findstring pdp10-ki,${MAKECMDGOALS})))
VIDEO_USEFUL = true
endif
# building the Altair8800 could use video support
ifneq (,$(findstring altair8800,${MAKECMDGOALS}))
VIDEO_USEFUL = true
endif
# building the AltairZ80 could use video support
ifneq (,$(findstring altairz80,${MAKECMDGOALS}))
VIDEO_USEFUL = true
@@ -2218,6 +2222,29 @@ ALTAIR = ${ALTAIRD}/altair_sio.c ${ALTAIRD}/altair_cpu.c ${ALTAIRD}/altair_dsk.c
ALTAIR_OPT = -I ${ALTAIRD}
ALTAIR8800D = ${SIMHD}/Altair8800
ALTAIR8800 = \
${ALTAIR8800D}/altair8800_sys.c \
${ALTAIR8800D}/altair8800_dsk.c \
${ALTAIR8800D}/s100_bus.c \
${ALTAIR8800D}/s100_bram.c \
${ALTAIR8800D}/s100_cpu.c \
${ALTAIR8800D}/s100_po.c \
${ALTAIR8800D}/s100_simh.c \
${ALTAIR8800D}/s100_sio.c \
${ALTAIR8800D}/s100_ssw.c \
${ALTAIR8800D}/s100_ram.c \
${ALTAIR8800D}/s100_rom.c \
${ALTAIR8800D}/s100_z80.c \
${ALTAIR8800D}/mits_2sio.c \
${ALTAIR8800D}/mits_dsk.c \
${ALTAIR8800D}/sds_sbc200.c \
${ALTAIR8800D}/sds_vfii.c \
${ALTAIR8800D}/tarbell_fdc.c \
${ALTAIR8800D}/wd_17xx.c
ALTAIR8800_OPT = $(ALTAIR8800_GCC_OPT) -I ${ALTAIR8800D} -DUSE_SIM_VIDEO ${VIDEO_CCDEFS}
ALTAIRZ80D = ${SIMHD}/AltairZ80
ALTAIRZ80 = ${ALTAIRZ80D}/altairz80_cpu.c ${ALTAIRZ80D}/altairz80_cpu_nommu.c \
${ALTAIRZ80D}/s100_dazzler.c \
@@ -2540,8 +2567,8 @@ ALL = pdp1 pdp4 pdp7 pdp8 pdp9 pdp15 pdp11 pdp10 \
microvax2000 infoserver100 infoserver150vxt microvax3100 microvax3100e \
vaxstation3100m30 vaxstation3100m38 vaxstation3100m76 vaxstation4000m60 \
microvax3100m80 vaxstation4000vlc infoserver1000 \
nd100 nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altairz80 gri \
i7094 ibm1130 id16 id32 sds lgp h316 cdc1700 \
nd100 nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altair8800 \
altairz80 gri i7094 ibm1130 id16 id32 sds lgp h316 cdc1700 \
swtp6800mp-a swtp6800mp-a2 tx-0 ssem b5500 intel-mds \
scelbi 3b2 3b2-700 i701 i704 i7010 i7070 i7080 i7090 \
sigma uc15 pdp10-ka pdp10-ki pdp10-kl pdp10-ks pdp6 i650 \
@@ -2869,6 +2896,12 @@ $(BIN)altair$(EXE) : ${ALTAIR} ${SIM}
$(MAKEIT) OPTS="$(ALTAIR_OPT)"
altair8800 : $(BIN)altair8800$(EXE)
$(BIN)altair8800$(EXE) : ${ALTAIR8800} ${SIM}
$(MAKEIT) OPTS="$(ALTAIR8800_OPT)"
altairz80 : $(BIN)altairz80$(EXE)
$(BIN)altairz80$(EXE) : ${ALTAIRZ80} ${SIM}