From 1740ac10fa29b7f468ea771f8ad4956746d9a3de Mon Sep 17 00:00:00 2001 From: Lars Brinkhoff Date: Sat, 11 Oct 2025 07:02:28 +0200 Subject: [PATCH 1/6] VIDEO: Accept the ISO "left backslash" key. Adds support for the ISO/European "left backslash" or "less than" key. It's found between left shift and Z. --- sim_video.c | 3 +++ 1 file changed, 3 insertions(+) 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; From fd3a79ebb56c8d08fc5de034318bbb8d96814cf5 Mon Sep 17 00:00:00 2001 From: Lars Brinkhoff Date: Wed, 17 Sep 2025 08:28:28 +0200 Subject: [PATCH 2/6] 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. --- .github/workflows/build.yml | 2 +- cmake/simh-simulators.cmake | 2 + display/display.c | 13 + display/display.h | 1 + linc/CMakeLists.txt | 32 ++ linc/README.md | 135 +++++ linc/linc_cpu.c | 1068 +++++++++++++++++++++++++++++++++++ linc/linc_crt.c | 116 ++++ linc/linc_defs.h | 73 +++ linc/linc_dpy.c | 40 ++ linc/linc_kbd.c | 267 +++++++++ linc/linc_sys.c | 696 +++++++++++++++++++++++ linc/linc_tape.c | 470 +++++++++++++++ linc/linc_tty.c | 124 ++++ makefile | 22 +- 15 files changed, 3059 insertions(+), 2 deletions(-) create mode 100644 linc/CMakeLists.txt create mode 100644 linc/README.md create mode 100644 linc/linc_cpu.c create mode 100644 linc/linc_crt.c create mode 100644 linc/linc_defs.h create mode 100644 linc/linc_dpy.c create mode 100644 linc/linc_kbd.c create mode 100644 linc/linc_sys.c create mode 100644 linc/linc_tape.c create mode 100644 linc/linc_tty.c 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/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/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} From d4eb628d83f6c3ee95abd727a41a4974621aa91d Mon Sep 17 00:00:00 2001 From: Lars Brinkhoff Date: Sat, 18 Oct 2025 17:33:57 +0200 Subject: [PATCH 3/6] LINC: Test for teletype output. --- linc/tests/tty.do | 53 +++++++++++++++++++++++++++++++++++++++++++++ linc/tests/tty.lap6 | 53 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 linc/tests/tty.do create mode 100644 linc/tests/tty.lap6 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 From d049ac9918710929dcb6242e8306c7678759bab8 Mon Sep 17 00:00:00 2001 From: Lars Brinkhoff Date: Fri, 3 Oct 2025 22:05:53 +0200 Subject: [PATCH 4/6] LINC: Diagnostics for classic LINC. These are tests for the classic LINC, including the features new in 1965: 2048 words of memory, a Z register, an overflow flag, and an interrupt facility. --- linc/tests/.gitignore | 1 + linc/tests/classic-test.do | 112 +++++++++++++++++++++++++++++++++++ linc/tests/classic-test.linc | Bin 0 -> 271878 bytes linc/tests/linc_test.ini | 4 ++ 4 files changed, 117 insertions(+) create mode 100644 linc/tests/.gitignore create mode 100644 linc/tests/classic-test.do create mode 100644 linc/tests/classic-test.linc create mode 100644 linc/tests/linc_test.ini 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..418e37544 --- /dev/null +++ b/linc/tests/classic-test.do @@ -0,0 +1,112 @@ +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 +#Bad block. +;call test 31 iBETA1 035 +call test 32 iBETA2 036 +call test 33 iBETA3 037 +call test 34 iBETA4 040 +#Block 51 same as 52. Test 35 missing? +;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 0000000000000000000000000000000000000000..df7a9f2cd5faf1fc0bb7d6d81f99e51ec8845784 GIT binary patch literal 271878 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 Date: Fri, 10 Oct 2025 13:41:37 +0200 Subject: [PATCH 5/6] LINC: Fix test 31 and 35 on the diagnostics tape. Block 45 held damaged data, and block 51 was identical to block 52. --- linc/tests/classic-test.do | 6 ++---- linc/tests/classic-test.linc | Bin 271878 -> 271878 bytes 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/linc/tests/classic-test.do b/linc/tests/classic-test.do index 418e37544..859ff4042 100644 --- a/linc/tests/classic-test.do +++ b/linc/tests/classic-test.do @@ -58,13 +58,11 @@ call test 25 SCRT4 027 call test 26 ADDT1 031 call test 27 FADRT1 032 call test 30 FADRT2 033 -#Bad block. -;call test 31 iBETA1 035 +call test 31 iBETA1 035 call test 32 iBETA2 036 call test 33 iBETA3 037 call test 34 iBETA4 040 -#Block 51 same as 52. Test 35 missing? -;call test 35 LDAT1 041 +call test 35 LDAT1 041 call test 36 STAT1 042 call test 37 ADMT1 043 call test 40 LAMT1 044 diff --git a/linc/tests/classic-test.linc b/linc/tests/classic-test.linc index df7a9f2cd5faf1fc0bb7d6d81f99e51ec8845784..8f5ecd3f47c438bdb9160712eec45333a165e83c 100644 GIT binary patch delta 138 zcmZoWBhYq6V1XB-&>z{#M_ieRg;YO<DxpYs$6Y8U zG87ZJxK=Qh*usR1Bd!SX#Fs!qi6oW;NkvK~xfD`LCAANvkybkCWsp%OnPrhxHreHn zQ!csXkyk$X6;Mzig?;EFA1k7$PZU#J2_=vzu^)}dOlg+l+YMbqL*lCyD_SkEm{SE{jbjV>x9CgfbCw%97 zC!KOyj34~yjI+);?59Mn?H^ZNbKMR9`p-@OyXCe!?z-o` r2OfIlu_vB-=D8PMdgZk@;=J`vbW}uiAZ%D56o?%PgzX4~N1T5Tp1PTh From 4d38373206cd7c0ce8b94f5e16bd4429cb96430f Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Sun, 12 Oct 2025 07:58:51 +0200 Subject: [PATCH 6/6] LINC: Add Visual Studio project files. --- Visual Studio Projects/Simh.ci.sln | 2 + Visual Studio Projects/Simh.sln | 5 + Visual Studio Projects/linc.vcproj | 377 ++++++++++++++++++++ Visual Studio Projects/linc.vcxproj | 178 +++++++++ Visual Studio Projects/linc.vcxproj.filters | 135 +++++++ 5 files changed, 697 insertions(+) create mode 100644 Visual Studio Projects/linc.vcproj create mode 100644 Visual Studio Projects/linc.vcxproj create mode 100644 Visual Studio Projects/linc.vcxproj.filters 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