/* pdp8_ct.c: PDP-8 cassette tape simulator

   Copyright (c) 2006, 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

   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.

   Note that the read and write sequences for the cassette are asymmetric:

   Read:    KLSA            /SELECT READ
            KGOA            /INIT READ, CLEAR DF
            <data flag sets, char in buf>
            KGOA            /READ 1ST CHAR, CLEAR DF
            DCA CHAR
            :
            <data flag sets, char in buf>
            KGOA            /READ LAST CHAR, CLEAR DF
            DCA CHAR
            <data flag sets, CRC1 in buf>
            KLSA            /SELECT CRC MODE
            KGOA            /READ 1ST CRC
            <data flag sets, CRC2 in buf>
            KGOA            /READ 2ND CRC
            <ready flag/CRC error flag sets>

   Write:   KLSA            /SELECT WRITE
            TAD CHAR        /1ST CHAR
            KGOA            /INIT WRITE, CHAR TO BUF, CLEAR DF
            <data flag sets, char to tape>
            :
            TAD CHAR        /LAST CHAR
            KGOA            /CHAR TO BUF, CLEAR DF
            <data flag sets, char to tape>
            KLSA            /SELECT CRC MODE
            KGOA            /WRITE CRC, CLEAR DF
            <ready flag sets, CRC on tape>
*/

#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;

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
    };

/* 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 ((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;
        }
    if (fnc == SRA_REW) ct_srb |= SRB_REW;              /* rew? set flag */
    if ((fnc != SRA_REW) && !(flg & OP_WRI)) {          /* read cmd? */
        uptr->UST = flg & UST_REV;                      /* save direction */
        if ((old_ust ^ uptr->UST) == (UST_REV|UST_GAP)) { /* rev in gap? */
            t_mtrlnt t;                                 /* skip tape mark */
            if (uptr->UST) 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) return ct_db;                    /* read? AC <- buf */
        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_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) ct_srb |= SRB_CRC;         /* rec in err? */
        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 ()) break;                    /* read, overrun? */
        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++) ct_xb[i] = 0;    /* clear buffer */
        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) ct_srb |= SRB_CRC;            /* must be zero */
            }
         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);
        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 */
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 = uptr = ct_dev.units + GET_UNIT (ct_sra); /* use sel unit */
    }
else if (ct_srb & SRB_EOF) uptr->UST |= UST_GAP;        /* save gap */
if (uptr) {                                             /* any unit? */
    ct_srb &= ~(SRB_WLK|SRB_BEOT|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_bot (uptr) || sim_tape_eot (uptr)) /* update BEOT */
            ct_srb |= SRB_BEOT;
        }
    if (sim_tape_wrp (uptr) || (ct_srb & SRB_REW))      /* locked or rew? */
        ct_srb |= SRB_WLK;                              /* set locked */
    }
if (ct_sra & SRA_ENAB) srb = ct_srb;                    /* can TA see TU60? */
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) ct_srb |= SRB_TIM;                 /* flag still set? */
ct_df = 1;                                              /* set data flag */
if (ct_sra & SRA_IE) int_req |= INT_CT;                 /* if ie, int req */
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 */
        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)) return SCPE_OK;          /* check attached */
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;
}
