blob: 8cc5ac161faf9b3ef4fbafd01c428a1e3c3d76e5 [file] [log] [blame] [raw]
/*
* 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;
}
}