diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be41a285..8b5c07a0 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 bd456fa5..27e0c64a 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 020ea62d..4b2a8ed4 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 cce79f9f..ad18c0a4 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 00000000..bce48ea8 --- /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 00000000..57687814 --- /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 00000000..c8d174bc --- /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 00000000..d9d1ab84 --- /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 00000000..a1b9f26e --- /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 00000000..861873c8 --- /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 00000000..6a6ae1f8 --- /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 00000000..1de0d3ce --- /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 00000000..f5014769 --- /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 00000000..89044e8c --- /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 b60a6fee..3d98d72c 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}