/* pdp8_td.c: PDP-8 simple DECtape controller (TD8E) simulator

   Copyright (c) 1993-2013, 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.

   This module was inspired by Gerold Pauler's TD8E simulator for Doug Jones'
   PDP8 simulator but tracks the hardware implementation more closely.

   td           TD8E/TU56 DECtape

   17-Sep-13    RMS     Changed to use central set_bootpc routine
   23-Mar-11    RMS     Fixed SDLC to clear AC (from Dave Gesswein)
   23-Jun-06    RMS     Fixed switch conflict in ATTACH
   16-Aug-05    RMS     Fixed C++ declaration and cast problems
   09-Jan-04    RMS     Changed sim_fsize calling sequence, added STOP_OFFR

   PDP-8 DECtapes are represented in memory by fixed length buffer of 12b words.
   Three file formats are supported:

        18b/36b                 256 words per block [256 x 18b]
        16b                     256 words per block [256 x 16b]
        12b                     129 words per block [129 x 12b]

   When a 16b or 18/36b DECtape file is read in, it is converted to 12b format.

   DECtape motion is measured in 3b lines.  Time between lines is 33.33us.
   Tape density is nominally 300 lines per inch.  The format of a DECtape (as
   taken from the TD8E formatter) is:

        reverse end zone        8192 reverse end zone codes ~ 10 feet
        reverse buffer          200 interblock codes
        block 0
         :
        block n
        forward buffer          200 interblock codes
        forward end zone        8192 forward end zone codes ~ 10 feet

   A block consists of five 18b header words, a tape-specific number of data
   words, and five 18b trailer words.  All systems except the PDP-8 use a
   standard block length of 256 words; the PDP-8 uses a standard block length
   of 86 words (x 18b = 129 words x 12b).

   Because a DECtape file only contains data, the simulator cannot support
   write timing and mark track and can only do a limited implementation
   of non-data words.  Read assumes that the tape has been conventionally
   written forward:

        header word 0           0
        header word 1           block number (for forward reads)
        header words 2,3        0
        header word 4           checksum (for reverse reads)
        :
        trailer word 4          checksum (for forward reads)
        trailer words 3,2       0
        trailer word 1          block number (for reverse reads)
        trailer word 0          0

   Write modifies only the data words and dumps the non-data words in the
   bit bucket.
*/

#include "pdp8_defs.h"

#define DT_NUMDR        2                               /* #drives */
#define UNIT_V_8FMT     (UNIT_V_UF + 0)                 /* 12b format */
#define UNIT_V_11FMT    (UNIT_V_UF + 1)                 /* 16b format */
#define UNIT_8FMT       (1 << UNIT_V_8FMT)
#define UNIT_11FMT      (1 << UNIT_V_11FMT)
#define STATE           u3                              /* unit state */
#define LASTT           u4                              /* last time update */
#define WRITTEN         u5                              /* device buffer is dirty and needs flushing */

/* System independent DECtape constants */

#define DT_LPERMC       6                               /* lines per mark track */
#define DT_EZLIN        (8192 * DT_LPERMC)              /* end zone length */
#define DT_BFLIN        (200 * DT_LPERMC)               /* end zone buffer */
#define DT_HTLIN        (5 * DT_LPERMC)                 /* lines per hdr/trlr */

/* 16b, 18b, 36b DECtape constants */

#define D18_WSIZE       6                               /* word size in lines */
#define D18_BSIZE       384                             /* block size in 12b */
#define D18_TSIZE       578                             /* tape size */
#define D18_LPERB       (DT_HTLIN + (D18_BSIZE * DT_WSIZE) + DT_HTLIN)
#define D18_FWDEZ       (DT_EZLIN + (D18_LPERB * D18_TSIZE))
#define D18_CAPAC       (D18_TSIZE * D18_BSIZE)         /* tape capacity */

#define D18_NBSIZE      ((D18_BSIZE * D8_WSIZE) / D18_WSIZE)
#define D18_FILSIZ      (D18_NBSIZE * D18_TSIZE * sizeof (int32))
#define D11_FILSIZ      (D18_NBSIZE * D18_TSIZE * sizeof (int16))

/* 12b DECtape constants */

#define D8_WSIZE        4                               /* word size in lines */
#define D8_BSIZE        129                             /* block size in 12b */
#define D8_TSIZE        1474                            /* tape size */
#define D8_LPERB        (DT_HTLIN + (D8_BSIZE * DT_WSIZE) + DT_HTLIN)
#define D8_FWDEZ        (DT_EZLIN + (D8_LPERB * D8_TSIZE))
#define D8_CAPAC        (D8_TSIZE * D8_BSIZE)           /* tape capacity */
#define D8_FILSIZ       (D8_CAPAC * sizeof (int16))

/* This controller */

#define DT_CAPAC        D8_CAPAC                        /* default */
#define DT_WSIZE        D8_WSIZE

/* Calculated constants, per unit */

#define DTU_BSIZE(u)    (((u)->flags & UNIT_8FMT)? D8_BSIZE: D18_BSIZE)
#define DTU_TSIZE(u)    (((u)->flags & UNIT_8FMT)? D8_TSIZE: D18_TSIZE)
#define DTU_LPERB(u)    (((u)->flags & UNIT_8FMT)? D8_LPERB: D18_LPERB)
#define DTU_FWDEZ(u)    (((u)->flags & UNIT_8FMT)? D8_FWDEZ: D18_FWDEZ)
#define DTU_CAPAC(u)    (((u)->flags & UNIT_8FMT)? D8_CAPAC: D18_CAPAC)

#define DT_LIN2BL(p,u)  (((p) - DT_EZLIN) / DTU_LPERB (u))
#define DT_LIN2OF(p,u)  (((p) - DT_EZLIN) % DTU_LPERB (u))

/* Command register */

#define TDC_UNIT        04000                           /* unit select */
#define TDC_FWDRV       02000                           /* fwd/rev */
#define TDC_STPGO       01000                           /* stop/go */
#define TDC_RW          00400                           /* read/write */
#define TDC_MASK        07400                           /* implemented */
#define TDC_GETUNIT(x)  (((x) & TDC_UNIT)? 1: 0)

/* Status register */

#define TDS_WLO         00200                           /* write lock */
#define TDS_TME         00100                           /* timing/sel err */

/* Mark track register and codes */

#define MTK_MASK        077
#define MTK_REV_END     055                             /* rev end zone */
#define MTK_INTER       025                             /* interblock */
#define MTK_FWD_BLK     026                             /* fwd block */
#define MTK_REV_GRD     032                             /* reverse guard */
#define MTK_FWD_PRE     010                             /* lock, etc */
#define MTK_DATA        070                             /* data */
#define MTK_REV_PRE     073                             /* lock, etc */
#define MTK_FWD_GRD     051                             /* fwd guard */
#define MTK_REV_BLK     045                             /* rev block */
#define MTK_FWD_END     022                             /* fwd end zone */

/* DECtape state */

#define STA_STOP        0                               /* stopped */
#define STA_DEC         2                               /* decelerating */
#define STA_ACC         4                               /* accelerating */
#define STA_UTS         6                               /* up to speed */
#define STA_DIR         1                               /* fwd/rev */

#define ABS(x)          (((x) < 0)? (-(x)): (x))
#define MTK_BIT(c,p)    (((c) >> (DT_LPERMC - 1 - ((p) % DT_LPERMC))) & 1)

/* State and declarations */

int32 td_cmd = 0;                                       /* command */
int32 td_dat = 0;                                       /* data */
int32 td_mtk = 0;                                       /* mark track */
int32 td_slf = 0;                                       /* single line flag */
int32 td_qlf = 0;                                       /* quad line flag */
int32 td_tme = 0;                                       /* timing error flag */
int32 td_csum = 0;                                      /* save check sum */
int32 td_qlctr = 0;                                     /* quad line ctr */
int32 td_ltime = 20;                                    /* interline time */
int32 td_dctime = 40000;                                /* decel time */
int32 td_stopoffr = 0;
static uint8 tdb_mtk[DT_NUMDR][D18_LPERB];              /* mark track bits */

int32 td77 (int32 IR, int32 AC);
t_stat td_svc (UNIT *uptr);
t_stat td_reset (DEVICE *dptr);
t_stat td_attach (UNIT *uptr, CONST char *cptr);
void td_flush (UNIT *uptr);
t_stat td_detach (UNIT *uptr);
t_stat td_boot (int32 unitno, DEVICE *dptr);
t_bool td_newsa (int32 newf);
t_bool td_setpos (UNIT *uptr);
int32 td_header (UNIT *uptr, int32 blk, int32 line);
int32 td_trailer (UNIT *uptr, int32 blk, int32 line);
int32 td_read (UNIT *uptr, int32 blk, int32 line);
void td_write (UNIT *uptr, int32 blk, int32 line, int32 datb);
int32 td_set_mtk (int32 code, int32 u, int32 k);
t_stat td_show_pos (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
const char *td_description (DEVICE *dptr);

extern uint16 M[];

/* TD data structures

   td_dev       DT device descriptor
   td_unit      DT unit list
   td_reg       DT register list
   td_mod       DT modifier list
*/

DIB td_dib = { DEV_TD8E, 1, { &td77 } };

UNIT td_unit[] = {
    { UDATA (&td_svc, UNIT_8FMT+UNIT_FIX+UNIT_ATTABLE+
             UNIT_DISABLE+UNIT_ROABLE, DT_CAPAC) },
    { UDATA (&td_svc, UNIT_8FMT+UNIT_FIX+UNIT_ATTABLE+
             UNIT_DISABLE+UNIT_ROABLE, DT_CAPAC) }
    };

REG td_reg[] = {
    { GRDATAD (TDCMD, td_cmd, 8, 4, 8, "command register") },
    { ORDATAD (TDDAT, td_dat, 12, "data register") },
    { ORDATAD (TDMTK, td_mtk, 6, "mark track register") },
    { FLDATAD (TDSLF, td_slf, 0, "single line flag") },
    { FLDATAD (TDQLF, td_qlf, 0, "quad line flag") },
    { FLDATAD (TDTME, td_tme, 0, "timing error flag") },
    { ORDATAD (TDQL, td_qlctr, 2, "quad line counter") },
    { ORDATA (TDCSUM, td_csum, 6), REG_RO },
    { DRDATAD (LTIME, td_ltime, 31, "time between lines"), REG_NZ | PV_LEFT },
    { DRDATAD (DCTIME, td_dctime, 31, "time to decelerate to a full stop"), REG_NZ | PV_LEFT },
    { URDATAD (POS, td_unit[0].pos, 10, T_ADDR_W, 0,
              DT_NUMDR, PV_LEFT | REG_RO, "positions, in lines, units 0 and 1") },
    { URDATAD (STATT, td_unit[0].STATE, 8, 18, 0,
              DT_NUMDR, REG_RO, "unit state, units 0 and 1") },
    { URDATA (LASTT, td_unit[0].LASTT, 10, 32, 0,
              DT_NUMDR, REG_HRO) },
    { FLDATAD (STOP_OFFR, td_stopoffr, 0, "stop on off-reel error") },
    { ORDATA (DEVNUM, td_dib.dev, 6), REG_HRO },
    { NULL }
    };

MTAB td_mod[] = {
    { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", 
        &set_writelock, &show_writelock,   NULL, "Write enable drive" },
    { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", 
        &set_writelock, NULL,   NULL, "Write lock drive" },
    { UNIT_8FMT + UNIT_11FMT, 0, "18b", NULL, NULL },
    { UNIT_8FMT + UNIT_11FMT, UNIT_8FMT, "12b", NULL, NULL },
    { UNIT_8FMT + UNIT_11FMT, UNIT_11FMT, "16b", NULL, NULL },
    { MTAB_XTD|MTAB_VDV, 0, "DEVNO", "DEVNO",
      &set_dev, &show_dev, NULL },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 0, "POSITION", NULL, NULL, &td_show_pos },
    { 0 }
    };

DEVICE td_dev = {
    "TD", td_unit, td_reg, td_mod,
    DT_NUMDR, 8, 24, 1, 8, 12,
    NULL, NULL, &td_reset,
    &td_boot, &td_attach, &td_detach,
    &td_dib, DEV_DISABLE | DEV_DIS, 0,
    NULL, NULL, NULL, NULL, NULL, NULL,
    &td_description
    };

/* IOT routines */

int32 td77 (int32 IR, int32 AC)
{
int32 pulse = IR & 07;
int32 u = TDC_GETUNIT (td_cmd);                         /* get unit */
int32 diff, t;

switch (pulse) {

    case 01:                                            /* SDSS */
        if (td_slf)
            return AC | IOT_SKP;
        break;

    case 02:                                            /* SDST */
        if (td_tme)
            return AC | IOT_SKP;
        break;

    case 03:                                            /* SDSQ */
        if (td_qlf)
            return AC | IOT_SKP;
        break;

    case 04:                                            /* SDLC */
        td_tme = 0;                                     /* clear tim err */
        diff = (td_cmd ^ AC) & TDC_MASK;                /* cmd changes */
        td_cmd = AC & TDC_MASK;                         /* update cmd */
        if ((diff != 0) && (diff != TDC_RW)) {          /* signif change? */
            if (td_newsa (td_cmd))                      /* new command */
                return AC | (IORETURN (td_stopoffr, STOP_DTOFF) << IOT_V_REASON);
            }
        AC = 0;
        break;

    case 05:                                            /* SDLD */
        td_slf = 0;                                     /* clear flags */
        td_qlf = 0;
        td_qlctr = 0;
        td_dat = AC;                                    /* load data reg */
        break;

    case 06:                                            /* SDRC */
        td_slf = 0;                                     /* clear flags */
        td_qlf = 0;
        td_qlctr = 0;
        t = td_cmd | td_mtk;                            /* form status */
        if (td_tme || !(td_unit[u].flags & UNIT_ATT))   /* tim/sel err? */
            t = t | TDS_TME;
        if (td_unit[u].flags & UNIT_WPRT)               /* write locked? */
            t = t | TDS_WLO;
        return t;                                       /* return status */

    case 07:                                            /* SDRD */
        td_slf = 0;                                     /* clear flags */
        td_qlf = 0;
        td_qlctr = 0;
        return td_dat;                                  /* return data */
        }

return AC;
}

/* Command register change (start/stop, forward/reverse, new unit)

   1. If change in motion, stop to start
        - schedule up to speed
        - set function as next state
   2. If change in motion, start to stop, or change in direction
        - schedule stop
*/

t_bool td_newsa (int32 newf)
{
int32 prev_mving, new_mving, prev_dir, new_dir;
UNIT *uptr;

uptr = td_dev.units + TDC_GETUNIT (newf);               /* new unit */
if ((uptr->flags & UNIT_ATT) == 0)                      /* new unit attached? */
    return FALSE;

new_mving = ((newf & TDC_STPGO) != 0);                  /* new moving? */
prev_mving = (uptr->STATE != STA_STOP);                 /* previous moving? */
new_dir = ((newf & TDC_FWDRV) != 0);                    /* new dir? */
prev_dir = ((uptr->STATE & STA_DIR) != 0);              /* previous dir? */

td_mtk = 0;                                             /* mark trk reg cleared */

if (!prev_mving && !new_mving)                          /* stop from stop? */
    return FALSE;

if (new_mving && !prev_mving) {                         /* start from stop? */
    if (td_setpos (uptr))                               /* update pos */
        return TRUE;
    sim_cancel (uptr);                                  /* stop current */
    sim_activate (uptr, td_dctime - (td_dctime >> 2));  /* sched accel */
    uptr->STATE = STA_ACC | new_dir;                    /* set status */
    td_slf = td_qlf = td_qlctr = 0;                     /* clear state */
    return FALSE;
    }

if ((prev_mving && !new_mving) ||                       /* stop from moving? */
    (prev_dir != new_dir)) {                            /* dir chg while moving? */
    if (uptr->STATE >= STA_ACC) {                       /* not stopping? */
        if (td_setpos (uptr))                           /* update pos */
            return TRUE;
        sim_cancel (uptr);                              /* stop current */
        sim_activate (uptr, td_dctime);                 /* schedule decel */
        uptr->STATE = STA_DEC | prev_dir;               /* set status */
        td_slf = td_qlf = td_qlctr = 0;                 /* clear state */
        }
    return FALSE;
    }

return FALSE;   
}

/* Update DECtape position

   DECtape motion is modeled as a constant velocity, with linear
   acceleration and deceleration.  The motion equations are as follows:

        t       =       time since operation started
        tmax    =       time for operation (accel, decel only)
        v       =       at speed velocity in lines (= 1/td_ltime)

   Then:
        at speed dist = t * v
        accel dist = (t^2 * v) / (2 * tmax)
        decel dist = (((2 * t * tmax) - t^2) * v) / (2 * tmax)

   This routine uses the relative (integer) time, rather than the absolute
   (floating point) time, to allow save and restore of the start times.
*/

t_bool td_setpos (UNIT *uptr)
{
uint32 new_time, ut, ulin, udelt;
int32 delta = 0;

new_time = sim_grtime ();                               /* current time */
ut = new_time - uptr->LASTT;                            /* elapsed time */
if (ut == 0)                                            /* no time gone? exit */
    return FALSE;
uptr->LASTT = new_time;                                 /* update last time */
switch (uptr->STATE & ~STA_DIR) {                       /* case on motion */

    case STA_STOP:                                      /* stop */
        delta = 0;
        break;

    case STA_DEC:                                       /* slowing */
        ulin = ut / (uint32) td_ltime;
        udelt = td_dctime / td_ltime;
        delta = ((ulin * udelt * 2) - (ulin * ulin)) / (2 * udelt);
        break;

    case STA_ACC:                                       /* accelerating */
        ulin = ut / (uint32) td_ltime;
        udelt = (td_dctime - (td_dctime >> 2)) / td_ltime;
        delta = (ulin * ulin) / (2 * udelt);
        break;

    case STA_UTS:                                       /* at speed */
        delta = ut / (uint32) td_ltime;
        break;
        }

if (uptr->STATE & STA_DIR)                              /* update pos */
    uptr->pos = uptr->pos - delta;
else uptr->pos = uptr->pos + delta;
if (((int32) uptr->pos < 0) ||
    ((int32) uptr->pos > (DTU_FWDEZ (uptr) + DT_EZLIN))) {
    detach_unit (uptr);                                 /* off reel */
    sim_cancel (uptr);                                  /* no timing pulses */
    return TRUE;
    }
return FALSE;
}

/* Unit service - unit is either changing speed, or it is up to speed */

t_stat td_svc (UNIT *uptr)
{
int32 mot = uptr->STATE & ~STA_DIR;
int32 dir = uptr->STATE & STA_DIR;
int32 unum = uptr - td_dev.units;
int32 su = TDC_GETUNIT (td_cmd);
int32 mtkb, datb;

/* Motion cases

   Decelerating - if go, next state must be accel as specified by td_cmd
   Accelerating - next state must be up to speed, fall through
   Up to speed - process line */

if (mot == STA_STOP)                                    /* stopped? done */
    return SCPE_OK;
if ((uptr->flags & UNIT_ATT) == 0) {                    /* not attached? */
    uptr->STATE = uptr->pos = 0;                        /* also done */
    return SCPE_UNATT;
    }

switch (mot) {                                          /* case on motion */

    case STA_DEC:                                       /* deceleration */
        if (td_setpos (uptr))                           /* upd pos; off reel? */
            return IORETURN (td_stopoffr, STOP_DTOFF);
        if ((unum != su) || !(td_cmd & TDC_STPGO))      /* not sel or stop? */
            uptr->STATE = 0;                            /* stop */
        else {                                          /* selected and go */
            uptr->STATE = STA_ACC |                     /* accelerating */
                ((td_cmd & TDC_FWDRV)? STA_DIR: 0);     /* in new dir */
            sim_activate (uptr, td_dctime - (td_dctime >> 2));
            }
        return SCPE_OK;

    case STA_ACC:                                       /* accelerating */
        if (td_setpos (uptr))                           /* upd pos; off reel? */
            return IORETURN (td_stopoffr, STOP_DTOFF);
        uptr->STATE = STA_UTS | dir;                    /* set up to speed */
        break;

    case STA_UTS:                                       /* up to speed */
        if (dir)                                        /* adjust position */
            uptr->pos = uptr->pos - 1;
        else uptr->pos = uptr->pos + 1;
        uptr->LASTT = sim_grtime ();                    /* save time */
        if (((int32) uptr->pos < 0) ||                  /* off reel? */
           (uptr->pos >= (((uint32) DTU_FWDEZ (uptr)) + DT_EZLIN))) {
            detach_unit (uptr);
            return IORETURN (td_stopoffr, STOP_DTOFF);
            }
        break;                                          /* check function */
        }

/* At speed - process the current line

   Once the TD8E is running at speed, it operates line by line.  If reading,
   the current mark track bit is shifted into the mark track register, and
   the current data nibble (3b) is shifted into the data register.  If
   writing, the current mark track bit is shifted into the mark track
   register, the top nibble from the data register is written to tape, and
   the data register is shifted up.  The complexity here comes from 
   synthesizing the mark track, based on tape position, and the header data. */

sim_activate (uptr, td_ltime);                          /* sched next line */
if (unum != su)                                         /* not sel? done */
    return SCPE_OK;
td_slf = 1;                                             /* set single */
td_qlctr = (td_qlctr + 1) % DT_WSIZE;                   /* count words */
if (td_qlctr == 0) {                                    /* lines mod 4? */
    if (td_qlf) {                                       /* quad line set? */
        td_tme = 1;                                     /* timing error */
        td_cmd = td_cmd & ~TDC_RW;                      /* clear write */
        }
    else td_qlf = 1;                                    /* no, set quad */
    }

datb = 0;                                               /* assume no data */
if (uptr->pos < (DT_EZLIN - DT_BFLIN))                  /* rev end zone? */
    mtkb = MTK_BIT (MTK_REV_END, uptr->pos);
else if (uptr->pos < DT_EZLIN)                          /* rev buffer? */
    mtkb = MTK_BIT (MTK_INTER, uptr->pos);
else if (uptr->pos < ((uint32) DTU_FWDEZ (uptr))) {     /* data zone? */
    int32 blkno = DT_LIN2BL (uptr->pos, uptr);          /* block # */
    int32 lineno = DT_LIN2OF (uptr->pos, uptr);         /* line # within block */
    if (lineno < DT_HTLIN) {                            /* header? */
        if ((td_cmd & TDC_RW) == 0)                     /* read? */                     
            datb = td_header (uptr, blkno, lineno);     /* get nibble */
        }
    else if (lineno < (DTU_LPERB (uptr) - DT_HTLIN)) {  /* data? */
        if (td_cmd & TDC_RW)                            /* write? */
            td_write (uptr, blkno,                      /* write data nibble */
                      lineno - DT_HTLIN,                /* data rel line num */
                      (td_dat >> 9) & 07);
        else datb = td_read (uptr, blkno,               /* no, read */
                             lineno - DT_HTLIN);
        }
    else if ((td_cmd & TDC_RW) == 0)                    /* trailer; read? */
        datb = td_trailer (uptr, blkno, lineno -        /* get trlr nibble */
                           (DTU_LPERB (uptr) - DT_HTLIN));
    mtkb = tdb_mtk[unum][lineno];
    }
else if (uptr->pos < (((uint32) DTU_FWDEZ (uptr)) + DT_BFLIN))
    mtkb = MTK_BIT (MTK_INTER, uptr->pos);              /* fwd buffer? */
else mtkb = MTK_BIT (MTK_FWD_END, uptr->pos);           /* fwd end zone */

if (dir) {                                              /* reverse? */
    mtkb = mtkb ^ 01;                                   /* complement mark bit, */
    datb = datb ^ 07;                                   /* data bits */
    }
td_mtk = ((td_mtk << 1) | mtkb) & MTK_MASK;             /* shift mark reg */
td_dat = ((td_dat << 3) | datb) & 07777;                /* shift data reg */
return SCPE_OK;
}

/* Header read - reads out 18b words in 3b increments

        word    lines           contents
        0       0-5             0
        1       6-11            block number
        2       12-17           0
        3       18-23           0
        4       24-29           reverse checksum (0777777)
*/

int32 td_header (UNIT *uptr, int32 blk, int32 line)
{
int32 nibp;

switch (line) {

    case 8: case 9: case 10: case 11:                   /* block num */
        nibp = 3 * (DT_LPERMC - 1 - (line % DT_LPERMC));
        return (blk >> nibp) & 07;

    case 24: case 25: case 26: case 27: case 28: case 29: /* rev csum */
        return 07;                                      /* 777777 */

    default:
        return 0;
        }
}

/* Trailer read - reads out 18b words in 3b increments
   Checksum is stored to avoid double calculation

   word         lines           contents
   0            0-5             forward checksum (lines 0-1, rest 0)
   1            6-11            0
   2            12-17           0
   3            18-23           reverse block mark
   4            24-29           0

   Note that the reverse block mark (when read forward) appears
   as the complement obverse (3b nibbles swapped end for end and
   complemented).
*/

int32 td_trailer (UNIT *uptr, int32 blk, int32 line)
{
int32 nibp, i, ba;
int16 *fbuf= (int16 *) uptr->filebuf;

switch (line) {

    case 0:
        td_csum = 07777;                                /* init csum */
        ba = blk * DTU_BSIZE (uptr);
        for (i = 0; i < DTU_BSIZE (uptr); i++)          /* loop thru buf */
            td_csum = (td_csum ^ ~fbuf[ba + i]) & 07777;
        td_csum = ((td_csum >> 6) ^ td_csum) & 077;
        return (td_csum >> 3) & 07;

    case 1:
        return (td_csum & 07);

    case 18: case 19: case 20: case 21:
        nibp = 3 * (line % DT_LPERMC);
        return ((blk >> nibp) & 07) ^ 07;

    default:
        return 0;
        }
}

/* Data read - convert block number/data line # to offset in data array */

int32 td_read (UNIT *uptr, int32 blk, int32 line)
{
int16 *fbuf = (int16 *) uptr->filebuf;                  /* buffer */
uint32 ba = blk * DTU_BSIZE (uptr);                     /* block base */
int32 nibp = 3 * (DT_WSIZE - 1 - (line % DT_WSIZE));    /* nibble pos */

ba = ba + (line / DT_WSIZE);                            /* block addr */
return (fbuf[ba] >> nibp) & 07;                         /* get data nibble */
}

/* Data write - convert block number/data line # to offset in data array */

void td_write (UNIT *uptr, int32 blk, int32 line, int32 dat)
{
int16 *fbuf = (int16 *) uptr->filebuf;                  /* buffer */
uint32 ba = blk * DTU_BSIZE (uptr);                     /* block base */
int32 nibp = 3 * (DT_WSIZE - 1 - (line % DT_WSIZE));    /* nibble pos */

ba = ba + (line / DT_WSIZE);                            /* block addr */
fbuf[ba] = (fbuf[ba] & ~(07 << nibp)) | (dat << nibp);  /* upd data nibble */
uptr->WRITTEN = TRUE;
if (ba >= uptr->hwmark)                                 /* upd length */
    uptr->hwmark = ba + 1;
return;
}

/* Reset routine */

t_stat td_reset (DEVICE *dptr)
{
int32 i;
UNIT *uptr;

for (i = 0; i < DT_NUMDR; i++) {                        /* stop all activity */
    uptr = td_dev.units + i;
    if (sim_is_running) {                               /* CAF? */
        if (uptr->STATE >= STA_ACC) {                   /* accel or uts? */
            if (td_setpos (uptr))                       /* update pos */
                continue;
            sim_cancel (uptr);
            sim_activate (uptr, td_dctime);             /* sched decel */
            uptr->STATE = STA_DEC | (uptr->STATE & STA_DIR);
            }
         }
    else {
        sim_cancel (uptr);                              /* sim reset */
        uptr->STATE = 0;  
        uptr->LASTT = sim_grtime ();
        }
    }
td_slf = td_qlf = td_qlctr = 0;                         /* clear state */
td_cmd = td_dat = td_mtk = 0;
td_csum = 0;
return SCPE_OK;
}

/* Bootstrap routine - OS/8 only 

   1) Read reverse until reverse end zone (mark track is complement obverse)
   2) Read forward until mark track code 031.  This is a composite code from
      the last 4b of the forward block number and the first two bits of the
      reverse guard (01 -0110 01- 1010).  There are 16 lines before the first
      data word.
   3) Store data words from 7354 to end of page.  This includes header and
      trailer words.
   4) Continue at location 7400.
*/

#define BOOT_START      07300
#define BOOT_LEN        (sizeof (boot_rom) / sizeof (int16))

static const uint16 boot_rom[] = {
    01312,                      /* ST,  TAD L4MT        ;=2000, reverse */
    04312,                      /*      JMS L4MT        ; rev lk for 022 */
    04312,                      /*      JMS L4MT        ; fwd lk for 031 */
    06773,                      /* DAT, SDSQ            ; wait for 12b */
    05303,                      /*      JMP .-1 */
    06777,                      /*      SDRD            ; read word */
    03726,                      /*      DCA I BUF       ; store */
    02326,                      /*      ISZ BUF         ; incr ptr */
    05303,                      /*      JMP DAT         ; if not 0, cont */
    05732,                      /*      JMP I SCB       ; jump to boot */
    02000,                      /* L4MT,2000            ; overwritten */
    01300,                      /*      TAD ST          ; =1312, go */
    06774,                      /*      SDLC            ; new command */
    06771,                      /* MTK, SDSS            ; wait for mark */
    05315,                      /*      JMP .-1 */
    06776,                      /*      SDRC            ; get mark code */
    00331,                      /*      AND K77         ; mask to 6b */
    01327,                      /* CMP, TAD MCD         ; got target code? */
    07640,                      /*      SZA CLA         ; skip if yes */
    05315,                      /*      JMP MTK         ; wait for mark */
    02321,                      /*      ISZ CMP         ; next target */
    05712,                      /*      JMP I L4MT      ; exit */
    07354,                      /* BUF, 7354            ; loading point */
    07756,                      /* MCD, -22             ; target 1 */
    07747,                      /*      -31             ; target 2 */
    00077,                      /*      77              ; mask */
    07400                       /* SCB, 7400            ; secondary boot */
    };

t_stat td_boot (int32 unitno, DEVICE *dptr)
{
size_t i;

if (unitno)
    return SCPE_ARG;                                    /* only unit 0 */
if (td_dib.dev != DEV_TD8E)
    return STOP_NOTSTD;                                 /* only std devno */
td_unit[unitno].pos = DT_EZLIN;
for (i = 0; i < BOOT_LEN; i++)
    M[BOOT_START + i] = boot_rom[i];
cpu_set_bootpc (BOOT_START);
return SCPE_OK;
}

/* Attach routine

   Determine 12b, 16b, or 18b/36b format
   Allocate buffer
   If 16b or 18b, read 16b or 18b format and convert to 12b in buffer
   If 12b, read data into buffer
   Set up mark track bit array
*/

t_stat td_attach (UNIT *uptr, CONST char *cptr)
{
uint32 pdp18b[D18_NBSIZE];
uint16 pdp11b[D18_NBSIZE], *fbuf;
int32 i, k, mtkpb;
int32 u = uptr - td_dev.units;
t_stat r;
uint32 ba, sz;

r = attach_unit (uptr, cptr);                           /* attach */
if (r != SCPE_OK)                                       /* fail? */
    return r;
if ((sim_switches & SIM_SW_REST) == 0) {                /* not from rest? */
    uptr->flags = (uptr->flags | UNIT_8FMT) & ~UNIT_11FMT;
    if (sim_switches & SWMASK ('F'))                    /* att 18b? */
        uptr->flags = uptr->flags & ~UNIT_8FMT;
    else if (sim_switches & SWMASK ('S'))               /* att 16b? */
        uptr->flags = (uptr->flags | UNIT_11FMT) & ~UNIT_8FMT;
    else if (!(sim_switches & SWMASK ('A')) &&          /* autosize? */
        (sz = sim_fsize (uptr->fileref))) {
        if (sz == D11_FILSIZ)
            uptr->flags = (uptr->flags | UNIT_11FMT) & ~UNIT_8FMT;
        else if (sz > D8_FILSIZ)
            uptr->flags = uptr->flags & ~UNIT_8FMT;
        }
    }
uptr->capac = DTU_CAPAC (uptr);                         /* set capacity */
uptr->filebuf = calloc (uptr->capac, sizeof (int16));
if (uptr->filebuf == NULL) {                            /* can't alloc? */
    detach_unit (uptr);
    return SCPE_MEM;
    }
fbuf = (uint16 *) uptr->filebuf;                        /* file buffer */
sim_printf ("%s%d: ", sim_dname (&td_dev), u);
if (uptr->flags & UNIT_8FMT)
    sim_printf ("12b format");
else if (uptr->flags & UNIT_11FMT)
    sim_printf ("16b format");
else sim_printf ("18b/36b format");
sim_printf (", buffering file in memory\n");
uptr->io_flush = td_flush;
if (uptr->flags & UNIT_8FMT)                            /* 12b? */
    uptr->hwmark = fxread (uptr->filebuf, sizeof (uint16),
            uptr->capac, uptr->fileref);
else {                                                  /* 16b/18b */
    for (ba = 0; ba < uptr->capac; ) {                  /* loop thru file */
        if (uptr->flags & UNIT_11FMT) {
            k = fxread (pdp11b, sizeof (uint16), D18_NBSIZE, uptr->fileref);
            for (i = 0; i < k; i++)
                pdp18b[i] = pdp11b[i];
            }
        else k = fxread (pdp18b, sizeof (uint32), D18_NBSIZE, uptr->fileref);
        if (k == 0)
            break;
        for ( ; k < D18_NBSIZE; k++)
            pdp18b[k] = 0;
        for (k = 0; k < D18_NBSIZE; k = k + 2) {        /* loop thru blk */
            fbuf[ba] = (pdp18b[k] >> 6) & 07777;
            fbuf[ba + 1] = ((pdp18b[k] & 077) << 6) |
                ((pdp18b[k + 1] >> 12) & 077);
            fbuf[ba + 2] = pdp18b[k + 1] & 07777;
            ba = ba + 3;
            }                                           /* end blk loop */
        }                                               /* end file loop */
    uptr->hwmark = ba;
    }                                                   /* end else */
uptr->flags = uptr->flags | UNIT_BUF;                   /* set buf flag */
uptr->pos = DT_EZLIN;                                   /* beyond leader */
uptr->LASTT = sim_grtime ();                            /* last pos update */
uptr->STATE = STA_STOP;                                 /* stopped */

mtkpb = (DTU_BSIZE (uptr) * DT_WSIZE) / DT_LPERMC;      /* mtk codes per blk */
k = td_set_mtk (MTK_INTER, u, 0);                       /* fill mark track */
k = td_set_mtk (MTK_FWD_BLK, u, k);                     /* bit array */
k = td_set_mtk (MTK_REV_GRD, u, k);
for (i = 0; i < 4; i++)
    k = td_set_mtk (MTK_FWD_PRE, u, k);
for (i = 0; i < (mtkpb - 4); i++)
    k = td_set_mtk (MTK_DATA, u, k);
for (i = 0; i < 4; i++)
    k = td_set_mtk (MTK_REV_PRE, u, k);
k = td_set_mtk (MTK_FWD_GRD, u, k);
k = td_set_mtk (MTK_REV_BLK, u, k);
k = td_set_mtk (MTK_INTER, u, k);
return SCPE_OK;
}

/* Detach routine

   If 12b, write buffer to file
   If 16b or 18b, convert 12b buffer to 16b or 18b and write to file
   Deallocate buffer
*/

void td_flush (UNIT* uptr)
{
uint32 pdp18b[D18_NBSIZE];
uint16 pdp11b[D18_NBSIZE], *fbuf;
int32 i, k;
uint32 ba;

if (uptr->WRITTEN && uptr->hwmark && ((uptr->flags & UNIT_RO)== 0)) {    /* any data? */
    sim_printf ("%s: writing buffer to file: %s\n", sim_uname (uptr), uptr->filename);
    rewind (uptr->fileref);                             /* start of file */
    fbuf = (uint16 *) uptr->filebuf;                    /* file buffer */
    if (uptr->flags & UNIT_8FMT)                        /* PDP8? */
        fxwrite (uptr->filebuf, sizeof (uint16),        /* write file */
            uptr->hwmark, uptr->fileref);
    else {                                              /* 16b/18b */
        for (ba = 0; ba < uptr->hwmark; ) {             /* loop thru buf */
            for (k = 0; k < D18_NBSIZE; k = k + 2) {
                pdp18b[k] = ((uint32) (fbuf[ba] & 07777) << 6) |
                    ((uint32) (fbuf[ba + 1] >> 6) & 077);
                pdp18b[k + 1] = ((uint32) (fbuf[ba + 1] & 077) << 12) |
                    ((uint32) (fbuf[ba + 2] & 07777));
                ba = ba + 3;
                }                                       /* end loop blk */
            if (uptr->flags & UNIT_11FMT) {             /* 16b? */
                for (i = 0; i < D18_NBSIZE; i++)
                    pdp11b[i] = pdp18b[i];
                fxwrite (pdp11b, sizeof (uint16),
                    D18_NBSIZE, uptr->fileref);
                }
            else fxwrite (pdp18b, sizeof (uint32),
                D18_NBSIZE, uptr->fileref);
            }                                           /* end loop buf */
        }                                               /* end else */
    if (ferror (uptr->fileref))
        sim_perror ("I/O error");
    }
uptr->WRITTEN = FALSE;                                  /* no longer dirty */
}

t_stat td_detach (UNIT* uptr)
{
int u = (int)(uptr - td_dev.units);

if (!(uptr->flags & UNIT_ATT))
    return SCPE_OK;
if (uptr->hwmark && ((uptr->flags & UNIT_RO)== 0))      /* any data? */
    td_flush (uptr);
free (uptr->filebuf);                                   /* release buf */
uptr->flags = uptr->flags & ~UNIT_BUF;                  /* clear buf flag */
uptr->filebuf = NULL;                                   /* clear buf ptr */
uptr->flags = (uptr->flags | UNIT_8FMT) & ~UNIT_11FMT;  /* default fmt */
uptr->capac = DT_CAPAC;                                 /* default size */
uptr->pos = uptr->STATE = 0;
sim_cancel (uptr);                                      /* no more pulses */
return detach_unit (uptr);
}

/* Set mark track code into bit array */

int32 td_set_mtk (int32 code, int32 u, int32 k)
{
int32 i;

for (i = 5; i >= 0; i--)
    tdb_mtk[u][k++] = (code >> i) & 1;
return k;
}

/* Show position */

t_stat td_show_pos (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
if ((uptr->flags & UNIT_ATT) == 0) return SCPE_UNATT;
if (uptr->pos < DT_EZLIN)                               /* rev end zone? */
    fprintf (st, "Reverse end zone\n");
else if (uptr->pos < ((uint32) DTU_FWDEZ (uptr))) {     /* data zone? */
    int32 blkno = DT_LIN2BL (uptr->pos, uptr);          /* block # */
    int32 lineno = DT_LIN2OF (uptr->pos, uptr);         /* line # within block */
    fprintf (st, "Block %d, line %d, ", blkno, lineno);
    if (lineno < DT_HTLIN)                              /* header? */
        fprintf (st, "header cell %d, nibble %d\n",
            lineno / DT_LPERMC, lineno % DT_LPERMC);
    else if (lineno < (DTU_LPERB (uptr) - DT_HTLIN))    /* data? */
        fprintf (st, "data word %d, nibble %d\n",
            (lineno - DT_HTLIN) / DT_WSIZE, (lineno - DT_HTLIN) % DT_WSIZE);
    else fprintf (st, "trailer cell %d, nibble %d\n",
        (lineno - (DTU_LPERB (uptr) - DT_HTLIN)) / DT_LPERMC,
        (lineno - (DTU_LPERB (uptr) - DT_HTLIN)) % DT_LPERMC);
    }
else fprintf (st, "Forward end zone\n");                /* fwd end zone */
return SCPE_OK;
}

const char *td_description (DEVICE *dptr)
{
return "TD8E/TU56 DECtape";
}
