1
0
mirror of https://github.com/open-simh/simh.git synced 2026-03-22 09:04:33 +00:00
Files
open-simh.simh/linc/linc_tape.c
Lars Brinkhoff fd3a79ebb5 LINC: New emulator for the classic LINC.
This emulates the classic LINC.  The design was settled in 1965,
increasing memory to 2048 words, and adding a Z register, an overflow
flag, and an interrupt facility.
2025-10-23 09:17:15 +02:00

471 lines
12 KiB
C

#include "linc_defs.h"
#define POS u3
#define SPEED u4
#define ACC u5
#define OFFSET u6
#define P (*(uint16 *)cpu_reg[0].loc)
#define C (*(uint16 *)cpu_reg[1].loc)
#define A (*(uint16 *)cpu_reg[2].loc)
#define S (*(uint16 *)cpu_reg[6].loc)
#define B (*(uint16 *)cpu_reg[7].loc)
#define LSW (*(uint16 *)cpu_reg[8].loc)
#define RSW (*(uint16 *)cpu_reg[9].loc)
#define paused (*(int *)cpu_reg[11].loc)
#define IBZ (*(int *)cpu_reg[12].loc)
#define ACC_START 3
#define ACC_REVERSE 6
#define ACC_STOP 1
#define MAX_SPEED (ACC_START * 625) /* 0.1s / 160µs */
#define IBZ_WORDS 5
#define DATA_WORDS 256
#define OTHER_WORDS 7
#define BLOCK_WORDS (IBZ_WORDS + DATA_WORDS + OTHER_WORDS)
#define START_POS (ACC_START * (625 + (625 * 625))/2)
#define MAX_BLOCKS 512
#define MAX_POS ((BLOCK_WORDS * MAX_BLOCKS + IBZ_WORDS) * MAX_SPEED)
#define GOOD_CHECKSUM 07777
#define RDC 0 /* read tape and check */
#define RCG 1 /* read tape group */
#define RDE 2 /* read tape */
#define MTB 3 /* move toward block */
#define WRC 4 /* write tape and check */
#define WCG 5 /* write tape group */
#define WRI 6 /* write tape */
#define CHK 7 /* check tape */
#define DBG 0001
#define DBG_SEEK 0002
#define DBG_READ 0004
#define DBG_WRITE 0010
#define DBG_POS 0020
static uint16 GROUP;
static int16 CURRENT_BLOCK;
static int16 WANTED_BLOCK;
static t_stat tape_svc(UNIT *uptr);
static t_stat tape_reset(DEVICE *dptr);
static t_stat tape_boot(int32 u, DEVICE *dptr);
static t_stat tape_attach(UNIT *uptr, CONST char *cptr);
static t_stat tape_detach(UNIT *uptr);
#define UNIT_FLAGS (UNIT_IDLE|UNIT_FIX|UNIT_ATTABLE|UNIT_DISABLE|UNIT_ROABLE)
#define CAPACITY (MAX_BLOCKS * DATA_WORDS)
static UNIT tape_unit[] = {
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) },
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) },
{ UDATA(&tape_svc, UNIT_DIS, 0) },
{ UDATA(&tape_svc, UNIT_DIS, 0) },
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) },
{ UDATA(&tape_svc, UNIT_FLAGS, CAPACITY) }
};
static DEBTAB tape_deb[] = {
{ "DBG", DBG },
{ "SEEK", DBG_SEEK },
{ "READ", DBG_READ },
{ "WRITE", DBG_WRITE },
{ "POSITION", DBG_POS },
{ NULL, 0 }
};
DEVICE tape_dev = {
"TAPE", tape_unit, NULL, NULL,
6, 8, 12, 1, 8, 12,
NULL, NULL, &tape_reset,
&tape_boot, &tape_attach, &tape_detach,
NULL, DEV_DEBUG, 0, tape_deb,
NULL, NULL, NULL, NULL, NULL, NULL
};
void tape_op(void)
{
uint16 u = (C & 050) >> 3;
UNIT *uptr = &tape_unit[u];
if ((uptr->flags & UNIT_ATT) == 0)
return;
if (uptr->SPEED < 0) {
if ((C & 7) != MTB) {
sim_debug(DBG_SEEK, &tape_dev, "Reverse to forward\n");
uptr->ACC = ACC_REVERSE;
}
} else if (uptr->POS >= MAX_POS) {
sim_debug(DBG_SEEK, &tape_dev, "End zone; reverse\n");
uptr->ACC = ACC_REVERSE;
} else if (uptr->SPEED < MAX_SPEED || uptr->ACC < 0) {
sim_debug(DBG_SEEK, &tape_dev, "Speed up\n");
uptr->ACC = ACC_START;
}
if (!sim_is_active(uptr))
sim_activate(uptr, 20);
paused = 1;
A = 0;
WANTED_BLOCK = B & TMASK;
switch (C & 7) {
case RDC: case RDE: case WRC: case WRI: case CHK:
S = 256 * (B >> 9);
GROUP = 0;
sim_debug(DBG, &tape_dev, "Single tranfer: S=%04o, BN=%03o\n",
S, WANTED_BLOCK);
break;
case RCG: case WCG:
S = 256 * (B & 7);
GROUP = B >> 9;
sim_debug(DBG, &tape_dev, "Group transfer: S=%04o, BN=%03o/%o\n",
S, WANTED_BLOCK, GROUP+1);
break;
case MTB:
sim_debug(DBG, &tape_dev, "Move towards block %03o\n", WANTED_BLOCK);
break;
}
}
static t_stat tape_seek(FILE *fileref, t_addr block, t_addr offset)
{
offset = DATA_WORDS * block + offset;
offset *= 2;
if (sim_fseek(fileref, offset, SEEK_SET) == -1)
return SCPE_IOERR;
return SCPE_OK;
}
static uint16 read_word(FILE *fileref, t_addr block, t_addr offset)
{
t_stat stat;
uint8 data[2];
uint16 word;
stat = tape_seek(fileref, block, offset);
if (stat != SCPE_OK)
;
if (sim_fread(data, 1, 2, fileref) != 2)
;
if (data[1] & 0xF0)
;
word = data[1];
word <<= 8;
word |= data[0];
return word;
}
static void write_word(FILE *fileref, t_addr block, t_addr offset, uint16 word)
{
t_stat stat;
uint8 data[2];
stat = tape_seek(fileref, block, offset);
if (stat != SCPE_OK)
;
data[0] = word & 0xFF;
data[1] = word >> 8;
if (sim_fwrite(data, 1, 2, fileref) != 2)
;
}
/*
IBZ BN G block CS C C G BN IBZ
5 1 1 256 1 1 1 1 1 5
---------------------
263
--------------------------
268
start - 100 ms
stop - 300 ms
reverse - 100 ms
BN to BN at 60 ips - 43 ms
block length = 43 ms * 60 inch/s = 2.58 inch
per word - 160 µs
word length = 0.0096 inch
words per inch = 104
words per second = 6250
end zone to end zone - 23 s
tape length = 23 * 60 = 1380 inch = 115 feet
end zone length = 5 feet
*/
static void tape_done(UNIT *uptr)
{
sim_debug(DBG, &tape_dev, "Done with block\n");
switch (C & 7) {
case RDC: case RCG: case RDE: case CHK:
A = GOOD_CHECKSUM;
break;
case WRI:
A = (A ^ 07777) + 1;
A &= WMASK;
break;
case MTB:
A = (WANTED_BLOCK + ~CURRENT_BLOCK);
A += A >> 12;
A &= WMASK;
break;
}
switch (C & 7) {
case RDC:
if (A != GOOD_CHECKSUM) {
sim_debug(DBG, &tape_dev, "Check failed; read again\n");
S &= ~0377;
} else {
sim_debug(DBG, &tape_dev, "Check passed\n");
paused = 0;
}
break;
case WRC:
sim_debug(DBG, &tape_dev, "Block written, go back and check\n");
// For now, done.
A = GOOD_CHECKSUM;
paused = 0;
break;
case RCG: case WCG:
if (GROUP == 0) {
sim_debug(DBG, &tape_dev, "Done with group\n");
paused = 0;
} else {
sim_debug(DBG, &tape_dev, "Blocks left in group: %d\n", GROUP);
GROUP--;
}
WANTED_BLOCK = (WANTED_BLOCK + 1) & TMASK;
break;
case RDE: case WRI:
sim_debug(DBG, &tape_dev, "Transfer done\n");
paused = 0;
break;
case MTB:
sim_debug(DBG, &tape_dev, "Move towards block done, result %04o\n", A);
paused = 0;
break;
case CHK:
sim_debug(DBG, &tape_dev, "Check done\n");
paused = 0;
break;
}
if (paused)
;
else if ((C & IMASK) == 0) {
sim_debug(DBG_SEEK, &tape_dev, "Instruction done, stop tape\n");
uptr->ACC = uptr->SPEED > 0 ? -ACC_STOP : ACC_STOP;
} else {
sim_debug(DBG_SEEK, &tape_dev, "Instruction done, keep moving\n");
}
}
static void tape_word(UNIT *uptr, uint16 block, uint16 offset)
{
switch (C & 7) {
case RDC: case RCG: case RDE: case CHK:
B = read_word(uptr->fileref, block, offset);
sim_debug(DBG_READ, &tape_dev,
"Read block %03o offset %03o data %04o address %04o\n",
block, offset, B, S);
if ((C & 7) != CHK)
M[S] = B;
break;
case WRC: case WCG: case WRI:
B = M[S];
sim_debug(DBG_WRITE, &tape_dev,
"Write block %03o offset %03o data %04o address %04o\n",
block, offset, B, S);
write_word(uptr->fileref, block, offset, B);
break;
}
S = (S+1) & AMASK;
A += B;
A &= WMASK;
}
static t_stat tape_svc(UNIT *uptr)
{
long pos, block, offset;
uptr->SPEED += uptr->ACC;
if (uptr->SPEED >= MAX_SPEED) {
uptr->SPEED = MAX_SPEED;
uptr->ACC = 0;
}
else if (uptr->SPEED <= -MAX_SPEED) {
uptr->SPEED = -MAX_SPEED;
uptr->ACC = 0;
} else if (uptr->SPEED == 0 && (uptr->ACC == ACC_STOP || uptr->ACC == -ACC_STOP))
uptr->ACC = 0;
uptr->POS += uptr->SPEED;
sim_debug(DBG_POS, &tape_dev, "Speed %d, position %d (block %03o)\n",
uptr->SPEED, uptr->POS, uptr->POS / MAX_SPEED / BLOCK_WORDS);
if (uptr->POS < 0 && uptr->ACC <= 0) {
sim_debug(DBG_SEEK, &tape_dev, "End zone; stop tape\n");
uptr->ACC = ACC_STOP;
} else if(uptr->POS >= MAX_POS && uptr->ACC >= 0) {
sim_debug(DBG_SEEK, &tape_dev, "End zone; stop tape\n");
uptr->ACC = -ACC_STOP;
}
if (uptr->SPEED != 0)
/* The tape takes 160 microseconds between words. This is
approximately 20 memory cycles, 8 microseconds each. */
sim_activate(uptr, 20);
pos = uptr->POS / MAX_SPEED;
if (pos < 0)
return SCPE_OK;
block = pos / BLOCK_WORDS;
offset = pos % BLOCK_WORDS;
if (block >= MAX_BLOCKS)
return SCPE_OK;
IBZ = offset < IBZ_WORDS;
if (IBZ)
sim_debug(DBG, &tape_dev, "Interblock zone\n");
if (uptr->SPEED > -MAX_SPEED && uptr->SPEED < MAX_SPEED)
return SCPE_OK;
if (!paused)
return SCPE_OK;
if (uptr->SPEED > 0) {
if (offset == 5) {
/* Forward block number. */
CURRENT_BLOCK = (uint16)(block + uptr->OFFSET);
sim_debug(DBG_SEEK, &tape_dev,
"Found block number %03o; looking for %03o\n",
CURRENT_BLOCK, WANTED_BLOCK);
if (CURRENT_BLOCK > WANTED_BLOCK) {
sim_debug(DBG_SEEK, &tape_dev, "Reverse to find lower block numbers\n");
uptr->ACC = -ACC_REVERSE;
}
if ((C & 7) == MTB)
tape_done(uptr);
/* Word 6 is a guard. */
} else if (offset >= 7 && offset < 263) {
if (CURRENT_BLOCK == WANTED_BLOCK)
tape_word(uptr, (uint16)block, (uint16)(offset - 7));
}
else if (offset == 263 && CURRENT_BLOCK == WANTED_BLOCK)
/* Checksum here. */
tape_done(uptr);
}
/* Word 264-265 are "C". */
/* Word 266 is a guard. */
else if (offset == 267 && uptr->SPEED < 0) {
/* Reverse block number. */
CURRENT_BLOCK = (uint16)(block + uptr->OFFSET);
sim_debug(DBG_SEEK, &tape_dev,
"Found reverse block number %03o; looking for %03o\n",
CURRENT_BLOCK, WANTED_BLOCK);
if (CURRENT_BLOCK <= WANTED_BLOCK) {
sim_debug(DBG_SEEK, &tape_dev, "Reverse to find higher block numbers\n");
uptr->ACC = ACC_REVERSE;
uptr->POS -= MAX_SPEED * BLOCK_WORDS;
}
if ((C & 7) == MTB)
tape_done(uptr);
}
return SCPE_OK;
}
static t_stat tape_reset(DEVICE *dptr)
{
return SCPE_OK;
}
static t_stat tape_boot(int32 unit_num, DEVICE *dptr)
{
uint16 block = 0300;
uint16 blocks = 8;
uint16 quarter = 0;
t_stat stat;
if (unit_num >= 2 && unit_num <= 3)
return SCPE_ARG;
if (blocks == 0)
return SCPE_ARG;
if (blocks == 1)
LSW = RDC;
else
LSW = RCG, quarter = blocks - 1;
LSW |= 0700 | (unit_num << 3);
RSW = (quarter << 9) | block;
stat = cpu_do();
if (stat != SCPE_OK)
return stat;
P = 020;
return SCPE_OK;
}
t_stat tape_metadata(FILE *fileref, uint16 *block_size, int16 *forward_offset, int16 *reverse_offset)
{
t_offset size = sim_fsize(fileref);
if (size == MAX_BLOCKS * DATA_WORDS * 2) {
/* Plain image. */
*block_size = DATA_WORDS;
*forward_offset = 0;
*reverse_offset = 0;
} else if ((size % (2 * DATA_WORDS)) == 6) {
/* Extended image with additional meta data. */
uint16 metadata = (uint16)(size / (2 * DATA_WORDS));
*block_size = read_word(fileref, metadata, 0);
*forward_offset = (int16)read_word(fileref, metadata, 1);
*reverse_offset = (int16)read_word(fileref, metadata, 2);
} else
return SCPE_FMT;
return SCPE_OK;
}
static t_stat tape_attach(UNIT *uptr, CONST char *cptr)
{
t_stat stat;
uint16 block_size;
int16 forward_offset, reverse_offset;
if (uptr - tape_unit >= 2 && uptr - tape_unit <= 3)
return SCPE_ARG;
stat = attach_unit(uptr, cptr);
if (stat != SCPE_OK)
return stat;
stat = tape_metadata(uptr->fileref, &block_size, &forward_offset, &reverse_offset);
if (stat != SCPE_OK)
return stat;
sim_debug(DBG, &tape_dev,
"Tape image with block size %o, block offset %d/%d\r\n",
block_size, forward_offset, reverse_offset);
if (block_size != DATA_WORDS)
return SCPE_FMT;
if (forward_offset != reverse_offset)
return SCPE_FMT;
uptr->OFFSET = forward_offset;
uptr->POS = -2 * START_POS;
uptr->SPEED = 0;
return SCPE_OK;
}
static t_stat tape_detach(UNIT *uptr)
{
if (uptr - tape_unit >= 2 && uptr - tape_unit <= 3)
return SCPE_ARG;
if ((uptr->flags & UNIT_ATT) == 0)
return SCPE_OK;
if (sim_is_active(uptr))
sim_cancel(uptr);
return detach_unit(uptr);
}