mirror of
https://github.com/aap/pdp6.git
synced 2026-01-13 07:20:13 +00:00
617 lines
12 KiB
C
617 lines
12 KiB
C
#include "common.h"
|
|
#include "pdp6.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#define MOVEDLY 33333
|
|
// very approximate, unfortunately no hard data for this
|
|
#define STARTDLY 200000000
|
|
#define STOPDLY 100000000 // not sure how to use this one here
|
|
#define TURNDLY 250000000
|
|
// ~350 lines per inch, ~4.56in per block
|
|
// again very approximate
|
|
#define STARTDIST 7*350
|
|
#define STOPDIST 8*350
|
|
// after turnaround we will have passed the location that we turned at
|
|
// TENDMP assumes one block of turnaround space
|
|
// MACDMP assumes two
|
|
#define TURNDIST 2*350
|
|
|
|
struct Ux555
|
|
{
|
|
int fd;
|
|
|
|
u8 *buf;
|
|
int size, pos;
|
|
bool written;
|
|
bool flapping;
|
|
u64 timer;
|
|
bool tp;
|
|
|
|
bool wrlock;
|
|
int num; // 0-7
|
|
// from controller:
|
|
int go;
|
|
int rev;
|
|
};
|
|
|
|
struct Ut551
|
|
{
|
|
Dc136 *dc;
|
|
int dcdev;
|
|
|
|
Ux555 *transports[8];
|
|
Ux555 *sel;
|
|
|
|
bool btm_sw;
|
|
|
|
bool units_select;
|
|
bool tape_end_en;
|
|
bool jb_done_en;
|
|
bool go;
|
|
bool rev;
|
|
bool time_en;
|
|
int time;
|
|
int fcn;
|
|
int units;
|
|
int pia;
|
|
|
|
bool incomp_block;
|
|
bool wren;
|
|
bool time_flag;
|
|
bool info_error;
|
|
bool illegal_op;
|
|
bool tape_end_flag;
|
|
bool jb_done_flag;
|
|
|
|
int utek;
|
|
bool uteck;
|
|
|
|
int lb;
|
|
int rwb;
|
|
int wb;
|
|
|
|
int tbm;
|
|
int tdata;
|
|
int tmk;
|
|
int tct;
|
|
|
|
int ramp; // read amplifiers
|
|
int state;
|
|
bool start_dly;
|
|
u64 dlyend;
|
|
};
|
|
|
|
|
|
|
|
Ux555*
|
|
attach_ux(Ut551 *ut, int num)
|
|
{
|
|
int i;
|
|
Ux555 *ux = malloc(sizeof(Ux555));
|
|
|
|
ux->fd = -1;
|
|
ux->buf = nil;
|
|
ux->size = 0;
|
|
ux->pos = 0;
|
|
ux->tp = 0;
|
|
|
|
ux->wrlock = 0;
|
|
ux->num = num & 7;
|
|
ux->go = 0;
|
|
ux->rev = 0;
|
|
|
|
for(i = 0; i < 8; i++)
|
|
if(ut->transports[i] == nil) {
|
|
ut->transports[i] = ux;
|
|
break;
|
|
}
|
|
|
|
addcleanup((void (*)(void*))uxunmount, ux);
|
|
|
|
return ux;
|
|
}
|
|
|
|
void
|
|
uxunmount(Ux555 *ux)
|
|
{
|
|
if(ux->fd < 0)
|
|
return;
|
|
|
|
if(ux->written) {
|
|
lseek(ux->fd, 0, SEEK_SET);
|
|
write(ux->fd, ux->buf, ux->size);
|
|
}
|
|
|
|
close(ux->fd);
|
|
ux->fd = -1;
|
|
|
|
free(ux->buf);
|
|
ux->buf = nil;
|
|
}
|
|
|
|
void
|
|
uxmount(Ux555 *ux, const char *path)
|
|
{
|
|
uxunmount(ux);
|
|
|
|
ux->fd = open(path, O_RDWR);
|
|
if(ux->fd < 0) {
|
|
ux->fd = open(path, O_RDONLY);
|
|
if(ux->fd < 0) {
|
|
fprintf(stderr, "can't open file <%s>\n", path);
|
|
return;
|
|
}
|
|
ux->wrlock = 1;
|
|
}
|
|
ux->size = lseek(ux->fd, 0, SEEK_END);
|
|
ux->buf = malloc(ux->size);
|
|
lseek(ux->fd, 0, SEEK_SET);
|
|
read(ux->fd, ux->buf, ux->size);
|
|
ux->written = 0;
|
|
ux->pos = 100;
|
|
ux->flapping = ux->pos >= ux->size; // hopefully always true
|
|
ux->tp = 0;
|
|
ux->go = 0;
|
|
ux->rev = 0;
|
|
printf("μt unit %d file <%s>, len %d lock? %d\n", ux->num, path, ux->size, ux->wrlock);
|
|
}
|
|
|
|
static void
|
|
uxmove(Ux555 *ux)
|
|
{
|
|
if(!ux->go || ux->timer >= simtime)
|
|
return;
|
|
ux->timer += MOVEDLY;
|
|
if(ux->rev) {
|
|
if(--ux->pos < 0)
|
|
//printf("flap back\n"),
|
|
ux->flapping = 1;
|
|
} else {
|
|
if(++ux->pos >= ux->size)
|
|
//printf("flap fwd\n"),
|
|
ux->flapping = 1;
|
|
}
|
|
if(!ux->flapping)
|
|
ux->tp = 1;
|
|
}
|
|
|
|
static void
|
|
uxsetmotion(Ux555 *ux, int go, int rev)
|
|
{
|
|
if(ux->go != go) {
|
|
if(!ux->go) {
|
|
//printf("start transport\n");
|
|
// start transport
|
|
ux->timer = simtime + STARTDLY;
|
|
ux->pos += rev ? -STARTDIST : STARTDIST;
|
|
} else {
|
|
// stop transport
|
|
//printf("stop transport\n");
|
|
ux->pos += ux->rev ? -STOPDIST : STOPDIST;
|
|
}
|
|
ux->go = go;
|
|
} else if(ux->go && ux->rev != rev) {
|
|
//printf("turn transport\n");
|
|
// turn around transport
|
|
ux->timer = simtime + TURNDLY;
|
|
ux->pos += rev ? -TURNDIST : TURNDIST;
|
|
}
|
|
ux->rev = rev;
|
|
}
|
|
|
|
|
|
|
|
|
|
#define IOB pdp->iob
|
|
|
|
enum {
|
|
RW_NULL,
|
|
RW_RQ,
|
|
RW_ACTIVE,
|
|
};
|
|
|
|
#define UT_DN (ut->fcn == 0)
|
|
#define UT_RDA (ut->fcn == 1)
|
|
#define UT_RDBM (ut->fcn == 2)
|
|
#define UT_RDD (ut->fcn == 3)
|
|
#define UT_WRTM (ut->fcn == 4)
|
|
#define UT_WRA (ut->fcn == 5)
|
|
#define UT_WRBM (ut->fcn == 6)
|
|
#define UT_WRD (ut->fcn == 7)
|
|
#define UT_WRITE (ut->fcn & 4)
|
|
#define UT_READ !UT_WRITE
|
|
#define UT_ALL (UT_RDA || UT_WRA)
|
|
#define UT_BM (UT_RDBM || UT_WRBM)
|
|
#define UT_DATA (UT_RDD || UT_WRD)
|
|
|
|
#define MK_BM_SPACE ((ut->tmk & 0477) == 0425)
|
|
#define MK_BM_SYNC (ut->tmk == 0751)
|
|
#define MK_DATA_SYNC (ut->tmk == 0632)
|
|
#define MK_BM_END (ut->tmk == 0526)
|
|
#define MK_FWD_DATA_END (ut->tmk == 0773 || ut->tmk == 0473)
|
|
#define MK_REV_DATA_END (ut->tmk == 0610 || ut->tmk == 0410)
|
|
#define MK_END (ut->tmk == 0622)
|
|
#define MK_DATA (ut->tmk == 0470)
|
|
#define MK_DATA_END (MK_FWD_DATA_END || MK_REV_DATA_END)
|
|
|
|
#define UTE_DC_DISCONNECT (ut->dc->darq || ut->dc->device != ut->dcdev)
|
|
#define UTE_MK (MK_BM_END || MK_BM_SYNC || MK_DATA_END || MK_DATA_SYNC || MK_DATA)
|
|
|
|
static void calc_ut_req(PDP6 *pdp, Ut551 *ut);
|
|
|
|
static int
|
|
utsel(Ut551 *ut)
|
|
{
|
|
int i, num, nsel;
|
|
|
|
num = ut->units;
|
|
// weirdness: ~UNITS SELECT only disables 0 and 1
|
|
if(!ut->units_select && num < 2)
|
|
num = -1;
|
|
|
|
nsel = 0;
|
|
ut->sel = nil;
|
|
for(i = 0; i < 8; i++)
|
|
if(ut->transports[i] && ut->transports[i]->num == num) {
|
|
ut->sel = ut->transports[i];
|
|
nsel++;
|
|
}
|
|
if(nsel != 1)
|
|
ut->sel = nil;
|
|
return nsel;
|
|
}
|
|
|
|
static void
|
|
tp0(Ut551 *ut)
|
|
{
|
|
Ux555 *ux = ut->sel;
|
|
|
|
if(ut->state == RW_ACTIVE) {
|
|
ut->wren = 1;
|
|
|
|
if(UT_WRITE) {
|
|
// RWB(J)->WB
|
|
ut->wb = (ut->rwb >> (ut->tct?0:3))&7;
|
|
if(ut->rev) ut->wb ^= 7;
|
|
if(ux->wrlock)
|
|
ut->illegal_op = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp1(Ut551 *ut)
|
|
{
|
|
// write complement - rather useless for us here
|
|
if(UT_WRITE)
|
|
ut->wb ^= 7;
|
|
|
|
// advance TMK
|
|
if(!ut->start_dly && !UT_WRTM)
|
|
ut->tmk = ((ut->tmk<<1) | (ut->ramp>>3)&1)&0777 | ut->tmk&0400;
|
|
|
|
// strobe data into RWB
|
|
if(ut->state == RW_ACTIVE && UT_READ) {
|
|
int c = ut->rev && !UT_RDBM ? 7 : 0;
|
|
if(ut->tct)
|
|
ut->rwb |= ut->ramp&7 ^ c;
|
|
else
|
|
ut->rwb |= (ut->ramp&7 ^ c) << 3;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp2(Ut551 *ut)
|
|
{
|
|
bool wasdone = ut->jb_done_flag;
|
|
|
|
if(ut->state == RW_ACTIVE) {
|
|
if(ut->tct) {
|
|
// RW EVEN
|
|
ut->lb ^= ~ut->rwb & 077;
|
|
if(UT_READ && (ut->tdata&0102)==0) {
|
|
// RWB<->DC
|
|
ut->rwb |= dctkgv(ut->dc, ut->dcdev, ut->rwb, ut->rev && !UT_RDBM);
|
|
if(UTE_DC_DISCONNECT)
|
|
ut->incomp_block = 1;
|
|
}
|
|
}
|
|
ut->tct ^= 1;
|
|
|
|
if(UT_BM && MK_BM_END)
|
|
ut->jb_done_flag = 1;
|
|
|
|
if(!UT_ALL && (MK_BM_SYNC || MK_DATA_SYNC)) {
|
|
ut->info_error = 1;
|
|
ut->state = RW_NULL;
|
|
}
|
|
} else
|
|
ut->tct = 0;
|
|
|
|
if(ut->uteck && UTE_MK != (ut->utek==040))
|
|
ut->info_error = 1;
|
|
|
|
if(!MK_BM_SPACE)
|
|
ut->utek = (ut->utek>>1) | (ut->utek&1)<<5;
|
|
|
|
|
|
// only interested in rising edges here
|
|
int tbmedge = ~ut->tbm;
|
|
if(MK_BM_SYNC) ut->tbm = 010 | ut->tbm>>1;
|
|
else if(MK_BM_SPACE) ut->tbm >>= 1;
|
|
tbmedge &= ut->tbm;
|
|
|
|
// need both rising and falling edges
|
|
int tdedge = ut->tdata;
|
|
if(MK_DATA_SYNC) ut->tdata = 0200 | ut->tdata>>1;
|
|
else if(MK_DATA_END) ut->tdata >>= 1;
|
|
tdedge ^= ut->tdata;
|
|
|
|
if(tdedge & ut->tdata & 0100)
|
|
ut->lb = 0;
|
|
|
|
if(ut->state == RW_RQ) {
|
|
if(UT_BM && tbmedge & 1 ||
|
|
UT_ALL && tbmedge & 010 ||
|
|
UT_DATA && tdedge & ut->tdata & 0100)
|
|
ut->state = RW_ACTIVE;
|
|
} else if(ut->state == RW_ACTIVE && UT_DATA && (tdedge & ~ut->tdata & 2)) {
|
|
if(UTE_DC_DISCONNECT)
|
|
ut->jb_done_flag = 1;
|
|
else
|
|
ut->state = RW_RQ;
|
|
}
|
|
|
|
if(ut->jb_done_flag && !wasdone)
|
|
ut->state = RW_NULL;
|
|
}
|
|
|
|
static void
|
|
tp3(Ut551 *ut)
|
|
{
|
|
if(ut->state == RW_ACTIVE && !ut->tct)
|
|
ut->rwb = 0;
|
|
|
|
if(UT_READ && (UT_ALL | UT_DATA) && ut->tdata & 1 &&
|
|
ut->lb != 077)
|
|
ut->info_error = 1;
|
|
|
|
if(ut->tbm & 1) {
|
|
if(MK_BM_SPACE)
|
|
ut->utek = 040;
|
|
else
|
|
ut->uteck = 1;
|
|
}
|
|
if(MK_BM_SYNC)
|
|
ut->uteck = 0;
|
|
|
|
if(MK_END) {
|
|
ut->tape_end_flag = 1;
|
|
ut->state = RW_NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
tp4(Ut551 *ut)
|
|
{
|
|
if(ut->state != RW_ACTIVE)
|
|
ut->wren = 0;
|
|
else if(!ut->tct) {
|
|
if(UT_WRITE && UT_DATA && ut->tdata & 2)
|
|
ut->rwb |= ut->lb;
|
|
if(UT_WRITE && (ut->tdata&0102)==0 || UT_WRTM) {
|
|
// RWB<->DC
|
|
// writing - so no need to check RDBM
|
|
ut->rwb |= dctkgv(ut->dc, ut->dcdev, ut->rwb, ut->rev);
|
|
if(UTE_DC_DISCONNECT)
|
|
ut->incomp_block = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
cycle_ut(PDP6 *pdp, IOdev *dev, int pwr)
|
|
{
|
|
int i;
|
|
Ut551 *ut = (Ut551*)dev->dev;
|
|
Ux555 *ux;
|
|
|
|
if(!pwr) {
|
|
ut->dlyend = NEVER;
|
|
return;
|
|
}
|
|
|
|
int doreq = 0;
|
|
if(ut->start_dly && ut->dlyend < simtime) {
|
|
//printf("delay done\n");
|
|
ut->dlyend = NEVER;
|
|
ut->start_dly = 0;
|
|
ut->time_flag = 1;
|
|
if(utsel(ut) != 1) {
|
|
ut->go = 0;
|
|
ut->illegal_op = 1;
|
|
}
|
|
if(UT_WRTM != ut->btm_sw)
|
|
ut->illegal_op = 1;
|
|
if(UT_WRTM)
|
|
ut->state = RW_ACTIVE;
|
|
else if(!UT_DN)
|
|
ut->state = RW_RQ;
|
|
doreq = 1;
|
|
}
|
|
|
|
if(ut->tape_end_flag)
|
|
ut->go = 0;
|
|
|
|
// move transports
|
|
ux = ut->sel;
|
|
if(ux)
|
|
uxsetmotion(ux, ut->go, ut->rev);
|
|
for(i = 0; i < 8; i++)
|
|
if(ut->transports[i])
|
|
uxmove(ut->transports[i]);
|
|
|
|
if(ux && ux->tp) {
|
|
ux->tp = 0;
|
|
doreq = 1;
|
|
|
|
// rising edge of time track
|
|
int mask = ux->rev ? 017 : 0;
|
|
ut->ramp = ux->buf[ux->pos] ^ mask;
|
|
|
|
// which fires TP0 - write load
|
|
tp0(ut);
|
|
|
|
// write back at some point
|
|
if(UT_WRITE && ut->wren && !ux->wrlock) {
|
|
if(UT_WRTM && ut->btm_sw)
|
|
ux->buf[ux->pos] = ((ut->wb<<1)&010 | ut->wb) ^ mask;
|
|
else
|
|
ux->buf[ux->pos] = (ut->ramp&010 | ut->wb) ^ mask;
|
|
ux->written = 1;
|
|
}
|
|
|
|
// ca 16.6μs later
|
|
|
|
// falling edge of time track
|
|
// write complement - read strobe
|
|
tp1(ut);
|
|
// NB we don't also write the complement
|
|
tp2(ut);
|
|
tp3(ut);
|
|
tp4(ut);
|
|
}
|
|
|
|
if(doreq) calc_ut_req(pdp, ut);
|
|
}
|
|
|
|
static void
|
|
handle_ut(PDP6 *pdp, IOdev *dev, int cmd)
|
|
{
|
|
Ut551 *ut = (Ut551*)dev->dev;
|
|
|
|
switch(cmd) {
|
|
case IOB_RESET:
|
|
case IOB_CONO_CLR:
|
|
ut->incomp_block = 0;
|
|
ut->wren = 0;
|
|
ut->time_flag = 0;
|
|
ut->info_error = 0;
|
|
ut->illegal_op = 0;
|
|
ut->tape_end_flag = 0;
|
|
ut->jb_done_flag = 0;
|
|
|
|
ut->units_select = 0;
|
|
ut->tape_end_en = 0;
|
|
ut->jb_done_en = 0;
|
|
ut->go = 0;
|
|
ut->rev = 0;
|
|
ut->time_en = 0;
|
|
ut->time = 0;
|
|
ut->fcn = 0;
|
|
ut->units = 0;
|
|
ut->pia = 0;
|
|
|
|
ut->state = RW_NULL;
|
|
break;
|
|
|
|
case IOB_CONO_SET:
|
|
//printf("CONO UT %o (PC %06o)\n", (int)IOB&0777777, pdp->pc);
|
|
if(IOB & F1) ut->units_select = 1;
|
|
if(IOB & F2) ut->tape_end_en = 1;
|
|
if(IOB & F3) ut->jb_done_en = 1;
|
|
if(IOB & F4) ut->go = 1;
|
|
if(IOB & F5) ut->rev = 1;
|
|
if(IOB & F6) ut->time_en = 1;
|
|
ut->time |= (IOB>>27) & 3;
|
|
ut->fcn |= (IOB>>24) & 7;
|
|
ut->units |= (IOB>>21) & 7;
|
|
ut->pia |= (IOB>>18) & 7;
|
|
|
|
// command is effective before selection changes
|
|
if(ut->sel) uxsetmotion(ut->sel, ut->go, ut->rev);
|
|
|
|
utsel(ut);
|
|
|
|
if(ut->time == 0) {
|
|
// bit strange for DN and WRTM
|
|
ut->state = RW_RQ;
|
|
} else {
|
|
static int dlytab[4] = { 0, 35000000, 225000000, 300000000 };
|
|
ut->dlyend = simtime + dlytab[ut->time];
|
|
ut->start_dly = 1;
|
|
// T CLEAR
|
|
ut->tbm = 0;
|
|
ut->tdata = 0;
|
|
ut->tmk = 0;
|
|
ut->uteck = 0;
|
|
}
|
|
break;
|
|
|
|
case IOB_STATUS:
|
|
if(ut->units_select) IOB |= F19;
|
|
if(ut->tape_end_en) IOB |= F20;
|
|
if(ut->jb_done_en) IOB |= F21;
|
|
if(ut->go) IOB |= F22;
|
|
if(ut->rev) IOB |= F23;
|
|
if(ut->time_en) IOB |= F24;
|
|
IOB |= ut->time << 9;
|
|
IOB |= ut->fcn << 6;
|
|
IOB |= ut->units << 3;
|
|
IOB |= ut->pia;
|
|
break;
|
|
}
|
|
calc_ut_req(pdp, ut);
|
|
}
|
|
|
|
static void
|
|
handle_uts(PDP6 *pdp, IOdev *dev, int cmd)
|
|
{
|
|
Ut551 *ut = (Ut551*)dev->dev;
|
|
|
|
switch(cmd) {
|
|
case IOB_STATUS:
|
|
if(ut->start_dly) IOB |= F25;
|
|
if(ut->state == RW_RQ) IOB |= F26;
|
|
if(ut->state == RW_ACTIVE) IOB |= F27;
|
|
if(ut->state == RW_NULL) IOB |= F28;
|
|
if(ut->incomp_block) IOB |= F29;
|
|
if(ut->wren) IOB |= F30;
|
|
if(ut->time_flag) IOB |= F31;
|
|
if(ut->info_error) IOB |= F32;
|
|
if(ut->illegal_op) IOB |= F33;
|
|
if(ut->tape_end_flag) IOB |= F34;
|
|
if(ut->jb_done_flag) IOB |= F35;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static Ut551 ut;
|
|
static IOdev ut_dev = { 0, 0210, &ut, handle_ut, cycle_ut };
|
|
static IOdev uts_dev = { 0, 0214, &ut, handle_uts, nil };
|
|
|
|
static void
|
|
calc_ut_req(PDP6 *pdp, Ut551 *ut)
|
|
{
|
|
int req = 0;
|
|
if(ut->pia &&
|
|
(ut->time_flag && ut->time_en ||
|
|
ut->jb_done_flag && ut->jb_done_en ||
|
|
ut->tape_end_flag && ut->tape_end_en ||
|
|
ut->illegal_op || ut->info_error))
|
|
req = 0200>>ut->pia;
|
|
setreq(pdp, &ut_dev, req);
|
|
}
|
|
|
|
Ut551*
|
|
attach_ut(PDP6 *pdp, Dc136 *dc)
|
|
{
|
|
ut.dc = dc;
|
|
ut.dcdev = 1;
|
|
installdev(pdp, &ut_dev);
|
|
installdev(pdp, &uts_dev);
|
|
return &ut;
|
|
}
|