diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index be41a285c..8b5c07a07 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -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
diff --git a/Visual Studio Projects/Simh.ci.sln b/Visual Studio Projects/Simh.ci.sln
index a1d5c49b3..204d43b4b 100755
--- a/Visual Studio Projects/Simh.ci.sln
+++ b/Visual Studio Projects/Simh.ci.sln
@@ -159,6 +159,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PDP10-KS", "PDP10-KS.vcxpro
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SEL32", "SEL32.vcxproj", "{9B214A06-3727-44D4-99B7-2C3E44B86B32}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "linc", "linc.vcxproj", "{58E9E172-1CC9-4BA6-8176-F832B11DB1EC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
diff --git a/Visual Studio Projects/Simh.sln b/Visual Studio Projects/Simh.sln
index 7c472fec6..a96a23ed5 100755
--- a/Visual Studio Projects/Simh.sln
+++ b/Visual Studio Projects/Simh.sln
@@ -393,6 +393,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ND100", "nd100.vcproj", "{F
{D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8}
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "linc", "linc.vcproj", "{58E9E172-1CC9-4BA6-8176-F832B11DB1EC}"
+ ProjectSection(ProjectDependencies) = postProject
+ {D40F3AF1-EEE7-4432-9807-2AD287B490F8} = {D40F3AF1-EEE7-4432-9807-2AD287B490F8}
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
diff --git a/Visual Studio Projects/linc.vcproj b/Visual Studio Projects/linc.vcproj
new file mode 100644
index 000000000..10cc51af7
--- /dev/null
+++ b/Visual Studio Projects/linc.vcproj
@@ -0,0 +1,377 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Visual Studio Projects/linc.vcxproj b/Visual Studio Projects/linc.vcxproj
new file mode 100644
index 000000000..1fb9e7751
--- /dev/null
+++ b/Visual Studio Projects/linc.vcxproj
@@ -0,0 +1,178 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ 17.0
+ {58E9E172-1CC9-4BA6-8176-F832B11DB1EC}
+ linc
+ Win32Proj
+
+
+
+ Application
+ v143
+ NotSet
+
+
+ Application
+ v143
+ NotSet
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>17.0.36327.8
+
+
+ ..\BIN\NT\$(Platform)-$(Configuration)\
+ ..\BIN\NT\Project\simh\$(ProjectName)\$(Platform)-$(Configuration)\
+ false
+
+
+ ..\BIN\NT\$(Platform)-$(Configuration)\
+ ..\BIN\NT\Project\simh\$(ProjectName)\$(Platform)-$(Configuration)\
+ false
+
+
+
+ Check for required build dependencies & git commit id
+ Pre-Build-Event.cmd "$(TargetDir)$(TargetName).exe" LIBPCRE ROM BUILD LIBSDL
+
+
+ Disabled
+ ../linc/;./;../;../../windows-build/include;../../windows-build/include/SDL2;%(AdditionalIncludeDirectories)
+ USE_DISPLAY;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;PTW32_STATIC_LIB;SIM_ASYNCH_IO;USE_READER_THREAD;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG;%(PreprocessorDefinitions)
+ false
+ Default
+ MultiThreadedDebug
+
+ Level3
+ true
+ ProgramDatabase
+ CompileAsC
+ false
+
+
+ libcmtd.lib;wsock32.lib;winmm.lib;Iphlpapi.lib;pcrestaticd.lib;SDL2-StaticD.lib;SDL2_ttf-StaticD.lib;freetype2412MT_D.lib;libpng16.lib;zlib.lib;dxguid.lib;Imm32.lib;Version.lib;Setupapi.lib;%(AdditionalDependencies)
+ ../../windows-build/lib/Debug/;%(AdditionalLibraryDirectories)
+ true
+ Console
+ 10485760
+ 10485760
+ false
+
+ MachineX86
+
+
+ Running Available Tests
+ Post-Build-Event.cmd linc "$(TargetDir)$(TargetName).exe"
+
+
+
+
+ Check for required build dependencies & git commit id
+ Pre-Build-Event.cmd "$(TargetDir)$(TargetName).exe" LIBPCRE ROM BUILD LIBSDL
+
+
+ MaxSpeed
+ OnlyExplicitInline
+ true
+ true
+ ../linc/;./;../;../../windows-build/include;../../windows-build/include/SDL2;%(AdditionalIncludeDirectories)
+ USE_SHARED;USE_DISPLAY;SIM_BUILD_TOOL=simh-Visual-Studio-Project;_CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;PTW32_STATIC_LIB;SIM_ASYNCH_IO;USE_READER_THREAD;SIM_NEED_GIT_COMMIT_ID;HAVE_PCRE_H;PCRE_STATIC;HAVE_SLIRP_NETWORK;USE_SIMH_SLIRP_DEBUG;USE_SIM_VIDEO;HAVE_LIBSDL;HAVE_LIBPNG;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ true
+
+ Level3
+ true
+ ProgramDatabase
+ CompileAsC
+
+
+ libcmt.lib;wsock32.lib;winmm.lib;Iphlpapi.lib;pcrestatic.lib;SDL2-Static.lib;SDL2_ttf-Static.lib;freetype2412MT.lib;libpng16.lib;zlib.lib;dxguid.lib;Imm32.lib;Version.lib;Setupapi.lib;%(AdditionalDependencies)
+ ../../windows-build/lib/Release/;%(AdditionalLibraryDirectories)
+ false
+ Console
+ 10485760
+ 10485760
+ true
+ true
+ UseLinkTimeCodeGeneration
+ false
+
+ MachineX86
+
+
+ Running Available Tests
+ Post-Build-Event.cmd linc "$(TargetDir)$(TargetName).exe"
+
+
+
+
+ HAVE_CONFIG_H;PTW32_BUILD_INLINED;PTW32_STATIC_LIB;__CLEANUP_C
+ CompileAsC
+ false
+ HAVE_CONFIG_H;PTW32_BUILD_INLINED;PTW32_STATIC_LIB;__CLEANUP_C
+ CompileAsC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Visual Studio Projects/linc.vcxproj.filters b/Visual Studio Projects/linc.vcxproj.filters
new file mode 100644
index 000000000..959b76056
--- /dev/null
+++ b/Visual Studio Projects/linc.vcxproj.filters
@@ -0,0 +1,135 @@
+
+
+
+
+ {a7463719-42f3-4f7d-b375-e5e1238f2072}
+ cpp;c;cxx;def;odl;idl;hpj;bat;asm
+
+
+ {a3f124b8-d7f6-4540-8414-793285c5799a}
+ h;hpp;hxx;hm;inl;inc
+
+
+ {39a1b0cc-dcdb-4030-810d-d5d4eecb6c49}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/cmake/simh-simulators.cmake b/cmake/simh-simulators.cmake
index bd456fa5e..27e0c64a1 100644
--- a/cmake/simh-simulators.cmake
+++ b/cmake/simh-simulators.cmake
@@ -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)
diff --git a/display/display.c b/display/display.c
index 020ea62de..4b2a8ed4b 100644
--- a/display/display.c
+++ b/display/display.c
@@ -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.
diff --git a/display/display.h b/display/display.h
index cce79f9f2..ad18c0a4b 100644
--- a/display/display.h
+++ b/display/display.h
@@ -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,
diff --git a/linc/CMakeLists.txt b/linc/CMakeLists.txt
new file mode 100644
index 000000000..bce48ea8f
--- /dev/null
+++ b/linc/CMakeLists.txt
@@ -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)
diff --git a/linc/README.md b/linc/README.md
new file mode 100644
index 000000000..576878147
--- /dev/null
+++ b/linc/README.md
@@ -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
+`. 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
diff --git a/linc/linc_cpu.c b/linc/linc_cpu.c
new file mode 100644
index 000000000..c8d174bcb
--- /dev/null
+++ b/linc/linc_cpu.c
@@ -0,0 +1,1068 @@
+/* linc_cpu.c: LINC CPU simulator
+
+ 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"
+
+
+/* Debug */
+#define DBG_CPU 0001
+#define DBG_INT 0002
+
+#define INSN_ENI 00010
+#define INSN_NOP 00016
+#define INSN_OPR 00500
+#define INSN_MTP 00700
+#define INSN_JMP 06000
+
+#define X(_X) ((_X) & XMASK)
+#define C03 (C & BMASK)
+
+/* CPU state. */
+static uint16 P;
+static uint16 C;
+static uint16 S;
+static uint16 B;
+static uint16 A;
+static uint16 L;
+static uint16 Z;
+static uint16 R;
+static uint16 LSW, RSW, SSW;
+static uint16 SAM[16];
+static uint16 XL[12];
+static int paused;
+static int IBZ;
+static int OVF;
+static int INTREQ;
+static int ENI = 0;
+static int PINFF;
+static int DO = 0;
+
+static t_stat stop_reason;
+
+typedef struct {
+ uint16 P;
+ uint16 C;
+ uint16 S;
+ uint16 B;
+ uint16 A;
+ uint16 L;
+} HISTORY;
+static HISTORY *history = NULL;
+static uint32 history_i, history_m, history_n;
+
+/* Function declaration. */
+static t_stat cpu_ex(t_value *vptr, t_addr ea, UNIT *uptr, int32 sw);
+static t_stat cpu_dep(t_value val, t_addr ea, UNIT *uptr, int32 sw);
+static t_stat cpu_reset(DEVICE *dptr);
+static t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
+static t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
+static t_stat linc_boot(int32 flag, CONST char *ptr);
+static t_stat linc_do(int32 flag, CONST char *ptr);
+
+static UNIT cpu_unit = { UDATA(NULL, UNIT_FIX + UNIT_BINK, MEMSIZE) };
+
+REG cpu_reg[] = {
+ { ORDATAD(P, P, 10, "Program Location") },
+ { ORDATAD(C, C, 12, "Control Register") },
+ { ORDATAD(A, A, 12, "Accumulator") },
+ { ORDATAD(L, L, 1, "Link") },
+ { ORDATAD(Z, Z, 12, "?") },
+ { ORDATAD(R, R, 6, "Relay Register") },
+ { ORDATAD(S, S, 12, "Memory Address") },
+ { ORDATAD(B, B, 12, "Memory Buffer") },
+ { ORDATAD(LSW, LSW, 12, "Left Switches") },
+ { ORDATAD(RSW, RSW, 12, "Right Switches") },
+ { ORDATAD(SSW, SSW, 6, "Sense Switches") },
+
+ { FLDATAD(paused, paused, 1, "Paused") },
+ { FLDATAD(IBZ, IBZ, 1, "Interblock zone") },
+ { FLDATAD(OVF, OVF, 1, "Overflow") },
+ { FLDATAD(INTREQ, INTREQ, 1, "Interrupt") },
+ { FLDATAD(ENI, ENI, 1, "Interrupt Enable") },
+ { FLDATAD(PIN, PINFF, 1, "Pause Interrupt") },
+
+ { BRDATAD(SAM, SAM, 8, 8, 16, "Sampled analog inputs") },
+ { BRDATAD(XL, XL, 8, 1, 12, "External levels") },
+ { NULL }
+};
+
+static MTAB cpu_mod[] = {
+ { MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY",
+ &cpu_set_hist, &cpu_show_hist },
+ { 0 }
+};
+
+static DEBTAB cpu_deb[] = {
+ { "CPU", DBG_CPU },
+ { "INTERRUPT", DBG_INT },
+ { NULL, 0 }
+};
+
+DEVICE cpu_dev = {
+ "CPU", &cpu_unit, cpu_reg, cpu_mod,
+ 0, 8, 11, 1, 8, 12,
+ &cpu_ex, &cpu_dep, &cpu_reset,
+ NULL, NULL, NULL, NULL, DEV_DEBUG, 0, cpu_deb,
+ NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+static CTAB linc_cmd[] = {
+ { "BOOT", &linc_boot, 0,
+ "BOOT {unit} boot simulator\n"
+ "BOOT TAPE{n} RCG={blocks} boot tape from specified blocks\n", NULL, &run_cmd_message },
+ { "DO", &linc_do, 0,
+ "DO {unit} boot simulator\n"
+ "DO execute instruction in LSW and RSW\n", NULL, NULL },
+ { NULL }
+};
+
+static void cpu_ndxp(int flag)
+{
+ if (flag)
+ P = X(P + 1);
+}
+
+static void cpu_ndxc()
+{
+ C = (C & ~BMASK) | ((C + 1) & BMASK);
+}
+
+static void cpu_set_S(uint16 addr)
+{
+ S = addr & WMASK;
+}
+
+static void cpu_set_B(uint16 data)
+{
+ B = data & WMASK;
+}
+
+static void cpu_4ndxb()
+{
+ cpu_set_B(B + 4);
+}
+
+static void cpu_4ndxa()
+{
+ A = (A + 4) & WMASK;
+}
+
+static void cpu_mem_read(void)
+{
+ cpu_set_B(M[S & AMASK]);
+ sim_interval--;
+ if (sim_brk_summ && sim_brk_test(S & AMASK, SWMASK('R')))
+ stop_reason = STOP_RBKPT;
+}
+
+static void cpu_mem_modify(void)
+{
+ M[S & AMASK] = B;
+ if (sim_brk_summ && sim_brk_test(S & AMASK, SWMASK('W')))
+ stop_reason = STOP_WBKPT;
+}
+
+static void cpu_mem_write(void)
+{
+ sim_interval--;
+ cpu_mem_modify();
+}
+
+static void cpu_insn_addr()
+{
+ if (!DO) {
+ cpu_set_S(P);
+ cpu_ndxp(1);
+ }
+}
+
+static void cpu_insn_read()
+{
+ if (!DO)
+ cpu_mem_read();
+}
+
+static void cpu_fetch()
+{
+ cpu_insn_addr();
+ cpu_insn_read();
+}
+
+static int cpu_halfword(void)
+{
+ switch (C & 07740) {
+ case 01300: //LDH
+ case 01340: //STH
+ case 01400: //SHD
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void cpu_index(void)
+{
+ uint16 tmp;
+ if (C & IMASK) {
+ if (cpu_halfword()) {
+ B += HMASK;
+ tmp = B >> 12;
+ } else {
+ tmp = 1;
+ }
+ cpu_set_B((B & 06000) | X(B + tmp));
+ cpu_mem_modify();
+ }
+}
+
+static void cpu_indexing(void)
+{
+ uint16 a = C03;
+ if (a == 0) {
+ cpu_insn_addr();
+ if ((C & IMASK) == 0) {
+ cpu_insn_read();
+ cpu_set_S(B);
+ }
+ } else {
+ cpu_set_S(a);
+ cpu_mem_read();
+ cpu_index();
+ cpu_set_S(B);
+ }
+}
+
+static void
+cpu_misc(void)
+{
+ switch (C) {
+ case 00000: //HLT
+ stop_reason = STOP_HALT;
+ break;
+ case 00002: //PDP
+ sim_debug(DBG_CPU, &cpu_dev, "This is not a PDP-12.\n");
+ break;
+ case 00005: //ZTA
+ A = Z >> 1;
+ break;
+ case 00010: //ENI
+ sim_debug(DBG_INT, &cpu_dev, "Interrupt enabled.\n");
+ ENI = 1;
+ break;
+ case 00011: //CLR
+ A = L = Z = 0;
+ break;
+ case 00012: //DIN
+ sim_debug(DBG_INT, &cpu_dev, "Interrupt disabled.\n");
+ ENI = 0;
+ break;
+ case 00013: //Write gate on.
+ break;
+ case 00014: //ATR
+ R = A & RMASK;
+ break;
+ case 00015: //RTA
+ A = R & RMASK;
+ break;
+ case 00016: //NOP
+ break;
+ case 00017: //COM
+ A = (~A) & WMASK;
+ break;
+ }
+}
+
+static void cpu_set(void)
+{
+ cpu_fetch();
+ if ((C & IMASK) == 0) {
+ cpu_set_S(B);
+ cpu_mem_read();
+ }
+ cpu_set_S(C03);
+ cpu_mem_write();
+}
+
+static void cpu_sam(void)
+{
+ // sample analog input C03
+ // 0-7 are pots, 10-17 are high speed inputs
+ // i=0 wait 24 microseconds, i=1 do not wait
+ if ((C & IMASK) == 0)
+ sim_interval -= 3;
+ A = SAM[C03];
+ if (A & 0200) /* One's complement +/-177. */
+ A |= 07400;
+}
+
+static void cpu_dis(void)
+{
+ cpu_set_S(C03);
+ cpu_mem_read();
+ cpu_index();
+ sim_debug(DBG_CPU, &cpu_dev, "DIS α=%02o B=%04o A=%04o\n", S, B, A);
+ dpy_dis(B >> 11, B & DMASK, A & DMASK);
+}
+
+static void cpu_xsk(void)
+{
+ cpu_set_S(C03);
+ cpu_mem_read();
+ cpu_index();
+ cpu_ndxp(X(B) == 01777);
+}
+
+static void cpu_rol(void)
+{
+ C = (C & ~BMASK) | (~B & BMASK);
+ while (C03 != 017) {
+ if (C & IMASK) {
+ A = (A << 1) | L;
+ L = A >> 12;
+ } else {
+ A = (A << 1) | (A >> 11);
+ }
+ A &= WMASK;
+ cpu_ndxc();
+ }
+}
+
+static void cpu_ror(void)
+{
+ C = (C & ~BMASK) | (~B & BMASK);
+ while (C03 != 017) {
+ Z = (Z >> 1) | ((A & 1) << 11);
+ if (C & IMASK) {
+ A |= L << 12;
+ L = A & 1;
+ A = A >> 1;
+ } else {
+ A = (A >> 1) | (A << 11);
+ A &= WMASK;
+ }
+ cpu_ndxc();
+ }
+}
+
+static void cpu_scr(void)
+{
+ C = (C & ~BMASK) | (~B & BMASK);
+ while (C03 != 017) {
+ Z = (Z >> 1) | ((A & 1) << 11);
+ if (C & IMASK)
+ L = A & 1;
+ A = (A & 04000) | (A >> 1);
+ cpu_ndxc();
+ }
+}
+
+int cpu_skip(void)
+{
+ int flag;
+ switch (C & 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: //SXL
+ flag = XL[C03];
+ break;
+ case 015: //KST
+ flag = kbd_struck();
+ break;
+ case 040: case 041: case 042: case 043: case 044: case 045: //SNS
+ flag = SSW & (1 << (C & 7));
+ break;
+ case 046: //PIN
+ flag = PINFF;
+ sim_debug(DBG_INT, &cpu_dev, "Pause interrupt enabled.\n");
+ PINFF = 0;
+ break;
+ case 050: //AZE
+ flag = (A == 0) || (A == WMASK);
+ break;
+ case 051: //APO
+ flag = (A & 04000) == 0;
+ break;
+ case 052: //LZE
+ flag = L == 0;
+ break;
+ case 053: //IBZ
+ flag = IBZ;
+ sim_debug(DBG_CPU, &cpu_dev, "IBZ%s => %d\n", C & IMASK ? " i" : "", flag);
+ break;
+ case 054: //OVF
+ flag = OVF;
+ break;
+ case 055: //ZZZ
+ flag = (Z & 1) == 0;
+ break;
+ default:
+ flag = 0;
+ break;
+ }
+ if (C & IMASK)
+ flag = !flag;
+ return flag;
+}
+
+static void cpu_opr(void)
+{
+ switch (C03) {
+ case 000: case 001: case 002: case 003: case 004: case 005: case 006: case 007:
+ case 010: case 011: case 012: case 013:
+ if (C & IMASK)
+ ; //Pause.
+ break;
+ case 015: //KBD
+ A = kbd_key(C & IMASK);
+ break;
+ case 016: //RSW
+ A = RSW;
+ break;
+ case 017: //LSW
+ A = LSW;
+ break;
+ }
+}
+
+static void cpu_lmb(void)
+{
+ /* Lower memory bank. */
+ sim_debug(DBG_CPU, &cpu_dev, "This is not micro-LINC 300.\n");
+}
+
+static void cpu_umb(void)
+{
+ /* Upper memory bank. */
+ sim_debug(DBG_CPU, &cpu_dev, "This is not micro-LINC 300.\n");
+}
+
+static void cpu_tape(void)
+{
+ cpu_fetch();
+ tape_op();
+}
+
+static void cpu_lda(void)
+{
+ cpu_mem_read();
+ A = B;
+}
+
+static void cpu_sta(void)
+{
+ cpu_set_B(A);
+ /* Do not write immediate value if executing out of switches. */
+ if (!DO || (C & IMASK) == 0)
+ cpu_mem_write();
+}
+
+static void cpu_ada(void)
+{
+ cpu_mem_read();
+ OVF = ~(A ^ B);
+ A += B;
+ A += A >> 12;
+ A &= WMASK;
+ OVF &= (A ^ B) & 04000;
+}
+
+static void cpu_adm(void)
+{
+ cpu_ada();
+ cpu_set_B(A);
+ cpu_mem_modify();
+}
+
+static void cpu_lam(void)
+{
+ cpu_mem_read();
+ A += L;
+ L = A >> 12;
+ A &= WMASK;
+ A += B;
+ if (A & 010000)
+ L = 1;
+ A &= WMASK;
+ cpu_set_B(A);
+ cpu_mem_modify();
+}
+
+static void cpu_mul(void)
+{
+ uint32 factor, product;
+ cpu_mem_read();
+
+ C &= ~BMASK;
+ L = (A ^ B) >> 11;
+ if (A & HMASK)
+ A ^= WMASK;
+ if (B & HMASK)
+ B ^= WMASK;
+ Z = B;
+ cpu_set_B(A);
+ factor = B;
+ product = A = 0;
+ while (C03 < 12) {
+ if (Z & 1)
+ product += factor;
+ Z >>= 1;
+ factor <<= 1;
+ cpu_ndxc();
+ }
+ if (S & HMASK)
+ A = product >> 11;
+ else
+ A = product & 03777;
+ if (L)
+ A ^= 07777;
+}
+
+static void cpu_ldh(void)
+{
+ cpu_mem_read();
+ if ((S & HMASK) == 0)
+ B >>= 6;
+ A = B & RMASK;
+}
+
+static void cpu_sth(void)
+{
+ cpu_mem_read();
+ if (S & HMASK)
+ cpu_set_B((A & RMASK) | (B & LMASK));
+ else
+ cpu_set_B((A << 6) | (B & RMASK));
+ cpu_mem_modify();
+}
+
+static void cpu_shd(void)
+{
+ cpu_mem_read();
+ if ((S & HMASK) == 0)
+ B >>= 6;
+ cpu_ndxp((A & RMASK) != (B & RMASK));
+}
+
+static void cpu_sae(void)
+{
+ cpu_mem_read();
+ cpu_ndxp(A == B);
+}
+
+static void cpu_sro(void)
+{
+ cpu_mem_read();
+ cpu_ndxp((B & 1) == 0);
+ cpu_set_B((B >> 1) | (B << 11));
+ cpu_mem_modify();
+}
+
+static void cpu_bcl(void)
+{
+ cpu_mem_read();
+ A &= ~B;
+}
+
+static void cpu_bse(void)
+{
+ cpu_mem_read();
+ A |= B;
+}
+
+static void cpu_bco(void)
+{
+ cpu_mem_read();
+ A ^= B;
+}
+
+static void cpu_dsc(void)
+{
+ cpu_mem_read();
+ Z = B;
+
+ cpu_set_S(1);
+ cpu_mem_read();
+ sim_debug(DBG_CPU, &crt_dev, "DSC B=%04o A=%04o\n", B, A);
+
+ C &= ~BMASK;
+ while (C03 < 12) {
+ if (C03 == 0 || C03 == 6) {
+ A &= 07740;
+ cpu_4ndxb();
+ }
+ if (Z & 1)
+ dpy_dis(B >> 11, B & DMASK, A & DMASK);
+ Z >>= 1;
+ cpu_4ndxa();
+ cpu_ndxc();
+ }
+ cpu_mem_write();
+}
+
+static void cpu_add(void)
+{
+ cpu_set_S(X(C));
+ cpu_ada();
+}
+
+static void cpu_stc(void)
+{
+ cpu_set_S(X(C));
+ cpu_set_B(A);
+ A = 0;
+ cpu_mem_write();
+}
+
+static void cpu_jmp(void)
+{
+ uint16 tmp = P;
+ P = X(C);
+ if (P != 0) {
+ cpu_set_B(INSN_JMP | tmp);
+ cpu_set_S(0);
+ cpu_mem_write();
+ }
+}
+
+static void
+cpu_insn(void)
+{
+ /* Cycle 0, or I. */
+ cpu_fetch();
+ if (!DO)
+ C = B;
+
+ /* Cycle 1, or X. */
+ if ((C & 07000) == 01000)
+ cpu_indexing();
+
+ if (history) {
+ history[history_i].P = P;
+ history[history_i].C = C;
+ history[history_i].S = S;
+ }
+
+ /* Cycle 2, or O. */
+
+ /* Cycle 3, or E. */
+ switch (C & 07740) {
+ case 00000:
+ cpu_misc();
+ break;
+ case 00040:
+ cpu_set();
+ break;
+ case 00100:
+ cpu_sam();
+ break;
+ case 00140:
+ cpu_dis();
+ break;
+ case 00200:
+ cpu_xsk();
+ break;
+ case 00240:
+ cpu_rol();
+ break;
+ case 00300:
+ cpu_ror();
+ break;
+ case 00340:
+ cpu_scr();
+ break;
+ case 00400:
+ case 00440:
+ cpu_ndxp(cpu_skip());
+ break;
+ case 00500:
+ case 00540:
+ cpu_opr();
+ break;
+ case 00600:
+ cpu_lmb();
+ break;
+ case 00640:
+ cpu_umb();
+ break;
+ case 00700:
+ case 00740:
+ cpu_tape();
+ break;
+ case 01000:
+ cpu_lda();
+ break;
+ case 01040:
+ cpu_sta();
+ break;
+ case 01100:
+ cpu_ada();
+ break;
+ case 01140:
+ cpu_adm();
+ break;
+ case 01200:
+ cpu_lam();
+ break;
+ case 01240:
+ cpu_mul();
+ break;
+ case 01300:
+ cpu_ldh();
+ break;
+ case 01340:
+ cpu_sth();
+ break;
+ case 01400:
+ cpu_shd();
+ break;
+ case 01440:
+ cpu_sae();
+ break;
+ case 01500:
+ cpu_sro();
+ break;
+ case 01540:
+ cpu_bcl();
+ break;
+ case 01600:
+ cpu_bse();
+ break;
+ case 01640:
+ cpu_bco();
+ break;
+ case 01740:
+ cpu_dsc();
+ break;
+ 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:
+ cpu_add();
+ 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:
+ cpu_stc();
+ 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:
+ cpu_jmp();
+ break;
+ }
+
+ if (history) {
+ history[history_i].B = B;
+ history[history_i].A = A;
+ history[history_i].L = L;
+ history_i = (history_i + 1) % history_m;
+ if (history_n < history_m)
+ history_n++;
+ }
+}
+
+t_stat cpu_do(void)
+{
+ t_stat stat;
+
+ DO = 1;
+ C = LSW;
+ cpu_set_B(RSW);
+ cpu_insn();
+ DO = 0;
+
+ sim_interval = 1;
+ /* Can not return from DO until the instruction is done,
+ i.e. not paused. */
+ while (paused) {
+ AIO_CHECK_EVENT;
+ if (sim_interval <= 0) {
+ if ((stat = sim_process_event()) != SCPE_OK)
+ return stat;
+ }
+ sim_interval--;
+ }
+ return SCPE_OK;
+}
+
+static int jmp_or_eni(void)
+{
+ return (C & 06000) == INSN_JMP || (C == INSN_ENI);
+}
+
+static int mtp_or_opr(void)
+{
+ return (C & 07700) == INSN_MTP || (C & 07700) == INSN_OPR;
+}
+
+static void cpu_interrupt(void)
+{
+ if (!INTREQ)
+ return;
+ if (!ENI)
+ return;
+
+ sim_debug(DBG_INT, &cpu_dev, "Interrupt requested and enabled.\n");
+
+ if (jmp_or_eni()) {
+ sim_debug(DBG_INT, &cpu_dev, "Interrupt not taken after JMP or ENI.\n");
+ return;
+ }
+
+ if (paused) {
+ if (!mtp_or_opr()) {
+ sim_debug(DBG_INT, &cpu_dev, "Pause only interrupted for MTP or OPR.\n");
+ return;
+ }
+ if (PINFF)
+ return;
+ sim_debug(DBG_INT, &cpu_dev, "Pause interrupted.\n");
+ PINFF = 1;
+ paused = 0;
+ }
+
+ sim_debug(DBG_INT, &cpu_dev, "Interrupt taken.\n");
+
+ cpu_set_S(021);
+ cpu_mem_read();
+ C = B;
+ if (history) {
+ history[history_i].P = 07777;
+ history[history_i].C = C;
+ history[history_i].S = S;
+ }
+
+ ENI = 0; /* Except for OPR. */
+ if ((C & 06000) == INSN_JMP)
+ cpu_jmp();
+ else if ((C & 07700) == INSN_OPR) {
+ ENI = 1; /* OPR doesn't disable interrupts. */
+ cpu_opr();
+ } else if (C == INSN_NOP)
+ ;
+ else
+ sim_debug(DBG_INT, &cpu_dev, "Invalid interrupt instruction.\n");
+
+ if (history) {
+ history[history_i].B = B;
+ history[history_i].A = A;
+ history_i = (history_i + 1) % history_m;
+ if (history_n < history_m)
+ history_n++;
+ }
+}
+
+t_stat sim_instr(void)
+{
+ t_stat stat;
+
+ if ((stat = build_dev_tab()) != SCPE_OK)
+ return stat;
+
+ /* Stepping is based on sim_step, not sim_interval. The latter is
+ approximately memory cycles, not instructions. */
+ sim_cancel_step();
+
+ /* Because we check sim_step before cpu_insn. */
+ if (sim_step)
+ sim_step++;
+
+ stop_reason = 0;
+ paused = 0;
+ PINFF = 0;
+ ENI = 0;
+
+ for (;;) {
+ AIO_CHECK_EVENT;
+ if (sim_interval <= 0) {
+ if ((stat = sim_process_event()) != SCPE_OK)
+ return stat;
+ }
+
+ if (sim_brk_summ && sim_brk_test(P, SWMASK('E')))
+ return STOP_IBKPT;
+
+ /* Can not return from a STEP until the instruction is done,
+ i.e. not paused. */
+ if (!paused && sim_step != 0) {
+ if (--sim_step == 0)
+ return SCPE_STEP;
+ }
+
+ if (paused)
+ sim_interval--;
+ else
+ cpu_insn();
+
+ cpu_interrupt();
+
+ if (stop_reason)
+ return stop_reason;
+ }
+
+ return SCPE_OK;
+}
+
+static t_stat cpu_ex(t_value *vptr, t_addr ea, UNIT *uptr, int32 sw)
+{
+ if (vptr == NULL)
+ return SCPE_ARG;
+ if (ea >= MEMSIZE)
+ return SCPE_NXM;
+ *vptr = M[ea];
+ return SCPE_OK;
+}
+
+static t_stat cpu_dep(t_value val, t_addr ea, UNIT *uptr, int32 sw)
+{
+ if (ea >= MEMSIZE)
+ return SCPE_NXM;
+ M[ea] = val & WMASK;
+ return SCPE_OK;
+}
+
+static t_stat
+cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
+{
+ t_stat r;
+ uint32 x;
+
+ if (cptr == NULL)
+ return SCPE_ARG;
+
+ x = get_uint (cptr, 10, 1000000, &r);
+ if (r != SCPE_OK)
+ return r;
+
+ history = (HISTORY *)calloc (x, sizeof (*history));
+ if (history == NULL)
+ return SCPE_MEM;
+
+ history_m = x;
+ history_n = 0;
+ history_i = 0;
+ return SCPE_OK;
+}
+
+static t_stat
+cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
+{
+ t_value insn;
+ uint32 i, j;
+
+ fprintf (st, "P___ C___ S___ B___ A___ L\n");
+
+ if (history_i >= history_n)
+ j = history_i - history_n;
+ else
+ j = history_m + history_i - history_n;
+
+ for (i = 0; i < history_n; i++) {
+ if (history[j].P == 07777)
+ fprintf (st, "---- ");
+ else
+ fprintf (st, "%04o ", history[j].P);
+ fprintf (st, "%04o %04o %04o %04o %d ",
+ history[j].C,
+ history[j].S,
+ history[j].B,
+ history[j].A,
+ history[j].L);
+ insn = history[j].C;
+ fprint_sym(st, history[j].P, &insn, NULL, SWMASK('M'));
+ fputc('\n', st);
+ j = (j + 1) % history_m;
+ }
+
+ return SCPE_OK;
+}
+
+static t_stat
+cpu_reset(DEVICE *dptr)
+{
+ sim_brk_types = SWMASK('E') | SWMASK('R') | SWMASK('W');
+ sim_brk_dflt = SWMASK('E');
+ sim_vm_cmd = linc_cmd;
+ return SCPE_OK;
+}
+
+static t_stat linc_boot(int32 flag, CONST char *cptr)
+{
+ char dev[CBUFSIZE], arg[CBUFSIZE];
+ char bbuf[CBUFSIZE], gbuf[CBUFSIZE];
+ t_value block;
+ t_stat stat;
+
+ /* Is it BOOT TAPE? */
+ cptr = get_glyph(cptr, dev, 0);
+ if (*dev == 0)
+ return SCPE_ARG;
+ if (strncmp(dev, "TAPE", 4) != 0)
+ return run_cmd(RU_BOOT, dev);
+
+ /* Yes. Is there an argument after? */
+ if (*cptr == 0)
+ return run_cmd(RU_BOOT, dev);
+
+ bbuf[0] = 0;
+ strcpy(gbuf, "20");
+ while (*cptr) {
+ cptr = get_glyph(cptr, arg, 0);
+ if (strncmp(arg, "RDC=", 4) == 0)
+ LSW = 0700, strcpy(bbuf, arg + 4);
+ else if (strncmp(arg, "RCG=", 4) == 0)
+ LSW = 0701, strcpy(bbuf, arg + 4);
+ else if (strncmp(arg, "START=", 6) == 0)
+ LSW = 0701, strcpy(gbuf, arg + 6);
+ else
+ return SCPE_ARG;
+ }
+
+ if (*bbuf == 0)
+ return SCPE_ARG;
+
+ /* It's a BOOT TAPE RDC= or RCG=, so start from switches. */
+ block = get_uint(bbuf, 8, ~0, &stat);
+ if (stat != SCPE_OK)
+ return stat;
+
+ RSW = block;
+ stat = cpu_do();
+ if (stat != SCPE_OK)
+ return stat;
+ return run_cmd(RU_GO, gbuf);
+}
+
+static t_stat linc_do(int32 flag, CONST char *cptr)
+{
+ /* With arguments, regular DO to execute script. */
+ if (*cptr != 0)
+ return do_cmd(flag, cptr);
+
+ /* No arguments, push the DO button on the LINC control panel. */
+ return cpu_do();
+}
diff --git a/linc/linc_crt.c b/linc/linc_crt.c
new file mode 100644
index 000000000..d9d1ab84f
--- /dev/null
+++ b/linc/linc_crt.c
@@ -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 ());
+}
diff --git a/linc/linc_defs.h b/linc/linc_defs.h
new file mode 100644
index 000000000..a1b9f26eb
--- /dev/null
+++ b/linc/linc_defs.h
@@ -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_ */
diff --git a/linc/linc_dpy.c b/linc/linc_dpy.c
new file mode 100644
index 000000000..861873c80
--- /dev/null
+++ b/linc/linc_dpy.c
@@ -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)
+{
+}
diff --git a/linc/linc_kbd.c b/linc/linc_kbd.c
new file mode 100644
index 000000000..6a6ae1f8b
--- /dev/null
+++ b/linc/linc_kbd.c
@@ -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;
+}
diff --git a/linc/linc_sys.c b/linc/linc_sys.c
new file mode 100644
index 000000000..1de0d3cec
--- /dev/null
+++ b/linc/linc_sys.c
@@ -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;
+}
diff --git a/linc/linc_tape.c b/linc/linc_tape.c
new file mode 100644
index 000000000..f5014769d
--- /dev/null
+++ b/linc/linc_tape.c
@@ -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);
+}
diff --git a/linc/linc_tty.c b/linc/linc_tty.c
new file mode 100644
index 000000000..89044e8ce
--- /dev/null
+++ b/linc/linc_tty.c
@@ -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);
+}
diff --git a/linc/tests/.gitignore b/linc/tests/.gitignore
new file mode 100644
index 000000000..2546579af
--- /dev/null
+++ b/linc/tests/.gitignore
@@ -0,0 +1 @@
+clobbered.linc
diff --git a/linc/tests/classic-test.do b/linc/tests/classic-test.do
new file mode 100644
index 000000000..859ff4042
--- /dev/null
+++ b/linc/tests/classic-test.do
@@ -0,0 +1,110 @@
+cd %~p0
+
+# The tests check writing to the tape, so use a copy.
+copy classic-test.linc clobbered.linc
+attach tape0 clobbered.linc
+
+echo CONTRL
+load -e classic-test.linc block=0 start=0 length=400
+break 34
+
+# Special treatment for this test, because it's supposed to halt.
+echo *** Test: 70 - HLTTST ***
+load -e classic-test.linc block=1 start=400 length=400
+assert 400==70
+go 401
+assert P==402
+continue
+assert P==34
+
+deposit RSW 0300
+deposit LSW 0700
+deposit SSW 77
+deposit SAM[0] 177
+deposit XL[0] 1
+deposit XL[1] 1
+deposit XL[2] 1
+deposit XL[3] 1
+deposit XL[4] 1
+deposit XL[5] 1
+deposit XL[6] 1
+deposit XL[7] 1
+deposit XL[8] 1
+deposit XL[9] 1
+deposit XL[10] 1
+deposit XL[11] 1
+
+call test 1 SAETST 002
+call test 2 BCLTST 003
+call test 3 BSETST 004
+call test 4 BCOTST 005
+call test 5 ROTL1 006
+call test 6 ROTL2 007
+call test 7 ROTL3 010
+call test 10 ROTL4 011
+call test 11 ROTL5 012
+call test 12 ROTR1 013
+call test 13 ROTR2 014
+call test 14 ROTR3 015
+call test 15 ROTR4 016
+call test 16 ROTR5 017
+call test 17 CLRTST 020
+call test 20 ADDONE 021
+call test 21 COMT1 022
+call test 22 SCRT1 023
+call test 23 SCRT2 024
+call test 24 SCRT3 025
+call test 25 SCRT4 027
+call test 26 ADDT1 031
+call test 27 FADRT1 032
+call test 30 FADRT2 033
+call test 31 iBETA1 035
+call test 32 iBETA2 036
+call test 33 iBETA3 037
+call test 34 iBETA4 040
+call test 35 LDAT1 041
+call test 36 STAT1 042
+call test 37 ADMT1 043
+call test 40 LAMT1 044
+call test 41 MULT1 045
+call test 42 SROT1 046
+call test 43 SETT1 047
+call test 44 SETT2 050
+call test 45 XSKT1 051
+call test 46 XSKT2 052
+call test 47 AZET1 053
+call test 50 APOT1 054
+call test 51 LZET1 055
+call test 52 HWCT1 056
+call test 53 HWCT2 057
+call test 54 HWCT3 060
+call test 55 HWCT4 061
+call test 56 HWCT5 062
+call test 57 RANADD 063
+call test 60 ATRT1 064
+call test 61 IBZT1 065
+call test 62 JMPUP 066
+call test 63 JMPDWN 067
+call test 64 TAPETS 070
+call test 65 MTBTST 101
+call test 66 DISTST 102
+call test 67 DSCTST 103
+call test 700 OVFT1 104
+call test 701 ZTAT1 105
+call test 702 ZCLR1 106
+call test 703 ZCLRT2 107
+deposit INTREQ 1
+call test 704 ENIT1 110
+call test 71 MISCTS 111
+
+echo DIAGNOSTICS PASSED
+quit
+
+:test
+echo *** Test: %1 - %2 ***
+load -e classic-test.linc block=%3 start=400 length=400
+assert 400==%1
+deposit 21 1%3
+go 401
+assert P==34
+return
diff --git a/linc/tests/classic-test.linc b/linc/tests/classic-test.linc
new file mode 100644
index 000000000..8f5ecd3f4
Binary files /dev/null and b/linc/tests/classic-test.linc differ
diff --git a/linc/tests/linc_test.ini b/linc/tests/linc_test.ini
new file mode 100644
index 000000000..7fede77e0
--- /dev/null
+++ b/linc/tests/linc_test.ini
@@ -0,0 +1,4 @@
+cd %~p0
+set crt disabled
+reset
+do classic-test.do
diff --git a/linc/tests/tty.do b/linc/tests/tty.do
new file mode 100644
index 000000000..b70a3008e
--- /dev/null
+++ b/linc/tests/tty.do
@@ -0,0 +1,53 @@
+SET CRT DISABLED
+RESET
+DELETE printer.txt
+ATTACH TTY printer.txt
+DEPOSIT 0020 RTA
+DEPOSIT 0021 BSE i
+DEPOSIT 0022 1
+DEPOSIT 0023 ATR
+DEPOSIT 0024 SET i 12
+DEPOSIT 0025 -430
+DEPOSIT 0026 XSK i 12
+DEPOSIT 0027 JMP 26
+DEPOSIT 0030 SET i 1
+DEPOSIT 0031 57
+DEPOSIT 0032 SET i 2
+DEPOSIT 0033 -20
+DEPOSIT 0034 LDA i 1
+DEPOSIT 0035 STC 44
+DEPOSIT 0036 RTA
+DEPOSIT 0037 SET i 11
+DEPOSIT 0040 -13
+DEPOSIT 0041 BCL i
+DEPOSIT 0042 1
+DEPOSIT 0043 SRO i
+DEPOSIT 0045 ADD 42
+DEPOSIT 0046 ATR
+DEPOSIT 0047 SET i 12
+DEPOSIT 0050 -430
+DEPOSIT 0051 XSK i 12
+DEPOSIT 0052 JMP 51
+DEPOSIT 0053 XSK i 11
+DEPOSIT 0054 JMP 41
+DEPOSIT 0055 XSK i 2
+DEPOSIT 0056 JMP 34
+DEPOSIT 0057 HLT
+DEPOSIT 0060 3220
+DEPOSIT 0061 3212
+DEPOSIT 0062 3230
+DEPOSIT 0063 3230
+DEPOSIT 0064 3236
+DEPOSIT 0065 3100
+DEPOSIT 0066 3250
+DEPOSIT 0067 3212
+DEPOSIT 0070 3230
+DEPOSIT 0071 3212
+DEPOSIT 0072 3250
+DEPOSIT 0073 3262
+DEPOSIT 0074 3240
+DEPOSIT 0075 3212
+DEPOSIT 0076 3032
+DEPOSIT 0077 3024
+GO 20
+QUIT
diff --git a/linc/tests/tty.lap6 b/linc/tests/tty.lap6
new file mode 100644
index 000000000..fefd4cdd3
--- /dev/null
+++ b/linc/tests/tty.lap6
@@ -0,0 +1,53 @@
+ @20
+ RTA
+ BSE i [MARK STATE
+ 1
+ ATR
+ SET i 12 [GIVE TTY TIME
+ -430
+ XSK i 12
+ JMP p-1
+ SET i 1 [TEXT DATA
+ 2A-1
+ SET i 2 [TEXT LENGTH
+ 2A-2B
+#1A LDA i 1 [GET NEXT CHARACTER
+ STC 1D
+ RTA
+ SET i 11 [OUTPUT 11 BITS
+ -13 [1 START, 8 DATA
+#1B BCL i [AND 2 STOP
+#1C 1
+ SRO i
+#1D
+ ADD 1C
+ ATR [OUTPUT BIT TO RELAY
+ SET i 12
+ -430 [DELAY FOR 110 BAUD
+ XSK i 12
+ JMP p-1
+ XSK i 11
+ JMP 1B [NEXT BIT
+ XSK i 2
+ JMP 1A [NEXT CHARACTER
+ HLT
+
+ [ASCII TEXT WITH ONE 0 START
+ [BIT AND TWO 1 STOP BITS
+#2A 3220 [H
+ 3212 [E
+ 3230 [L
+ 3230 [L
+ 3236 [O
+ 3100 [
+ 3250 [T
+ 3212 [E
+ 3230 [L
+ 3212 [E
+ 3250 [T
+ 3262 [Y
+ 3240 [P
+ 3212 [E
+ 3032 [CR
+ 3024 [LF
+#2B
diff --git a/makefile b/makefile
index b60a6fee1..3d98d72c6 100644
--- a/makefile
+++ b/makefile
@@ -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}
diff --git a/sim_video.c b/sim_video.c
index e1e0c13c1..bfafe421d 100644
--- a/sim_video.c
+++ b/sim_video.c
@@ -1140,6 +1140,9 @@ switch (key) {
case SDLK_BACKSLASH:
return SIM_KEY_BACKSLASH;
+ case SDLK_LESS:
+ return SIM_KEY_LEFT_BACKSLASH;
+
case SDLK_RIGHTBRACKET:
return SIM_KEY_RIGHT_BRACKET;