/* | |
* besm6_punchcard.c: BESM-6 punchcard devices | |
* | |
* Copyright (c) 2017, 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" | |
#if 0 | |
/* | |
* Punchcard input not yet implemented. | |
*/ | |
t_stat uvvk_event (UNIT *u); /* punched card reader */ | |
UNIT uvvk_unit [] = { | |
{ UDATA (uvvk_event, UNIT_SEQ+UNIT_ATTABLE, 0) }, | |
{ UDATA (uvvk_event, UNIT_SEQ+UNIT_ATTABLE, 0) }, | |
}; | |
#endif | |
t_stat pi_event (UNIT *u); /* punched card writer */ | |
UNIT pi_unit [] = { | |
{ UDATA (pi_event, UNIT_SEQ+UNIT_ATTABLE, 0) }, | |
{ UDATA (pi_event, UNIT_SEQ+UNIT_ATTABLE, 0) }, | |
}; | |
/* | |
* Each line has 3 phases: | |
* - striking: the "PUNCH" interrupt line goes high, | |
* puncher solenoids can be activated | |
* - moving: solenoids are turned off, the "PUNCH" interrupt line goes low | |
* - checking: the "CHECK" interrupt goes high, | |
* querying of the check brushes can be done. | |
*/ | |
typedef enum { | |
PI_STRIKE, | |
PI_MOVE, | |
PI_CHECK, | |
PI_LAST = PI_STRIKE + 3*12 - 1, | |
PI_PAUSE, | |
PI_IDLE | |
} pi_state_t; | |
typedef struct { | |
/* | |
* A 3-card long tract, with 12 lines per card, | |
* represented as 4 20-bit registers each. | |
*/ | |
uint32 image[3][12][4]; | |
int cur; /* FIFO position */ | |
int running; /* continue with the next card */ | |
pi_state_t state; | |
void (*punch_fn)(int, int); | |
} pi_t; | |
/* | |
* There are 2 card punchers */ | |
pi_t PI[2]; | |
#define PI1_READY (1<<15) | |
#define PI2_READY (1<<13) | |
#define PI1_START (1<<14) | |
#define PI2_START (1<<12) | |
// #define NEGATIVE_RDY | |
#ifndef NEGATIVE_RDY | |
# define ENB_RDY2 SET_RDY2 | |
# define DIS_RDY2 CLR_RDY2 | |
# define IS_RDY2 ISSET_RDY2 | |
#else | |
# define ENB_RDY2 CLR_RDY2 | |
# define DIS_RDY2 SET_RDY2 | |
# define IS_RDY2 ISCLR_RDY2 | |
#endif | |
#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) | |
/* | |
* Per one line of a punched card. | |
*/ | |
#define PI_RATE (20*MSEC) | |
const uint32 pi_punch_mask[2] = { PRP_PCARD1_PUNCH, PRP_PCARD2_PUNCH }; | |
const uint32 pi_check_mask[2] = { PRP_PCARD1_CHECK, PRP_PCARD2_CHECK }; | |
const uint32 pi_ready_mask[2] = { PI1_READY, PI2_READY }; | |
const uint32 pi_start_mask[2] = { PI1_START, PI2_START }; | |
REG pi_reg[] = { | |
{ REGDATA ( "READY", READY2, 2, 4, 12, 1, NULL, NULL, 0, 0, 0) }, | |
{ 0 } | |
}; | |
MTAB pi_mod[] = { | |
{ 0 } | |
}; | |
t_stat pi_reset (DEVICE *dptr); | |
t_stat pi_attach (UNIT *uptr, CONST char *cptr); | |
t_stat pi_detach (UNIT *uptr); | |
DEVICE pi_dev = { | |
"PI", pi_unit, pi_reg, pi_mod, | |
2, 8, 19, 1, 8, 50, | |
NULL, NULL, &pi_reset, NULL, &pi_attach, &pi_detach, | |
NULL, DEV_DISABLE | DEV_DEBUG | |
}; | |
/* | |
* Outputs 12 lines of 80 characters plus an empty line. | |
*/ | |
static void pi_punch_dots(int unit, int card) { | |
UNIT *u = &pi_unit[unit]; | |
FILE * f = u->fileref; | |
int l, p, c; | |
for (l = 0; l < 12; ++l) { | |
for (p = 0; p < 4; ++p) | |
for (c = 19; c >= 0; --c) | |
putc((PI[unit].image[card][l][p] >> c) & 1 ? 'O' : '.', f); | |
putc('\n', f); | |
} | |
putc('\n', f); | |
} | |
static void pi_to_bytes(int unit, int card, unsigned char buf[120]) { | |
int byte = 0; | |
int cnt = 0; | |
int l, p, c; | |
for (l = 0; l < 12; ++l) { | |
for (p = 0; p < 4; ++p) { | |
for (c = 19; c >= 0; --c) { | |
int bit = (PI[unit].image[card][l][p] >> c) & 1 ? 1 : 0; | |
buf[byte] <<= 1; | |
buf[byte] |= bit; | |
if (++cnt == 8) { | |
cnt = 0; | |
++byte; | |
} | |
} | |
} | |
} | |
} | |
/* | |
* Outputs 120 bytes, read linewise. | |
*/ | |
static void pi_punch_binary(int unit, int card) { | |
UNIT *u = &pi_unit[unit]; | |
FILE * f = u->fileref; | |
static unsigned char buf[120]; | |
pi_to_bytes(unit, card, buf); | |
fwrite(buf, 120, 1, f); | |
} | |
/* | |
* Outputs a visual representation of the card | |
* using 3 lines of 40 Braille patterns, plus an empty line. | |
*/ | |
static void pi_punch_visual(int unit, int card) { | |
UNIT *u = &pi_unit[unit]; | |
FILE * f = u->fileref; | |
// Print 3 lines of 40 Braille characters per line representing a punchcard. | |
unsigned char bytes[3][40]; | |
int line, col, p, c; | |
memset(bytes, 0, 120); | |
for (line = 0; line < 12; ++line) { | |
for (p = 0; p < 4; ++p) | |
for (c = 19; c >= 0; --c) { | |
int bit = (PI[unit].image[card][line][p] >> c) & 1 ? 1 : 0; | |
int col = p*20 + 19-c; | |
if (bit) { | |
/* | |
* Braille Unicode codepoints are U+2800 plus | |
* an 8-bit mask of punches according to the map | |
* 0 3 | |
* 1 4 | |
* 2 5 | |
* 6 7 | |
*/ | |
bytes[line/4][col/2] |= | |
"\x01\x08\x02\x10\x04\x20\x40\x80"[line%4*2+col%2]; | |
} | |
} | |
} | |
for (line = 0; line < 3; ++line) { | |
for (col = 0; col < 40; ++col) { | |
fprintf(f, "\342%c%c", | |
0240+(bytes[line][col] >> 6), | |
0200 + (bytes[line][col] & 077)); | |
} | |
putc('\n', f); | |
} | |
putc('\n', f); | |
} | |
/* | |
* Attempts to interpret a card as GOST-10859 with odd parity; | |
* if fails, dumps visual. | |
*/ | |
static void pi_punch_gost(int unit, int card) { | |
UNIT *u = &pi_unit[unit]; | |
FILE * f = u->fileref; | |
static unsigned char buf[120]; | |
int len; | |
int cur; | |
int zero_expected = 0; | |
pi_to_bytes(unit, card, buf); | |
/* | |
* Bytes in the buffer must have odd parity, with the exception | |
* of optional zero bytes at the end of lines and at the end of a card. | |
* Trailing zeros are trimmed, intermediate zeros become blanks. | |
* The first character in each line must be valid. | |
*/ | |
for (len = 120; len && !buf[len-1]; --len); | |
for (cur = 0; cur < len; ++cur) { | |
if (cur % 10 == 0) { | |
/* A new line */ | |
zero_expected = 0; | |
} | |
if (zero_expected) { | |
if (buf[cur]) | |
break; | |
} else if (!buf[cur]) { | |
if (cur % 10 == 0) { | |
/* The first char in a line is zero */ | |
break; | |
} | |
zero_expected = 1; | |
} else if (!odd_parity(buf[cur]) || (buf[cur] & 0177) >= 0140) { | |
break; | |
} | |
} | |
if (cur != len) { | |
/* Bad parity or invalid codepoint detected */ | |
pi_punch_visual(unit, card); | |
} else { | |
for (cur = 0; cur < len; ++cur) { | |
if (buf[cur]) { | |
gost_putc(buf[cur] & 0177, f); | |
} else { | |
putc(' ', f); | |
} | |
} | |
putc('\n', f); | |
} | |
} | |
/* | |
* Dumps the last card in the FIFO and advances the FIFO pointer | |
* (this is equivalent to advancing the FIFO pointer and dumping | |
* the "current" card). | |
*/ | |
static void pi_output (int unit, int cull) { | |
int card; | |
PI[unit].cur = card = (PI[unit].cur + 1) % 3; | |
if (cull) { | |
besm6_debug("<<< PI-%d: Culling bad card", unit); | |
} else { | |
(*PI[unit].punch_fn)(unit, card); | |
} | |
pi_unit[unit].pos = ftell(pi_unit[unit].fileref); | |
memset(PI[unit].image[card], 0, sizeof(PI[unit].image[card])); | |
} | |
/* | |
* Reset routine | |
*/ | |
t_stat pi_reset (DEVICE *dptr) | |
{ | |
sim_cancel (&pi_unit[0]); | |
sim_cancel (&pi_unit[1]); | |
PI[0].state = PI[1].state = PI_IDLE; | |
DIS_RDY2(PI1_READY | PI2_READY); | |
if (pi_unit[0].flags & UNIT_ATT) | |
ENB_RDY2(PI1_READY|PI1_START); | |
if (pi_unit[1].flags & UNIT_ATT) | |
ENB_RDY2(PI2_READY|PI2_START); | |
return SCPE_OK; | |
} | |
/* | |
* Punching mode switches: | |
* -b - raw binary, line-wise, 120 bytes per p/c; | |
* -v - a visual form using Unicode Braille patterns; | |
* -d - a visual form using dots and Os; | |
* -g or -u - attempts to interpret the card as GOST/UPP text. | |
* The default is -v. | |
*/ | |
t_stat pi_attach (UNIT *u, CONST char *cptr) | |
{ | |
t_stat s; | |
int unit = u - pi_unit; | |
PI[unit].punch_fn = NULL; | |
while (sim_switches & | |
(SWMASK('B')|SWMASK('V')|SWMASK('D')|SWMASK('G')|SWMASK('U'))) { | |
if (PI[unit].punch_fn) { | |
return SCPE_ARG; | |
} | |
if (sim_switches & SWMASK('B')) { | |
PI[unit].punch_fn = pi_punch_binary; | |
sim_switches &= ~SWMASK('B'); | |
} else if (sim_switches & SWMASK('V')) { | |
PI[unit].punch_fn = pi_punch_visual; | |
sim_switches &= ~SWMASK('V'); | |
} else if (sim_switches & SWMASK('D')) { | |
PI[unit].punch_fn = pi_punch_dots; | |
sim_switches &= ~SWMASK('D'); | |
} else if (sim_switches & SWMASK('G')) { | |
PI[unit].punch_fn = pi_punch_gost; | |
sim_switches &= ~SWMASK('G'); | |
} else if (sim_switches & SWMASK('U')) { | |
PI[unit].punch_fn = pi_punch_gost; | |
sim_switches &= ~SWMASK('U'); | |
} | |
} | |
if (PI[unit].punch_fn == NULL) { | |
PI[unit].punch_fn = pi_punch_visual; | |
} | |
s = attach_unit (u, cptr); | |
if (s != SCPE_OK) | |
return s; | |
ENB_RDY2(pi_ready_mask[unit]); | |
return SCPE_OK; | |
} | |
t_stat pi_detach (UNIT *u) | |
{ | |
int unit = u - pi_unit; | |
DIS_RDY2(pi_ready_mask[unit]); | |
return detach_unit (u); | |
} | |
void pi_control (int num, uint32 cmd) | |
{ | |
UNIT *u = &pi_unit[num]; | |
if (pi_dev.dctrl) | |
besm6_debug("<<<PI-%d cmd %o, state %d", num, cmd, PI[num].state); | |
cmd &= 011; | |
if (! IS_RDY2(pi_ready_mask[num])) { | |
if (pi_dev.dctrl) | |
besm6_debug("<<< PI-%d not ready", num, cmd); | |
return; | |
} | |
switch (cmd) { | |
case 000: /* stop */ | |
case 010: /* stop with culling (doesn't make much sense) */ | |
if (PI[num].state == PI_LAST) { | |
pi_output(num, cmd & 010); | |
} | |
sim_cancel (u); | |
PI[num].state = PI_IDLE; | |
ENB_RDY2(pi_start_mask[num]); | |
break; | |
case 001: /* start without culling */ | |
case 011: /* start with culling */ | |
switch (PI[num].state) { | |
case PI_IDLE: | |
sim_activate (u, PI_RATE); | |
break; | |
case PI_PAUSE: | |
/* Switching on during pause ignored */ | |
besm6_debug("<<< PI-%d switching on during pause ignored", num); | |
break; | |
case PI_LAST: | |
PI[num].running = 1; | |
/* This is the only state when the cull bit is honored */ | |
pi_output(num, cmd & 010); | |
break; | |
default: | |
PI[num].running = 1; | |
break; | |
} break; | |
} | |
} | |
t_stat pi_event (UNIT *u) | |
{ | |
int unit = u - pi_unit; | |
if (++PI[unit].state > PI_IDLE) { | |
/* Starting a new card */ | |
PI[unit].state = PI_STRIKE; | |
} | |
switch (PI[unit].state) { | |
case PI_LAST: | |
/* | |
* At the last check interrupt, | |
* the "permission to start" flag is cleared. | |
*/ | |
DIS_RDY2(pi_start_mask[unit]); | |
break; | |
case PI_PAUSE: | |
/* | |
* The permission to start is re-enabled. | |
*/ | |
ENB_RDY2(pi_start_mask[unit]); | |
PI[unit].state = PI_IDLE; | |
if (PI[unit].running) { | |
if (pi_dev.dctrl) | |
besm6_debug ("<<< PI-%d re-enabled", unit); | |
sim_activate(u, PI_RATE); | |
PI[unit].running = 0; | |
} else { | |
/* | |
* The unit is going idle without an explicit "stop" command: | |
* The last card (the separator) falls into the "good" bin. | |
*/ | |
pi_output(unit, 0); | |
} | |
break; | |
default: | |
break; | |
} | |
if (pi_dev.dctrl) | |
besm6_debug ("<<< PI-%d event, state %d", unit, PI[unit].state); | |
if (PI[unit].state <= PI_LAST) { | |
switch (PI[unit].state % 3) { | |
case PI_STRIKE: | |
/* Punch interrupt */ | |
PRP |= pi_punch_mask[unit]; | |
sim_activate(u, PI_RATE/3); | |
break; | |
case PI_MOVE: | |
/* Punchers off */ | |
PRP &= ~pi_punch_mask[unit]; | |
sim_activate(u, 2*PI_RATE/3); | |
break; | |
case PI_CHECK: | |
/* Check interrupt */ | |
PRP |= pi_check_mask[unit]; | |
sim_activate(u, PI_RATE); | |
} | |
} | |
return SCPE_OK; | |
} | |
/* | |
* Writing to the register punches the current card. | |
*/ | |
void pi_write (int num, uint32 val) | |
{ | |
int unit = num >> 2; | |
int card = PI[unit].cur; | |
int pos = (num & 3) ^ 3; | |
int line = PI[unit].state / 3; | |
if (line > 11 || PI[unit].state % 3 != PI_STRIKE) { | |
besm6_debug("<<< PI-%d, writing out of turn, useless", num); | |
return; | |
} | |
if (pi_dev.dctrl) { | |
besm6_debug("Card %d line %d pos %d <- val %05x", | |
card, line, pos, val); | |
} | |
PI[unit].image[card][line][pos] = val; | |
} | |
/* | |
* Reading from the register reads the previous card | |
* and returns the inverted value. | |
*/ | |
int pi_read (int num) | |
{ | |
int unit = num >> 2; | |
int pos = (num & 3) ^ 3; | |
int line = PI[unit].state / 3; | |
int card = (PI[unit].cur + 2) % 3; | |
if (line > 11 || PI[unit].state % 3 != PI_CHECK) { | |
/* Reading out of turn */ | |
return 0xFFFFF; | |
} else { | |
if (pi_dev.dctrl) { | |
besm6_debug("Card %d line %d pos %d -> val %05x", | |
card, line, pos, PI[unit].image[card][line][pos]); | |
} | |
return PI[unit].image[card][line][pos] ^ 0xFFFFF; | |
} | |
} | |