/* * 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" 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_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; } }