#include "pdp6.h" #include #include char *dt_ident = DT_IDENT; char *dx_ident = DX_IDENT; // TODO: find out how this should be controlled #define DEVNO 1 enum { // DTSIZE = 922512 // 01102 blocks, 2700 word for start/end each + safe zone DTSIZE = 987288 + 1000 }; /* Transport */ static uchar dxread(Dx555 *dx) { if(dx->dt->ut_rev) return *dx->cur ^ 017; else return *dx->cur; } static void dxwrite(Dx555 *dx, uchar d) { if(dx->dt->ut_rev) *dx->cur = d ^ 017; else *dx->cur = d; } static void dxmove(Dx555 *dx) { // At end of tape read the last word forever if(dx->dt->ut_rev){ dx->cur--; if(dx->cur < dx->start) dx->cur = dx->start+5; }else{ dx->cur++; if(dx->cur >= dx->end) dx->cur = dx->end-6; } } word dxex(Device *dev, const char *reg) { Dx555 *dx; dx = (Dx555*)dev; if(strcmp(reg, "pos") == 0) return dx->cur - dx->start; else if(strcmp(reg, "size") == 0) return dx->size; return ~0; } int dxdep(Device *dev, const char *reg, word data) { Dx555 *dx; dx = (Dx555*)dev; if(strcmp(reg, "pos") == 0){ if(data == 0) dx->cur = dx->start; else if(data == 1) dx->cur = dx->end; }else return 1; return 0; } static void resetdx(Dx555 *dx) { dx->size = DTSIZE; dx->cur = dx->start; dx->end = dx->start + dx->size; memset(dx->start, 0, dx->size); } static void dxunmount(Device *dev) { Dx555 *dx; dx = (Dx555*)dev; if(dx->fd < 0) return; lseek(dx->fd, 0, SEEK_SET); write(dx->fd, dx->start, dx->size); close(dx->fd); dx->fd = -1; resetdx(dx); } static void dxmount(Device *dev, const char *path) { Dx555 *dx; dx = (Dx555*)dev; dxunmount(dev); dx->fd = open(path, O_RDWR | O_CREAT, 0666); if(dx->fd < 0) fprintf(stderr, "couldn't open file %s\n", path); else{ /* Resize buffer to fit file */ dx->size = read(dx->fd, dx->start, dx->size); /* Use full buffer if file isn't a valid tape */ if(dx->size < 6) dx->size = DTSIZE; dx->cur = dx->start; dx->end = dx->start + dx->size; } } /* Connect a transport to a control */ void dxconn(Dt551 *dt, Dx555 *dx, int n) { dt->dx[n%8] = dx; dx->dt = dt; dx->unit = n; } static Device dxproto = { nil, nil, "", dxmount, dxunmount, nil, dxex, dxdep }; Device* makedx(int argc, char *argv[]) { Dx555 *dx; dx = malloc(sizeof(Dx555)); memset(dx, 0, sizeof(Dx555)); dx->dev = dxproto; dx->dev.type = dx_ident; dx->fd = -1; dx->start = malloc(DTSIZE); resetdx(dx); return &dx->dev; } /* Control */ int dtdbg; enum { TapeEndF = 022, TapeEndR = 055, /* block mark high half */ BlockSpace = 025, /* block mark low half */ BlockEndF = 026, BlockEndR = 045, DataSync = 032, /* rev guard */ BlockSync = 051, /* guard */ DataEndF = 073, /* pre-final, final, ck, rev lock */ DataEndR = 010, /* lock, rev ck, rev final, rev pre-final */ Data = 070, RW_NULL = 0, RW_RQ = 1, RW_ACTIVE = 2, }; #define DATA_SYNC (dt->tmk == (0600|DataSync)) #define END_ZONE (dt->tmk == (0600|TapeEndF)) #define BM_SPACE ((dt->tmk&0477) == (0400|BlockSpace)) #define BM_END (dt->tmk == (0500|BlockEndF)) #define BM_SYNC (dt->tmk == (0700|BlockSync)) #define REV_BM (dt->tmk == (0500|BlockEndR)) #define FWD_DATA_END (dt->tmk == (0400|DataEndF) ||\ dt->tmk == (0700|DataEndF)) #define REV_DATA_END (dt->tmk == (0400|DataEndR) ||\ dt->tmk == (0600|DataEndR)) #define DATA (dt->tmk == (0400|Data)) #define STOP (dt->tdata == 0 || dt->tdata == 1) #define SYNC (dt->tdata == 0200) #define REV_CKS (dt->tdata == 0100) #define BLOCK (dt->tdata == 040 || dt->tdata == 020 || dt->tdata == 010) #define PRE_FINAL (dt->tdata == 004) #define FWD_CKS (dt->tdata == 002) #define UT_WRITE ((dt->ut_fcn & 04) == 04) #define UT_READ ((dt->ut_fcn & 04) == 0) #define UT_ALL ((dt->ut_fcn & 03) == 01) #define UT_BM ((dt->ut_fcn & 03) == 02) #define UT_DATA ((dt->ut_fcn & 03) == 03) #define UT_DN (dt->ut_fcn == 0) #define UT_WRTM (dt->ut_fcn == 04) #define DC_SELECT1 (dt->dc->devp[dt->dc->device] == dt) #define UT_WRITE_PREVENT (UT_WRITE && dt->seldx->wrlock) #define UT_WREN_DATA (UT_WRITE && dt->ut_wren && !UT_WRITE_PREVENT) #define UT_WREN_BTM (UT_WREN_DATA && dt->ut_btm_switch && UT_WRTM) // 3-14 #define RW_ODD (!dt->tct && dt->rw_state == RW_ACTIVE) // 3-6 #define RW_EVEN (dt->tct && dt->rw_state == RW_ACTIVE) #define RW_BM_DONE (UT_BM && BM_END) #define RW_DATA_CONT (DC_SELECT1 && !dt->dc->da_rq && UT_DATA && prev.rw_state == RW_ACTIVE) // TODO: what is RW_DATA_WR_CONT? // TODO: this is really RW_DATA_WR_STOP #define RW_DATA_STOP (UTE_DC_DISCONNECT && UT_DATA && prev.rw_state == RW_ACTIVE) #define TBM0 (dt->tbm == 10) #define TBM3 (dt->tbm == 1) #define UTE_UNIT_OK (nunits == 1) #define UTE_DC_DISCONNECT (!(DC_SELECT1 && !dt->dc->da_rq)) // all but block space and rev block mark #define UTE_MK (DATA_SYNC || END_ZONE || BM_END ||\ BM_SYNC || FWD_DATA_END || REV_DATA_END || DATA) #define UTE_ERROR (dt->uteck && (prev.utek == 040) != UTE_MK) // TODO: not sure if BM SYNC || DATA SYNC is correct #define UTE_ACTIVE_ERROR (!UT_ALL && prev.rw_state == RW_ACTIVE && (BM_SYNC || DATA_SYNC)) static void dbg(char *fmt, ...) { if(dtdbg){ va_list ap; va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); } } //#define debug(...) dbg(__VA_ARGS__) #define debug(...) #define PRINT1 \ printf("%c%02o(%o,%02o,%d)", dt->rw_state == RW_ACTIVE ? '+' : dt->rw_state == RW_RQ ? '-' : ' ', \ dt->tmk&077, l&07, dt->rwb, dt->tct); \ putchar(' '); \ if(STOP) \ printf(" [STOP] "); \ else if(SYNC) \ printf(" [SYNC] "); \ else if(REV_CKS) \ printf(" [REV_CKS] "); \ else if(BLOCK) \ printf(" [BLOCK] "); \ else if(PRE_FINAL) \ printf(" [PRE FINAL] "); \ else if(FWD_CKS) \ printf(" [FWD CKS %02o] ", dt->lb); #define PRINT2 \ if(END_ZONE) \ debug("%c%d %02o TapeEndF ", state, dt->tct, dt->lb); \ else if(BM_SPACE) \ debug("%c%d %02o BlockSpace %o ", state, dt->tct, dt->lb, dt->tbm); \ else if(BM_END) \ debug("%c%d %02o BlockEndF %o ", state, dt->tct, dt->lb, dt->tbm); \ else if(REV_BM) \ debug("%c%d %02o BlockEndR %o ", state, dt->tct, dt->lb, dt->tbm); \ else if(DATA_SYNC) \ debug("%c%d %02o DataSync %o ", state, dt->tct, dt->lb, dt->tdata); \ else if(BM_SYNC) \ debug("%c%d %02o BlockSync %o ", state, dt->tct, dt->lb, dt->tbm); \ else if(FWD_DATA_END) \ debug("%c%d %02o DataEndF %o ", state, dt->tct, dt->lb, dt->tdata); \ else if(REV_DATA_END) \ debug("%c%d %02o DataEndR %o ", state, dt->tct, dt->lb, dt->tdata); \ else if(DATA) \ debug("%c%d %02o Data %o ", state, dt->tct, dt->lb, dt->tdata); static void recalc_dt_req(Dt551 *dt) { int req; if(dt->ut_jb_done_flag && dt->ut_jb_done_enable || dt->ut_tape_end_flag && dt->ut_tape_end_enable || dt->time_flag && dt->time_enable || dt->ut_info_error || dt->ut_illegal_op) req = dt->ut_pia; else req = 0; setreq(dt->bus, UTC, req); } #define LDB(p, s, w) ((w)>>(p) & (1<<(s))-1) #define XLDB(ppss, w) LDB((ppss)>>6 & 077, (ppss)&077, w) #define MASK(p, s) ((1<<(s))-1 << (p)) #define DPB(b, p, s, w) ((w)&~MASK(p,s) | (b)<<(p) & MASK(p,s)) #define XDPB(b, ppss, w) DPB(b, (ppss)>>6 & 077, (ppss)&077, w) word dtbuf; char state = 'n'; #define REV (dt->ut_rev && !UT_BM) /* This is a function for debugging purposes */ static void setstat(Dt551 *dt, int state) { dt->rw_state = state; } static void dtsetgo(Dt551 *dt, int go) { /* printf("setting GO %d %d\n", dt->ut_go, go); if(dt->ut_go != go){ if(go) printf("DECtape started\n\n\n"); else printf("DECtape stopped\n\n\n"); } */ dt->ut_go = go; } static void dt_tp0(Dt551 *dt) { dt->sense = dxread(dt->seldx); // 3-7 if(UT_WRITE && dt->rw_state == RW_ACTIVE) dt->ut_wren = 1; // 3-7 if(UT_WRITE){ // TODO int c; c = REV ? 7 : 0; if(dt->tct) dt->wb = dt->rwb&7 ^ c; else dt->wb = dt->rwb>>3&7 ^ c; /* copy to mark track; 3-13/14 */ if(UT_WREN_BTM) dt->wb |= dt->wb<<1 & 010; else dt->wb = dt->wb&7 | dt->sense&010; if(UT_WRITE_PREVENT) dt->ut_illegal_op = 1; // 3-16 if(UT_WREN_DATA){ dxwrite(dt->seldx, dt->wb); debug("writing %02o\n", dt->wb); } } } static void dt_tp1(Dt551 *dt) { /* complement WB here, totally useless */ //dt->wb = ~dt->wb & 07; /* advance mark track window */ if(!UT_WRTM) dt->tmk = ((dt->tmk << 1) | LDB(3, 1, dt->sense)) & 0777 | dt->tmk&0400; /* Strobe sensors into RWB */ // 3-12 if(dt->rw_state == RW_ACTIVE && UT_READ){ int c = REV ? 7 : 0; if(dt->tct) dt->rwb |= (dt->sense^c)&07; else dt->rwb |= ((dt->sense^c)&07) << 3; } } static void dt_tp2(Dt551 *dt) { Dt551 prev; int tbedge, tdedge; prev = *dt; if(END_ZONE){ dtsetgo(dt, 0); dt->ut_tape_end_flag = 1; /* TODO: is this right? */ setstat(dt, RW_NULL); } /* Block mark timing. * Shift down starting at block sync, * shifts to 10 before rev block number, * shifts to 01 before fwd block number. */ // 3-5 if(BM_SYNC) dt->tbm |= 020; if(BM_SYNC || BM_SPACE) dt->tbm >>= 1; tbedge = ~prev.tbm & dt->tbm; /* Data timing. * Shift down starting at data sync. * shifts to 100 before reverse check sum, * shifts to 001 after forward check sum */ // 3-8, 3-9 if(DATA_SYNC) dt->tdata = 0400; if(FWD_DATA_END || REV_DATA_END || DATA_SYNC) dt->tdata >>= 1; tdedge = ~prev.tdata & dt->tdata; /* Send 6 bit byte to DC every two steps when reading */ if(UT_READ && RW_EVEN){ /* except checksums */ if((prev.tdata & 0102) == 0){ int rwb = dt->rwb; if(REV) rwb = rwb>>3 & 07 | rwb<<3 & 070; dcgv(dt->dc, DEVNO, rwb, REV); if(UTE_DC_DISCONNECT) dt->ut_incomp_block = 1; // 3-17 } } /* Clear LB before reading the reverse checksum */ if(tdedge & 0100) dt->lb = 0; // 3-9 /* Keep track of checksum */ if(dt->tct) // Manual says XOR, but also talks of parity of zeros // dt->lb ^= dt->rwb; dt->lb ^= ~dt->rwb & 077; if(prev.rw_state != RW_ACTIVE) dt->tct = 0; else dt->tct ^= 1; /* Ring count UTEK */ if(!BM_SPACE){ if(dt->utek == 1) dt->utek = 0100; dt->utek >>= 1; } /* Go into active state */ if(prev.rw_state == RW_RQ) if(UT_BM && tbedge & 1 || // 3-6 UT_DATA && tdedge & 0100 || // 3-9 UT_ALL && tbedge & 010) // 3-11 setstat(dt, RW_ACTIVE); /* What to do after block */ if(tdedge & 1){ if(RW_DATA_CONT) // 3-10 setstat(dt, RW_RQ); if(RW_DATA_STOP) // 3-11 dt->ut_jb_done_flag = 1; /* test checksum. NB: manual says should be 0 * TODO: not sure about ACTIVE */ if(prev.rw_state == RW_ACTIVE && dt->lb != 077){ debug("CHECKSUM ERR\n"); dt->ut_info_error = 1; // 3-13 } } /* Job done after block mark */ if(prev.rw_state == RW_ACTIVE && RW_BM_DONE) // 3-8 dt->ut_jb_done_flag = 1; /* Catch wrongly set active state; 3-16 */ if(UTE_ACTIVE_ERROR){ dt->ut_info_error = 1; setstat(dt, RW_NULL); } /* Catch invalid mark code */ if(UTE_ERROR){ debug("TRACK ERROR\n"); dt->ut_info_error = 1; // 3-15 } /* Stop activity once job gets done */ if(!prev.ut_jb_done_flag && dt->ut_jb_done_flag) // 3-8 setstat(dt, RW_NULL); } static void dt_tp3(Dt551 *dt) { // 3-6 if(RW_ODD) dt->rwb = 0; // 3-15 /* before fwd block mark */ if(dt->tbm & 1){ if(BM_SPACE) dt->utek = 040; // UTE PRESET 100000 else dt->uteck = 1; } if(BM_SYNC) dt->uteck = 0; } static void dt_tp4(Dt551 *dt) { /* stop writing WB here */ if(dt->rw_state != RW_ACTIVE) dt->ut_wren = 0; /* get 6 bit byte from DC every two steps when writing */ if(UT_WRITE && RW_ODD){ /* except checksums */ if((dt->tdata & 0102) == 0){ dt->rwb |= dctk(dt->dc, DEVNO, REV); if(UTE_DC_DISCONNECT) dt->ut_incomp_block = 1; // 3-17 }else{ debug("Getting LB\n"); dt->rwb |= dt->lb; } // 3-6 if(REV) dt->rwb = dt->rwb>>3 & 07 | dt->rwb<<3 & 070; } } /* count dialled transports and select last */ static int selectdx(Dt551 *dt) { int i, n, nunits; n = dt->ut_units == 0 ? 8 : dt->ut_units; nunits = 0; dt->seldx = nil; if(dt->ut_units_select) for(i = 0; i < 8; i++) if(dt->dx[i] && dt->dx[i]->unit == n){ dt->seldx = dt->dx[i]; nunits++; } return nunits; } /* Read one line of bits and move the tape. * This should occur once every 33.33μs */ static void dtcycle(void *p) { Dt551 *dt; dt = (Dt551*)p; if(dt->delay){ if(--dt->delay == 0){ // UT_START goes to 0 here int nunits; dt->time_flag = 1; /* check for legal selection */ nunits = selectdx(dt); // UTE_SW_ERROR; 3-16 if(!UTE_UNIT_OK || dt->ut_btm_switch != UT_WRTM){ debug("ILL op %d %d %d\n", nunits, dt->ut_btm_switch, UT_WRTM); dt->ut_illegal_op = 1; } if(!UTE_UNIT_OK){ dtsetgo(dt, 0); // 3-3 goto ret; } if(UT_WRTM) // 3-14 setstat(dt, RW_ACTIVE); else if(!UT_DN) // TODO: is this right? setstat(dt, RW_RQ); }else /* Don't move during delay. * TODO: is this right? */ goto ret; } // TODO: maybe do something else here? if(dt->seldx == nil) goto ret; if(!dt->ut_go) goto ret; // sense; start writing dt_tp0(dt); dtbuf = dtbuf<<3 & FW; dtbuf |= dt->sense & 07; // read strobe dt_tp1(dt); // interpret mark code dt_tp2(dt); // clear rwb; mark track error sync dt_tp3(dt); // get next rwb from DC to write dt_tp4(dt); state = dt->rw_state == RW_NULL ? 'n' : dt->rw_state == RW_RQ ? 'r' : dt->rw_state == RW_ACTIVE ? 'a' : 'x'; PRINT2 else debug("%c%d -- ", state, dt->tct); debug(" %012lo %02o %d.%02o\n", dtbuf, dt->rwb, dt->uteck, dt->utek); dxmove(dt->seldx); ret: recalc_dt_req(dt); } static void wake_dt(void *dev) { Dt551 *dt; IOBus *bus; dt = (Dt551*)dev; bus = dt->bus; if(IOB_RESET){ dt->ut_incomp_block = 0; dt->ut_pia = 0; dt->ut_units = 0; dt->ut_units_select = 0; dt->ut_fcn = 0; dt->ut_time = 0; dt->ut_wren = 0; dtsetgo(dt, 0); dt->ut_rev = 0; dt->ut_tape_end_flag = 0; dt->ut_tape_end_enable = 0; dt->ut_jb_done_flag = 0; dt->ut_jb_done_enable = 0; dt->time_enable = 0; dt->time_flag = 0; dt->ut_illegal_op = 0; dt->ut_info_error = 0; dt->tmk = 0; dt->rwb = 0; dt->tbm = 0; dt->tdata = 0; dt->utek = 0; dt->uteck = 0; dt->rw_state = RW_NULL; } if(bus->devcode == UTS){ if(IOB_STATUS){ //printf("UTS STATUS\n"); if(dt->delay) bus->c12 |= F25; if(dt->rw_state == RW_RQ) bus->c12 |= F26; if(dt->rw_state == RW_ACTIVE) bus->c12 |= F27; if(dt->rw_state == RW_NULL) bus->c12 |= F28; if(dt->ut_incomp_block) bus->c12 |= F29; if(dt->ut_wren) bus->c12 |= F30; if(dt->time_flag) bus->c12 |= F31; if(dt->ut_info_error) bus->c12 |= F32; if(dt->ut_illegal_op) bus->c12 |= F33; if(dt->ut_tape_end_flag) bus->c12 |= F34; if(dt->ut_jb_done_flag) bus->c12 |= F35; } } if(bus->devcode == UTC){ if(IOB_STATUS){ dbg("UTC STATUS\n"); if(dt->ut_units_select) bus->c12 |= F19; if(dt->ut_tape_end_enable) bus->c12 |= F20; if(dt->ut_jb_done_enable) bus->c12 |= F21; if(dt->ut_go) bus->c12 |= F22; if(dt->ut_rev) bus->c12 |= F23; if(dt->time_enable) bus->c12 |= F24; bus->c12 |= (dt->ut_time & 03) << 9; bus->c12 |= (dt->ut_fcn & 07) << 6; bus->c12 |= (dt->ut_units & 07) << 3; bus->c12 |= dt->ut_pia & 07; } if(IOB_CONO_CLEAR){ dbg("UTC CONO CLEAR\n"); dt->ut_incomp_block = 0; dt->ut_wren = 0; dt->time_flag = 0; dt->ut_info_error = 0; dt->ut_illegal_op = 0; dt->ut_tape_end_flag = 0; dt->ut_jb_done_flag = 0; // 3-5 setstat(dt, RW_NULL); } if(IOB_CONO_SET){ dt->ut_pia = bus->c12 & 07; dt->ut_units = bus->c12>>3 & 07; dt->ut_fcn = bus->c12>>6 & 07; dt->ut_time = bus->c12>>9 & 03; dt->time_enable = bus->c12>>11 & 1; dt->ut_rev = bus->c12>>12 & 1; dtsetgo(dt, bus->c12>>13 & 1); dt->ut_jb_done_enable = bus->c12>>14 & 1; dt->ut_tape_end_enable = bus->c12>>15 & 1; dt->ut_units_select = bus->c12>>16 & 1; // UT CONO SET LONG, 1ms after UT CONO SET // triggers UT START level until end of delay dbg("UTC CONO SET %06lo\n", bus->c12 & 0777777); dbg("SEL/%o GO/%o REV/%o TIME/%o FCN/%o\n", dt->ut_units_select, dt->ut_go, dt->ut_rev, dt->ut_time, dt->ut_fcn); /* update selection, also done after delay */ selectdx(dt); dt->delay = 0; if(dt->ut_time){ /* TODO: unsure what to do here. * The state cleared here usually * becomes invalid in cases where * a delay is needed. * Ideally we simulate those circumstances * better, but for now clear it here. */ dt->tmk = 0; dt->rwb = 0; dt->tbm = 0; dt->tdata = 0; dt->utek = 0; dt->uteck = 0; switch(dt->ut_time){ case 1: // 20ms dt->delay = 600; break; case 2: // 160ms dt->delay = 4800; break; case 3: // 300ms dt->delay = 9000; break; } // T CLEAR; 3-4 dt->tmk = 0; dt->uteck = 0; // 3-15 dt->time_flag = 0; // now wait dbg("starting delay %d\n", dt->delay); }else if(!UT_DN && // TODO: is this right? !UT_WRTM) // 3-14 setstat(dt, RW_RQ); } } recalc_dt_req(dt); } static void dtioconnect(Device *dev, IOBus *bus) { Dt551 *dt; dt = (Dt551*)dev; dt->bus = bus; bus->dev[UTC] = (Busdev){ dt, wake_dt, 0 }; bus->dev[UTS] = (Busdev){ dt, wake_dt, 0 }; } /* Connect a dt control to a data control */ void dtconn(Dc136 *dc, Dt551 *dt) { dt->dc = dc; dc->devp[DEVNO] = dt; } word dtex(Device *dev, const char *reg) { Dt551 *dt; dt = (Dt551*)dev; if(strcmp(reg, "btm_wr") == 0) return dt->ut_btm_switch; else if(strcmp(reg, "go") == 0) return dt->ut_go; return ~0; } int dtdep(Device *dev, const char *reg, word data) { Dt551 *dt; dt = (Dt551*)dev; if(strcmp(reg, "btm_wr") == 0) dt->ut_btm_switch = data&1; else if(strcmp(reg, "dbg") == 0) dtdbg = data&1; else return 1; return 0; } static Device dtproto = { nil, nil, "", nil, nil, dtioconnect, dtex, dtdep }; Device* makedt(int argc, char *argv[]) { Dt551 *dt; Task t; dt = malloc(sizeof(Dt551)); memset(dt, 0, sizeof(Dt551)); dt->dev = dtproto; dt->dev.type = dt_ident; // should have 30000 cycles per second, so one every 33μs // APR at 1 has an approximate cycle time of 200-300ns t = (Task){ nil, dtcycle, dt, 150, 0 }; addtask(t); return &dt->dev; }