mirror of
https://github.com/simh/simh.git
synced 2026-01-11 23:52:58 +00:00
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.
1069 lines
22 KiB
C
1069 lines
22 KiB
C
/* 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();
|
||
}
|