simh-testsetgenerator/BESM6/besm6_vu.c
2022-10-26 10:57:52 -04:00

579 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* besm6_vu.c: BESM-6 punchcard reader
*
* Copyright (c) 2020, Leonid Broukhis
*
* 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
* SERGE VAKULENKO OR LEONID BROUKHIS 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 Leonid Broukhis or
* Serge Vakulenko shall not be used in advertising or otherwise to promote
* the sale, use or other dealings in this Software without prior written
* authorization from Leonid Broukhis and Serge Vakulenko.
*/
#include "besm6_defs.h"
t_stat vu_event (UNIT *u); /* punched card reader */
UNIT vu_unit [] = {
{ UDATA (vu_event, UNIT_SEQ+UNIT_ATTABLE, 0) },
{ UDATA (vu_event, UNIT_SEQ+UNIT_ATTABLE, 0) },
};
/* Dispak seems to only care about the NOTREADY flag,
* the proper behavior of FEED and MAYSTART may vary.
*/
#define VU1_NOTREADY (1<<23)
#define VU1_FEED (1<<22)
#define VU1_MAYSTART (1<<21)
#define VU2_NOTREADY (1<<19)
#define VU2_FEED (1<<18)
#define VU2_MAYSTART (1<<17)
#define SET_RDY2(x) do READY2 |= x; while (0)
#define CLR_RDY2(x) do READY2 &= ~(x); while (0)
#define ISSET_RDY2(x) ((READY2 & (x)) != 0)
#define ISCLR_RDY2(x) ((READY2 & (x)) == 0)
#define VU_RATE_CPM 600
/* Interrupts are every 2 columns */
#define CARD_LEN (80/2)
#define DFLT_DELAY (60*1000*MSEC/VU_RATE_CPM/CARD_LEN)
/*
* The lines are first converted to GOST 10859; some GOST codes need to be known to the emulator.
*/
#define GOST_DOT 016 /* unpunched position */
#define GOST_O 056 /* punched position */
/* 6 "open quote" characters and an end-of-card indicator, can be entered as `````` */
#define DISP_END "\032\032\032\032\032\032\377"
unsigned int vu_col_dly = DFLT_DELAY;
unsigned int vu_end_dly = DFLT_DELAY/20; /* that seems to work */
unsigned int vu_card_dly = 10*DFLT_DELAY;
unsigned int vu_updkstart[2], vu_updkend[2];
unsigned int VU[2];
REG vu_reg[] = {
{ REGDATA ( Готов, READY2, 2, 8, 16, 1, NULL, NULL, 0, 0, 0) },
{ ORDATA ( ВУ-0, VU[0], 24) },
{ ORDATA ( ВУ-1, VU[1], 24) },
{ 0 }
};
t_stat vu_set_coldly (UNIT *u, int32 val, CONST char *cptr, void *desc)
{
if (cptr && atoi(cptr) > 0) {
vu_col_dly = atoi(cptr);
return SCPE_OK;
} else
sim_printf("Integer value required\n");
return SCPE_ARG;
}
t_stat vu_set_enddly (UNIT *u, int32 val, CONST char *cptr, void *desc)
{
if (cptr && atoi(cptr) > 0) {
vu_end_dly = atoi(cptr);
return SCPE_OK;
} else
sim_printf("Integer value required\n");
return SCPE_ARG;
}
t_stat vu_set_carddly (UNIT *u, int32 val, CONST char *cptr, void *desc)
{
if (cptr && atoi(cptr) > 0) {
vu_card_dly = atoi(cptr);
return SCPE_OK;
} else
sim_printf("Integer value required\n");
return SCPE_ARG;
}
t_stat vu_show_coldly (FILE *st, UNIT *u, int32 v, CONST void *dp)
{
fprintf(st, "Column delay is %d", vu_col_dly);
return SCPE_OK;
}
t_stat vu_show_enddly (FILE *st, UNIT *u, int32 v, CONST void *dp)
{
fprintf(st, "Delay before the end of card is %d", vu_end_dly);
return SCPE_OK;
}
t_stat vu_show_carddly (FILE *st, UNIT *u, int32 v, CONST void *dp)
{
fprintf(st, "Card delay is %d", vu_card_dly);
return SCPE_OK;
}
t_stat vu_set_updk (UNIT *u, int32 val, CONST char *cptr, void *desc)
{
unsigned start, end;
int num = u - vu_unit;
if (!cptr) {
sim_printf("Range set to MAX\n");
vu_updkstart[num] = 1;
vu_updkend[num] = 0;
return SCPE_OK;
}
if (sscanf(cptr, "%u-%u", &start, &end) != 2 ||
(start == 0 && end != 0) || (end != 0 && end < start)) {
sim_printf("Range required, e.g. 10-100, or 0-0 to disable.\n");
return SCPE_ARG;
}
vu_updkstart[num] = start;
vu_updkend[num] = end;
return SCPE_OK;
}
t_stat vu_show_updk (FILE *st, UNIT *u, int32 v, CONST void *dp)
{
int num = u - vu_unit;
if (vu_updkstart[num] == 0 && vu_updkend[num] == 0)
fprintf(st, "UPDK disabled");
else if (vu_updkend[num] == 0)
fprintf(st, "UPDK card %d to EOF", vu_updkstart[num]);
else
fprintf(st, "UPDK cards %d-%d", vu_updkstart[num], vu_updkend[num]);
return SCPE_OK;
}
MTAB vu_mod[] = {
{ MTAB_XTD|MTAB_VDV,
0, "COLDLY", "COLDLY", &vu_set_coldly, &vu_show_coldly, NULL,
"Delay between pair-of-columns interrupts,\n"
"and between the last column interrupt and posedge of the end-of-card signal." },
{ MTAB_XTD|MTAB_VDV,
0, "ENDDLY", "ENDDLY", &vu_set_enddly, &vu_show_enddly, NULL,
"Duration of the end-of-card signal." },
{ MTAB_XTD|MTAB_VDV,
0, "CARDDLY", "CARDDLY", &vu_set_carddly, &vu_show_carddly, NULL,
"Delay between the negedge of the end-of-card signal and the next card interrupt." },
{ MTAB_XTD|MTAB_VUN,
0, "UPDK", "UPDK", &vu_set_updk, &vu_show_updk, NULL,
"Range of cards to be converted to UPDK, e.g. SET UPDK 10-100. Use 0-0 to disable." },
{ 0 }
};
t_stat vu_reset (DEVICE *dptr);
t_stat vu_attach (UNIT *uptr, CONST char *cptr);
t_stat vu_detach (UNIT *uptr);
DEVICE vu_dev = {
"VU", vu_unit, vu_reg, vu_mod,
2, 8, 19, 1, 8, 50,
NULL, NULL, &vu_reset, NULL, &vu_attach, &vu_detach,
NULL, DEV_DISABLE | DEV_DEBUG
};
typedef enum {
VU_IDLE,
VU_STARTING,
VU_COL,
VU_COL_LAST = VU_COL + CARD_LEN - 1,
VU_TAIL, VU_TAIL2
} VU_state;
VU_state vu_state[2], vu_next[2];
int vu_isfifo[2];
// Each card can hold up to 120 bytes; potentially valid GOST chars, expressible in UPDK, are 0-0177.
// True spaces are 017; bytes past the end of line (empty columns) are 0377.
unsigned char vu_gost[2][120];
unsigned short vu_image[2][80];
unsigned int vu_cardcnt[2];
/*
* Reset routine
*/
t_stat vu_reset (DEVICE *dptr)
{
sim_cancel (&vu_unit[0]);
sim_cancel (&vu_unit[1]);
vu_state[0] = vu_state[1] = VU_IDLE;
SET_RDY2(VU1_NOTREADY | VU2_NOTREADY);
if (vu_unit[0].flags & UNIT_ATT) {
CLR_RDY2(VU1_NOTREADY);
}
if (vu_unit[1].flags & UNIT_ATT) {
CLR_RDY2(VU2_NOTREADY);
}
return SCPE_OK;
}
/*
* Attaches a text file in UTF-8. By default the lines are converted to the linewise GOST/UPP
* code as it allows each card to contain up to 120 characters. The columnwise GOST/UPDK code
* is not supported yet.
*/
t_stat vu_attach (UNIT *u, CONST char *cptr)
{
t_stat s;
int num = u - vu_unit;
s = attach_unit (u, cptr);
if (s != SCPE_OK)
return s;
vu_isfifo[num] = (0 == sim_set_fifo_nonblock (u->fileref));
CLR_RDY2(VU1_NOTREADY >> (num*4));
vu_cardcnt[num] = 0;
return SCPE_OK;
}
t_stat vu_detach (UNIT *u)
{
int num = u - vu_unit;
SET_RDY2(VU1_NOTREADY >>(num*4));
return detach_unit (u);
}
/*
* Controlling the card reader.
*/
void vu_control (int num, uint32 cmd)
{
UNIT *u = &vu_unit[num];
if (vu_dev.dctrl)
besm6_debug("<<< VU-%d cmd %o", num, cmd);
if (ISSET_RDY2(VU1_NOTREADY >> (num*4))) {
if (vu_dev.dctrl)
besm6_debug("<<< VU-%d not ready", num);
return;
}
if (cmd & 010) {
// Resetting the column buffer.
if (vu_dev.dctrl)
besm6_debug("<<< VU-%d buffer reset", num);
VU[num] = 0;
cmd &= ~010;
}
switch (cmd) {
case 2: /* stop */
sim_cancel (u);
vu_state[num] = VU_IDLE;
SET_RDY2(VU1_MAYSTART >> (num*4));
if (vu_dev.dctrl)
besm6_debug("<<< VU-%d OFF", num);
if (vu_state[num] == VU_TAIL) {
if (! vu_isfifo[num]) {
vu_detach(u);
return;
}
}
break;
case 4: /* read card */
case 1: /* read deck */
vu_state[num] = VU_STARTING;
CLR_RDY2(VU1_MAYSTART >> (num*4));
vu_next[num] = cmd == 1 ? VU_STARTING : VU_IDLE;
if (vu_dev.dctrl)
besm6_debug("<<< VU-%d %s read.", num, cmd == 1 ? "DECK" : "CARD");
sim_activate (u, vu_col_dly);
break;
case 0:
break;
default:
besm6_debug ("<<< VU-%d unknown cmd %o", num, cmd);
}
}
extern unsigned char unicode_to_gost(unsigned short);
extern unsigned short gost_to_unicode(unsigned char);
void uni2utf8(unsigned short ch, char buf[5]) {
int i = 0;
if (ch < 0x80) {
buf[i++] = ch & 0x7F;
} else if (ch < 0x800) {
buf[i++] = (ch >> 6 | 0xc0);
buf[i++] = ((ch & 0x3f) | 0x80);
} else {
buf[i++] = (ch >> 12 | 0xe0);
buf[i++] = (((ch >> 6) & 0x3f) | 0x80);
buf[i++] = ((ch & 0x3f) | 0x80);
}
buf[i] = '\0';
}
/*
* Converts a string consisting of 0-9+- digits, plus, or minus to a 12-bit map of punches.
*/
static int punch(const char * s) {
int r = 0;
while (*s) {
r |= *s >= '0' ? 4 << (*s - '0') : *s == '+' ? 1 : *s == '-' ? 2 : 0;
++s;
}
return r;
}
/*
* The UPDK code is a modified
* [GOST 10859-CARD](https://ub.fnwi.uva.nl/computermuseum//DWcodes.html#A056)
* for better distinctiveness wrt other column codes.
* The UPDK codes are taken from Maznyj, "Programming in the Dubna system".
*/
static unsigned short gost_to_updk (unsigned char ch) {
unsigned short ret;
// Assuming that bits in the card are 9876543210-+
// Bits from the upper and lower halves are XORed
static char * upper[4] = { "", "+0", "-0", "+-" };
static char * lower[2][16] = {
{ "0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "082", "083", "084", "085", "086", "087" },
{ "390", "391", "392", "39210", "394", "395", "396", "397",
"398", "39801", "39802", "39821", "39804", "39805", "39806", "39807" }
};
if (ch == 0377 /* filler */ || ch == 017 /* space */) {
ret = 0;
} else {
ret = punch(upper[(ch>>4)&3]) ^ punch(lower[ch>=0100][ch&0xF]);
}
return ret;
}
/*
* The UPP code is the GOST 10859 code with odd parity.
* UPP stood for "unit for preparation of punchards".
*/
static unsigned char gost_to_upp (unsigned char ch) {
unsigned char ret = ch;
ch = (ch & 0x55) + ((ch >> 1) & 0x55);
ch = (ch & 0x33) + ((ch >> 2) & 0x33);
ch = (ch & 0x0F) + ((ch >> 4) & 0x0F);
return (ch & 1) ? ret : ret | 0x80;
}
static void display_card(int num) {
if (vu_updkstart[num] != 0 || vu_updkend[num] != 0) {
char buf[80];
int i, j;
for (i = 0; i < 12; ++i) {
for (j = 0; j < 80; ++j)
buf[j] = (vu_image[num][j] >> i) & 1 ? 'O' : '.';
besm6_debug("<<< VU-%d: %.80s", num, buf);
}
besm6_debug("<<< VU-%d: ###", num);
}
}
static void reverse_card(int num, int raw) {
char content[500];
int i, j;
memset(vu_image[num], 0, 160);
content[0] = 0;
for (i = 0; i < 120; ++i) {
unsigned char ch = vu_gost[num][i];
int mask = 1 << (i / 10);
int pos = 8 * (i % 10);
if (!raw) {
if (ch == 0377)
break;
ch = gost_to_upp(ch);
}
for (j = 7; j >= 0; --j) {
if (ch & 1)
vu_image[num][pos+j] |= mask;
ch >>= 1;
}
}
}
extern int utf8_getc(FILE*);
static int
is_prettycard (unsigned char *s)
{
int i;
for (i=0; i<80; ++i)
if (s[i] != GOST_DOT && s[i] != GOST_O) {
return 0;
}
for (i = 80; i < 120; ++i)
if (s[i] != 0377)
return 0;
return 1;
}
static int chad (int num, int bit, char val)
{
int index = bit / 8;
switch (val) {
case GOST_O:
vu_gost[num][index] <<= 1;
vu_gost[num][index] |= 1;
return 0;
case GOST_DOT:
vu_gost[num][index] <<= 1;
return 0;
default:
return -1;
}
}
int
prettycard (UNIT *u)
{
int bit, ch;
int num = u - vu_unit;
for (bit = 0; bit < 80; bit++) {
/* The first line is good, no need to check */
chad(num, bit, vu_gost[num][bit]);
}
for (bit = 80; bit < 12*80; bit++) {
ch = utf8_getc(u->fileref);
if (ch == '\n' && bit % 80 == 0)
ch = utf8_getc(u->fileref);
ch = unicode_to_gost(ch);
if (chad(num, bit, ch))
return -1;
if (bit % 80 == 79) {
do ch = utf8_getc(u->fileref); while (ch == '\r');
if (ch != '\n')
return -1;
}
}
/* there may be an empty line after a card */
ch = getc(u->fileref);
if (ch != '\n')
ungetc(ch, u->fileref);
return 0;
}
/*
* Event: reading two characters (two columns) into the register, sending an interrupt.
*/
t_stat vu_event (UNIT *u)
{
int num = u - vu_unit;
if (vu_state[num] == VU_STARTING) {
// Reading a line and forming the GOST array.
int ch;
do
ch = utf8_getc(u->fileref);
while (ch == '\r');
if (ch == EOF) {
if (vu_dev.dctrl) {
besm6_debug("<<< VU-%d: EOF, detaching", num);
}
vu_state[num] = VU_IDLE;
vu_detach(u);
} else {
int endline = 0, i;
++vu_cardcnt[num];
for (i = 0; i < 120; ++i) {
if (endline) {
vu_gost[num][i] = 0377;
} else {
int gost;
if (ch == EOF || ch == '\n') {
endline = 1;
gost = 0377;
} else {
gost = unicode_to_gost(ch);
}
vu_gost[num][i] = gost;
if (!endline && i != 119)
do
ch = utf8_getc(u->fileref);
while (ch == '\r');
}
}
if (!endline) {
int ch;
do
ch = utf8_getc(u->fileref);
while (ch == '\n' || ch == EOF);
}
if (0 == strncmp((char *)vu_gost[num], DISP_END, 7)) {
// The "dispatcher's end" card, end of card image mode.
memset(vu_image[num], 0, 160);
vu_image[num][0] = vu_image[num][40] = 0xFFF;
} else if (is_prettycard(vu_gost[num])) {
if (prettycard(u) < 0) {
sim_printf("VU-%d: A badly formatted card image at card %d, garbage will follow",
num, vu_cardcnt[num]);
}
reverse_card(num, 1); /* raw */
} else if (vu_updkstart[num] != 0 && vu_cardcnt[num] >= vu_updkstart[num] &&
(vu_updkend[num] == 0 || vu_cardcnt[num] <= vu_updkend[num])) {
int i;
for (i = 0; i < 80; ++i)
vu_image[num][i] = gost_to_updk(vu_gost[num][i]);
} else {
reverse_card(num, 0); /* add parity */
}
if (vu_dev.dctrl) {
display_card(num);
besm6_debug("<<< VU-%d: card start", num);
}
GRP |= GRP_VU1_SYNC >> num;
sim_activate(u, vu_col_dly);
vu_state[num] = VU_COL;
VU[num] = 0;
}
} else if (VU_COL <= vu_state[num] && vu_state[num] <= VU_COL_LAST) {
int pos = (vu_state[num]++ - VU_COL) * 2;
VU[num] = (vu_image[num][pos] << 12) | vu_image[num][pos+1];
if (vu_dev.dctrl) {
besm6_debug("<<< VU-%d: cols %d-%d: reg %06x", num, pos+1, pos+2, VU[num]);
}
GRP |= GRP_VU1_SYNC >> num;
sim_activate (u, vu_col_dly);
} else if (vu_state[num] == VU_TAIL) {
PRP |= num == 0 ? PRP_VU1_END : PRP_VU2_END;
vu_state[num] = VU_TAIL2;
sim_activate(u, vu_end_dly);
if (vu_dev.dctrl) {
besm6_debug("<<< VU-%d: ------", num);
}
} else if (vu_state[num] == VU_TAIL2) {
PRP &= ~(num == 0 ? PRP_VU1_END : PRP_VU2_END);
SET_RDY2(VU1_FEED >> (num*4));
if (vu_next[num] == VU_STARTING) {
sim_activate (u, vu_card_dly);
}
vu_state[num] = vu_next[num];
if (vu_dev.dctrl) {
besm6_debug("<<< VU-%d: ======", num);
}
} else {
besm6_debug("<<< VU-%d: spurious event", num);
}
return SCPE_OK;
}
int vu_read(int num) {
if (vu_dev.dctrl)
besm6_debug("<<< VU-%d: reg %06x", num, VU[num]);
return VU[num];
}