/* pdp8_ct.c: PDP-8 cassette tape simulator Copyright (c) 2006-2011, Robert M Supnik 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 ROBERT M SUPNIK 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 Robert M Supnik shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Robert M Supnik. ct TA8E/TU60 cassette tape 13-Aug-07 RMS Fixed handling of BEOT 06-Aug-07 RMS Foward op at BOT skips initial file gap 30-May-2007 RMS Fixed typo (Norm Lastovica) Magnetic tapes are represented as a series of variable records of the form: 32b byte count byte 0 byte 1 : byte n-2 byte n-1 32b byte count If the byte count is odd, the record is padded with an extra byte of junk. File marks are represented by a byte count of 0. Cassette format differs in one very significant way: it has file gaps rather than file marks. If the controller spaces or reads into a file gap and then reverses direction, the file gap is not seen again. This is in contrast to magnetic tapes, where the file mark is a character sequence and is seen again if direction is reversed. In addition, cassettes have an initial file gap which is automatically skipped on forward operations from beginning of tape. Note that the read and write sequences for the cassette are asymmetric: Read: KLSA /SELECT READ KGOA /INIT READ, CLEAR DF KGOA /READ 1ST CHAR, CLEAR DF DCA CHAR : KGOA /READ LAST CHAR, CLEAR DF DCA CHAR KLSA /SELECT CRC MODE KGOA /READ 1ST CRC KGOA /READ 2ND CRC Write: KLSA /SELECT WRITE TAD CHAR /1ST CHAR KGOA /INIT WRITE, CHAR TO BUF, CLEAR DF : TAD CHAR /LAST CHAR KGOA /CHAR TO BUF, CLEAR DF KLSA /SELECT CRC MODE KGOA /WRITE CRC, CLEAR DF */ #include "pdp8_defs.h" #include "sim_tape.h" #define CT_NUMDR 2 /* #drives */ #define FNC u3 /* unit function */ #define UST u4 /* unit status */ #define CT_MAXFR (CT_SIZE) /* max record lnt */ #define CT_SIZE 93000 /* chars/tape */ /* Status Register A */ #define SRA_ENAB 0200 /* enable */ #define SRA_V_UNIT 6 /* unit */ #define SRA_M_UNIT (CT_NUMDR - 1) #define SRA_V_FNC 3 /* function */ #define SRA_M_FNC 07 #define SRA_READ 00 #define SRA_REW 01 #define SRA_WRITE 02 #define SRA_SRF 03 #define SRA_WFG 04 #define SRA_SRB 05 #define SRA_CRC 06 #define SRA_SFF 07 #define SRA_2ND 010 #define SRA_IE 0001 /* int enable */ #define GET_UNIT(x) (((x) >> SRA_V_UNIT) & SRA_M_UNIT) #define GET_FNC(x) (((x) >> SRA_V_FNC) & SRA_M_FNC) /* Function code flags */ #define OP_WRI 01 /* op is a write */ #define OP_REV 02 /* op is rev motion */ #define OP_FWD 04 /* op is fwd motion */ /* Unit status flags */ #define UST_REV (OP_REV) /* last op was rev */ #define UST_GAP 01 /* last op hit gap */ /* Status Register B, ^ = computed on the fly */ #define SRB_WLE 0400 /* "write lock err" */ #define SRB_CRC 0200 /* CRC error */ #define SRB_TIM 0100 /* timing error */ #define SRB_BEOT 0040 /* ^BOT/EOT */ #define SRB_EOF 0020 /* end of file */ #define SRB_EMP 0010 /* ^drive empty */ #define SRB_REW 0004 /* rewinding */ #define SRB_WLK 0002 /* ^write locked */ #define SRB_RDY 0001 /* ^ready */ #define SRB_ALLERR (SRB_WLE|SRB_CRC|SRB_TIM|SRB_BEOT|SRB_EOF|SRB_EMP) #define SRB_XFRERR (SRB_WLE|SRB_CRC|SRB_TIM|SRB_EOF) extern int32 int_req, stop_inst; extern UNIT cpu_unit; extern FILE *sim_deb; uint32 ct_sra = 0; /* status reg A */ uint32 ct_srb = 0; /* status reg B */ uint32 ct_db = 0; /* data buffer */ uint32 ct_df = 0; /* data flag */ uint32 ct_write = 0; /* TU60 write flag */ uint32 ct_bptr = 0; /* buf ptr */ uint32 ct_blnt = 0; /* buf length */ int32 ct_stime = 1000; /* start time */ int32 ct_ctime = 100; /* char latency */ uint32 ct_stopioe = 1; /* stop on error */ uint8 *ct_xb = NULL; /* transfer buffer */ static uint8 ct_fnc_tab[SRA_M_FNC + 1] = { OP_FWD, 0 , OP_WRI|OP_FWD, OP_REV, OP_WRI|OP_FWD, OP_REV, 0, OP_FWD }; DEVICE ct_dev; int32 ct70 (int32 IR, int32 AC); t_stat ct_svc (UNIT *uptr); t_stat ct_reset (DEVICE *dptr); t_stat ct_attach (UNIT *uptr, char *cptr); t_stat ct_detach (UNIT *uptr); t_stat ct_boot (int32 unitno, DEVICE *dptr); uint32 ct_updsta (UNIT *uptr); int32 ct_go_start (int32 AC); int32 ct_go_cont (UNIT *uptr, int32 AC); t_stat ct_map_err (UNIT *uptr, t_stat st); UNIT *ct_busy (void); void ct_set_df (t_bool timchk); t_bool ct_read_char (void); uint32 ct_crc (uint8 *buf, uint32 cnt); /* CT data structures ct_dev CT device descriptor ct_unit CT unit list ct_reg CT register list ct_mod CT modifier list */ DIB ct_dib = { DEV_CT, 1, { &ct70 } }; UNIT ct_unit[] = { { UDATA (&ct_svc, UNIT_ATTABLE+UNIT_ROABLE, CT_SIZE) }, { UDATA (&ct_svc, UNIT_ATTABLE+UNIT_ROABLE, CT_SIZE) }, }; REG ct_reg[] = { { ORDATA (CTSRA, ct_sra, 8) }, { ORDATA (CTSRB, ct_srb, 8) }, { ORDATA (CTDB, ct_db, 8) }, { FLDATA (CTDF, ct_df, 0) }, { FLDATA (RDY, ct_srb, 0) }, { FLDATA (WLE, ct_srb, 8) }, { FLDATA (WRITE, ct_write, 0) }, { FLDATA (INT, int_req, INT_V_CT) }, { DRDATA (BPTR, ct_bptr, 17) }, { DRDATA (BLNT, ct_blnt, 17) }, { DRDATA (STIME, ct_stime, 24), PV_LEFT + REG_NZ }, { DRDATA (CTIME, ct_ctime, 24), PV_LEFT + REG_NZ }, { FLDATA (STOP_IOE, ct_stopioe, 0) }, { URDATA (UFNC, ct_unit[0].FNC, 8, 4, 0, CT_NUMDR, 0), REG_HRO }, { URDATA (UST, ct_unit[0].UST, 8, 2, 0, CT_NUMDR, 0), REG_HRO }, { URDATA (POS, ct_unit[0].pos, 10, T_ADDR_W, 0, CT_NUMDR, PV_LEFT | REG_RO) }, { FLDATA (DEVNUM, ct_dib.dev, 6), REG_HRO }, { NULL } }; MTAB ct_mod[] = { { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL }, { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL }, // { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT", // &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, { MTAB_XTD|MTAB_VUN, 0, "CAPACITY", NULL, NULL, &sim_tape_show_capac, NULL }, { MTAB_XTD|MTAB_VDV, 0, "DEVNO", "DEVNO", &set_dev, &show_dev, NULL }, { 0 } }; DEVICE ct_dev = { "CT", ct_unit, ct_reg, ct_mod, CT_NUMDR, 10, 31, 1, 8, 8, NULL, NULL, &ct_reset, &ct_boot, &ct_attach, &ct_detach, &ct_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG }; /* IOT routines */ int32 ct70 (int32 IR, int32 AC) { int32 srb; UNIT *uptr; srb = ct_updsta (NULL); /* update status */ switch (IR & 07) { /* decode IR<9:11> */ case 0: /* KCLR */ ct_reset (&ct_dev); /* reset the world */ break; case 1: /* KSDR */ if (ct_df) AC |= IOT_SKP; break; case 2: /* KSEN */ if (srb & SRB_ALLERR) AC |= IOT_SKP; break; case 3: /* KSBF */ if ((srb & SRB_RDY) && !(srb & SRB_EMP)) AC |= IOT_SKP; break; case 4: /* KLSA */ ct_sra = AC & 0377; ct_updsta (NULL); return ct_sra ^ 0377; case 5: /* KSAF */ if (ct_df || (srb & (SRB_ALLERR|SRB_RDY))) AC |= IOT_SKP; break; case 6: /* KGOA */ ct_df = 0; /* clear data flag */ if ((uptr = ct_busy ())) /* op in progress? */ AC = ct_go_cont (uptr, AC); /* yes */ else AC = ct_go_start (AC); /* no, start */ ct_updsta (NULL); break; case 7: /* KSRB */ return srb & 0377; } /* end switch */ return AC; } /* Start a new operation - cassette is not busy */ int32 ct_go_start (int32 AC) { UNIT *uptr = ct_dev.units + GET_UNIT (ct_sra); uint32 fnc = GET_FNC (ct_sra); uint32 flg = ct_fnc_tab[fnc]; uint32 old_ust = uptr->UST; if (DEBUG_PRS (ct_dev)) fprintf (sim_deb, ">>CT start: op=%o, old_sta = %o, pos=%d\n", fnc, uptr->UST, uptr->pos); if ((ct_sra & SRA_ENAB) && (uptr->flags & UNIT_ATT)) { /* enabled, att? */ ct_srb &= ~(SRB_XFRERR|SRB_REW); /* clear err, rew */ if (flg & OP_WRI) { /* write-type op? */ if (sim_tape_wrp (uptr)) { /* locked? */ ct_srb |= SRB_WLE; /* set flag, abort */ return AC; } ct_write = 1; /* set TU60 wr flag */ ct_db = AC & 0377; } else { ct_write = 0; ct_db = 0; } ct_srb &= ~SRB_BEOT; /* tape in motion */ if (fnc == SRA_REW) /* rew? set flag */ ct_srb |= SRB_REW; if ((fnc != SRA_REW) && !(flg & OP_WRI)) { /* read cmd? */ t_mtrlnt t; t_stat st; uptr->UST = flg & UST_REV; /* save direction */ if (sim_tape_bot (uptr) && (flg & OP_FWD)) { /* spc/read fwd bot? */ st = sim_tape_rdrecf (uptr, ct_xb, &t, CT_MAXFR); /* skip file gap */ if (st != MTSE_TMK) /* not there? */ sim_tape_rewind (uptr); /* restore tap pos */ else old_ust = 0; /* defang next */ } if ((old_ust ^ uptr->UST) == (UST_REV|UST_GAP)) { /* rev in gap? */ if (DEBUG_PRS (ct_dev)) fprintf (sim_deb, ">>CT skip gap: op=%o, old_sta = %o, pos=%d\n", fnc, uptr->UST, uptr->pos); if (uptr->UST) /* skip file gap */ sim_tape_rdrecr (uptr, ct_xb, &t, CT_MAXFR); else sim_tape_rdrecf (uptr, ct_xb, &t, CT_MAXFR); } } else uptr->UST = 0; ct_bptr = 0; /* init buffer */ ct_blnt = 0; uptr->FNC = fnc; /* save function */ sim_activate (uptr, ct_stime); /* schedule op */ } if ((fnc == SRA_READ) || (fnc == SRA_CRC)) /* read or CRC? */ return 0; /* get "char" */ return AC; } /* Continue an in-progress operation - cassette is in motion */ int32 ct_go_cont (UNIT *uptr, int32 AC) { int32 fnc = GET_FNC (ct_sra); switch (fnc) { /* case on function */ case SRA_READ: /* read */ return ct_db; /* return data */ case SRA_WRITE: /* write */ ct_db = AC & 0377; /* save data */ break; case SRA_CRC: /* CRC */ if ((uptr->FNC & SRA_M_FNC) != SRA_CRC) /* if not CRC */ uptr->FNC = SRA_CRC; /* start CRC seq */ if (!ct_write) /* read? AC <- buf */ return ct_db; break; default: break; } return AC; } /* Unit service */ t_stat ct_svc (UNIT *uptr) { uint32 i, crc; uint32 flgs = ct_fnc_tab[uptr->FNC & SRA_M_FNC]; t_mtrlnt tbc; t_stat st, r; if ((uptr->flags & UNIT_ATT) == 0) { /* not attached? */ ct_updsta (uptr); /* update status */ return (ct_stopioe? SCPE_UNATT: SCPE_OK); } if (((flgs & OP_REV) && sim_tape_bot (uptr)) || /* rev at BOT or */ ((flgs & OP_FWD) && sim_tape_eot (uptr))) { /* fwd at EOT? */ ct_srb |= SRB_BEOT; /* error */ ct_updsta (uptr); /* op done */ return SCPE_OK; } r = SCPE_OK; switch (uptr->FNC) { /* case on function */ case SRA_READ: /* read start */ st = sim_tape_rdrecf (uptr, ct_xb, &ct_blnt, CT_MAXFR); /* get rec */ if (st == MTSE_RECE) /* rec in err? */ ct_srb |= SRB_CRC; else if (st != MTSE_OK) { /* other error? */ r = ct_map_err (uptr, st); /* map error */ break; } crc = ct_crc (ct_xb, ct_blnt); /* calculate CRC */ ct_xb[ct_blnt++] = (crc >> 8) & 0377; /* append to buffer */ ct_xb[ct_blnt++] = crc & 0377; uptr->FNC |= SRA_2ND; /* next state */ sim_activate (uptr, ct_ctime); /* sched next char */ return SCPE_OK; case SRA_READ|SRA_2ND: /* read char */ if (!ct_read_char ()) /* read, overrun? */ break; ct_set_df (TRUE); /* set data flag */ sim_activate (uptr, ct_ctime); /* sched next char */ return SCPE_OK; case SRA_WRITE: /* write start */ for (i = 0; i < CT_MAXFR; i++) /* clear buffer */ ct_xb[i] = 0; uptr->FNC |= SRA_2ND; /* next state */ sim_activate (uptr, ct_ctime); /* sched next char */ return SCPE_OK; case SRA_WRITE|SRA_2ND: /* write char */ if ((ct_bptr < CT_MAXFR) && /* room in buf? */ ((uptr->pos + ct_bptr) < uptr->capac)) /* room on tape? */ ct_xb[ct_bptr++] = ct_db; /* store char */ ct_set_df (TRUE); /* set data flag */ sim_activate (uptr, ct_ctime); /* sched next char */ return SCPE_OK; case SRA_CRC: /* CRC */ if (ct_write) { /* write? */ if ((st = sim_tape_wrrecf (uptr, ct_xb, ct_bptr))) /* write, err? */ r = ct_map_err (uptr, st); /* map error */ break; /* write done */ } ct_read_char (); /* get second CRC */ ct_set_df (FALSE); /* set df */ uptr->FNC |= SRA_2ND; /* next state */ sim_activate (uptr, ct_ctime); return SCPE_OK; case SRA_CRC|SRA_2ND: /* second read CRC */ if (ct_bptr != ct_blnt) { /* partial read? */ crc = ct_crc (ct_xb, ct_bptr); /* actual CRC */ if (crc != 0) /* must be zero */ ct_srb |= SRB_CRC; } break; /* read done */ case SRA_WFG: /* write file gap */ if ((st = sim_tape_wrtmk (uptr))) /* write tmk, err? */ r = ct_map_err (uptr, st); /* map error */ break; case SRA_REW: /* rewind */ sim_tape_rewind (uptr); ct_srb |= SRB_BEOT; /* set BOT */ break; case SRA_SRB: /* space rev blk */ if ((st = sim_tape_sprecr (uptr, &tbc))) /* space rev, err? */ r = ct_map_err (uptr, st); /* map error */ break; case SRA_SRF: /* space rev file */ while ((st = sim_tape_sprecr (uptr, &tbc)) == MTSE_OK) ; r = ct_map_err (uptr, st); /* map error */ break; case SRA_SFF: /* space fwd file */ while ((st = sim_tape_sprecf (uptr, &tbc)) == MTSE_OK) ; r = ct_map_err (uptr, st); /* map error */ break; default: /* never get here! */ return SCPE_IERR; } /* end case */ ct_updsta (uptr); /* update status */ if (DEBUG_PRS (ct_dev)) fprintf (sim_deb, ">>CT done: op=%o, statusA = %o, statusB = %o, pos=%d\n", uptr->FNC, ct_sra, ct_srb, uptr->pos); return r; } /* Update controller status */ uint32 ct_updsta (UNIT *uptr) { int32 srb; if (uptr == NULL) { /* unit specified? */ uptr = ct_busy (); /* use busy unit */ if ((uptr == NULL) && (ct_sra & SRA_ENAB)) /* none busy? */ uptr = ct_dev.units + GET_UNIT (ct_sra); /* use sel unit */ } else if (ct_srb & SRB_EOF) /* save gap */ uptr->UST |= UST_GAP; if (uptr) { /* any unit? */ ct_srb &= ~(SRB_WLK|SRB_EMP|SRB_RDY); /* clear dyn flags */ if ((uptr->flags & UNIT_ATT) == 0) /* unattached? */ ct_srb = (ct_srb | SRB_EMP|SRB_WLK) & ~SRB_REW; /* empty, locked */ if (!sim_is_active (uptr)) { /* not busy? */ ct_srb = (ct_srb | SRB_RDY) & ~SRB_REW; /* ready, ~rew */ } if (sim_tape_wrp (uptr) || (ct_srb & SRB_REW)) /* locked or rew? */ ct_srb |= SRB_WLK; /* set locked */ } if (ct_sra & SRA_ENAB) /* can TA see TU60? */ srb = ct_srb; else srb = 0; /* no */ if ((ct_sra & SRA_IE) && /* int enabled? */ (ct_df || (srb & (SRB_ALLERR|SRB_RDY)))) /* any flag? */ int_req |= INT_CT; /* set int req */ else int_req &= ~INT_CT; /* no, clr int req */ return srb; } /* Set data flag */ void ct_set_df (t_bool timchk) { if (ct_df && timchk) /* flag still set? */ ct_srb |= SRB_TIM; ct_df = 1; /* set data flag */ if (ct_sra & SRA_IE) /* if ie, int req */ int_req |= INT_CT; return; } /* Read character */ t_bool ct_read_char (void) { if (ct_bptr < ct_blnt) { /* more chars? */ ct_db = ct_xb[ct_bptr++]; return TRUE; } ct_db = 0; ct_srb |= SRB_CRC; /* overrun */ return FALSE; } /* Test if controller busy */ UNIT *ct_busy (void) { uint32 u; UNIT *uptr; for (u = 0; u < CT_NUMDR; u++) { /* loop thru units */ uptr = ct_dev.units + u; if (sim_is_active (uptr)) return uptr; } return NULL; } /* Calculate CRC on buffer */ uint32 ct_crc (uint8 *buf, uint32 cnt) { uint32 crc, i, j; crc = 0; for (i = 0; i < cnt; i++) { crc = crc ^ (((uint32) buf[i]) << 8); for (j = 0; j < 8; j++) { if (crc & 1) crc = (crc >> 1) ^ 0xA001; else crc = crc >> 1; } } return crc; } /* Map error status */ t_stat ct_map_err (UNIT *uptr, t_stat st) { switch (st) { case MTSE_FMT: /* illegal fmt */ case MTSE_UNATT: /* unattached */ ct_srb |= SRB_CRC; case MTSE_OK: /* no error */ return SCPE_IERR; /* never get here! */ case MTSE_TMK: /* end of file */ ct_srb |= SRB_EOF; break; case MTSE_IOERR: /* IO error */ ct_srb |= SRB_CRC; /* set crc err */ if (ct_stopioe) return SCPE_IOERR; break; case MTSE_INVRL: /* invalid rec lnt */ ct_srb |= SRB_CRC; /* set crc err */ return SCPE_MTRLNT; case MTSE_RECE: /* record in error */ case MTSE_EOM: /* end of medium */ ct_srb |= SRB_CRC; /* set crc err */ break; case MTSE_BOT: /* reverse into BOT */ ct_srb |= SRB_BEOT; /* set BOT */ break; case MTSE_WRP: /* write protect */ ct_srb |= SRB_WLE; /* set wlk err */ break; } return SCPE_OK; } /* Reset routine */ t_stat ct_reset (DEVICE *dptr) { uint32 u; UNIT *uptr; ct_sra = 0; ct_srb = 0; ct_df = 0; ct_db = 0; ct_write = 0; ct_bptr = 0; ct_blnt = 0; int_req = int_req & ~INT_CT; /* clear interrupt */ for (u = 0; u < CT_NUMDR; u++) { /* loop thru units */ uptr = ct_dev.units + u; sim_cancel (uptr); /* cancel activity */ sim_tape_reset (uptr); /* reset tape */ } if (ct_xb == NULL) ct_xb = (uint8 *) calloc (CT_MAXFR + 2, sizeof (uint8)); if (ct_xb == NULL) return SCPE_MEM; return SCPE_OK; } /* Attach routine */ t_stat ct_attach (UNIT *uptr, char *cptr) { t_stat r; r = sim_tape_attach (uptr, cptr); if (r != SCPE_OK) return r; ct_updsta (NULL); uptr->UST = 0; return r; } /* Detach routine */ t_stat ct_detach (UNIT* uptr) { t_stat r; if (!(uptr->flags & UNIT_ATT)) /* check attached */ return SCPE_OK; r = sim_tape_detach (uptr); ct_updsta (NULL); uptr->UST = 0; return r; } /* Bootstrap routine */ #define BOOT_START 04000 #define BOOT_LEN (sizeof (boot_rom) / sizeof (int16)) static const uint16 boot_rom[] = { 01237, /* BOOT, TAD M50 /change CRC to REW */ 01206, /* CRCCHK, TAD L260 /crc op */ 06704, /* KLSA /load op */ 06706, /* KGOA /start */ 06703, /* KSBF /ready? */ 05204, /* RDCOD, JMP .-1 /loop */ 07264, /* L260, CML STA RAL /L = 1, AC = halt */ 06702, /* KSEN /error? */ 07610, /* SKP CLA /halt on any error */ 03211, /* DCA . /except REW or FFG */ 03636, /* DCA I PTR /TAD I PTR mustn't change L */ 01205, /* TAD RDCOD /read op */ 06704, /* KLSA /load op */ 06706, /* KGOA /start */ 06701, /* LOOP, KSDF /data ready? */ 05216, /* JMP .-1 /loop */ 07002, /* BSW /to upper 6b */ 07430, /* SZL /second byte? */ 01636, /* TAD I PTR /yes */ 07022, /* CML BSW /swap back */ 03636, /* DCA I PTR /store in mem */ 07420, /* SNL /done with both bytes? */ 02236, /* ISZ PTR /yes, bump mem ptr */ 02235, /* ISZ KNT /done with record? */ 05215, /* JMP LOOP /next byte */ 07346, /* STA CLL RTL */ 07002, /* BSW /AC = 7757 */ 03235, /* STA KNT /now read 200 byte record */ 05201, /* JMP CRCCHK /go check CRC */ 07737, /* KNT, 7737 /1's compl of byte count */ 03557, /* PTR, 3557 /load point */ 07730, /* M50, 7730 /CLA SPA SZL */ }; t_stat ct_boot (int32 unitno, DEVICE *dptr) { int32 i; extern int32 saved_PC; extern uint16 M[]; if ((ct_dib.dev != DEV_CT) || unitno) /* only std devno */ return STOP_NOTSTD; for (i = 0; i < BOOT_LEN; i++) M[BOOT_START + i] = boot_rom[i]; saved_PC = BOOT_START; return SCPE_OK; }