1
0
mirror of https://github.com/simh/simh.git synced 2026-01-25 19:56:25 +00:00

Altair8800: New DAZZLER, PMMI devices and other features

Adds Cromemco DAZZLER video device
Adds PMMI MM-103 modem device
Adds SET RAM PROT/UNPROT=pages
Adds Martin Eberhard's CDBL ROM

Corrects file format for cromemco and pmmi header files.
This commit is contained in:
Patrick Linstruth
2026-01-18 08:22:41 -05:00
parent a9b3369657
commit 627e6a6b13
15 changed files with 1623 additions and 36 deletions

View File

@@ -74,6 +74,8 @@ DEVICE *sim_devices[] = {
&m2sio0_dev,
&m2sio1_dev,
&acr_dev,
&daz_dev,
&pmmi_dev,
&sio_dev,
&sbc200_dev,
&tarbell_dev,

View File

@@ -48,6 +48,8 @@ extern DEVICE mdsk_dev;
extern DEVICE m2sio0_dev;
extern DEVICE m2sio1_dev;
extern DEVICE acr_dev;
extern DEVICE daz_dev;
extern DEVICE pmmi_dev;
extern DEVICE sio_dev;
extern DEVICE sbc200_dev;
extern DEVICE tarbell_dev;

View File

@@ -0,0 +1,615 @@
/* cromemco_dazzler.c: Cromemco DAZZLER and JS-1 Joystick
Copyright (c) 2026 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:
18-Jan-2026 Initial version
==================================================================
This device simulates the Cromemco Dazzler and D+7A with JS-1 Joystick
Console.
*/
#include "sim_defs.h"
#include "sim_video.h"
#include "altair8800_defs.h"
#include "s100_bus.h"
#include "s100_cpu.h"
#include "cromemco_dazzler.h"
/*
** Public VID_DISPLAY for other devices that may want
** to access the video display directly, such as joystick
** events.
*/
VID_DISPLAY *daz_vptr = NULL;
static t_bool daz_0e = 0x00;
static t_bool daz_0f = 0x80;
static uint32 daz_addr = 0x0000;
static t_bool daz_frame = 0x3f;
static uint8 daz_resolution = 32;
static uint16 daz_pages = 1;
static uint16 daz_window_width = 640;
static uint16 daz_window_height = 640;
static uint16 daz_screen_width = 32;
static uint16 daz_screen_height = 32;
static uint16 daz_screen_pixels = 32 * 32;
static uint8 daz_color = 0;
static uint32 daz_surface[DAZ_PIXELS];
static uint32 daz_cpalette[16];
static uint32 daz_gpalette[16];
#define DAZ_SHOW_VIDEO(b) (b & DAZ_ON) ? "ON" : "OFF"
#define DAZ_SHOW_RES(b) (b & DAZ_RESX4) ? "X4" : "NORMAL"
#define DAZ_SHOW_MEMSIZE(b) (b & DAZ_2K) ? "2K" : "512"
#define DAZ_SHOW_COLOR(b) (b & DAZ_COLOR) ? "COLOR" : "B/W"
extern t_stat exdep_cmd(int32 flag, CONST char *cptr);
static const char *daz_description(DEVICE *dptr);
static t_stat daz_svc(UNIT *uptr);
static t_stat daz_reset(DEVICE *dptr);
static t_stat daz_boot(int32 unitno, DEVICE *dptr);
static void daz_set_0f(uint8 val);
static t_stat daz_set_video(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat daz_show_video(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat daz_set_resolution(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat daz_show_resolution(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat daz_set_memsize(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat daz_show_memsize(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat daz_set_color(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat daz_show_color(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static int32 daz_io(const int32 port, const int32 io, const int32 data);
static t_stat daz_open_video(void);
static t_stat daz_close_video(void);
static void daz_resize_video(void);
static void daz_refresh(void);
static void daz_render_normal(void);
static void daz_render_x4(void);
static int32 daz_quad_surfacex(int q);
static int32 daz_quad_surfacey(int q);
static t_stat daz_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
/* Debug flags */
#define VERBOSE_MSG (1 << 0)
/*
DAZZLER data structures
daz_dev DAZ device descriptor
daz_unit DAZ unit descriptor
daz_reg DAZ register list
*/
static RES daz_res = { DAZ_IO_BASE, DAZ_IO_SIZE, 0 ,0, NULL };
static UNIT daz_unit = {
UDATA (&daz_svc, 0, 0), 33000 /* 30 fps */
};
static REG daz_reg[] = {
{ NULL }
};
static DEBTAB daz_debug[] = {
{ "VERBOSE", VERBOSE_MSG, "Verbose messages" },
{ "JOYSTICK", SIM_VID_DBG_JOYSTICK, "Joystick messages" },
{ "VIDEO", SIM_VID_DBG_VIDEO, "Video messages" },
{ 0 }
};
static MTAB daz_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "VIDEO", "VIDEO",
&daz_set_video, &daz_show_video, NULL, "DAZZLER Video [ ON | OFF ]" },
{ MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE",
&set_iobase, &show_iobase, NULL, "DAZZLER Base I/O Address" },
{ MTAB_XTD|MTAB_VDV, 0, "MEMSIZE", "MEMSIZE",
&daz_set_memsize, &daz_show_memsize, NULL, "DAZZLER Memory Size [ 512 | 2K ]" },
{ MTAB_XTD|MTAB_VDV, 0, "RESOLUTION", "RESOLUTION",
&daz_set_resolution, &daz_show_resolution, NULL, "DAZZLER Resolution [ NORMAL | HIGH ]" },
{ MTAB_XTD|MTAB_VDV, 0, "COLOR", "COLOR",
&daz_set_color, &daz_show_color, NULL, "DAZZLER Color [ BW | COLOR ]" },
{ 0 }
};
DEVICE daz_dev = {
"DAZZLER", &daz_unit, daz_reg, daz_mod,
1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH,
NULL, NULL, &daz_reset,
&daz_boot, NULL, NULL,
&daz_res, DEV_DEBUG | DEV_DIS | DEV_DISABLE, 0,
daz_debug, NULL, NULL, &daz_show_help, NULL, NULL,
&daz_description
};
/*
DAZZLER routines
daz_description
daz_svc
daz_reset
*/
static const char *daz_description (DEVICE *dptr)
{
return "Cromemco Dazzler";
}
static t_stat daz_svc(UNIT *uptr)
{
daz_refresh();
sim_activate_after_abs(uptr, uptr->wait);
return SCPE_OK;
}
static t_stat daz_reset(DEVICE *dptr)
{
t_stat r = SCPE_OK;
if (dptr->flags & DEV_DIS) {
s100_bus_remio(daz_res.io_base, daz_res.io_size, &daz_io);
sim_cancel(&daz_unit);
if (daz_vptr != NULL) {
return daz_close_video();
}
} else {
s100_bus_addio(daz_res.io_base, daz_res.io_size, &daz_io, "DAZZLER");
if (daz_vptr == NULL) {
daz_open_video();
} else {
sim_activate_after_abs(&daz_unit, daz_unit.wait);
}
}
return r;
}
static t_stat daz_boot(int32 unitno, DEVICE *dptr)
{
ChipType chiptype = cpu_get_chiptype();
if (chiptype == CHIP_TYPE_8080) {
exdep_cmd(EX_D, "-m 100 MVI A,01H");
exdep_cmd(EX_D, "-m 102 ORI 80H");
exdep_cmd(EX_D, "-m 104 OUT 0EH");
exdep_cmd(EX_D, "-m 106 MVI A,10H");
exdep_cmd(EX_D, "-m 108 OUT 0FH");
exdep_cmd(EX_D, "-m 10A LXI H,200H");
exdep_cmd(EX_D, "-m 10D MVI C,32");
exdep_cmd(EX_D, "-m 10F MVI B,16");
exdep_cmd(EX_D, "-m 111 XRA A");
exdep_cmd(EX_D, "-m 112 MOV M,A");
exdep_cmd(EX_D, "-m 113 ADI 11H");
exdep_cmd(EX_D, "-m 115 INX H");
exdep_cmd(EX_D, "-m 116 DCR B");
exdep_cmd(EX_D, "-m 117 JNZ 112H");
exdep_cmd(EX_D, "-m 11A DCR C");
exdep_cmd(EX_D, "-m 11B JNZ 10FH");
exdep_cmd(EX_D, "-m 11E JMP 11EH");
} else if (chiptype == CHIP_TYPE_Z80) {
exdep_cmd(EX_D, "-m 100 LD A,01H");
exdep_cmd(EX_D, "-m 102 OR 80H");
exdep_cmd(EX_D, "-m 104 OUT (0EH),A");
exdep_cmd(EX_D, "-m 106 LD A,10H");
exdep_cmd(EX_D, "-m 108 OUT (0FH),A");
exdep_cmd(EX_D, "-m 10A LD HL,200H");
exdep_cmd(EX_D, "-m 10D LD C,32");
exdep_cmd(EX_D, "-m 10F LD B,16");
exdep_cmd(EX_D, "-m 111 XOR A");
exdep_cmd(EX_D, "-m 112 LD (HL),A");
exdep_cmd(EX_D, "-m 113 ADD A,11H");
exdep_cmd(EX_D, "-m 115 INC HL");
exdep_cmd(EX_D, "-m 116 DEC B");
exdep_cmd(EX_D, "-m 117 JP NZ,112H");
exdep_cmd(EX_D, "-m 11A DEC C");
exdep_cmd(EX_D, "-m 11B JP NZ,10FH");
exdep_cmd(EX_D, "-m 11E JP 11EH");
}
*((int32 *) sim_PC->loc) = 0x0100;
return SCPE_OK;
}
static int32 daz_io(const int32 port, const int32 io, const int32 data)
{
int32 p = port - daz_res.io_base;
if (io == 0) { /* IN */
switch (p) {
case 0x00: /* 0E */
daz_frame = 0x7f;
if ((sim_os_msec() % 30) > 25) {
daz_frame &= ~DAZ_EOF;
} else {
daz_frame |= (sim_os_msec() & 1) ? 0x00 : DAZ_EVEN;
}
return daz_frame;
case 0x01: /* 0F */
sim_debug(VERBOSE_MSG, &daz_dev, "Unspecified IN 0x%02X\n", port);
break;
}
} else { /* OUT */
switch (p) {
case 0x00: /* 0E */
daz_0e = data;
daz_addr = (data & 0x7f) << 9;
sim_debug(VERBOSE_MSG, &daz_dev, "New video address 0x%04X Video is %s\n", daz_addr, daz_0e & DAZ_ON ? "ON" : "OFF");
break;
case 0x01: /* 0F */
daz_set_0f(data);
break;
}
}
return 0xff;
}
static t_stat daz_open_video(void)
{
t_stat r = SCPE_OK;
int i;
if (daz_vptr == NULL) {
sim_debug(VERBOSE_MSG, &daz_dev, "Opening new video window w:%d h:%d\n", daz_window_width, daz_window_height);
r = vid_open_window(&daz_vptr, &daz_dev, "Display", daz_window_width, daz_window_height, SIM_VID_IGNORE_VBAR | SIM_VID_RESIZABLE); /* video buffer size */
if (r != SCPE_OK) {
sim_printf("Could not open video window r=%X\n", r);
return r;
}
daz_resize_video();
daz_cpalette[0] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x00);
daz_cpalette[1] = vid_map_rgb_window(daz_vptr, 0x80, 0x00, 0x00);
daz_cpalette[2] = vid_map_rgb_window(daz_vptr, 0x00, 0x80, 0x00);
daz_cpalette[3] = vid_map_rgb_window(daz_vptr, 0x80, 0x80, 0x00);
daz_cpalette[4] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x80);
daz_cpalette[5] = vid_map_rgb_window(daz_vptr, 0x80, 0x00, 0x80);
daz_cpalette[6] = vid_map_rgb_window(daz_vptr, 0x00, 0x80, 0x80);
daz_cpalette[7] = vid_map_rgb_window(daz_vptr, 0x80, 0x80, 0x80);
daz_cpalette[8] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x00);
daz_cpalette[9] = vid_map_rgb_window(daz_vptr, 0xff, 0x00, 0x00);
daz_cpalette[10] = vid_map_rgb_window(daz_vptr, 0x00, 0xff, 0x00);
daz_cpalette[11] = vid_map_rgb_window(daz_vptr, 0xff, 0xff, 0x00);
daz_cpalette[12] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0xff);
daz_cpalette[13] = vid_map_rgb_window(daz_vptr, 0xff, 0x00, 0xff);
daz_cpalette[14] = vid_map_rgb_window(daz_vptr, 0x00, 0xff, 0xff);
daz_cpalette[15] = vid_map_rgb_window(daz_vptr, 0xff, 0xff, 0xff);
daz_gpalette[0] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x00);
daz_gpalette[1] = vid_map_rgb_window(daz_vptr, 0x10, 0x10, 0x10);
daz_gpalette[2] = vid_map_rgb_window(daz_vptr, 0x20, 0x20, 0x20);
daz_gpalette[3] = vid_map_rgb_window(daz_vptr, 0x30, 0x30, 0x30);
daz_gpalette[4] = vid_map_rgb_window(daz_vptr, 0x40, 0x40, 0x40);
daz_gpalette[5] = vid_map_rgb_window(daz_vptr, 0x50, 0x50, 0x50);
daz_gpalette[6] = vid_map_rgb_window(daz_vptr, 0x60, 0x60, 0x60);
daz_gpalette[7] = vid_map_rgb_window(daz_vptr, 0x70, 0x70, 0x70);
daz_gpalette[8] = vid_map_rgb_window(daz_vptr, 0x80, 0x80, 0x80);
daz_gpalette[9] = vid_map_rgb_window(daz_vptr, 0x90, 0x90, 0x90);
daz_gpalette[10] = vid_map_rgb_window(daz_vptr, 0xa0, 0xa0, 0xa0);
daz_gpalette[11] = vid_map_rgb_window(daz_vptr, 0xb0, 0xb0, 0xb0);
daz_gpalette[12] = vid_map_rgb_window(daz_vptr, 0xc0, 0xc0, 0xc0);
daz_gpalette[13] = vid_map_rgb_window(daz_vptr, 0xd0, 0xd0, 0xd0);
daz_gpalette[14] = vid_map_rgb_window(daz_vptr, 0xe0, 0xe0, 0xe0);
daz_gpalette[15] = vid_map_rgb_window(daz_vptr, 0xff, 0xff, 0xff);
for (i = 0; i < daz_screen_pixels; i++) {
daz_surface[i] = 0;
}
}
sim_activate_after_abs(&daz_unit, daz_unit.wait);
return r;
}
static t_stat daz_close_video(void)
{
t_stat r;
sim_debug(VERBOSE_MSG, &daz_dev, "Closing video window\n");
if ((r = vid_close_window(daz_vptr)) == SCPE_OK) {
sim_cancel(&daz_unit);
daz_vptr = NULL;
}
return r;
}
static void daz_resize_video(void)
{
if (daz_vptr != NULL) {
vid_render_set_logical_size(daz_vptr, daz_screen_width, daz_screen_height);
if (!sim_is_running) {
daz_refresh();
}
}
}
/*
* Draw and refresh the screen in the video window
*/
static void daz_refresh(void) {
if (daz_vptr != NULL) {
if (daz_0f & DAZ_RESX4) {
daz_render_x4();
} else {
daz_render_normal();
}
vid_draw_window(daz_vptr, 0, 0, daz_screen_width, daz_screen_height, daz_surface);
vid_refresh_window(daz_vptr);
}
}
static void daz_render_normal(void)
{
int q, x, y;
int32 maddr = daz_addr;
int32 saddr = 0;
for (q = 0; q < daz_pages; q++) {
for (y = daz_quad_surfacey(q); y < daz_quad_surfacey(q) + 32; y++) {
for (x = daz_quad_surfacex(q); x < daz_quad_surfacex(q) + 32; x+= 2) {
saddr = (y * daz_resolution) + x;
if (!(daz_0e & DAZ_ON)) {
daz_surface[saddr] = 0x00;
daz_surface[saddr+1] = 0x00;
} else if (daz_0f & DAZ_COLOR) {
daz_surface[saddr] = daz_cpalette[s100_bus_memr(maddr) & 0x0f];
daz_surface[saddr+1] = daz_cpalette[(s100_bus_memr(maddr) & 0xf0) >> 4];
} else {
daz_surface[saddr] = daz_gpalette[s100_bus_memr(maddr) & 0x0f];
daz_surface[saddr+1] = daz_gpalette[(s100_bus_memr(maddr) & 0xf0) >> 4];
}
maddr++;
}
}
}
}
static void daz_render_x4(void)
{
int b, q, x, y;
int32 maddr = daz_addr;
int32 saddr = 0;
int32 soffset[] = {0, 1, daz_resolution, daz_resolution + 1, 2, 3, daz_resolution + 2, daz_resolution + 3};
uint32 color;
if (daz_0f & DAZ_COLOR) {
color = daz_cpalette[daz_color];
} else {
color = daz_gpalette[daz_color];
}
for (q = 0; q < daz_pages; q++) {
for (y = daz_quad_surfacey(q); y < daz_quad_surfacey(q) + 64; y+=2) {
for (x = daz_quad_surfacex(q); x < daz_quad_surfacex(q) + 64; x += 4) {
saddr = (y * daz_resolution) + x;
for (b = 0; b < 8; b++) {
if (daz_0e & DAZ_ON) {
daz_surface[saddr + soffset[b]] = (s100_bus_memr(maddr) & (1 << b)) ? color : 0;
} else {
daz_surface[saddr + soffset[b]] = 0x00;
}
}
maddr++;
}
}
}
}
static int32 daz_quad_surfacex(int q)
{
if (q == 1 || q == 3) {
return daz_resolution / ((daz_0f & DAZ_RESX4) ? 2 : 2);
}
return 0;
}
static int32 daz_quad_surfacey(int q)
{
if (q == 2 || q == 3) {
return daz_resolution / ((daz_0f & DAZ_RESX4) ? 2 : 2);
}
return 0;
}
static void daz_set_0f(uint8 val) {
uint8 old = daz_0f;
/* Update daz_0f register */
daz_0f = val;
daz_color = daz_0f & 0x0f;
/* Did resolution change? */
if ((daz_0f & (DAZ_RESX4 | DAZ_2K)) != (old & (DAZ_RESX4 | DAZ_2K))) {
daz_resolution = 32;
daz_pages = 1;
if (daz_0f & DAZ_RESX4) {
daz_resolution *= 2;
}
if (daz_0f & DAZ_2K) {
daz_pages = 4;
daz_resolution *= 2;
}
sim_debug(VERBOSE_MSG, &daz_dev, "Setting resolution to %02X %dx%d (%d pages) %s %s\n",
daz_0f, daz_resolution, daz_resolution, daz_pages, DAZ_SHOW_RES(daz_0f), DAZ_SHOW_MEMSIZE(daz_0f));
daz_screen_width = daz_resolution;
daz_screen_height = daz_resolution;
daz_screen_pixels = daz_screen_width * daz_screen_height;
daz_resize_video();
}
if (!sim_is_running) {
daz_refresh();
}
}
static t_stat daz_set_video(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, "OFF", strlen(cptr))) {
daz_0e &= ~DAZ_ON;
} else if (!strncmp(cptr, "ON", strlen(cptr))) {
daz_0e |= DAZ_ON;
} else {
return SCPE_ARG;
}
if (!sim_is_running) {
daz_refresh();
}
return SCPE_OK;
}
static t_stat daz_show_video(FILE *st, UNIT *uptr, int32 val, CONST void *desc) {
if (!st) return SCPE_IERR;
fprintf(st, "VIDEO=%s", DAZ_SHOW_VIDEO(daz_0e));
return SCPE_OK;
}
static t_stat daz_set_resolution(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint8 old = daz_0f;
if (!cptr) return SCPE_IERR;
if (!strlen(cptr)) return SCPE_ARG;
/* this assumes that the parameter has already been upcased */
if (!strncmp(cptr, "NORMAL", strlen(cptr))) {
old &= ~DAZ_RESX4;
} else if (!strncmp(cptr, "HIGH", strlen(cptr))) {
old |= DAZ_RESX4;
} else {
return SCPE_ARG;
}
daz_set_0f(old);
return SCPE_OK;
}
static t_stat daz_show_resolution(FILE *st, UNIT *uptr, int32 val, CONST void *desc) {
if (!st) return SCPE_IERR;
fprintf(st, "RES=%s", DAZ_SHOW_RES(daz_0f));
return SCPE_OK;
}
static t_stat daz_set_memsize(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint8 old = daz_0f;
if (!cptr) return SCPE_IERR;
if (!strlen(cptr)) return SCPE_ARG;
/* this assumes that the parameter has already been upcased */
if (!strncmp(cptr, "512", strlen(cptr))) {
old &= ~DAZ_2K;
} else if (!strncmp(cptr, "2K", strlen(cptr))) {
old |= DAZ_2K;
} else {
return SCPE_ARG;
}
daz_set_0f(old);
return SCPE_OK;
}
static t_stat daz_show_memsize(FILE *st, UNIT *uptr, int32 val, CONST void *desc) {
if (!st) return SCPE_IERR;
fprintf(st, "MEMSIZE=%s @ %04X", DAZ_SHOW_MEMSIZE(daz_0f), daz_addr);
return SCPE_OK;
}
static t_stat daz_set_color(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint8 old = daz_0f;
if (!cptr) return SCPE_IERR;
if (!strlen(cptr)) return SCPE_ARG;
/* this assumes that the parameter has already been upcased */
if (!strncmp(cptr, "BW", strlen(cptr))) {
old &= ~DAZ_COLOR;
} else if (!strncmp(cptr, "COLOR", strlen(cptr))) {
old |= DAZ_COLOR;
} else {
return SCPE_ARG;
}
daz_set_0f(old);
return SCPE_OK;
}
static t_stat daz_show_color(FILE *st, UNIT *uptr, int32 val, CONST void *desc) {
if (!st) return SCPE_IERR;
fprintf(st, "%s", DAZ_SHOW_COLOR(daz_0f));
return SCPE_OK;
}
static t_stat daz_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "\nCROMEMCO DAZZLER (%s)\n", dptr->name);
fprint_set_help (st, dptr);
fprint_show_help (st, dptr);
fprint_reg_help (st, dptr);
return SCPE_OK;
}

View File

@@ -0,0 +1,56 @@
/* cromemco_dazzler.h
Copyright (c) 2026 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:
01/18/26 Initial version
*/
#ifndef _CROMEMCO_DAZZLER_H
#define _CROMEMCO_DAZZLER_H
#define DAZ_PIXELS (128 * 128) /* total number of pixels */
#define DAZ_IO_BASE 0x0e
#define DAZ_IO_SIZE 2
#define DAZ_MEM_SIZE 2048
#define DAZ_MEM_MASK (2048 - 1)
#define DAZ_ON 0x80 /* On/Off */
#define DAZ_RESX4 0x40 /* Resolution X 4 */
#define DAZ_2K 0x20 /* Picture in 2K bytes of memory */
#define DAZ_COLOR 0x10 /* Picture in 2K bytes of memory */
#define DAZ_HIGH 0x08 /* High intensity color */
#define DAZ_BLUE 0x04 /* Blue */
#define DAZ_GREEN 0x02 /* Green */
#define DAZ_RED 0x01 /* Red */
#define DAZ_EOF 0x40 /* End of Frame */
#define DAZ_EVEN 0x80 /* Even Line */
extern VID_DISPLAY *daz_vptr;
#endif

705
Altair8800/pmmi_mm103.c Normal file
View File

@@ -0,0 +1,705 @@
/* pmmi_mm103: PMMI MM-103 MODEM
Copyright (c) 2026 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:
18-Jan-2026 Initial version
==================================================================
This device emulates a PMMI Communications MM-103 Modem & Communications
adapter.
The MM-103 uses 4 input and 4 output addresses. This driver defaults to
E0-E3 hex.
The MM-103 uses the Motorola MC6860L digital modem chip. This device does
not have the ability to emulate the modulation and demodulation functions
or the ability to connect to a phone line. All modem features, such as
switch hook, dial tone detection, and dialing, are emulated in such a way
that most software written for the MM-103 should function in some useful
fashion.
To provide any useful functionality, this device needs to be attached to
a socket or serial port. Enter "HELP PMMI" at the "simh>" prompt for
additional information.
*/
#include "sim_defs.h"
#include "sim_tmxr.h"
#include "altair8800_defs.h"
#include "s100_bus.h"
#include "pmmi_mm103.h"
#define DEVICE_DESC "PMMI MM-103 MODEM"
#define DEVICE_NAME "PMMI"
static int32 poc = TRUE; /* Power On Clear */
/* Debug flags */
#define STATUS_MSG (1 << 0)
#define ERROR_MSG (1 << 1)
#define VERBOSE_MSG (1 << 2)
typedef struct {
int32 conn; /* Connected Status */
int32 baud; /* Baud rate */
int32 dtr; /* DTR Status */
int32 txp; /* Transmit Pending */
int32 stb; /* Status Buffer */
int32 ireg0; /* In Register 0 */
int32 ireg1; /* In Register 1 */
int32 ireg2; /* In Register 2 */
int32 ireg3; /* In Register 3 */
int32 oreg0; /* Out Register 0 */
int32 oreg1; /* Out Register 1 */
int32 oreg2; /* Out Register 2 */
int32 oreg3; /* Out Register 3 */
int32 intmsk; /* Interrupt Mask */
uint32 ptimer; /* Next Pulse Timer */
uint32 dtimer; /* Next DT Timer */
uint32 flags; /* Original Flags */
} PMMI_CTX;
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 uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap);
static const char* pmmi_description(DEVICE *dptr);
static t_stat pmmi_svc(UNIT *uptr);
static t_stat pmmi_reset(DEVICE *dptr);
static t_stat pmmi_attach(UNIT *uptr, CONST char *cptr);
static t_stat pmmi_detach(UNIT *uptr);
static t_stat pmmi_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc);
static t_stat pmmi_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc);
static t_stat pmmi_config_line(UNIT *uptr);
static int32 pmmi_io(int32 addr, int32 io, int32 data);
static int32 pmmi_reg0(int32 io, int32 data);
static int32 pmmi_reg1(int32 io, int32 data);
static int32 pmmi_reg2(int32 io, int32 data);
static int32 pmmi_reg3(int32 io, int32 data);
static t_stat pmmi_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc);
static t_stat pmmi_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
/* Debug Flags */
static DEBTAB pmmi_dt[] = {
{ "STATUS", STATUS_MSG, "Status messages" },
{ "ERROR", ERROR_MSG, "Error messages" },
{ "VERBOSE", VERBOSE_MSG, "Verbose messages" },
{ NULL, 0 }
};
/* Terminal multiplexer library descriptors */
static TMLN pmmi_tmln[] = { /* line descriptors */
{ 0 }
};
static TMXR pmmi_tmxr = { /* multiplexer descriptor */
1, /* number of terminal lines */
0, /* listening port (reserved) */
0, /* master socket (reserved) */
pmmi_tmln, /* line descriptor array */
NULL, /* line connection order */
NULL /* multiplexer device (derived internally) */
};
static RES pmmi_res = { PMMI_IOBASE, PMMI_IOSIZE, 0, 0, &pmmi_tmxr };
static MTAB pmmi_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE",
&set_iobase, &show_iobase, NULL, "Sets MITS 2SIO base I/O address" },
{ UNIT_PMMI_RTS, UNIT_PMMI_RTS, "RTS", "RTS", NULL, NULL, NULL,
"RTS follows DTR (default)" },
{ UNIT_PMMI_RTS, 0, "NORTS", "NORTS", NULL, NULL, NULL,
"RTS does not follow DTR" },
{ MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "BAUD", "BAUD", &pmmi_set_baud, &pmmi_show_baud,
NULL, "Set baud rate (default=300)" },
{ MTAB_XTD | MTAB_VUN, UNIT_PMMI_CONSOLE, NULL, "CONSOLE", &pmmi_set_console, NULL, NULL, "Set as CONSOLE" },
{ MTAB_XTD | MTAB_VUN, 0, NULL, "NOCONSOLE", &pmmi_set_console, NULL, NULL, "Remove as CONSOLE" },
{ 0 }
};
static PMMI_CTX pmmi_ctx = { 0, PMMI_BAUD, 1 };
static UNIT pmmi_unit[] = {
{ UDATA (&pmmi_svc, UNIT_ATTABLE | UNIT_DISABLE | UNIT_PMMI_RTS, 0), PMMI_WAIT },
};
static REG pmmi_reg[] = {
{ HRDATAD (IREG0, pmmi_ctx.ireg0, 8, "PMMI input register 0"), },
{ HRDATAD (IREG1, pmmi_ctx.ireg1, 8, "PMMI input register 1"), },
{ HRDATAD (IREG2, pmmi_ctx.ireg2, 8, "PMMI input register 2"), },
{ HRDATAD (IREG3, pmmi_ctx.ireg3, 8, "PMMI input register 3"), },
{ HRDATAD (OREG0, pmmi_ctx.oreg0, 8, "PMMI output register 0"), },
{ HRDATAD (OREG1, pmmi_ctx.oreg1, 8, "PMMI output register 1"), },
{ HRDATAD (OREG2, pmmi_ctx.oreg2, 8, "PMMI output register 2"), },
{ HRDATAD (OREG3, pmmi_ctx.oreg3, 8, "PMMI output register 3"), },
{ HRDATAD (TXP, pmmi_ctx.txp, 8, "PMMI tx data pending"), },
{ FLDATAD (CON, pmmi_ctx.conn, 0, "PMMI connection status"), },
{ DRDATAD (BAUD, pmmi_ctx.baud, 8, "PMMI calculated baud rate"), },
{ HRDATAD (INTMSK, pmmi_ctx.intmsk, 8, "PMMI interrupt mask"), },
{ FLDATAD (TBMT, pmmi_ctx.ireg0, 0, "PMMI TBMT status"), },
{ FLDATAD (DAV, pmmi_ctx.ireg0, 1, "PMMI DAV status"), },
{ FLDATAD (OR, pmmi_ctx.ireg0, 4, "PMMI OVRN status"), },
{ FLDATAD (DT, pmmi_ctx.ireg2, 0, "PMMI dial tone status (active low)"), },
{ FLDATAD (RNG, pmmi_ctx.ireg2, 1, "PMMI ringing status (active low)"), },
{ FLDATAD (CTS, pmmi_ctx.ireg2, 2, "PMMI CTS status (active low)"), },
{ FLDATAD (AP, pmmi_ctx.ireg2, 0, "PMMI answer phone status (active low)"), },
{ FLDATAD (PULSE, pmmi_ctx.ireg2, 7, "PMMI timer pulse"), },
{ DRDATAD (TIMER, pmmi_ctx.ptimer, 32, "PMMI timer pulse ms"), },
{ DRDATAD (WAIT, pmmi_unit[0].wait, 32, "PMMI wait cycles"), },
{ NULL }
};
DEVICE pmmi_dev = {
DEVICE_NAME, /* name */
pmmi_unit, /* unit */
pmmi_reg, /* registers */
pmmi_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 */
&pmmi_reset, /* reset routine */
NULL, /* boot routine */
&pmmi_attach, /* attach routine */
&pmmi_detach, /* detach routine */
&pmmi_res, /* context */
(DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_MUX), /* flags */
0, /* debug control */
pmmi_dt, /* debug flags */
NULL, /* mem size routine */
NULL, /* logical name */
&pmmi_show_help, /* help */
NULL, /* attach help */
NULL, /* context for help */
&pmmi_description /* description */
};
static const char* pmmi_description(DEVICE *dptr)
{
return DEVICE_DESC;
}
static t_stat pmmi_reset(DEVICE *dptr)
{
if (dptr->flags & DEV_DIS) { /* Disable Device */
s100_bus_remio(pmmi_res.io_base, pmmi_res.io_size, &pmmi_io);
poc = TRUE;
}
else {
if (poc) {
s100_bus_addio(pmmi_res.io_base, pmmi_res.io_size, &pmmi_io, DEVICE_NAME);
poc = FALSE;
}
}
/* Set DEVICE for this UNIT */
dptr->units[0].dptr = dptr;
/* Enable TMXR modem control passthrough */
tmxr_set_modem_control_passthru(pmmi_res.tmxr);
/* Reset status registers */
pmmi_ctx.ireg0 = 0;
pmmi_ctx.ireg1 = 0;
pmmi_ctx.ireg2 = PMMI_RNG | PMMI_CTS | PMMI_DT | PMMI_AP;
pmmi_ctx.ireg3 = 0;
pmmi_ctx.oreg0 = 0;
pmmi_ctx.oreg1 = 0;
pmmi_ctx.oreg2 = 0;
pmmi_ctx.oreg3 = 0;
pmmi_ctx.txp = 0;
pmmi_ctx.intmsk = 0;
pmmi_ctx.ptimer = sim_os_msec() + 40;
pmmi_ctx.dtimer = 0;
if (!(dptr->flags & DEV_DIS)) {
sim_activate(&dptr->units[0], dptr->units[0].wait);
} else {
sim_cancel(&dptr->units[0]);
}
sim_debug(STATUS_MSG, dptr, "reset adapter.\n");
return SCPE_OK;
}
static t_stat pmmi_svc(UNIT *uptr)
{
int32 c,s,ireg2;
t_stat r = SCPE_OK;
uint32 ms;
/* Check for new incoming connection */
if (uptr->flags & UNIT_ATT) {
if (tmxr_poll_conn(pmmi_res.tmxr) >= 0) { /* poll connection */
/* Clear DTR and RTS if serial port */
if (pmmi_res.tmxr->ldsc->serport) {
s = TMXR_MDM_DTR | ((pmmi_dev.units[0].flags & UNIT_PMMI_RTS) ? TMXR_MDM_RTS : 0);
tmxr_set_get_modem_bits(pmmi_res.tmxr->ldsc, 0, s, NULL);
}
pmmi_res.tmxr->ldsc->rcve = 1; /* Enable receiver */
pmmi_ctx.conn = 1; /* 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(pmmi_res.tmxr->ldsc, 0, 0, &s);
ireg2 = pmmi_ctx.ireg2;
pmmi_ctx.ireg2 &= ~PMMI_CTS;
pmmi_ctx.ireg2 |= (s & TMXR_MDM_CTS) ? 0 : PMMI_CTS; /* Active Low */
/* CTS status changed */
if ((ireg2 ^ pmmi_ctx.ireg2) & PMMI_CTS) {
if (pmmi_ctx.ireg2 & PMMI_CTS) { /* If no CTS, set AP bit */
pmmi_ctx.ireg2 |= PMMI_AP; /* Answer Phone Bit (active low) */
}
sim_debug(STATUS_MSG, uptr->dptr, "CTS state changed to %s.\n", (pmmi_ctx.ireg2 & PMMI_CTS) ? "LOW" : "HIGH");
}
pmmi_ctx.ireg2 &= ~PMMI_RNG;
pmmi_ctx.ireg2 |= (s & TMXR_MDM_RNG) ? 0 : PMMI_RNG; /* Active Low */
/* RNG status changed */
if ((ireg2 ^ pmmi_ctx.ireg2) & PMMI_RNG) {
/* Answer Phone Bit on RI */
if (!(pmmi_ctx.ireg2 & PMMI_RNG)) {
pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */
}
sim_debug(STATUS_MSG, uptr->dptr, "RNG state changed to %s.\n", (pmmi_ctx.ireg2 & PMMI_RNG) ? "LOW" : "HIGH");
}
/* Enable receiver if CTS is active low */
pmmi_res.tmxr->ldsc->rcve = !(pmmi_ctx.ireg2 & PMMI_CTS);
/* If socket, connection status follows CTS */
if (!pmmi_res.tmxr->ldsc->serport) {
pmmi_ctx.conn = !(pmmi_ctx.ireg2 & PMMI_CTS);
}
}
/* TX data */
if (pmmi_ctx.txp) {
if (uptr->flags & UNIT_ATT) {
/*
** If CTS active low, send byte
** otherwise, toss character
*/
if (!(pmmi_ctx.ireg2 & PMMI_CTS)) {
r = tmxr_putc_ln(pmmi_res.tmxr->ldsc, pmmi_ctx.oreg1);
}
pmmi_ctx.txp = 0; /* Reset TX Pending */
} else {
r = sim_putchar(pmmi_ctx.oreg1);
pmmi_ctx.txp = 0; /* Reset TX Pending */
}
if (r == SCPE_LOST) {
pmmi_ctx.conn = 0; /* Connection was lost */
sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n");
}
}
/* Update TBMT if not set and no character pending */
if (!pmmi_ctx.txp && !(pmmi_ctx.ireg0 & PMMI_TBMT)) {
if (uptr->flags & UNIT_ATT) {
tmxr_poll_tx(pmmi_res.tmxr);
pmmi_ctx.ireg0 |= (tmxr_txdone_ln(pmmi_res.tmxr->ldsc) && pmmi_ctx.conn) ? (PMMI_TBMT | PMMI_TEOC) : 0;
} else {
pmmi_ctx.ireg0 |= (PMMI_TBMT | PMMI_TEOC);
}
}
/* Check for Data if RX buffer empty */
if (!(pmmi_ctx.ireg0 & PMMI_DAV)) {
if (uptr->flags & UNIT_ATT) {
tmxr_poll_rx(pmmi_res.tmxr);
c = tmxr_getc_ln(pmmi_res.tmxr->ldsc);
} else {
c = s100_bus_poll_kbd(uptr);
}
if (c & (TMXR_VALID | SCPE_KFLAG)) {
pmmi_ctx.ireg1 = c & 0xff;
pmmi_ctx.ireg0 |= PMMI_DAV;
pmmi_ctx.ireg0 &= ~(PMMI_FE | PMMI_OR | PMMI_RPE);
}
}
/* Timer Pulses */
ms = sim_os_msec();
if (ms > pmmi_ctx.ptimer) {
if (pmmi_ctx.oreg2) {
if (pmmi_ctx.ireg2 & PMMI_TMR) {
pmmi_ctx.ireg2 &= ~PMMI_TMR;
pmmi_ctx.ptimer = sim_os_msec() + 600 / (PMMI_CLOCK / pmmi_ctx.oreg2); /* 60% off */
} else {
pmmi_ctx.ireg2 |= PMMI_TMR;
pmmi_ctx.ptimer = sim_os_msec() + 400 / (PMMI_CLOCK / pmmi_ctx.oreg2); /* 40% on */
}
} else {
pmmi_ctx.ptimer = sim_os_msec() + 100; /* default to 100ms if timer rate is 0 */
}
}
/* Emulate dial tone */
if ((ms > pmmi_ctx.dtimer) && (pmmi_ctx.oreg0 & PMMI_SH) && (pmmi_ctx.ireg2 & PMMI_DT)) {
pmmi_ctx.ireg2 &= ~PMMI_DT;
sim_debug(STATUS_MSG, uptr->dptr, "dial tone active.\n");
}
/* Don't let TMXR clobber our wait time */
uptr->wait = PMMI_WAIT;
sim_activate_abs(uptr, uptr->wait);
return SCPE_OK;
}
/* Attach routine */
static t_stat pmmi_attach(UNIT *uptr, CONST char *cptr)
{
t_stat r;
sim_debug(VERBOSE_MSG, uptr->dptr, "attach (%s).\n", cptr);
if ((r = tmxr_attach(pmmi_res.tmxr, uptr, cptr)) == SCPE_OK) {
pmmi_ctx.flags = uptr->flags; /* Save Flags */
if (!pmmi_res.tmxr->ldsc->serport) {
uptr->flags |= UNIT_PMMI_RTS; /* Force following DTR on sockets */
}
pmmi_res.tmxr->ldsc->rcve = 1;
sim_activate(uptr, uptr->wait);
sim_debug(VERBOSE_MSG, uptr->dptr, "activated service.\n");
}
return r;
}
/* Detach routine */
static t_stat pmmi_detach(UNIT *uptr)
{
sim_debug(VERBOSE_MSG, uptr->dptr, "detach.\n");
if (uptr->flags & UNIT_ATT) {
uptr->flags = pmmi_ctx.flags; /* Restore Flags */
sim_cancel(uptr);
return (tmxr_detach(pmmi_res.tmxr, uptr));
}
return SCPE_UNATT;
}
static t_stat pmmi_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc)
{
int32 baud;
t_stat r = SCPE_ARG;
if (!(uptr->flags & UNIT_ATT)) {
return SCPE_UNATT;
}
if (cptr != NULL) {
if (sscanf(cptr, "%d", &baud)) {
if (baud >= 61 && baud <=600) {
pmmi_ctx.baud = baud;
r = pmmi_config_line(uptr);
}
}
}
return r;
}
static t_stat pmmi_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc)
{
if (uptr->flags & UNIT_ATT) {
fprintf(st, "Baud rate: %d", pmmi_ctx.baud);
}
return SCPE_OK;
}
static t_stat pmmi_config_line(UNIT *uptr)
{
char config[20];
char b,p,s;
t_stat r = SCPE_IERR;
switch (pmmi_ctx.oreg0 & PMMI_BMSK) {
case PMMI_5BIT:
b = '5';
break;
case PMMI_6BIT:
b = '6';
break;
case PMMI_7BIT:
b = '7';
break;
case PMMI_8BIT:
default:
b = '8';
break;
}
switch (pmmi_ctx.oreg0 & PMMI_PMSK) {
case PMMI_OPAR:
p = 'O';
break;
case PMMI_EPAR:
p = 'E';
break;
case PMMI_NPAR:
default:
p = 'N';
break;
}
switch (pmmi_ctx.oreg0 & PMMI_SMSK) {
case PMMI_2SB:
s = '2';
break;
case PMMI_1SB:
default:
s = '1';
break;
}
sprintf(config, "%d-%c%c%c", pmmi_ctx.baud, b,p,s);
sim_debug(STATUS_MSG, uptr->dptr, "setting port configuration to '%s'.\n", config);
r = tmxr_set_config_line(pmmi_res.tmxr->ldsc, config);
return r;
}
static int32 pmmi_io(int32 addr, int32 io, int32 data)
{
int32 r = 0;
addr &= 0xff;
data &= 0xff;
if (io == S100_IO_WRITE) {
sim_debug(VERBOSE_MSG, &pmmi_dev, "OUT %02X,%02X\n", addr , data);
} else {
sim_debug(VERBOSE_MSG, &pmmi_dev, "IN %02X\n", addr);
}
switch (addr & 0x03) {
case PMMI_REG0:
r = pmmi_reg0(io, data);
break;
case PMMI_REG1:
r = pmmi_reg1(io, data);
break;
case PMMI_REG2:
r = pmmi_reg2(io, data);
break;
case PMMI_REG3:
r = pmmi_reg3(io, data);
break;
}
return(r);
}
static int32 pmmi_reg0(int32 io, int32 data)
{
int32 r;
if (io == S100_IO_READ) {
r = pmmi_ctx.ireg0;
} else { pmmi_ctx.oreg0 = data; /* Set UART configuration */
pmmi_config_line(&pmmi_dev.units[0]);
if (data & PMMI_SH) { /* If off-hook, clear dial tone bit (active low) */
pmmi_ctx.dtimer = sim_os_msec() + 500; /* Dial tone in 500ms */
if (pmmi_ctx.oreg0 & PMMI_SH) {
pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */
}
} else if (!(pmmi_ctx.ireg2 & PMMI_DT)) {
pmmi_ctx.dtimer = 0;
pmmi_ctx.ireg2 |= PMMI_DT;
sim_debug(STATUS_MSG, &pmmi_dev, "dial tone inactive.\n");
}
if (data & PMMI_RI) { /* Go off-hook in answer mode */
pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */
}
r = 0x00;
}
return(r);
}
static int32 pmmi_reg1(int32 io, int32 data)
{
int32 r;
if (io == S100_IO_READ) {
r = pmmi_ctx.ireg1;
pmmi_ctx.ireg0 &= ~(PMMI_DAV | PMMI_FE | PMMI_OR | PMMI_RPE);
} else {
pmmi_ctx.oreg1 = data;
pmmi_ctx.ireg0 &= ~(PMMI_TBMT | PMMI_TEOC);
pmmi_ctx.txp = 1;
r = 0x00;
}
return(r);
}
static int32 pmmi_reg2(int32 io, int32 data)
{
int32 r;
if (io == S100_IO_READ) {
r = pmmi_ctx.ireg2;
} else {
pmmi_ctx.oreg2 = data;
/*
** The actual baud rate is determined by the following:
** Rate = 250,000/(Reg X 16) where Reg = the binary
** value loaded into the rate generator.
*/
if (data) {
pmmi_ctx.baud = 250000/(data * 16);
pmmi_config_line(&pmmi_dev.units[0]);
}
r = 0x00;
}
return(r);
}
static int32 pmmi_reg3(int32 io, int32 data)
{
int32 s;
if (io == S100_IO_READ) {
pmmi_ctx.intmsk = pmmi_ctx.oreg2; /* Load int mask from rate generator */
} else {
pmmi_ctx.oreg3 = data;
/* Set/Clear DTR */
s = TMXR_MDM_DTR | ((pmmi_dev.units[0].flags & UNIT_PMMI_RTS) ? TMXR_MDM_RTS : 0);
if (data & PMMI_DTR) {
sim_debug(STATUS_MSG, &pmmi_dev, "setting DTR HIGH.\n");
tmxr_set_get_modem_bits(pmmi_res.tmxr->ldsc, s, 0, NULL);
if (pmmi_ctx.oreg0 & PMMI_SH) {
pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */
}
} else {
sim_debug(STATUS_MSG, &pmmi_dev, "setting DTR LOW.\n");
tmxr_set_get_modem_bits(pmmi_res.tmxr->ldsc, 0, s, NULL);
pmmi_ctx.ireg2 |= PMMI_AP;
}
}
return 0x00;
}
static t_stat pmmi_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc)
{
if (value == UNIT_PMMI_CONSOLE) {
s100_bus_console(uptr);
}
else {
s100_bus_noconsole(uptr);
}
return SCPE_OK;
}
static t_stat pmmi_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "\nPMMI MM-103 (%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");
fprintf(st, "This device may be attached to a serial port on the host computer\n");
fprintf(st, "with the ATTACH command:\n\n");
fprintf(st, " sim> ATTACH %s CONNECT=/dev/tty.usbserial-AB0NW409\n\n", sim_dname(dptr));
fprintf(st, "This device may also be attached to a TCP/IP port on the host computer\n");
fprintf(st, "with the ATTACH command. The following will listen for a connection\n");
fprintf(st, "on port 8800:\n\n");
fprintf(st, " sim> ATTACH %s 8800\n", sim_dname(dptr));
return SCPE_OK;
}

93
Altair8800/pmmi_mm103.h Normal file
View File

@@ -0,0 +1,93 @@
/* pmmi_mm103.h
Copyright (c) 2026 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:
01/18/26 Initial version
*/
#ifndef _PMMI_MM103_H
#define _PMMI_MM103_H
#define UNIT_V_PMMI_VERBOSE (UNIT_V_UF + 0) /* Verbose messages */
#define UNIT_PMMI_VERBOSE (1 << UNIT_V_PMMI_VERBOSE)
#define UNIT_V_PMMI_CONSOLE (UNIT_V_UF + 1) /* Use this device for console */
#define UNIT_PMMI_CONSOLE (1 << UNIT_V_PMMI_CONSOLE)
#define UNIT_V_PMMI_RTS (UNIT_V_UF + 2) /* RTS follows DTR */
#define UNIT_PMMI_RTS (1 << UNIT_V_PMMI_RTS)
#define PMMI_WAIT 500 /* Service Wait Interval */
#define PMMI_IOBASE 0xC0
#define PMMI_IOSIZE 4
#define PMMI_REG0 0 /* Relative Address 0 */
#define PMMI_REG1 1 /* Relative Address 1 */
#define PMMI_REG2 2 /* Relative Address 2 */
#define PMMI_REG3 3 /* Relative Address 3 */
#define PMMI_TBMT 0x01 /* Transmit Data Register Empty */
#define PMMI_DAV 0x02 /* Receive Data Register Full */
#define PMMI_TEOC 0x04 /* Transmit Serializer Empty */
#define PMMI_RPE 0x08 /* Parity Error */
#define PMMI_OR 0x10 /* Overrun */
#define PMMI_FE 0x20 /* Framing Error */
#define PMMI_DT 0x01 /* Dial Tone */
#define PMMI_RNG 0x02 /* Ringing */
#define PMMI_CTS 0x04 /* Clear to Send */
#define PMMI_RXBRK 0x08 /* RX Break */
#define PMMI_AP 0x10 /* Answer Phone */
#define PMMI_FO 0x20 /* Digital Carrier Signal */
#define PMMI_MODE 0x40 /* Mode */
#define PMMI_TMR 0x80 /* Timer Pulses */
#define PMMI_ST 0x10 /* Self Test */
#define PMMI_DTR 0x40 /* DTR */
#define PMMI_SH 0x01 /* Switch Hook */
#define PMMI_RI 0x02 /* Ring Indicator */
#define PMMI_5BIT 0x00 /* 5 Data Bits */
#define PMMI_6BIT 0x04 /* 6 Data Bits */
#define PMMI_7BIT 0x08 /* 7 Data Bits */
#define PMMI_8BIT 0x0C /* 8 Data Bits */
#define PMMI_BMSK 0x0C /* Data Bits Bit Mask */
#define PMMI_OPAR 0x00 /* Odd Parity */
#define PMMI_NPAR 0x10 /* No Parity */
#define PMMI_EPAR 0x20 /* Odd Parity */
#define PMMI_PMSK 0x30 /* Parity Bit Mask */
#define PMMI_1SB 0x00 /* 1 Stop Bit */
#define PMMI_15SB 0x40 /* 1.5 Stop Bits */
#define PMMI_2SB 0x40 /* 2 Stop Bits */
#define PMMI_SMSK 0x40 /* Stop Bits Bit Mask */
#define PMMI_CLOCK 2500 /* Rate Generator / 100 */
#define PMMI_BAUD 300 /* Default baud rate */
#endif

View File

@@ -45,8 +45,6 @@ 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 */
@@ -413,18 +411,6 @@ void s100_bus_memw(t_addr addr, int32 data)
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;

View File

@@ -159,6 +159,11 @@ void cpu_set_chiptype(ChipType new_type)
cpu_reset(&cpu_dev);
}
ChipType cpu_get_chiptype()
{
return cpu_type;
}
char * cpu_get_chipname(ChipType type)
{
return cpu_chipname[type];

View File

@@ -56,6 +56,7 @@ typedef struct {
} CPU;
extern void cpu_set_chiptype(ChipType type);
extern ChipType cpu_get_chiptype(void);
extern char * cpu_get_chipname(ChipType type);
#endif

View File

@@ -41,6 +41,7 @@ static t_stat ram_default_dis (UNIT *uptr, int32 value, const char *cptr,
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_prot_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);
@@ -54,7 +55,7 @@ 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 P[MAXBANKSIZE >> LOG2PAGESIZE]; /* Protected pages */
static int32 memsize = MAXBANKSIZE;
static const char* ram_description(DEVICE *dptr) {
@@ -86,6 +87,10 @@ static MTAB ram_mod[] = {
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_XTD | MTAB_VDV | MTAB_VALR, TRUE, NULL, "PROT={PAGE | START-END | ALL}", &ram_prot_command,
NULL, NULL, "Protect RAM page(s)" },
{ MTAB_XTD | MTAB_VDV | MTAB_VALR, FALSE, NULL, "UNPROT={PAGE | START-END | ALL}", &ram_prot_command,
NULL, NULL, "Unprotect 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,
@@ -93,8 +98,12 @@ static MTAB ram_mod[] = {
{ 0 }
};
/* Debug Flags */
#define VERBOSE_MSG (1 << 0)
/* Debug Flags */
static DEBTAB ram_dt[] = {
{ "VERBOSE", VERBOSE_MSG, "Verbose messages" },
{ NULL, 0 }
};
@@ -116,7 +125,7 @@ DEVICE ram_dev = {
NULL, /* attach routine */
NULL, /* detach routine */
NULL, /* context */
(DEV_DISABLE), /* flags */
(DEV_DISABLE | DEV_DEBUG), /* flags */
0, /* debug control */
ram_dt, /* debug flags */
NULL, /* mem size routine */
@@ -129,6 +138,8 @@ DEVICE ram_dev = {
static t_stat ram_reset(DEVICE *dptr)
{
t_addr addr;
if (dptr->flags & DEV_DIS) { /* Disable Device */
s100_bus_remmem(0x0000, MAXBANKSIZE, &ram_memio);
ram_default_dis(NULL, 0, NULL, NULL);
@@ -143,6 +154,14 @@ static t_stat ram_reset(DEVICE *dptr)
ram_default_ena(NULL, 0, NULL, NULL);
}
for (addr = 0; addr < MAXADDR; addr++) {
M[addr] = 0x00;
if ((addr & 0xff) == 0x00) {
P[addr >> LOG2PAGESIZE] = FALSE;
}
}
poc = FALSE;
}
}
@@ -175,12 +194,17 @@ static int32 ram_memio(const int32 addr, const int32 rw, const int32 data)
return 0x0ff;
}
static void PutBYTE(register uint32 Addr, const register uint32 Value)
static void PutBYTE(uint32 Addr, const register uint32 Value)
{
M[Addr & ADDRMASK] = Value & DATAMASK;
if (!P[(Addr & ADDRMASK) >> LOG2PAGESIZE]) {
M[Addr & ADDRMASK] = Value & DATAMASK;
}
else {
sim_debug(VERBOSE_MSG, &ram_dev, "RAM: Page %02X is protected\n", Addr >> LOG2PAGESIZE);
}
}
static uint32 GetBYTE(register uint32 Addr)
static uint32 GetBYTE(uint32 Addr)
{
return M[Addr & ADDRMASK] & DATAMASK; /* RAM */
}
@@ -200,9 +224,8 @@ static t_stat ram_default_dis(UNIT *uptr, int32 value, const char *cptr, void *d
}
/* set memory to 'size' kilo byte */
static t_stat ram_set_memsize(int32 size) {
int32 page;
static t_stat ram_set_memsize(int32 size)
{
size <<= KBLOG2;
if (size < KB) {
@@ -218,11 +241,6 @@ static t_stat ram_set_memsize(int32 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;
@@ -246,7 +264,8 @@ static void ram_randomize()
}
}
static t_stat ram_size_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)
{
int32 size, result;
if (cptr == NULL) {
@@ -263,12 +282,13 @@ static t_stat ram_size_command(UNIT *uptr, int32 value, const char *cptr, void *
return SCPE_ARG | SCPE_NOMESSAGE;
}
static t_stat ram_enable_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)
{
int32 size;
t_addr start, end;
if (cptr == NULL) {
sim_printf("Memory page(s) must be provided as SET RAM ENABLE=E0-EF\n");
sim_printf("Memory page(s) must be provided as SET RAM %s=E0-EF\n", value ? "ADDRAM" : "REMRAM");
return SCPE_ARG | SCPE_NOMESSAGE;
}
@@ -298,6 +318,35 @@ static t_stat ram_enable_command(UNIT *uptr, int32 value, const char *cptr, void
return SCPE_OK;
}
static t_stat ram_prot_command(UNIT *uptr, int32 value, const char *cptr, void *desc)
{
t_addr start, end;
uint16 page;
if (cptr == NULL) {
sim_printf("Memory page(s) must be provided as SET RAM %s=E0-EF\n", value ? "PROT" : "UNPROT");
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;
}
/* Protect or Unprotect Pages */
for (page = start; page <= end; page++) {
P[page] = value;
}
return SCPE_OK;
}
static t_stat ram_clear_command(UNIT *uptr, int32 value, const char *cptr, void *desc)
{
ram_clear();

View File

@@ -55,11 +55,12 @@ static t_stat rom_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, cons
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 },
{ 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_CDBL, rom_me_cdbl, ROM_ME_CDBL_BASEADDR, ROM_ME_CDBL_SIZE, ROM_ME_CDBL_NAME, ROM_ME_CDBL_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, "", "" }
};
@@ -108,6 +109,11 @@ static MTAB rom_mod[] = {
{ UNIT_ROM_TURMON, 0, "NO" ROM_MITS_TURMON_NAME, "NO" ROM_MITS_TURMON_NAME, &rom_dis_turmon, NULL,
NULL, "Disable " ROM_MITS_TURMON_DESC },
{ UNIT_ROM_CDBL, UNIT_ROM_CDBL, ROM_ME_CDBL_NAME, ROM_ME_CDBL_NAME, &rom_ena, NULL,
NULL, "Enable " ROM_ME_CDBL_DESC },
{ UNIT_ROM_CDBL, 0, "NO" ROM_ME_CDBL_NAME, "NO" ROM_ME_CDBL_NAME, &rom_dis_turmon, NULL,
NULL, "Disable " ROM_ME_CDBL_DESC },
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LIST", NULL, NULL, &rom_show_list, NULL, "Show available ROMs" },
{ 0 }

View File

@@ -43,7 +43,9 @@
#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_V_CDBL (UNIT_V_UF+5) /* Enable/Disable Combined Disk Boot Loader */
#define UNIT_ROM_CDBL (1 << UNIT_ROM_V_CDBL)
#define UNIT_ROM_V_AZ80DBL (UNIT_V_UF+6) /* Enable/Disable AltairZ80 Disk Boot Loader */
#define UNIT_ROM_AZ80DBL (1 << UNIT_ROM_V_AZ80DBL)
typedef struct {

View File

@@ -330,6 +330,53 @@ static int32 rom_altmon[ROM_ALTMON_SIZE] = {
0x9c, 0x23, 0xc0, 0x13, 0xc9, 0xff, 0xff, 0xff
};
/*
* Martin Eberhard Combined Disk Boot Loader
* https://deramp.com/downloads/altair/software/roms/custom_roms/M%20Eberhard%20Improved%20ROMs/CDBL.ASM
*/
#define ROM_ME_CDBL_BASEADDR 0xff00
#define ROM_ME_CDBL_SIZE 0x0100
#define ROM_ME_CDBL_NAME "CDBL"
#define ROM_ME_CDBL_DESC "Combined Disk Boot Loader"
static int32 rom_me_cdbl[ROM_ME_CDBL_SIZE] = {
0xf3, 0x11, 0xf5, 0xff, 0x21, 0xe1, 0x4c, 0x1b,
0x2b, 0x1a, 0x77, 0x7d, 0xb7, 0xc2, 0x07, 0xff,
0x01, 0x82, 0x06, 0xe9, 0xd3, 0x22, 0x2f, 0xd3,
0x23, 0x3e, 0x2c, 0xd3, 0x22, 0xaf, 0xd3, 0x08,
0xdb, 0x08, 0xe6, 0x08, 0xc2, 0x09, 0x4c, 0x3e,
0x04, 0xd3, 0x09, 0x3e, 0x01, 0xd3, 0x09, 0x0b,
0x78, 0xb1, 0xc2, 0x1b, 0x4c, 0x0c, 0xdb, 0x08,
0x0f, 0x0f, 0xda, 0x22, 0x4c, 0xe6, 0x10, 0x3e,
0x02, 0xc2, 0x19, 0x4c, 0xdb, 0x09, 0xe6, 0x3f,
0xfe, 0x1e, 0xc2, 0x30, 0x4c, 0xdb, 0x09, 0x0f,
0xd2, 0x39, 0x4c, 0xdb, 0x09, 0x0f, 0xda, 0x3f,
0x4c, 0xe6, 0x1f, 0xc6, 0x10, 0x4f, 0x65, 0x3e,
0x03, 0xd3, 0x10, 0x3e, 0x11, 0xd3, 0x10, 0x3e,
0x10, 0x31, 0x7b, 0x4d, 0xf5, 0xdb, 0x09, 0xe6,
0x3f, 0x0f, 0xb8, 0xc2, 0x59, 0x4c, 0x11, 0x7b,
0x4d, 0x7c, 0xaa, 0xe6, 0xfe, 0x3e, 0x4f, 0xca,
0xc5, 0x4c, 0xe5, 0xc5, 0x01, 0x80, 0x00, 0xdb,
0x08, 0x07, 0xda, 0x73, 0x4c, 0xdb, 0x0a, 0x12,
0x1c, 0xc2, 0x73, 0x4c, 0x1e, 0x7e, 0x1a, 0x77,
0xbe, 0xc2, 0xc3, 0x4c, 0x80, 0x47, 0x13, 0x23,
0x0d, 0xc2, 0x82, 0x4c, 0xeb, 0x4e, 0x0c, 0x23,
0xae, 0xb1, 0xc1, 0xc2, 0xb6, 0x4c, 0x2a, 0x7c,
0x4d, 0xeb, 0x7d, 0x93, 0x7c, 0x9a, 0xd2, 0xc7,
0x4c, 0x11, 0x53, 0x4c, 0xd5, 0x04, 0x04, 0x78,
0xb9, 0xd8, 0x06, 0x01, 0xc8, 0x78, 0xd3, 0x09,
0x05, 0xc9, 0x3e, 0x04, 0xd3, 0x09, 0xe1, 0xf1,
0x3d, 0xc2, 0x55, 0x4c, 0x3e, 0x43, 0x11, 0x3e,
0x4d, 0x47, 0x37, 0x3e, 0x80, 0xd3, 0x08, 0xd2,
0x00, 0x00, 0xfb, 0x22, 0x01, 0x00, 0x78, 0x32,
0x00, 0x00, 0xd3, 0x01, 0xd3, 0x11, 0xd3, 0x05,
0xd3, 0x23, 0xc3, 0xd6, 0x4c, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
/*
* AltairZ80 Disk Boot Loader
*/
#define ROM_AZ80_DBL_BASEADDR 0xff00
#define ROM_AZ80_DBL_SIZE 0x0100
#define ROM_AZ80_DBL_NAME "AZ80DBL"

View File

@@ -203,6 +203,10 @@
RelativePath="..\Altair8800\altair8800_sys.c"
>
</File>
<File
RelativePath="..\Altair8800\cromemco_dazzler.c"
>
</File>
<File
RelativePath="..\Altair8800\mits_2sio.c"
>
@@ -215,6 +219,10 @@
RelativePath="..\Altair8800\mits_dsk.c"
>
</File>
<File
RelativePath="..\Altair8800\pmmi_mm103.c"
>
</File>
<File
RelativePath="..\Altair8800\s100_bram.c"
>
@@ -336,6 +344,10 @@
RelativePath="..\Altair8800\altair8800_sys.h"
>
</File>
<File
RelativePath="..\Altair8800\cromemco_dazzler.h"
>
</File>
<File
RelativePath="..\Altair8800\mits_2sio.h"
>
@@ -348,6 +360,10 @@
RelativePath="..\Altair8800\mits_dsk.h"
>
</File>
<File
RelativePath="..\Altair8800\pmmi_mm103.h"
>
</File>
<File
RelativePath="..\Altair8800\s100_bram.h"
>

View File

@@ -2242,9 +2242,11 @@ ALTAIR8800 = \
${ALTAIR8800D}/s100_ram.c \
${ALTAIR8800D}/s100_rom.c \
${ALTAIR8800D}/s100_z80.c \
${ALTAIR8800D}/cromemco_dazzler.c \
${ALTAIR8800D}/mits_2sio.c \
${ALTAIR8800D}/mits_acr.c \
${ALTAIR8800D}/mits_dsk.c \
${ALTAIR8800D}/pmmi_mm103.c \
${ALTAIR8800D}/sds_sbc200.c \
${ALTAIR8800D}/sds_vfii.c \
${ALTAIR8800D}/tarbell_fdc.c \