1
0
mirror of https://github.com/open-simh/simh.git synced 2026-01-13 23:37:13 +00:00

LINC: New emulator for the classic LINC.

This emulates the classic LINC.  The design was settled in 1965,
increasing memory to 2048 words, and adding a Z register, an overflow
flag, and an interrupt facility.
This commit is contained in:
Lars Brinkhoff 2025-09-17 08:28:28 +02:00
parent 1740ac10fa
commit fd3a79ebb5
15 changed files with 3059 additions and 2 deletions

View File

@ -36,7 +36,7 @@ jobs:
- microvax2 vax730 vax750 vax780 vax8200 vax8600 microvax2000 infoserver100 infoserver150vxt microvax3100 microvax3100e vaxstation3100m30 vaxstation3100m38
- microvax3100m80 vaxstation4000vlc infoserver1000 nova eclipse hp2100 hp3000 i1401 i1620 s3 altair altairz80 gri i7094
- id16 id32 sds lgp h316 cdc1700 swtp6800mp-a swtp6800mp-a2 tx-0 ssem b5500 sage pdq3 alpha
- besm6 imlac tt2500 microvax3900 microvax1 rtvax1000 vaxstation3100m76 vaxstation4000m60
- besm6 imlac linc tt2500 microvax3900 microvax1 rtvax1000 vaxstation3100m76 vaxstation4000m60
- scelbi 3b2 i701 i704 i7010 i7070 i7080 i7090 sigma uc15 i650 sel32 intel-mds ibm1130
steps:
- uses: actions/checkout@v4

View File

@ -34,6 +34,7 @@ set(KA10D "${CMAKE_SOURCE_DIR}/PDP10")
set(KI10D "${CMAKE_SOURCE_DIR}/PDP10")
set(KL10D "${CMAKE_SOURCE_DIR}/PDP10")
set(KS10D "${CMAKE_SOURCE_DIR}/PDP10")
set(LINCD "${CMAKE_SOURCE_DIR}/linc")
set(LGPD "${CMAKE_SOURCE_DIR}/LGP")
set(ND100D "${CMAKE_SOURCE_DIR}/ND100")
set(NOVAD "${CMAKE_SOURCE_DIR}/NOVA")
@ -100,6 +101,7 @@ add_subdirectory(Ibm1130)
add_subdirectory(Intel-Systems/Intel-MDS)
add_subdirectory(Intel-Systems/scelbi)
add_subdirectory(Interdata)
add_subdirectory(linc)
add_subdirectory(LGP)
add_subdirectory(ND100)
add_subdirectory(NOVA)

View File

@ -131,6 +131,10 @@ struct color color_p31 = { p31, ELEMENTS(p31), 100000 };
static struct phosphor p39[] = {{0.2, 1.0, 0.0, 0.5, 0.01}};
struct color color_p39 = { p39, ELEMENTS(p39), 20000 };
/* orange phosphor for LINC */
static struct phosphor p19[] = {{1.0, 0.7, 0.0, 0.1, 0.22}};
struct color color_p19 = { p19, ELEMENTS(p19), 20000 };
static struct phosphor p40[] = {
/* P40 blue-white spot with yellow-green decay (.045s to 10%?) */
{0.4, 0.2, 0.924, 0.5, 0.0135},
@ -253,6 +257,15 @@ static struct display displays[] = {
*/
{ DIS_III, "III Display", &color_p39, NULL, 1024, 1024 },
/*
* LINC display
* 512x511 addressable points.
* The horizontal position is a 9-bit unsigned value, but the
* vertical is a one's complement signed 9-bit value with
* both +0 and -0 referring to the same position.
*/
{ DIS_LINC, "LINC Display", &color_p19, NULL, 512, 511 },
/*
* Imlac display
* 1024x1024 addressable points.

View File

@ -47,6 +47,7 @@ enum display_type {
DIS_VR17 = 17,
DIS_VR20 = 20,
DIS_TYPE30 = 30,
DIS_LINC = 40,
DIS_VR48 = 48,
DIS_III = 111,
DIS_TYPE340 = 340,

32
linc/CMakeLists.txt Normal file
View File

@ -0,0 +1,32 @@
## LINC simulator
##
## This is an automagically generated file. Do NOT EDIT.
## Any changes you make will be overwritten!!
##
## Make changes to the SIMH top-level makefile and then run the
## "cmake/generate.py" script to regenerate these files.
##
## cd cmake; python -m generate --help
##
## ------------------------------------------------------------
if (HAVE_UNITY_FRAMEWORK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/CMakeLists.txt")
add_subdirectory(unit-tests)
endif ()
add_simulator(linc
SOURCES
linc_cpu.c
linc_crt.c
linc_dpy.c
linc_kbd.c
linc_sys.c
linc_tape.c
linc_tty.c
INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}
FEATURE_DISPLAY
USES_AIO
LABEL linc
PKG_FAMILY default_family
TEST linc)

135
linc/README.md Normal file
View File

@ -0,0 +1,135 @@
# LINC emulator
This is an emulator for the classic LINC from 1965.
### Devices
- CPU - LINC processor. If throttled to 125k, approximately original speed.
- CRT - CRT display. It can be disabled to run headless.
- DPY - point-plotting display controller.
- KBD - keyboard. Input comes from typing in the CRT window.
- SAM - sampled analog inputs.
- TAPE - four tape drives.
- TTY - teletype.
### LOAD
Software can be loaded into memory using the `LOAD` command. It will
normally read a file with 16-bit little endian words; the most
significant four bits of each word should be zero. If the `-O` switch
is supplied, the input is a text file with octal numbers separated by
whitespace.
To start reading from some particular position in the input file, add
`OFFSET=` after the file name and supply a number in octal.
To specify where in the memory to put the data, add `START=` after the
file name; the default is 0. To specify how many words to load, use
`LENGTH=`; the default is to write until the end of memory.
A binary input file can be a tape image. A plain image is 512 blocks
of 256 words each, totalling 262144 bytes. If the `-E` switch is
used, there is a check for the extended image format that can have
empty guard blocks at the beginning and the end. The last three words
in the extended format specify the block size, first forward block
number, and first reverse block number.
To state which tape block to start reading, add `BLOCK=` after the
file name and supply an octal number in the range 0-777.
### DO
The SIMH `DO` command has been modified. With arguments, it will
execute a script file as normal. Without argument, it acts like the
DO button on the LINC control panel. This executes one instruction
from the left switches, with the right switches providing a second
word if needed.
### Tape drives
To mount a tape image *file* on drive *n*, type `ATTACH TAPE<n>
<file>`. The plain or extended image format will be detected
automatically. Tape drives are numbered 0, 1, 4, and 5.
The `BOOT TAPEn` command will act like entering a tape read command in
the switches and starting the computer. The default is to read eight
blocks starting at 300, and start from location 20. This is the
conventional way to run LAP6. You can also add `RDC=` or `RCG=` to
boot some particular blocks. `START=` can be used to specify the
start address; it defaults to 20.
### Keyboard
The keys `0-9`, `A-Z`, and `Space` are mapped to their corresponding
LINC keys. `Enter` is mapped to `EOL`, `Delete` and `Backspace` are
mapped to `DEL`, and `Shift` is mapped to `CASE`. To type an upper
case symbol on some key, press `CASE`, release it, and then type the
key. For convenience, `Alt` is mapped to `META`.
The remaining keys are mapped thusly:
- `F1` is mapped to the `pu` key.
- `=` is mapped to the `i=` key.
- `-` and `,` are mapped to the `-,` key.
- `.` is mapped to the `+. key.
- `\` is mapped to the `|⊟` key.
- `[` and `left backslash` are mapped to the `*[` key.
The remaining upper case symbols:
- `CASE A` - `"`.
- `CASE B` - `„`.
- `CASE C` - `<`.
- `CASE D` - `>`.
- `CASE E` - `]`.
- `CASE F` - `*`.
- `CASE G` - `:`.
- `CASE Space` - `?`.
### Teletype
The TTY device implmenents a teletype for printing output. When a
file is attached, it will receive text decoded at 110 baud from relay
output 0.
Some characters are translated by LAP6 from the LINC character to ASCII:
- `i` to `&`
- `p` to `'`
- `|` to `\`
- `u` to `%`
- `⊟` to `$`
- `_` to `@`
- `"` to `^`
- `„` to `;`
### CPU
Registers:
- P - Instruction location, 10 bits.
- C - Current instruction, 12 bits.
- S - Memory address, 12 bits - 11 for address, 1 for halfword select.
- B - Memory data buffer, 12 bits.
- A - Accumulator, 12 bits - one's complement.
- Z - Various, 12 bits.
- L - Link, 1 bit.
- OVF - Overflow, 1 bit.
- IBZ - Tape interblock zone, 1 bit.
- ENI - Interrupt enabled, 1 bit.
- PINFF - Pause Interrupt enabled, 1 bit.
Switches:
- LSW - Left switches, 12 bits.
- RSW - Right switches, 12 bits.
- SSW - Sense switches, 6 bits.
Inputs:
- INTREQ, Interrupt request, 1 bit.
- R - Relays, 6 bits.
- XL - External levels, 12 of 1 bit each.
- SAM - Sampled analog inputs, 16 of 8 bits each; one's complement.
### Documentation
Programming:
https://bitsavers.org/pdf/washingtonUniversity/linc/Programming_the_LINC_Second_Edition_Jan69.pdf
Using the LAP6 operating system, text editor, assembler, tape filing system:
https://bitsavers.org/pdf/washingtonUniversity/linc/LINC_Reference_Manuals/LINC_Vol_16_Section_3_LAP6_Handbook_May67.pdf

1068
linc/linc_cpu.c Normal file

File diff suppressed because it is too large Load Diff

116
linc/linc_crt.c Normal file
View File

@ -0,0 +1,116 @@
/* linc_crt.c: LINC CRT display
Copyright (c) 2025, 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.
*/
#include "linc_defs.h"
#include "sim_video.h"
#include "display/display.h"
/* Function declaration. */
static t_stat crt_svc (UNIT *uptr);
static t_stat crt_reset (DEVICE *dptr);
static int crt_quit = FALSE;
/* Debug */
#define DBG 0001
static UNIT crt_unit = {
UDATA (&crt_svc, UNIT_IDLE, 0)
};
static DEBTAB crt_deb[] = {
{ "DBG", DBG },
{ "VVID", SIM_VID_DBG_VIDEO },
{ "KVID", SIM_VID_DBG_KEY },
{ NULL, 0 }
};
#ifdef USE_DISPLAY
#define CRT_DIS 0
#else
#define CRT_DIS DEV_DIS
#endif
DEVICE crt_dev = {
"CRT", &crt_unit, NULL, NULL,
1, 8, 12, 1, 8, 12,
NULL, NULL, &crt_reset,
NULL, NULL, NULL,
NULL, DEV_DISABLE | DEV_DEBUG, 0, crt_deb,
NULL, NULL, NULL, NULL, NULL, NULL
};
static t_stat
crt_svc(UNIT *uptr)
{
#ifdef USE_DISPLAY
display_age (100, 0);
sim_activate_after (uptr, 100);
if (crt_quit) {
crt_quit = FALSE;
return SCPE_STOP;
}
#endif
return SCPE_OK;
}
static void crt_quit_callback (void)
{
crt_quit = TRUE;
}
static t_stat
crt_reset (DEVICE *dptr)
{
#ifdef USE_DISPLAY
if ((dptr->flags & DEV_DIS) != 0 || (sim_switches & SWMASK('P')) != 0) {
display_close (dptr);
sim_cancel (&crt_unit);
} else {
display_reset ();
display_init (DIS_LINC, 1, dptr);
vid_register_quit_callback (&crt_quit_callback);
sim_activate_abs (&crt_unit, 0);
}
#endif
return SCPE_OK;
}
void
crt_point (uint16 x, uint16 y)
{
sim_debug(DBG, &crt_dev, "Point %o,%o\n", x, y);
#ifdef USE_DISPLAY
if (crt_dev.flags & DEV_DIS)
return;
display_point(x, y, DISPLAY_INT_MAX, 0);
#endif
}
void crt_toggle_fullscreen(void)
{
vid_set_fullscreen(!vid_is_fullscreen ());
}

73
linc/linc_defs.h Normal file
View File

@ -0,0 +1,73 @@
/* linc_defs.h: LINC simulator definitions
Copyright (c) 2025, 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.
17-Sept-25 LB New simulator.
*/
#ifndef LINC_DEFS_H_
#define LINC_DEFS_H_ 0
#include "sim_defs.h"
#define STOP_HALT 1
#define STOP_IBKPT 2
#define STOP_RBKPT 3
#define STOP_WBKPT 3
#define WMASK 07777 /* Full word. */
#define HMASK 04000 /* H bit; half word select. */
#define AMASK 03777 /* Full memory address. */
#define XMASK 01777 /* X part; low memory address. */
#define DMASK 00777 /* Display coordinate. */
#define TMASK 00777 /* Tape block. */
#define LMASK 07700 /* Left half word. */
#define RMASK 00077 /* Right half word; character. */
#define IMASK 00020 /* Index bit. */
#define UMASK 00010 /* Tape unit bit. */
#define BMASK 00017 /* Beta; index register. */
#define MEMSIZE 2048
extern REG cpu_reg[];
extern uint16 M[];
extern DEVICE cpu_dev;
extern DEVICE crt_dev;
extern DEVICE dpy_dev;
extern DEVICE kbd_dev;
extern DEVICE tape_dev;
extern DEVICE tty_dev;
extern t_bool build_dev_tab(void);
extern t_stat cpu_do(void);
extern void dpy_dis(uint16 h, uint16 x, uint16 y);
extern void crt_point (uint16 x, uint16 y);
extern void crt_toggle_fullscreen(void);
extern uint16 kbd_key(uint16 wait);
extern int kbd_struck(void);
extern void tape_op(void);
extern t_stat tape_metadata(FILE *, uint16 *, int16 *, int16 *);
#endif /* LINC_DEFS_H_ */

40
linc/linc_dpy.c Normal file
View File

@ -0,0 +1,40 @@
#include "linc_defs.h"
/* Debug */
#define DBG 0001
static DEBTAB dpy_deb[] = {
{ "DBG", DBG },
{ NULL, 0 }
};
DEVICE dpy_dev = {
"DPY", NULL, NULL, NULL,
0, 8, 12, 1, 8, 12,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, DEV_DEBUG, 0, dpy_deb,
NULL, NULL, NULL, NULL, NULL, NULL
};
void dpy_dis(uint16 h, uint16 x, uint16 y)
{
sim_debug(DBG, &dpy_dev, "DIS %u;%03o, A=%03o\n", h, x, y);
/* Y coordinate +0 and -0 both refer to the same vertical position. */
if (y < 256)
y += 255;
else
y -= 256;
crt_point(x, y);
}
/* Called from display library to get data switches. */
void
cpu_get_switches (unsigned long *p1, unsigned long *p2)
{
}
/* Called from display library to set data switches. */
void
cpu_set_switches (unsigned long p1, unsigned long p2)
{
}

267
linc/linc_kbd.c Normal file
View File

@ -0,0 +1,267 @@
#include "linc_defs.h"
#include "sim_video.h"
/* Debug */
#define DBG 0001
#define A (*(uint16 *)cpu_reg[2].loc)
#define paused (*(int *)cpu_reg[11].loc)
static t_stat kbd_reset(DEVICE *dptr);
static DEBTAB kbd_deb[] = {
{ "DBG", DBG },
{ NULL, 0 }
};
DEVICE kbd_dev = {
"KBD", NULL, NULL, NULL,
0, 8, 12, 1, 8, 12,
NULL, NULL, &kbd_reset,
NULL, NULL, NULL, NULL, DEV_DEBUG, 0, kbd_deb,
NULL, NULL, NULL, NULL, NULL, NULL
};
/*
CASE 0 1 2 3 4 5 6 7 8 9 DEL
23 00 01 02 03 04 05 06 07 10 11 13
Q W E R T Y U I O P i=
44 52 30 45 47 54 50 34 42 43 15
A S D F G H J K L +. -,
24 46 27 31 32 33 35 36 37 10 17
#[ Z X C V B N M pu |⊟ META/EOL
22 55 53 26 51 25 41 40 16 21 12
SPACE
14
*/
static int kbd_pressed = 0;
static uint16 kbd_code;
int kbd_struck(void)
{
if (kbd_pressed)
sim_debug(DBG, &kbd_dev, "KST\n");
return kbd_pressed;
}
uint16 kbd_key(uint16 wait)
{
if (kbd_pressed) {
sim_debug(DBG, &kbd_dev, "KEY %02o\n", kbd_code);
kbd_pressed = 0;
return kbd_code;
} else if (wait) {
sim_debug(DBG, &kbd_dev, "KEY paused\n");
paused = 1;
}
return 0;
}
static void kbd_convert(uint32 key)
{
switch (key) {
case SIM_KEY_0: /* 0 Q */
case SIM_KEY_BACKQUOTE:
kbd_code = 000;
break;
case SIM_KEY_1: /* 1 R */
kbd_code = 001;
break;
case SIM_KEY_2: /* 2 S */
kbd_code = 002;
break;
case SIM_KEY_3: /* 3 T */
kbd_code = 003;
break;
case SIM_KEY_4: /* 4 U */
kbd_code = 004;
break;
case SIM_KEY_5: /* 5 V */
kbd_code = 005;
break;
case SIM_KEY_6: /* 6 W */
kbd_code = 006;
break;
case SIM_KEY_7: /* 7 X */
kbd_code = 007;
break;
case SIM_KEY_8: /* 8 Y */
kbd_code = 010;
break;
case SIM_KEY_9: /* 9 Z */
kbd_code = 011;
break;
case SIM_KEY_ENTER:
kbd_code = 012;
break;
case SIM_KEY_BACKSPACE:
case SIM_KEY_DELETE:
kbd_code = 013;
break;
case SIM_KEY_SPACE: /* Space ? */
case SIM_KEY_SLASH:
kbd_code = 014;
break;
case SIM_KEY_EQUALS: /* i = */
kbd_code = 015;
break;
case SIM_KEY_F1: /* p u */
kbd_code = 016;
break;
case SIM_KEY_MINUS: /* - , */
case SIM_KEY_COMMA:
kbd_code = 017;
break;
case SIM_KEY_PERIOD: /* + . */
kbd_code = 020;
break;
case SIM_KEY_BACKSLASH: /* | ⊟ */
kbd_code = 021;
break;
case SIM_KEY_LEFT_BRACKET: /* # [ */
case SIM_KEY_LEFT_BACKSLASH:
kbd_code = 022;
break;
case SIM_KEY_SHIFT_L: /* CASE _ */
case SIM_KEY_SHIFT_R:
kbd_code = 023;
break;
case SIM_KEY_A: /* A " */
case SIM_KEY_SINGLE_QUOTE:
kbd_code = 024;
break;
case SIM_KEY_B: /* B „ */
kbd_code = 025;
break;
case SIM_KEY_C: /* C < */
kbd_code = 026;
break;
case SIM_KEY_D: /* D > */
kbd_code = 027;
break;
case SIM_KEY_E: /* E ] */
case SIM_KEY_RIGHT_BRACKET:
kbd_code = 030;
break;
case SIM_KEY_F: /* F ˣ */
kbd_code = 031;
break;
case SIM_KEY_G: /* G : */
case SIM_KEY_SEMICOLON:
kbd_code = 032;
break;
case SIM_KEY_H: /* H */
kbd_code = 033;
break;
case SIM_KEY_I: /* I */
kbd_code = 034;
break;
case SIM_KEY_J: /* J */
kbd_code = 035;
break;
case SIM_KEY_K: /* K */
kbd_code = 036;
break;
case SIM_KEY_L: /* L */
kbd_code = 037;
break;
case SIM_KEY_M: /* M */
kbd_code = 040;
break;
case SIM_KEY_N: /* N */
kbd_code = 041;
break;
case SIM_KEY_O: /* O */
kbd_code = 042;
break;
case SIM_KEY_P: /* P */
kbd_code = 043;
break;
case SIM_KEY_Q: /* Q */
kbd_code = 044;
break;
case SIM_KEY_R: /* R */
kbd_code = 045;
break;
case SIM_KEY_S: /* S */
kbd_code = 046;
break;
case SIM_KEY_T: /* T */
kbd_code = 047;
break;
case SIM_KEY_U: /* U */
kbd_code = 050;
break;
case SIM_KEY_V: /* V */
kbd_code = 051;
break;
case SIM_KEY_W: /* W */
kbd_code = 052;
break;
case SIM_KEY_X: /* X */
kbd_code = 053;
break;
case SIM_KEY_Y: /* Y */
kbd_code = 054;
break;
case SIM_KEY_Z: /* Z */
kbd_code = 055;
break;
case SIM_KEY_ALT_L: /* META? */
case SIM_KEY_ALT_R:
kbd_code = 056;
break;
// → 57
// ? 60
// = 61
// u 62
// , 63
// . 64
// ⊟ 65
// [ 66
// _ 67
// " 70
// „ 71
// < 72
// > 73
// ] 74
// ˣ 75
// : 76
// ʸ 77
case SIM_KEY_F11:
crt_toggle_fullscreen();
return;
default:
return;
}
sim_debug(DBG, &kbd_dev, "Key struck %s -> %02o\n",
vid_key_name(key), kbd_code);
if (paused)
A = kbd_code;
else
kbd_pressed = 1;
paused = 0;
}
static int
kbd_event(SIM_KEY_EVENT *ev)
{
if (ev->state == SIM_KEYPRESS_DOWN)
kbd_convert(ev->key);
return 0;
}
static t_stat
kbd_reset(DEVICE *dptr)
{
#ifdef USE_DISPLAY
vid_display_kb_event_process = kbd_event;
#endif
return SCPE_OK;
}

696
linc/linc_sys.c Normal file
View File

@ -0,0 +1,696 @@
/* linc_sys.c: LINC simulator interface
Copyright (c) 2025, 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.
17-Sept-25 LB New simulator.
*/
#include "linc_defs.h"
int32 sim_emax = 1;
char sim_name[] = "LINC";
uint16 M[MEMSIZE];
REG *sim_PC = &cpu_reg[0];
DEVICE *sim_devices[] = {
&cpu_dev,
&crt_dev,
&dpy_dev,
&kbd_dev,
&tape_dev,
&tty_dev,
NULL
};
const char *sim_stop_messages[SCPE_BASE] = {
"Unknown error",
"HALT instruction",
"Breakpoint",
"Read Breakpoint",
"Write Breakpoint"
};
static t_stat
get_binary_word(FILE *fileref, uint16 *x)
{
uint16 y;
int c = Fgetc(fileref);
if (c == EOF)
return SCPE_EOF;
y = c & 0xFF;
c = Fgetc(fileref);
if (c == EOF)
return SCPE_IOERR;
if (c & 0xF0)
return SCPE_FMT;
*x = y | (c << 8);
return SCPE_OK;
}
static t_stat
get_octal_word(FILE *fileref, uint16 *x)
{
uint16 y, i;
int c;
for (i = 0;;) {
c = Fgetc(fileref);
if (c == EOF)
return SCPE_EOF;
if (c >= '0' && c <= '9') {
y = c - '0';
i++;
break;
}
}
for (; i < 4;) {
c = Fgetc(fileref);
if (c == EOF)
return SCPE_IOERR;
if (c < '0' || c > '9')
break;
y <<= 3;
y |= c - '0';
i++;
}
*x = y;
return SCPE_OK;
}
t_stat
sim_load(FILE *fileref, CONST char *cptr, CONST char *fnam, int flag)
{
t_stat (*get_word)(FILE *fileref, uint16 *x) = get_binary_word;
t_addr addr, length = MEMSIZE, start = 0, end;
int16 forward_offset = 0, reverse_offset;
uint16 block_size;
long offset = 0;
t_stat stat;
if (sim_switches & SWMASK('E')) {
stat = tape_metadata(fileref, &block_size, &forward_offset, &reverse_offset);
if (stat != SCPE_OK)
return stat;
if (block_size != 256)
return SCPE_FMT;
}
if (sim_switches & SWMASK('O'))
get_word = get_octal_word;
while (*cptr !=0) {
char gbuf[100];
cptr = get_glyph(cptr, gbuf, 0);
if (strncmp(gbuf, "START=", 6) == 0)
start = (t_addr)get_uint(gbuf + 6, 8, ~0, &stat);
else if (strncmp(gbuf, "OFFSET=", 7) == 0)
offset = 2 * (long)get_uint(gbuf + 7, 8, ~0, &stat);
else if (strncmp(gbuf, "BLOCK=", 6) == 0)
offset = 512 * ((long)get_uint(gbuf + 6, 8, ~0, &stat) - forward_offset);
else if (strncmp(gbuf, "LENGTH=", 7) == 0)
length = (t_addr)get_uint(gbuf + 7, 8, ~0, &stat);
else
return SCPE_ARG;
}
end = start + length;
if (end > MEMSIZE)
end = MEMSIZE;
sim_fseek(fileref, offset, SEEK_SET);
for (addr = start; addr < end; addr++) {
uint16 x;
t_stat stat = get_word(fileref, &x);
if (stat == SCPE_EOF)
return SCPE_OK;
if (stat != SCPE_OK)
return stat;
M[addr] = x;
}
return SCPE_OK;
}
t_bool build_dev_tab(void)
{
DEVICE *dev;
int i;
for (i = 0; (dev = sim_devices[i]) != NULL; i++) {
;
}
return SCPE_OK;
}
static t_stat fprint_next(FILE *of, uint16 addr)
{
fprintf(of, "\n");
fprint_val(of, ++addr & XMASK, 8, 10, PV_LEFT);
fprintf(of, ":\t%04o", M[addr]);
return -1;
}
static void fprint_misc(FILE *of, uint16 insn)
{
switch (insn) {
case 00000:
fprintf(of, "HLT");
break;
case 00005:
fprintf(of, "ZTA");
break;
case 00010:
fprintf(of, "ENI");
break;
case 00011:
fprintf(of, "CLR");
break;
case 00012:
fprintf(of, "DIN");
break;
case 00014:
fprintf(of, "ATR");
break;
case 00015:
fprintf(of, "RTA");
break;
case 00016:
fprintf(of, "NOP");
break;
case 00017:
fprintf(of, "COM");
break;
default:
fprintf(of, "MSC %o", insn);
break;
}
}
static t_stat fprint_index(FILE *of, uint16 insn, uint16 addr)
{
if (insn & IMASK)
fprintf(of, " i");
if (insn & BMASK)
fprintf(of, " %o", insn & BMASK);
else
return fprint_next(of, addr);
return SCPE_OK;
}
static void fprint_set(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "SET");
fprint_index(of, insn, addr);
fprint_next(of, addr);
}
static void fprint_sam(FILE *of, uint16 insn)
{
fprintf(of, "SAM%s %o", insn & IMASK ? " i" : "", insn & BMASK);
}
static t_stat fprint_dis(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "DIS");
return fprint_index(of, insn, addr);
}
static t_stat fprint_xsk(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "XSK");
return fprint_index(of, insn, addr);
}
static t_stat fprint_rol(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "ROL");
return fprint_index(of, insn, addr);
}
static t_stat fprint_ror(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "ROR");
return fprint_index(of, insn, addr);
}
static t_stat fprint_scr(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "SCR");
return fprint_index(of, insn, addr);
}
static void fprint_skip(FILE *of, uint16 insn)
{
char beta[3];
switch (insn & 057) {
case 000: case 001: case 002: case 003: case 004: case 005: case 006: case 007:
case 010: case 011: case 012: case 013:
fprintf(of, "SXL");
snprintf(beta, sizeof beta, "%o", insn & BMASK);
break;
case 015:
fprintf(of, "KST");
break;
case 040: case 041: case 042: case 043: case 044: case 045:
fprintf(of, "SNS ");
snprintf(beta, sizeof beta, "%o", insn & 7);
break;
case 046:
fprintf(of, "PIN");
break;
case 050:
fprintf(of, "AZE");
break;
case 051:
fprintf(of, "APO");
break;
case 052:
fprintf(of, "LZE");
break;
case 053:
fprintf(of, "IBZ");
break;
case 054:
fprintf(of, "OVF");
break;
case 055:
fprintf(of, "ZZZ");
break;
default:
fprintf(of, "%04o", insn);
return;
}
if (insn & IMASK)
fprintf(of, " i" );
fprintf(of, " %s", beta);
}
static void fprint_opr(FILE *of, uint16 insn)
{
switch (insn & 07757) {
case 0500: case 0501: case 0502: case 0503: case 0504: case 0505: case 0506: case 0507:
case 0510: case 0511: case 0512: case 0513:
break;
case 0535:
case 0515:
fprintf(of, "KBD ");
break;
case 0516:
fprintf(of, "RSW ");
break;
case 0517:
fprintf(of, "LSW ");
break;
default:
fprintf(of, "%04o", insn);
break;
}
if (insn & IMASK)
fprintf(of, "i" );
}
static void fprint_lmb(FILE *of, uint16 insn)
{
fprintf(of, "LMB ");
}
static void fprint_umb(FILE *of, uint16 insn)
{
fprintf(of, "UMB ");
}
static void fprint_tape(FILE *of, uint16 insn, uint16 addr)
{
switch (insn & 0707) {
case 0700:
fprintf(of, "RDC");
break;
case 0701:
fprintf(of, "RCG");
break;
case 0702:
fprintf(of, "RDE");
break;
case 0703:
fprintf(of, "MTB");
break;
case 0704:
fprintf(of, "WRC");
break;
case 0705:
fprintf(of, "WCG");
break;
case 0706:
fprintf(of, "WRI");
break;
case 0707:
fprintf(of, "CHK");
break;
}
if (insn & IMASK)
fprintf(of, " i");
if (insn & UMASK)
fprintf(of, " u");
fprint_next(of, addr);
}
static t_stat fprint_lda(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "LDA");
return fprint_index(of, insn, addr);
}
static t_stat fprint_sta(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "STA");
return fprint_index(of, insn, addr);
}
static t_stat fprint_ada(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "ADA");
return fprint_index(of, insn, addr);
}
static t_stat fprint_adm(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "ADM");
return fprint_index(of, insn, addr);
}
static t_stat fprint_lam(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "LAM");
return fprint_index(of, insn, addr);
}
static t_stat fprint_mul(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "MUL");
return fprint_index(of, insn, addr);
}
static t_stat fprint_ldh(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "LDH");
return fprint_index(of, insn, addr);
}
static t_stat fprint_sth(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "STH");
return fprint_index(of, insn, addr);
}
static t_stat fprint_shd(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "SHD");
return fprint_index(of, insn, addr);
}
static t_stat fprint_sae(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "SAE");
return fprint_index(of, insn, addr);
}
static t_stat fprint_sro(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "SRO");
return fprint_index(of, insn, addr);
}
static t_stat fprint_bcl(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "BCL");
return fprint_index(of, insn, addr);
}
static t_stat fprint_bse(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "BSE");
return fprint_index(of, insn, addr);
}
static t_stat fprint_bco(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "BCO");
return fprint_index(of, insn, addr);
}
static t_stat fprint_dsc(FILE *of, uint16 insn, uint16 addr)
{
fprintf(of, "DSC");
return fprint_index(of, insn, addr);
}
static void fprint_add(FILE *of, uint16 insn)
{
fprintf(of, "ADD %04o", insn & XMASK);
}
static void fprint_stc(FILE *of, uint16 insn)
{
fprintf(of, "STC %04o", insn & XMASK);
}
static void fprint_jmp(FILE *of, uint16 insn)
{
fprintf(of, "JMP %04o", insn & XMASK);
}
t_stat fprint_sym(FILE *of, t_addr addr, t_value *val, UNIT *uptr, int32 sw)
{
t_stat stat;
if ((sw & SWMASK ('M')) == 0)
return SCPE_ARG;
if ((stat = build_dev_tab()) != SCPE_OK)
return stat;
switch (*val & 07740) {
case 00000:
fprint_misc(of, *val);
break;
case 00040:
fprint_set(of, *val, addr);
return -1;
case 00100:
fprint_sam(of, *val);
break;
case 00140:
fprint_dis(of, *val, addr);
break;
case 00200:
fprint_xsk(of, *val, addr);
break;
case 00240:
fprint_rol(of, *val, addr);
break;
case 00300:
fprint_ror(of, *val, addr);
break;
case 00340:
fprint_scr(of, *val, addr);
break;
case 00400:
case 00440:
fprint_skip(of, *val);
break;
case 00500:
case 00540:
fprint_opr(of, *val);
break;
case 00600:
fprint_lmb(of, *val);
break;
case 00640:
fprint_umb(of, *val);
break;
case 00700:
case 00740:
fprint_tape(of, *val, addr);
break;
case 01000:
return fprint_lda(of, *val, addr);
case 01040:
return fprint_sta(of, *val, addr);
case 01100:
return fprint_ada(of, *val, addr);
case 01140:
return fprint_adm(of, *val, addr);
case 01200:
return fprint_lam(of, *val, addr);
case 01240:
return fprint_mul(of, *val, addr);
case 01300:
return fprint_ldh(of, *val, addr);
case 01340:
return fprint_sth(of, *val, addr);
case 01400:
return fprint_shd(of, *val, addr);
case 01440:
return fprint_sae(of, *val, addr);
case 01500:
return fprint_sro(of, *val, addr);
case 01540:
return fprint_bcl(of, *val, addr);
case 01600:
return fprint_bse(of, *val, addr);
case 01640:
return fprint_bco(of, *val, addr);
case 01740:
return fprint_dsc(of, *val, addr);
case 02000: case 02040: case 02100: case 02140: case 02200: case 02240: case 02300: case 02340:
case 02400: case 02440: case 02500: case 02540: case 02600: case 02640: case 02700: case 02740:
case 03000: case 03040: case 03100: case 03140: case 03200: case 03240: case 03300: case 03340:
case 03400: case 03440: case 03500: case 03540: case 03600: case 03640: case 03700: case 03740:
fprint_add(of, *val);
break;
case 04000: case 04040: case 04100: case 04140: case 04200: case 04240: case 04300: case 04340:
case 04400: case 04440: case 04500: case 04540: case 04600: case 04640: case 04700: case 04740:
case 05000: case 05040: case 05100: case 05140: case 05200: case 05240: case 05300: case 05340:
case 05400: case 05440: case 05500: case 05540: case 05600: case 05640: case 05700: case 05740:
fprint_stc(of, *val);
break;
case 06000: case 06040: case 06100: case 06140: case 06200: case 06240: case 06300: case 06340:
case 06400: case 06440: case 06500: case 06540: case 06600: case 06640: case 06700: case 06740:
case 07000: case 07040: case 07100: case 07140: case 07200: case 07240: case 07300: case 07340:
case 07400: case 07440: case 07500: case 07540: case 07600: case 07640: case 07700: case 07740:
fprint_jmp(of, *val);
break;
}
return SCPE_OK;
}
struct symbol {
const char *name;
uint16 value;
};
static const struct symbol symbols[] = {
{ "U", 00010 },
{ "I", 00020 },
{ "HLT", 00000 },
{ "ZTA", 00005 },
{ "CLR", 00011 },
{ "DIN", 00012 },
{ "ATR", 00014 },
{ "RTA", 00015 },
{ "NOP", 00016 },
{ "COM", 00017 },
{ "SET", 00040 },
{ "SAM", 00100 },
{ "DIS", 00140 },
{ "XSK", 00200 },
{ "ROL", 00240 },
{ "ROR", 00300 },
{ "SCR", 00340 },
{ "SXL", 00400 },
{ "KST", 00415 },
{ "SNS", 00440 },
{ "AZE", 00450 },
{ "APO", 00451 },
{ "LZE", 00452 },
{ "IBZ", 00453 },
{ "OVF", 00454 },
{ "ZZZ", 00455 },
{ "OPR", 00500 },
{ "KBD", 00515 },
{ "RSW", 00516 },
{ "LSW", 00517 },
{ "LMB", 00600 },
{ "UMB", 00640 },
{ "RDC", 00700 },
{ "RCG", 00701 },
{ "RDE", 00702 },
{ "MTB", 00703 },
{ "WRC", 00704 },
{ "WCG", 00705 },
{ "WRI", 00706 },
{ "CHK", 00707 },
{ "LDA", 01000 },
{ "STA", 01040 },
{ "ADA", 01100 },
{ "ADM", 01140 },
{ "LAM", 01200 },
{ "MUL", 01240 },
{ "LDH", 01300 },
{ "STH", 01340 },
{ "SHD", 01400 },
{ "SAE", 01440 },
{ "SRO", 01500 },
{ "BCL", 01540 },
{ "BSE", 01600 },
{ "BCO", 01640 },
{ "DSC", 01740 },
{ "ADD", 02000 },
{ "STC", 04000 },
{ "JMP", 06000 }
};
t_stat parse_sym(CONST char *cptr, t_addr addr, UNIT *uptr,
t_value *val, int32 sw)
{
char gbuf[CBUFSIZE];
t_value val2;
t_stat stat;
int i;
*val = get_uint(cptr, 8, ~0, &stat);
if (*val > 07777)
return SCPE_ARG;
if (stat == SCPE_OK)
return SCPE_OK;
if (*cptr == '-') {
stat = parse_sym(cptr + 1, addr, uptr, val, sw);
if (stat != SCPE_OK)
return stat;
*val ^= 07777;
if (stat == SCPE_OK)
return SCPE_OK;
}
cptr = get_glyph(cptr, gbuf, 0);
for (i = 0; i < sizeof symbols / sizeof symbols[0]; i++) {
if (strcmp(gbuf, symbols[i].name) != 0)
continue;
*val = symbols[i].value;
if (*cptr) {
stat = parse_sym(cptr, addr, uptr, &val2, sw);
if (stat != SCPE_OK)
return stat;
*val |= val2;
}
return SCPE_OK;
}
return SCPE_ARG;
}

470
linc/linc_tape.c Normal file
View File

@ -0,0 +1,470 @@
#include "linc_defs.h"
#define POS u3
#define SPEED u4
#define ACC u5
#define OFFSET u6
#define P (*(uint16 *)cpu_reg[0].loc)
#define C (*(uint16 *)cpu_reg[1].loc)
#define A (*(uint16 *)cpu_reg[2].loc)
#define S (*(uint16 *)cpu_reg[6].loc)
#define B (*(uint16 *)cpu_reg[7].loc)
#define LSW (*(uint16 *)cpu_reg[8].loc)
#define RSW (*(uint16 *)cpu_reg[9].loc)
#define paused (*(int *)cpu_reg[11].loc)
#define IBZ (*(int *)cpu_reg[12].loc)
#define ACC_START 3
#define ACC_REVERSE 6
#define ACC_STOP 1
#define MAX_SPEED (ACC_START * 625) /* 0.1s / 160µs */
#define IBZ_WORDS 5
#define DATA_WORDS 256
#define OTHER_WORDS 7
#define BLOCK_WORDS (IBZ_WORDS + DATA_WORDS + OTHER_WORDS)
#define START_POS (ACC_START * (625 + (625 * 625))/2)
#define MAX_BLOCKS 512
#define MAX_POS ((BLOCK_WORDS * MAX_BLOCKS + IBZ_WORDS) * MAX_SPEED)
#define GOOD_CHECKSUM 07777
#define RDC 0 /* read tape and check */
#define RCG 1 /* read tape group */
#define RDE 2 /* read tape */
#define MTB 3 /* move toward block */
#define WRC 4 /* write tape and check */
#define WCG 5 /* write tape group */
#define WRI 6 /* write tape */
#define CHK 7 /* check tape */
#define DBG 0001
#define DBG_SEEK 0002
#define DBG_READ 0004
#define DBG_WRITE 0010
#define DBG_POS 0020
static uint16 GROUP;
static int16 CURRENT_BLOCK;
static int16 WANTED_BLOCK;
static t_stat tape_svc(UNIT *uptr);
static t_stat tape_reset(DEVICE *dptr);
static t_stat tape_boot(int32 u, DEVICE *dptr);
static t_stat tape_attach(UNIT *uptr, CONST char *cptr);
static t_stat tape_detach(UNIT *uptr);
#define UNIT_FLAGS (UNIT_IDLE|UNIT_FIX|UNIT_ATTABLE|UNIT_DISABLE|UNIT_ROABLE)
#define CAPACITY (MAX_BLOCKS * DATA_WORDS)
static UNIT tape_unit[] = {
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) },
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) },
{ UDATA(&tape_svc, UNIT_DIS, 0) },
{ UDATA(&tape_svc, UNIT_DIS, 0) },
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) },
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) }
};
static DEBTAB tape_deb[] = {
{ "DBG", DBG },
{ "SEEK", DBG_SEEK },
{ "READ", DBG_READ },
{ "WRITE", DBG_WRITE },
{ "POSITION", DBG_POS },
{ NULL, 0 }
};
DEVICE tape_dev = {
"TAPE", tape_unit, NULL, NULL,
6, 8, 12, 1, 8, 12,
NULL, NULL, &tape_reset,
&tape_boot, &tape_attach, &tape_detach,
NULL, DEV_DEBUG, 0, tape_deb,
NULL, NULL, NULL, NULL, NULL, NULL
};
void tape_op(void)
{
uint16 u = (C & 050) >> 3;
UNIT *uptr = &tape_unit[u];
if ((uptr->flags & UNIT_ATT) == 0)
return;
if (uptr->SPEED < 0) {
if ((C & 7) != MTB) {
sim_debug(DBG_SEEK, &tape_dev, "Reverse to forward\n");
uptr->ACC = ACC_REVERSE;
}
} else if (uptr->POS >= MAX_POS) {
sim_debug(DBG_SEEK, &tape_dev, "End zone; reverse\n");
uptr->ACC = ACC_REVERSE;
} else if (uptr->SPEED < MAX_SPEED || uptr->ACC < 0) {
sim_debug(DBG_SEEK, &tape_dev, "Speed up\n");
uptr->ACC = ACC_START;
}
if (!sim_is_active(uptr))
sim_activate(uptr, 20);
paused = 1;
A = 0;
WANTED_BLOCK = B & TMASK;
switch (C & 7) {
case RDC: case RDE: case WRC: case WRI: case CHK:
S = 256 * (B >> 9);
GROUP = 0;
sim_debug(DBG, &tape_dev, "Single tranfer: S=%04o, BN=%03o\n",
S, WANTED_BLOCK);
break;
case RCG: case WCG:
S = 256 * (B & 7);
GROUP = B >> 9;
sim_debug(DBG, &tape_dev, "Group transfer: S=%04o, BN=%03o/%o\n",
S, WANTED_BLOCK, GROUP+1);
break;
case MTB:
sim_debug(DBG, &tape_dev, "Move towards block %03o\n", WANTED_BLOCK);
break;
}
}
static t_stat tape_seek(FILE *fileref, t_addr block, t_addr offset)
{
offset = DATA_WORDS * block + offset;
offset *= 2;
if (sim_fseek(fileref, offset, SEEK_SET) == -1)
return SCPE_IOERR;
return SCPE_OK;
}
static uint16 read_word(FILE *fileref, t_addr block, t_addr offset)
{
t_stat stat;
uint8 data[2];
uint16 word;
stat = tape_seek(fileref, block, offset);
if (stat != SCPE_OK)
;
if (sim_fread(data, 1, 2, fileref) != 2)
;
if (data[1] & 0xF0)
;
word = data[1];
word <<= 8;
word |= data[0];
return word;
}
static void write_word(FILE *fileref, t_addr block, t_addr offset, uint16 word)
{
t_stat stat;
uint8 data[2];
stat = tape_seek(fileref, block, offset);
if (stat != SCPE_OK)
;
data[0] = word & 0xFF;
data[1] = word >> 8;
if (sim_fwrite(data, 1, 2, fileref) != 2)
;
}
/*
IBZ BN G block CS C C G BN IBZ
5 1 1 256 1 1 1 1 1 5
---------------------
263
--------------------------
268
start - 100 ms
stop - 300 ms
reverse - 100 ms
BN to BN at 60 ips - 43 ms
block length = 43 ms * 60 inch/s = 2.58 inch
per word - 160 µs
word length = 0.0096 inch
words per inch = 104
words per second = 6250
end zone to end zone - 23 s
tape length = 23 * 60 = 1380 inch = 115 feet
end zone length = 5 feet
*/
static void tape_done(UNIT *uptr)
{
sim_debug(DBG, &tape_dev, "Done with block\n");
switch (C & 7) {
case RDC: case RCG: case RDE: case CHK:
A = GOOD_CHECKSUM;
break;
case WRI:
A = (A ^ 07777) + 1;
A &= WMASK;
break;
case MTB:
A = (WANTED_BLOCK + ~CURRENT_BLOCK);
A += A >> 12;
A &= WMASK;
break;
}
switch (C & 7) {
case RDC:
if (A != GOOD_CHECKSUM) {
sim_debug(DBG, &tape_dev, "Check failed; read again\n");
S &= ~0377;
} else {
sim_debug(DBG, &tape_dev, "Check passed\n");
paused = 0;
}
break;
case WRC:
sim_debug(DBG, &tape_dev, "Block written, go back and check\n");
// For now, done.
A = GOOD_CHECKSUM;
paused = 0;
break;
case RCG: case WCG:
if (GROUP == 0) {
sim_debug(DBG, &tape_dev, "Done with group\n");
paused = 0;
} else {
sim_debug(DBG, &tape_dev, "Blocks left in group: %d\n", GROUP);
GROUP--;
}
WANTED_BLOCK = (WANTED_BLOCK + 1) & TMASK;
break;
case RDE: case WRI:
sim_debug(DBG, &tape_dev, "Transfer done\n");
paused = 0;
break;
case MTB:
sim_debug(DBG, &tape_dev, "Move towards block done, result %04o\n", A);
paused = 0;
break;
case CHK:
sim_debug(DBG, &tape_dev, "Check done\n");
paused = 0;
break;
}
if (paused)
;
else if ((C & IMASK) == 0) {
sim_debug(DBG_SEEK, &tape_dev, "Instruction done, stop tape\n");
uptr->ACC = uptr->SPEED > 0 ? -ACC_STOP : ACC_STOP;
} else {
sim_debug(DBG_SEEK, &tape_dev, "Instruction done, keep moving\n");
}
}
static void tape_word(UNIT *uptr, uint16 block, uint16 offset)
{
switch (C & 7) {
case RDC: case RCG: case RDE: case CHK:
B = read_word(uptr->fileref, block, offset);
sim_debug(DBG_READ, &tape_dev,
"Read block %03o offset %03o data %04o address %04o\n",
block, offset, B, S);
if ((C & 7) != CHK)
M[S] = B;
break;
case WRC: case WCG: case WRI:
B = M[S];
sim_debug(DBG_WRITE, &tape_dev,
"Write block %03o offset %03o data %04o address %04o\n",
block, offset, B, S);
write_word(uptr->fileref, block, offset, B);
break;
}
S = (S+1) & AMASK;
A += B;
A &= WMASK;
}
static t_stat tape_svc(UNIT *uptr)
{
long pos, block, offset;
uptr->SPEED += uptr->ACC;
if (uptr->SPEED >= MAX_SPEED) {
uptr->SPEED = MAX_SPEED;
uptr->ACC = 0;
}
else if (uptr->SPEED <= -MAX_SPEED) {
uptr->SPEED = -MAX_SPEED;
uptr->ACC = 0;
} else if (uptr->SPEED == 0 && (uptr->ACC == ACC_STOP || uptr->ACC == -ACC_STOP))
uptr->ACC = 0;
uptr->POS += uptr->SPEED;
sim_debug(DBG_POS, &tape_dev, "Speed %d, position %d (block %03o)\n",
uptr->SPEED, uptr->POS, uptr->POS / MAX_SPEED / BLOCK_WORDS);
if (uptr->POS < 0 && uptr->ACC <= 0) {
sim_debug(DBG_SEEK, &tape_dev, "End zone; stop tape\n");
uptr->ACC = ACC_STOP;
} else if(uptr->POS >= MAX_POS && uptr->ACC >= 0) {
sim_debug(DBG_SEEK, &tape_dev, "End zone; stop tape\n");
uptr->ACC = -ACC_STOP;
}
if (uptr->SPEED != 0)
/* The tape takes 160 microseconds between words. This is
approximately 20 memory cycles, 8 microseconds each. */
sim_activate(uptr, 20);
pos = uptr->POS / MAX_SPEED;
if (pos < 0)
return SCPE_OK;
block = pos / BLOCK_WORDS;
offset = pos % BLOCK_WORDS;
if (block >= MAX_BLOCKS)
return SCPE_OK;
IBZ = offset < IBZ_WORDS;
if (IBZ)
sim_debug(DBG, &tape_dev, "Interblock zone\n");
if (uptr->SPEED > -MAX_SPEED && uptr->SPEED < MAX_SPEED)
return SCPE_OK;
if (!paused)
return SCPE_OK;
if (uptr->SPEED > 0) {
if (offset == 5) {
/* Forward block number. */
CURRENT_BLOCK = (uint16)(block + uptr->OFFSET);
sim_debug(DBG_SEEK, &tape_dev,
"Found block number %03o; looking for %03o\n",
CURRENT_BLOCK, WANTED_BLOCK);
if (CURRENT_BLOCK > WANTED_BLOCK) {
sim_debug(DBG_SEEK, &tape_dev, "Reverse to find lower block numbers\n");
uptr->ACC = -ACC_REVERSE;
}
if ((C & 7) == MTB)
tape_done(uptr);
/* Word 6 is a guard. */
} else if (offset >= 7 && offset < 263) {
if (CURRENT_BLOCK == WANTED_BLOCK)
tape_word(uptr, (uint16)block, (uint16)(offset - 7));
}
else if (offset == 263 && CURRENT_BLOCK == WANTED_BLOCK)
/* Checksum here. */
tape_done(uptr);
}
/* Word 264-265 are "C". */
/* Word 266 is a guard. */
else if (offset == 267 && uptr->SPEED < 0) {
/* Reverse block number. */
CURRENT_BLOCK = (uint16)(block + uptr->OFFSET);
sim_debug(DBG_SEEK, &tape_dev,
"Found reverse block number %03o; looking for %03o\n",
CURRENT_BLOCK, WANTED_BLOCK);
if (CURRENT_BLOCK <= WANTED_BLOCK) {
sim_debug(DBG_SEEK, &tape_dev, "Reverse to find higher block numbers\n");
uptr->ACC = ACC_REVERSE;
uptr->POS -= MAX_SPEED * BLOCK_WORDS;
}
if ((C & 7) == MTB)
tape_done(uptr);
}
return SCPE_OK;
}
static t_stat tape_reset(DEVICE *dptr)
{
return SCPE_OK;
}
static t_stat tape_boot(int32 unit_num, DEVICE *dptr)
{
uint16 block = 0300;
uint16 blocks = 8;
uint16 quarter = 0;
t_stat stat;
if (unit_num >= 2 && unit_num <= 3)
return SCPE_ARG;
if (blocks == 0)
return SCPE_ARG;
if (blocks == 1)
LSW = RDC;
else
LSW = RCG, quarter = blocks - 1;
LSW |= 0700 | (unit_num << 3);
RSW = (quarter << 9) | block;
stat = cpu_do();
if (stat != SCPE_OK)
return stat;
P = 020;
return SCPE_OK;
}
t_stat tape_metadata(FILE *fileref, uint16 *block_size, int16 *forward_offset, int16 *reverse_offset)
{
t_offset size = sim_fsize(fileref);
if (size == MAX_BLOCKS * DATA_WORDS * 2) {
/* Plain image. */
*block_size = DATA_WORDS;
*forward_offset = 0;
*reverse_offset = 0;
} else if ((size % (2 * DATA_WORDS)) == 6) {
/* Extended image with additional meta data. */
uint16 metadata = (uint16)(size / (2 * DATA_WORDS));
*block_size = read_word(fileref, metadata, 0);
*forward_offset = (int16)read_word(fileref, metadata, 1);
*reverse_offset = (int16)read_word(fileref, metadata, 2);
} else
return SCPE_FMT;
return SCPE_OK;
}
static t_stat tape_attach(UNIT *uptr, CONST char *cptr)
{
t_stat stat;
uint16 block_size;
int16 forward_offset, reverse_offset;
if (uptr - tape_unit >= 2 && uptr - tape_unit <= 3)
return SCPE_ARG;
stat = attach_unit(uptr, cptr);
if (stat != SCPE_OK)
return stat;
stat = tape_metadata(uptr->fileref, &block_size, &forward_offset, &reverse_offset);
if (stat != SCPE_OK)
return stat;
sim_debug(DBG, &tape_dev,
"Tape image with block size %o, block offset %d/%d\r\n",
block_size, forward_offset, reverse_offset);
if (block_size != DATA_WORDS)
return SCPE_FMT;
if (forward_offset != reverse_offset)
return SCPE_FMT;
uptr->OFFSET = forward_offset;
uptr->POS = -2 * START_POS;
uptr->SPEED = 0;
return SCPE_OK;
}
static t_stat tape_detach(UNIT *uptr)
{
if (uptr - tape_unit >= 2 && uptr - tape_unit <= 3)
return SCPE_ARG;
if ((uptr->flags & UNIT_ATT) == 0)
return SCPE_OK;
if (sim_is_active(uptr))
sim_cancel(uptr);
return detach_unit(uptr);
}

124
linc/linc_tty.c Normal file
View File

@ -0,0 +1,124 @@
#include "linc_defs.h"
/* Data bits, 110 baud rate. */
#define BIT_TIME 1120
/* Sample rate to find the start bit edge. */
#define START_TIME (BIT_TIME / 5)
/* After finding the edge, wait until the middle of the first data bit. */
#define FIRST_TIME (BIT_TIME + (BIT_TIME - START_TIME) / 2)
#define R (*(uint16 *)cpu_reg[5].loc)
/* Debug */
#define DBG 0001
#define DBG_BIT 0002
#define DATA u3 /* Character being assembled. */
#define STATE u4 /* 0 for start bit, 1 for stop bit, otherwise data. */
#define PREVIOUS u5 /* Previous level seen. */
/* When a start bit is found, the state is set to 10 and then
decremented for each bit that is processed. */
#define STATE_START 0
#define STATE_STOP 1
/* STATE_DATA 2-9 */
#define STATE_FIRST 10
/* Function declaration. */
static t_stat tty_svc(UNIT *uptr);
static t_stat tty_attach(UNIT *uptr, CONST char *cptr);
static t_stat tty_detach(UNIT *uptr);
static UNIT tty_unit = {
UDATA(&tty_svc, UNIT_IDLE | UNIT_ATTABLE, 0)
};
static DEBTAB tty_deb[] = {
{ "DBG", DBG },
{ "BIT", DBG_BIT },
{ NULL, 0 }
};
DEVICE tty_dev = {
"TTY", &tty_unit, NULL, NULL,
1, 8, 12, 1, 8, 12,
NULL, NULL, NULL,
NULL, &tty_attach, &tty_detach,
NULL, DEV_DISABLE | DEV_DEBUG, 0, tty_deb,
NULL, NULL, NULL, NULL, NULL, NULL
};
static void tty_output(UNIT *uptr)
{
uint8 ch = uptr->DATA;
sim_debug(DBG, &tty_dev, "Character %03o '%c'\n", ch, ch & 0177);
fputc(ch & 0177, uptr->fileref);
fflush(uptr->fileref);
}
static t_stat tty_svc(UNIT *uptr)
{
switch (uptr->STATE) {
case STATE_START:
if (uptr->PREVIOUS == 0 || (R & 1) == 1) {
/* Keep looking for start bit. */
uptr->PREVIOUS = R & 1;
sim_activate(uptr, START_TIME);
return SCPE_OK;
}
sim_debug(DBG_BIT, &tty_dev, "Start bit edge found.\n");
uptr->STATE = STATE_FIRST;
uptr->DATA = 0;
/* Wait until the middle of the first data bit. Since the edge
was just seen, this is a little longer than the time between
data bits. */
sim_activate(uptr, FIRST_TIME);
break;
default:
sim_debug(DBG_BIT, &tty_dev, "Data bit %d is %d\n",
STATE_FIRST - 1 - uptr->STATE, R & 1);
uptr->DATA >>= 1;
uptr->DATA |= (R & 1) << 7;
sim_activate(uptr, BIT_TIME);
break;
case STATE_STOP:
sim_debug(DBG_BIT, &tty_dev, "Stop bit is %d\n", R & 1);
if (R & 1)
tty_output(uptr);
else
sim_debug(DBG, &tty_dev, "Framing error.\n");
uptr->PREVIOUS = R & 1;
/* Look for next start bit. */
sim_activate(uptr, START_TIME);
break;
}
/* Decrease the state counter, first through the data bits, then
the stop bit, and finally the start bit. */
uptr->STATE--;
return SCPE_OK;
}
static t_stat tty_attach(UNIT *uptr, CONST char *cptr)
{
t_stat stat = attach_unit(uptr, cptr);
if (stat != SCPE_OK)
return stat;
uptr->STATE = 0;
uptr->PREVIOUS = 0;
sim_activate(uptr, 1);
return SCPE_OK;
}
static t_stat tty_detach(UNIT *uptr)
{
if ((uptr->flags & UNIT_ATT) == 0)
return SCPE_OK;
if (sim_is_active(uptr))
sim_cancel(uptr);
return detach_unit(uptr);
}

View File

@ -160,6 +160,10 @@ ifneq (3,${SIM_MAJOR})
ifneq (,$(findstring imlac,${MAKECMDGOALS}))
VIDEO_USEFUL = true
endif
# building the LINC needs video support
ifneq (,$(findstring linc,${MAKECMDGOALS}))
VIDEO_USEFUL = true
endif
# building the TT2500 needs video support
ifneq (,$(findstring tt2500,${MAKECMDGOALS}))
VIDEO_USEFUL = true
@ -1720,6 +1724,13 @@ IMLAC = ${IMLACD}/imlac_sys.c ${IMLACD}/imlac_cpu.c \
IMLAC_OPT = -I ${IMLACD} ${DISPLAY_OPT} ${AIO_CCDEFS}
LINCD = ${SIMHD}/linc
LINC = ${LINCD}/linc_cpu.c ${LINCD}/linc_crt.c ${LINCD}/linc_dpy.c \
${LINCD}/linc_kbd.c ${LINCD}/linc_sys.c \
${LINCD}/linc_tape.c ${LINCD}/linc_tty.c ${DISPLAYL}
LINC_OPT = -I ${LINCD} ${DISPLAY_OPT} ${AIO_CCDEFS}
STUBD = ${SIMHD}/stub
STUB = ${STUBD}/stub_sys.c ${STUBD}/stub_cpu.c
STUB_OPT = -I ${STUBD}
@ -2219,7 +2230,7 @@ ALL = pdp1 pdp4 pdp7 pdp8 pdp9 pdp15 pdp11 pdp10 \
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 \
imlac tt2500 sel32
imlac linc tt2500 sel32
all : ${ALL}
@ -2318,6 +2329,15 @@ ifneq (,$(call find_test,${IMLAC},imlac))
$@ $(call find_test,${IMLACD},imlac) ${TEST_ARG}
endif
linc : ${BIN}linc${EXE}
${BIN}linc${EXE} : ${LINC} ${SIM}
${MKDIRBIN}
${CC} ${LINC} ${SIM} ${LINC_OPT} ${CC_OUTSPEC} ${LDFLAGS}
ifneq (,$(call find_test,${LINC},imlac))
$@ $(call find_test,${LINCD},linc) ${TEST_ARG}
endif
stub : ${BIN}stub${EXE}
${BIN}stub${EXE} : ${STUB} ${SIM}