Custom TTL design by Marvin Minsky. There are two displays: one raster scan for bitmapped characters, and another random scan for vector graphics. There is also a keyboard, and a UART for talking to a host computer. The computer is normally booted off a ROM which reads and starts a secondary loader from the UART. The loader is responsible for reading the payload, which comes in checksummed blocks. The LOAD command accepts files in same format.
485 lines
12 KiB
C
485 lines
12 KiB
C
/* tt2500_sys.c: TT2500 simulator interface
|
|
|
|
Copyright (c) 2020, Lars Brinkhoff
|
|
|
|
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
|
|
LARS BRINKHOFF 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 Lars Brinkhoff shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Lars Brinkhoff.
|
|
|
|
24-Sep-20 LB New simulator.
|
|
*/
|
|
|
|
#include "tt2500_defs.h"
|
|
|
|
static uint16 null_read (uint16 reg);
|
|
static void null_write (uint16 reg, uint16 data);
|
|
|
|
int32 sim_emax = 1;
|
|
char sim_name[] = "TT2500";
|
|
|
|
uint16 CRM[4096];
|
|
uint16 MEM[65536];
|
|
REG *sim_PC = &cpu_reg[0];
|
|
TTDEV *dev_tab[0100];
|
|
|
|
static t_addr sym_addr = -1;
|
|
static int sym_immediate = 0;
|
|
|
|
static TTDEV null_dev = {
|
|
{ 0, 0, 0, 0 },
|
|
null_read,
|
|
null_write,
|
|
};
|
|
|
|
DEVICE *sim_devices[] = {
|
|
&cpu_dev,
|
|
&dpy_dev,
|
|
&crt_dev,
|
|
&tv_dev,
|
|
&key_dev,
|
|
&uart_dev,
|
|
NULL
|
|
};
|
|
|
|
const char *sim_stop_messages[SCPE_BASE] = {
|
|
"Unknown error",
|
|
"HALT instruction",
|
|
"Breakpoint",
|
|
"Invalid access",
|
|
};
|
|
|
|
static t_stat
|
|
get4 (FILE *fileref, uint16 *x)
|
|
{
|
|
int c = Fgetc (fileref);
|
|
if (c == EOF)
|
|
return SCPE_FMT;
|
|
*x = c & 017;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
get6 (FILE *fileref, uint16 *x)
|
|
{
|
|
int c = Fgetc (fileref);
|
|
if (c == EOF)
|
|
return SCPE_FMT;
|
|
*x = c & 077;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
get8 (FILE *fileref, uint16 *x)
|
|
{
|
|
uint16 y;
|
|
t_stat r;
|
|
r = get4 (fileref, x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
r = get4 (fileref, &y);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
*x = (*x << 4) | y;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
get16 (FILE *fileref, uint16 *x)
|
|
{
|
|
uint16 y;
|
|
t_stat r;
|
|
r = get8 (fileref, x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
r = get8 (fileref, &y);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
*x = (*x << 8) | y;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static uint16 checksum;
|
|
|
|
static t_stat
|
|
get18 (FILE *fileref, uint16 *x)
|
|
{
|
|
uint16 y;
|
|
t_stat r;
|
|
r = get6 (fileref, x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
r = get6 (fileref, &y);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
*x = (*x << 6) | y;
|
|
r = get6 (fileref, &y);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
*x = (*x << 6) | y;
|
|
checksum = (checksum + *x) & 0177777;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat load_loader (FILE *f, int verbose)
|
|
{
|
|
uint16 i, x, y, count, addr;
|
|
t_stat r;
|
|
|
|
x = 0;
|
|
do {
|
|
r = get4 (f, &y);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
x = ((x << 4) + y) & 0177777;
|
|
} while (x != 0147577);
|
|
|
|
/* Read count and address. */
|
|
r = get16 (f, &addr);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
r = get16 (f, &count);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
|
|
if (verbose)
|
|
fprintf (stderr, "Loader: address %06o, %o words\n", addr, count);
|
|
|
|
for (i = 1; i <= count; i++) {
|
|
r = get16 (f, &x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
CRM[addr - i] = x;
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat load_block (FILE *f, int verbose)
|
|
{
|
|
uint16 i, x, y, type, count, addr;
|
|
t_stat r;
|
|
|
|
x = 0;
|
|
do {
|
|
r = get6 (f, &y);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
x = ((x << 6) + y) & 0177777;
|
|
} while (x != 0120116);
|
|
|
|
checksum = 0;
|
|
|
|
/* Read type. */
|
|
r = get18 (f, &type);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
|
|
switch (type) {
|
|
case 0:
|
|
r = get18 (f, &x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
if (verbose) {
|
|
t_value val = x;
|
|
fprintf (stderr, "Execute: instruction %06o\n", x);
|
|
fprint_sym (stderr, 0, &val, 0, SWMASK ('M'));
|
|
fputc ('\n', stderr);
|
|
}
|
|
return SCPE_EOF;
|
|
case 1:
|
|
case 2:
|
|
break;
|
|
default:
|
|
return SCPE_FMT;
|
|
}
|
|
|
|
r = get18 (f, &addr);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
r = get18 (f, &count);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
|
|
if (type == 1) {
|
|
if (verbose)
|
|
fprintf (stderr, "Load control store: address %06o, %o words\n",
|
|
addr, count);
|
|
for (i = 0; i < count; i++) {
|
|
r = get18 (f, &x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
CRM[(addr++) & 07777] = x;
|
|
}
|
|
} else if (type == 2) {
|
|
if (verbose)
|
|
fprintf (stderr, "Load RAM: address %06o, %o words\n",
|
|
addr, count);
|
|
for (i = 0; i < count; i++) {
|
|
r = get18 (f, &x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
if ((addr & 0170000) == 0170000) {
|
|
FONT[(addr++) & 07777] = x & 0377;
|
|
FONT[(addr++) & 07777] = x >> 8;
|
|
i++;
|
|
} else
|
|
MEM[(addr++) & 0177777] = x;
|
|
}
|
|
}
|
|
|
|
r = get18 (f, &x);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
/* Should be 0, but some blocks checksum to 1. */
|
|
if (checksum != 0 && checksum != 1)
|
|
return SCPE_CSUM;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat
|
|
sim_load (FILE *fileref, CONST char *cptr, CONST char *fnam, int flag)
|
|
{
|
|
int verbose = sim_switches & SWMASK ('V');
|
|
t_stat r;
|
|
|
|
r = load_loader (fileref, verbose);
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
|
|
for (;;) {
|
|
r = load_block (fileref, verbose);
|
|
if (r == SCPE_EOF)
|
|
return SCPE_OK;
|
|
if (r != SCPE_OK)
|
|
return r;
|
|
}
|
|
}
|
|
|
|
static uint16 null_read (uint16 reg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void null_write (uint16 reg, uint16 data)
|
|
{
|
|
}
|
|
|
|
t_bool build_dev_tab (void)
|
|
{
|
|
TTDEV *ttdev;
|
|
DEVICE *dev;
|
|
int i, j;
|
|
|
|
for (i = 0; i < 0100; i++)
|
|
dev_tab[i] = &null_dev;
|
|
|
|
for (i = 0; (dev = sim_devices[i]) != NULL; i++) {
|
|
ttdev = (TTDEV *)dev->ctxt;
|
|
if (ttdev != NULL) {
|
|
for (j = 0; j < 4; j++)
|
|
if (ttdev->reg[j] != 0)
|
|
dev_tab[ttdev->reg[j]] = ttdev;
|
|
}
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static const char *register_names[] =
|
|
{
|
|
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
|
"A-latch", "s11", "s12", "s13", "MAGIC", "CHARTV", "s16", "s17",
|
|
"YCOR", "XCOR", "SCROLL", "XR", "UART", "DSR", "KEY", "d27",
|
|
"d30", "d31", "d32", "d33", "d34", "d35", "d36", "d37",
|
|
"scratch40", "scratch41", "scratch42", "scratch43", "scratch44", "scratch45", "scratch46", "scratch47",
|
|
"scratch50", "scratch51", "scratch52", "scratch53", "scratch54", "scratch55", "scratch56", "scratch57",
|
|
"scratch60", "scratch61", "scratch62", "scratch63", "scratch64", "scratch65", "scratch66", "scratch67",
|
|
"scratch70", "scratch71", "scratch72", "scratch73", "scratch74", "scratch75", "scratch76", "scratch77"
|
|
};
|
|
|
|
static t_stat
|
|
fprint_sto (FILE *of, uint16 insn)
|
|
{
|
|
uint16 a = (insn >> 6) & 7;
|
|
uint16 b = insn & 017;
|
|
|
|
const char *op;
|
|
switch (insn & 077060) {
|
|
case 020000: op = "READ"; break;
|
|
case 020020: op = "READD"; break;
|
|
case 021000: op = "CREAD"; break;
|
|
case 021020: op = "CREADD"; break;
|
|
case 022040: op = "READI"; break;
|
|
case 023040: op = "CREADI"; break;
|
|
case 024000: op = "WRITE"; break;
|
|
case 024020: op = "WRITED"; break;
|
|
case 025000: op = "CWRITE"; break;
|
|
case 025020: op = "CWRITED"; break;
|
|
case 026040: op = "WRITEI"; break;
|
|
case 027040: op = "CWRITEI"; break;
|
|
case 074000: op = "GET"; break;
|
|
default: fprintf (of, "???"); return SCPE_OK;
|
|
}
|
|
fprintf (of, "%s %s %s", op, register_names[a], register_names[b]);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
fprint_reg (FILE *of, uint16 insn)
|
|
{
|
|
static const char *name[] =
|
|
{ "A", "ANDN", "AND", "NOR", "IOR", "XOR", "MROT", "??",
|
|
"ROT", "DEC", "XADD", "ADD", "SUB", "XSUB", "INC", "ARS" };
|
|
uint16 op = (insn >> 4) & 3;
|
|
uint16 a = (insn >> 6) & 7;
|
|
uint16 b = insn & 017;
|
|
|
|
if (insn == 010000) {
|
|
fprintf (of, "NOP");
|
|
return SCPE_OK;
|
|
} else if ((insn & 037060) == 01000) {
|
|
sym_immediate = 1;
|
|
fprintf (of, "LOD %s", register_names[a]);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
switch (insn & 030000) {
|
|
case 000000: break;
|
|
case 010000: fprintf (of, "T "); break;
|
|
case 020000: return fprint_sto (of, insn);
|
|
case 030000: fprintf (of, "IFC "); break;
|
|
}
|
|
|
|
sym_immediate = insn & 01000;
|
|
|
|
op += (insn >> 8) & 014;
|
|
fprintf (of, "%s%s %s %s", name[op], sym_immediate ? "I" : "",
|
|
register_names[a], register_names[b]);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
fprint_dis (FILE *of, uint16 insn)
|
|
{
|
|
fprintf (of, "DIS ");
|
|
switch (insn & 01400) {
|
|
case 00000: fprintf (of, "BUS"); break;
|
|
case 00400: fprintf (of, "FLAGS"); break;
|
|
case 01000: fprintf (of, "INTS"); break;
|
|
case 01400: fprintf (of, "STARS"); break;
|
|
}
|
|
fprintf (of, " %o", (~insn >> 4) & 017);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
fprint_bus (FILE *of, uint16 insn)
|
|
{
|
|
uint16 a = (insn >> 6) & 7;
|
|
uint16 b = insn & 077;
|
|
|
|
if ((insn & 076000) == 072000)
|
|
return fprint_dis (of, insn);
|
|
|
|
switch (insn) {
|
|
case 0075400: fprintf (of, "MAGIC"); return SCPE_OK;
|
|
case 0076014: fprintf (of, "MAGIC"); return SCPE_OK;
|
|
case 0076015: fprintf (of, "CHARTV"); return SCPE_OK;
|
|
case 0075500: fprintf (of, "CHARTV"); return SCPE_OK;
|
|
case 0075600: fprintf (of, "POPJ"); return SCPE_OK;
|
|
case 0076016: fprintf (of, "POPJ"); return SCPE_OK;
|
|
case 0076716: fprintf (of, "POPJI"); return SCPE_OK;
|
|
}
|
|
|
|
fprintf (of, "%s ", insn & 02000 ? "PUT" : "GET");
|
|
fprintf (of, "%s %s", register_names[a], register_names[b]);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
fprint_branch (FILE *of, uint16 insn, uint16 addr)
|
|
{
|
|
static const char *condition[] =
|
|
{ "CC", "CS", "VS", "VC", "MI", "PL", "NE", "EQ",
|
|
"GE", "LT", "IS", "IC", "XCI", "XSI", "FS", "FC" };
|
|
uint16 target = insn & 03777;
|
|
if (insn & 02000)
|
|
target = target - 04000;
|
|
target += addr + 1;
|
|
fprintf (of, "B%s %06o", condition[(insn >> 11) & 017], target);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat
|
|
fprint_cpu (FILE *of, uint16 insn, uint16 addr)
|
|
{
|
|
switch ((insn >> 12) & 017) {
|
|
case 000: case 001: case 002: case 003:
|
|
return fprint_reg (of, insn);
|
|
case 004:
|
|
fprintf (of, "PUSHJ %04o", insn & 07777);
|
|
break;
|
|
case 005:
|
|
fprintf (of, "JUMP %04o", insn & 07777);
|
|
break;
|
|
case 006:
|
|
fprintf (of, "(undef)");
|
|
break;
|
|
case 007:
|
|
fprint_bus (of, insn);
|
|
break;
|
|
case 010: case 011: case 012: case 013:
|
|
case 014: case 015: case 016: case 017:
|
|
fprint_branch (of, insn, addr);
|
|
break;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat fprint_sym (FILE *of, t_addr addr, t_value *val,
|
|
UNIT *uptr, int32 sw)
|
|
{
|
|
t_stat reason;
|
|
|
|
if ((reason = build_dev_tab ()) != SCPE_OK)
|
|
return reason;
|
|
|
|
if (sym_addr == addr - 1 && sym_immediate) {
|
|
fprintf (of, "%06o", (unsigned)*val);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
sym_addr = addr;
|
|
sym_immediate = 0;
|
|
|
|
if (sw & SWMASK ('M'))
|
|
return fprint_cpu (of, *val, addr);
|
|
return SCPE_ARG;
|
|
}
|
|
|
|
t_stat parse_sym (CONST char *cptr, t_addr addr, UNIT *uptr,
|
|
t_value *val, int32 sw)
|
|
{
|
|
t_stat reason;
|
|
*val = get_uint (cptr, 8, ~0, &reason);
|
|
if (reason != SCPE_OK)
|
|
return reason;
|
|
return 0;
|
|
}
|