// license:BSD-3-Clause // copyright-holders:F. Ulivi /********************************************************************* hp_taco.cpp HP TApe COntroller (5006-3012) *********************************************************************/ // This device has been reverse engineered entirely through documents & study of HP software. // I had no access to the real device to experiment. // Available documentation on the internal working of TACO chip is close to nothing. The best // I could find is [1] (see below) where all that's described is a (too) brief summary of registers and little else. // In other words, no description of the commands that can be issued to TACO chips. // So, my main source of information was the careful study of HP software, especially the 9845 system test ROM (09845-66520). // The second half of this ROM holds a comprehensive set of tape drive tests. // Another source was the "SIF" utility tools which use a lot of peculiar command sequences. // The main shortcomings of my approach are: // * I could identify only those TACO commands that are actually used by the software. I managed // to identify 17 out of 32 possible commands. The purpose of the rest of commands is anyone's guess. // * I could only guess the behavior of TACO chips in corner cases (especially behavior in various error/abnormal // conditions) // // Documentation I used: // [1] HP, manual 64940-90905, may 80 rev. - Model 64940A tape control & drive service manual // [2] US patent 4,075,679 describing HP9825 system (this system had a discrete implementation of tape controller). The // firmware listing was quite useful in identifying sequences of commands (for example how to find a specific sector etc.). // [3] http://www.hp9845.net site // [4] April 1978 issue of HP Journal. There is a one-page summary of TACO chip on page 20. // [5] HP, manual 09845-10201, apr 81 rev. - General Utility Routines. This manual describes the SIF format and related // utility tools. // This is an overview of the TACO/CPU interface. // // Reg. | R/W | Content // ===================== // R4 | R/W | Data register: words read/written to/from tape pass through this register // R5 | R/W | Command and status register (see below) // R6 | R/W | Tachometer register. Writing it sets a pulse counter that counts up on either tachometer pulses or IRGs, depending // | | on command. When the counter rolls over from 0xffff to 0 it typically ends the command. // | | Current counter value is returned when reading from this register. // R7 | R | Checksum register. Reading it clears it next time the checksum is updated. // R7 | W | Timing register. It controls somehow the encoding and decoding of bits. For now I completely ignore it because its // | | content it's totally unknown to me. It seems safe to do so, anyway. I can see that it's always set to 0x661d before // | | writing to tape and to 0x0635 before reading (0x061d is set by SIF utilities). // // Format of TACO command/status register (R5) // Bit R/W Content // =============== // 15 RW Tape direction (1 = forward) // 14..10 RW Command (see the "enum" below) // 9 RW Automatic stopping of tape at command completion (when 1) // 8 RW Minimum size of gaps (1 = 1.5", 0 = 0.066") // 7 RW Speed of tape (1 = 90 ips, 0 = 22 ips) // 6 RW Option bit for various commands. Most of them use it to select read threshold (0 = low, 1 = high). // 5 R Current track (1 = B) // 4 R Gap detected (1) // 3 R Write protection (1) // 2 R Servo failure (1) // 1 R Cartridge out (1) // 0 R Hole detected (1) // Here's a summary of the on-tape format of HP9845 systems. // * A tape has two independent tracks (A & B). // * Each track holds 426 sectors. // * Each sector has an header and 256 bytes of payload (see below) // * Sectors are separated by gaps of uniform magnetization called IRGs (Inter-Record Gaps) // * The basic unit of data I/O are 16-bit words // * Bits are encoded by different distances between magnetic flux reversals // * The structure of tracks is: // - Begin of tape holes // - The deadzone: 350x 0xffff words // - 1" of IRG // - Sector #0 (track A) or #426 (track B) // - 1" of IRG (2.5" on track A) // - Sector #1 (track A) or #427 (track B) // - 1" of IRG // - Sector #2 (track A) or #428 (track B) // - ...and so on up to sector #425/#851 // - 6" of final gap // - Non-recorded tape // - End of tape holes // * Even though the tape format is not SIF (HP's own Standard Interchange Format), it is // clearly based on SIF itself. The whole tape content is stored according to SIF, // specifically it is "encapsulated" inside file #1. // * Sector #0 is not used. It serves as SIF "file identifier record" (i.e. it identifies // that the rest of tape is inside file #1 from SIF point of view). // * Sectors #1 and #2 hold the first copy of tape directory // * Sectors #3 and #4 hold the second/backup copy of tape directory // * User data are stored starting from sector #5 // * There is no "fragmentation" map (like file allocation table in FAT filesystem): a file // spanning more than 1 sector always occupy a single block of contiguous sectors. // // A sector is structured like this (see description of SIF in [5], pg 655 and following): // Word 0: Invisible preamble word (always 0). Preamble comes from 9825, don't know if it's // actually there in TACO encoding. I assumed it is. // Word 1: File word: file identifier bit, empty record indicator and file number // Word 2: Record word: sector number and "free field pattern" // Word 3: Length word: bytes available and used in sector // Word 4: Checksum (sum of words 1..3) // Words 5..132: Payload // Word 133: Checksum (sum of words 5..132) // // Physical encoding of words is borrowed from 9825 as I wasn't able // to gather any info on the actual encoding of TACO chips. // This is how 9825 encodes words on tape: // - the unit of encoding are 16-bit words // - each word is encoded from MSB to LSB // - each word has an extra invisible "1" encoded at the end (a kind of "stop" bit) // - tape is read/written at slow speed only (21.98 ips) // - a 0 is encoded with a distance between flux reversals of 1/35200 s // (giving a maximum density of about 1600 reversals per inch) // - a 1 is encoded with a distance that's 1.75 times that of a 0 // // This driver is based on the following model of the actual TACO/tape system: // * Tape immediately reaches working speed (no spin-up time) // * Change of speed is immediate as well // * Time & distance to stop and invert direction of tape are modeled, though. Firmware is upset by // a tape with null braking/inversion time/distance. // * Speed of tape is exceptionally accurate. Real tape was controlled by a closed loop // with something like 1% accuracy on speed. // * Storage is modeled by one "map" data structure per track. Each map maps the tape position // to the 16-bit word stored at that position. Gaps are modeled by lack of data in the map. // There is no model of the physical encoding of bits (except to compute how long each word // is on tape). // * Read threshold is ignored. Real tapes could be read with either a low or high threshold. // * "Flag" bit is used as a busy/ready signal in real TACO. Here I assumed the device is // always ready, so Flag is always active. // * I tried to fill the (many) gaps on chip behavior with "sensible" solutions. I could only // validate my solutions by running the original firmware in MAME, though (no real hw at hand). // // TODOs/issues: // * Commands 00 & 10 seem to do the same things. My bet is that they differ in some subtle way that // is not stimulated by all the tape software I used for R.E. Maybe it's just the length of the // no-data timeout they have. // * Find more info on TACO chips (does anyone with a working 9845 or access to internal HP docs want to // help me here, please?) // #include "emu.h" #include "hp_taco.h" #include "ui/uimain.h" // Debugging #define VERBOSE 1 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) #define VERBOSE_0 0 #define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) // Macros to clear/set single bits #define BIT_MASK(n) (1U << (n)) #define BIT_CLR(w , n) ((w) &= ~BIT_MASK(n)) #define BIT_SET(w , n) ((w) |= BIT_MASK(n)) // Timers enum { TAPE_TMR_ID, HOLE_TMR_ID, TIMEOUT_TMR_ID }; // Constants #define CMD_REG_MASK 0xffc0 // Command register mask #define STATUS_REG_MASK 0x003f // Status register mask #define TACH_TICKS_PER_INCH 968 // Tachometer pulses per inch of tape movement #define TAPE_POS_FRACT 1024 // 10 bits of fractional part in tape_pos_t #define ONE_INCH_POS (TACH_TICKS_PER_INCH * TAPE_POS_FRACT) // Value in tape_pos_t representing 1 inch of tape #define TACH_FREQ_SLOW 21276 // Tachometer pulse frequency for slow speed (21.98 ips) #define TACH_FREQ_FAST 87196 // Tachometer pulse frequency for fast speed (90.08 ips) #define TACH_FREQ_BRAKE_SLOW 11606 // Tachometer pulse frequency when stopping from slow speed (11.99 ips) #define TACH_FREQ_BRAKE_FAST 44566 // Tachometer pulse frequency when stopping from fast speed (46.04 ips) #define TAPE_LENGTH ((140 * 12 + 72 * 2) * ONE_INCH_POS) // Tape length: 140 ft of usable tape + 72" of punched tape at either end #define TAPE_INIT_POS (80 * ONE_INCH_POS) // Initial tape position: 80" from beginning (just past the punched part) #define ZERO_BIT_LEN 619 // Length of 0 bits at slow tape speed: 1/(35200 Hz) #define ONE_BIT_LEN 1083 // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN #define QUICK_CMD_USEC 25 // usec for "quick" command execution (totally made up) #define FAST_BRAKE_DIST 3350450 // Braking distance at fast speed (~3.38 in) // There are 2 braking distances here: The first one (commented out) is the theoretical value, the second one // is the value that works. Ideally we would always be using the first value but there is a kind of race // condition when loading the memory test from the exerciser that freezes the system. So, the second (shorter) // value is used to avoid this condition. //#define SLOW_BRAKE_DIST 197883 // Braking distance at slow speed (~0.2 in) #define SLOW_BRAKE_DIST 71000 // Braking distance at slow speed (~0.07 in) #define PREAMBLE_WORD 0 // Value of preamble word #define END_GAP_LENGTH (6 * ONE_INCH_POS) // Length of final gap: 6" // Minimum gap lengths are probably counted from tacho pulses in real TACO: short gaps could be equal to 64 pulses and long ones // to 1472 (23 * 64) #define SHORT_GAP_LENGTH ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Minimum length of short gaps: 0.066" ([1], pg 8-10) #define LONG_GAP_LENGTH ((tape_pos_t)(1.5 * ONE_INCH_POS)) // Minimum length of long gaps: 1.5" ([1], pg 8-10) #define NULL_TAPE_POS ((tape_pos_t)-1) // Special value for invalid/unknown tape position #define PREAMBLE_TIMEOUT ((tape_pos_t)(2.6 * ONE_INCH_POS)) // Min. length of gap making preamble search time out (totally made up) #define DATA_TIMEOUT ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Min. length of gap that will cause data reading to time out (totally made up) #define FILE_MAGIC 0x4f434154 // Magic value at start of image file: "TACO" // Parts of command register #define CMD_CODE(reg) \ (((reg) >> 10) & 0x1f) #define DIR_FWD(reg) \ (BIT(reg , 15)) #define AUTO_STOP(reg) \ (BIT(reg , 9)) #define LONG_GAP(reg) \ (BIT(reg , 8)) #define SPEED_FAST(reg) \ (BIT(reg , 7)) #define CMD_OPT(reg) \ (BIT(reg , 6)) #define DIR_FWD_MASK BIT_MASK(15) // Direction = forward #define SPEED_FAST_MASK BIT_MASK(7) // Speed = fast // Commands enum { CMD_INDTA_INGAP, // 00: scan for data first then for gap (see also cmd 10) CMD_UNK_01, // 01: unknown CMD_FINAL_GAP, // 02: write final gap CMD_INIT_WRITE, // 03: write words for tape formatting CMD_STOP, // 04: stop CMD_UNK_05, // 05: unknown CMD_SET_TRACK, // 06: set A/B track CMD_UNK_07, // 07: unknown CMD_UNK_08, // 08: unknown CMD_UNK_09, // 09: unknown CMD_MOVE, // 0a: move tape CMD_UNK_0b, // 0b: unknown CMD_INGAP_MOVE, // 0c: scan for gap then move a bit further (used to gain some margin when inverting tape movement) CMD_UNK_0d, // 0d: unknown CMD_CLEAR, // 0e: clear errors/unlatch status bits CMD_UNK_0f, // 0f: unknown CMD_NOT_INDTA, // 10: scan for end of data (at the moment it's the same as cmd 00) CMD_UNK_11, // 11: unknown CMD_UNK_12, // 12: unknown CMD_UNK_13, // 13: unknown CMD_UNK_14, // 14: unknown CMD_UNK_15, // 15: unknown CMD_WRITE_IRG, // 16: write inter-record gap CMD_UNK_17, // 17: unknown CMD_SCAN_RECORDS, // 18: scan records (count IRGs) CMD_RECORD_WRITE, // 19: write record words CMD_MOVE_INDTA, // 1a: move then scan for data CMD_UNK_1b, // 1b: unknown (for now it seems harmless to handle it as NOP) CMD_MOVE_INGAP, // 1c: move tape a given distance then scan for gap (as cmd 0c but in reverse order) CMD_START_READ, // 1d: start record reading CMD_DELTA_MOVE_IRG, // 1e: move tape a given distance, detect gaps in parallel CMD_END_READ // 1f: stop reading }; // Bits of status register #define STATUS_HOLE_BIT 0 // Hole detected #define STATUS_CART_OUT_BIT 1 // Cartridge out #define STATUS_SFAIL_BIT 2 // Servo failure #define STATUS_WPR_BIT 3 // Write protection #define STATUS_GAP_BIT 4 // Gap detected #define STATUS_TRACKB_BIT 5 // Track B selected #define STATUS_CART_OUT_MASK BIT_MASK(STATUS_CART_OUT_BIT) // Cartridge out #define STATUS_WPR_MASK BIT_MASK(STATUS_WPR_BIT) // Write protection #define STATUS_ERR_MASK (STATUS_CART_OUT_MASK) // Mask of errors in status reg. // *** Position of tape holes *** // At beginning of tape: // *START* // |<-----24"----->|<---12"--->|<---12"--->|<-----24"----->| // O O O O O O O // |<->| |<->| |<->| // 0.218" 0.218" 0.218" // At end of tape: // *END* // |<-----24"----->|<---12"--->|<---12"--->|<-----24"----->| // O O O O // static const hp_taco_device::tape_pos_t tape_holes[] = { (hp_taco_device::tape_pos_t)(23.891 * ONE_INCH_POS), // 24 - 0.218 / 2 (hp_taco_device::tape_pos_t)(24.109 * ONE_INCH_POS), // 24 + 0.218 / 2 (hp_taco_device::tape_pos_t)(35.891 * ONE_INCH_POS), // 36 - 0.218 / 2 (hp_taco_device::tape_pos_t)(36.109 * ONE_INCH_POS), // 36 + 0.218 / 2 (hp_taco_device::tape_pos_t)(47.891 * ONE_INCH_POS), // 48 - 0.218 / 2 (hp_taco_device::tape_pos_t)(48.109 * ONE_INCH_POS), // 48 + 0.218 / 2 72 * ONE_INCH_POS, // 72 1752 * ONE_INCH_POS, // 1752 1776 * ONE_INCH_POS, // 1776 1788 * ONE_INCH_POS, // 1788 1800 * ONE_INCH_POS // 1800 }; // Device type definition const device_type HP_TACO = device_creator; // Constructors hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, uint32_t clock, const char *shortname) : device_t(mconfig, type, name, tag, owner, clock, shortname, __FILE__), device_image_interface(mconfig , *this), m_irq_handler(*this), m_flg_handler(*this), m_sts_handler(*this), m_image_dirty(false) { clear_state(); } hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, HP_TACO, "HP TACO", tag, owner, clock, "hp_taco", __FILE__), device_image_interface(mconfig , *this), m_irq_handler(*this), m_flg_handler(*this), m_sts_handler(*this), m_image_dirty(false) { clear_state(); } WRITE16_MEMBER(hp_taco_device::reg_w) { LOG_0(("wr R%u = %04x\n", 4 + offset , data)); // Any I/O activity clears IRQ irq_w(false); switch (offset) { case 0: // Data register m_data_reg = data; m_data_reg_full = true; break; case 1: // Command register start_cmd_exec(data & CMD_REG_MASK); break; case 2: // Tachometer register m_tach_reg = data; freeze_tach_reg(true); break; case 3: // Timing register m_timing_reg = data; break; } } READ16_MEMBER(hp_taco_device::reg_r) { uint16_t res = 0; // Any I/O activity clears IRQ irq_w(false); switch (offset) { case 0: // Data register res = m_data_reg; break; case 1: // Command & status register res = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK); break; case 2: // Tachometer register update_tach_reg(); res = m_tach_reg; break; case 3: // Checksum register: it clears when read res = m_checksum_reg; m_clear_checksum_reg = true; break; } LOG_0(("rd R%u = %04x\n", 4 + offset , res)); return res; } READ_LINE_MEMBER(hp_taco_device::flg_r) { return m_flg; } READ_LINE_MEMBER(hp_taco_device::sts_r) { return m_sts; } // device_start void hp_taco_device::device_start() { LOG(("device_start")); m_irq_handler.resolve_safe(); m_flg_handler.resolve_safe(); m_sts_handler.resolve_safe(); save_item(NAME(m_data_reg)); save_item(NAME(m_data_reg_full)); save_item(NAME(m_cmd_reg)); //save_item(NAME(m_cmd_state)); save_item(NAME(m_status_reg)); save_item(NAME(m_tach_reg)); save_item(NAME(m_tach_reg_ref)); save_item(NAME(m_tach_reg_frozen)); save_item(NAME(m_checksum_reg)); save_item(NAME(m_clear_checksum_reg)); save_item(NAME(m_timing_reg)); save_item(NAME(m_irq)); save_item(NAME(m_flg)); save_item(NAME(m_sts)); save_item(NAME(m_tape_pos)); save_item(NAME(m_start_time)); save_item(NAME(m_tape_fwd)); save_item(NAME(m_tape_fast)); save_item(NAME(m_image_dirty)); save_item(NAME(m_tape_wr)); save_item(NAME(m_rw_pos)); save_item(NAME(m_next_word)); save_item(NAME(m_rd_it_valid)); save_item(NAME(m_gap_detect_start)); m_tape_timer = timer_alloc(TAPE_TMR_ID); m_hole_timer = timer_alloc(HOLE_TMR_ID); m_timeout_timer = timer_alloc(TIMEOUT_TMR_ID); } // device_stop void hp_taco_device::device_stop() { } // device_reset void hp_taco_device::device_reset() { LOG(("device_reset")); clear_state(); m_irq = false; m_flg = true; m_irq_handler(false); m_flg_handler(true); set_error(false); m_tape_timer->reset(); m_hole_timer->reset(); m_timeout_timer->reset(); } void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { update_tape_pos(); switch (id) { case TAPE_TMR_ID: LOG_0(("Tape tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); cmd_fsm(); break; case HOLE_TMR_ID: LOG_0(("Hole tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); BIT_SET(m_status_reg , STATUS_HOLE_BIT); if (m_cmd_state >= CMD_PH0 && m_cmd_state <= CMD_END) { switch (CMD_CODE(m_cmd_reg)) { case CMD_FINAL_GAP: case CMD_WRITE_IRG: write_gap(m_rw_pos , m_tape_pos); m_rw_pos = m_tape_pos; break; case CMD_INIT_WRITE: case CMD_RECORD_WRITE: case CMD_MOVE_INGAP: m_hole_timer->adjust(time_to_next_hole()); // No IRQ at holes return; case CMD_DELTA_MOVE_IRG: // Hit hole before end of programmed pulses terminate_cmd_now(); update_tach_reg(); freeze_tach_reg(true); return; case CMD_INDTA_INGAP: case CMD_INGAP_MOVE: case CMD_NOT_INDTA: case CMD_SCAN_RECORDS: // Commands are terminated at first hole (and failure is reported) terminate_cmd_now(); set_error(true); return; case CMD_START_READ: case CMD_END_READ: // Commands report failure at first hole set_error(true); break; default: // Other cmds: default processing (update tape pos, set IRQ, schedule timer for next hole) break; } irq_w(true); } m_hole_timer->adjust(time_to_next_hole()); break; case TIMEOUT_TMR_ID: LOG_0(("T/O tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); switch (CMD_CODE(m_cmd_reg)) { case CMD_START_READ: if (m_cmd_state == CMD_PH1) { irq_w(true); } break; default: // Most commands are terminated with failure on data T/O terminate_cmd_now(); break; } set_error(true); break; default: break; } } void hp_taco_device::clear_state(void) { m_data_reg = 0; m_data_reg_full = false; m_cmd_reg = 0; m_status_reg = 0; m_tach_reg = 0; m_tach_reg_ref = m_tape_pos; m_tach_reg_frozen = true; m_checksum_reg = 0; m_clear_checksum_reg = false; m_timing_reg = 0; m_cmd_state = CMD_IDLE; m_tape_pos = TAPE_INIT_POS; m_start_time = attotime::never; m_tape_fwd = false; m_tape_fast = false; // m_image_dirty is not touched m_tape_wr = false; m_rw_pos = 0; m_next_word = 0; m_rd_it_valid = false; m_gap_detect_start = NULL_TAPE_POS; set_tape_present(false); set_tape_present(is_loaded()); } void hp_taco_device::irq_w(bool state) { if (state != m_irq) { m_irq = state; m_irq_handler(state); LOG_0(("IRQ = %d\n" , state)); } } void hp_taco_device::set_error(bool state) { m_sts = !state; m_sts_handler(m_sts); LOG_0(("error = %d\n" , state)); } bool hp_taco_device::is_braking(void) const { return m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING; } unsigned hp_taco_device::speed_to_tick_freq(void) const { return is_braking() ? (m_tape_fast ? TACH_FREQ_BRAKE_FAST * TAPE_POS_FRACT : TACH_FREQ_BRAKE_SLOW * TAPE_POS_FRACT) : (m_tape_fast ? TACH_FREQ_FAST * TAPE_POS_FRACT : TACH_FREQ_SLOW * TAPE_POS_FRACT); } bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const { if (offset == 0) { return true; } if (!m_tape_fwd) { offset = -offset; } pos += offset; // In real life tape would unspool.. if (pos > TAPE_LENGTH) { pos = TAPE_LENGTH; return false; } else if (pos < 0) { pos = 0; return false; } else { return true; } } hp_taco_device::tape_pos_t hp_taco_device::current_tape_pos(void) const { if (m_start_time.is_never()) { // Tape not moving return m_tape_pos; } attotime delta_time(machine().time() - m_start_time); LOG_0(("delta_time = %g\n" , delta_time.as_double())); // How many tachometer ticks has the tape moved? tape_pos_t delta_tach = (tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); LOG_0(("delta_tach = %u\n" , delta_tach)); tape_pos_t tape_pos = m_tape_pos; if (!pos_offset(tape_pos , delta_tach)) { LOG(("Tape unspooled!\n")); } return tape_pos; } void hp_taco_device::update_tape_pos(void) { if (m_start_time.is_never()) { // Tape not moving return; } m_tape_pos = current_tape_pos(); m_start_time = machine().time(); LOG_0(("Tape pos = %u\n" , m_tape_pos)); // Gap detection bool gap_detected = false; if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= min_gap_size()) { tape_pos_t tmp = m_tape_pos; pos_offset(tmp , -min_gap_size()); gap_detected = just_gap(tmp , m_tape_pos); } if (gap_detected) { BIT_SET(m_status_reg, STATUS_GAP_BIT); } else { BIT_CLR(m_status_reg, STATUS_GAP_BIT); } // Tach register update update_tach_reg(); } void hp_taco_device::update_tach_reg(void) { if (m_tach_reg_frozen) { LOG_0(("Tach reg frozen\n")); return; } tape_pos_t pos = current_tape_pos(); tape_pos_t pos_int = pos / TAPE_POS_FRACT; tape_pos_t ref_int = m_tach_reg_ref / TAPE_POS_FRACT; uint16_t reg_value = (uint16_t)(abs(pos_int - ref_int) + m_tach_reg); LOG_0(("Tach = %04x @ pos = %d, ref_pos = %d\n" , reg_value , pos , m_tach_reg_ref)); m_tach_reg = reg_value; m_tach_reg_ref = pos; } void hp_taco_device::freeze_tach_reg(bool freeze) { if (freeze) { m_tach_reg_frozen = true; } else { m_tach_reg_frozen = false; m_tach_reg_ref = current_tape_pos(); } } void hp_taco_device::ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b) { if (a > b) { // Ensure A always comes before B tape_pos_t tmp; tmp = a; a = b; b = tmp; } } // Position of next hole tape will reach in a given direction hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const { if (m_tape_fwd) { for (tape_pos_t hole : tape_holes) { if (hole > m_tape_pos) { LOG_0(("next hole fwd @%u = %u\n" , m_tape_pos , hole)); return hole; } } // No more holes: will hit end of tape return NULL_TAPE_POS; } else { for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) { if (tape_holes[ i ] < m_tape_pos) { LOG_0(("next hole rev @%u = %u\n" , m_tape_pos , tape_holes[ i ])); return tape_holes[ i ]; } } // No more holes: will hit start of tape return NULL_TAPE_POS; } } attotime hp_taco_device::time_to_distance(tape_pos_t distance) const { // +1 for rounding return attotime::from_ticks(distance + 1 , speed_to_tick_freq()); } attotime hp_taco_device::time_to_target(tape_pos_t target) const { return time_to_distance(abs(target - m_tape_pos)); } attotime hp_taco_device::time_to_stopping_pos(void) const { return time_to_distance(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); } bool hp_taco_device::start_tape_cmd(uint16_t cmd_reg , uint16_t must_be_1 , uint16_t must_be_0) { m_cmd_reg = cmd_reg; uint16_t to_be_tested = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK); // Bits in STATUS_ERR_MASK must always be 0 must_be_0 |= STATUS_ERR_MASK; // It's not an error if the error state is already set (sts false) if (((to_be_tested & (must_be_1 | must_be_0)) ^ must_be_1) != 0) { return false; } else { bool prev_tape_wr = m_tape_wr; bool prev_tape_fwd = m_tape_fwd; bool prev_tape_fast = m_tape_fast; bool prev_tape_braking = is_braking(); bool not_moving = m_start_time.is_never(); m_start_time = machine().time(); m_tape_wr = (must_be_0 & STATUS_WPR_MASK) != 0; m_tape_fwd = DIR_FWD(m_cmd_reg); m_tape_fast = SPEED_FAST(m_cmd_reg); if (m_tape_wr) { // Write command: disable gap detector m_gap_detect_start = NULL_TAPE_POS; BIT_CLR(m_status_reg, STATUS_GAP_BIT); m_image_dirty = true; } else if (not_moving || prev_tape_braking || prev_tape_wr != m_tape_wr || prev_tape_fwd != m_tape_fwd || prev_tape_fast != m_tape_fast) { // Tape started or re-started right now, switched from writing to reading, direction changed or speed changed: (re)start gap detector m_gap_detect_start = m_tape_pos; BIT_CLR(m_status_reg, STATUS_GAP_BIT); } if (!not_moving && prev_tape_fwd != m_tape_fwd) { // Tape direction inverted, stop tape before executing command m_tape_fwd = prev_tape_fwd; m_tape_fast = prev_tape_fast; m_cmd_state = CMD_INVERTING; LOG_0(("Direction reversed! fwd = %d fast = %d\n" , m_tape_fwd , m_tape_fast)); if (!prev_tape_braking) { m_tape_timer->adjust(time_to_stopping_pos()); } } else { // No change in direction, immediate execution m_cmd_state = CMD_PH0; m_tape_timer->adjust(attotime::zero); } m_hole_timer->reset(); m_timeout_timer->reset(); return true; } } void hp_taco_device::stop_tape(void) { m_start_time = attotime::never; m_gap_detect_start = NULL_TAPE_POS; } hp_taco_device::tape_track_t& hp_taco_device::current_track(void) { return m_tracks[ BIT(m_status_reg , STATUS_TRACKB_BIT) ]; } // Return physical length of a 16-bit word on tape hp_taco_device::tape_pos_t hp_taco_device::word_length(tape_word_t w) { unsigned zeros , ones; // pop count of w ones = (w & 0x5555) + ((w >> 1) & 0x5555); ones = (ones & 0x3333) + ((ones >> 2) & 0x3333); ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f); ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff); zeros = 16 - ones; return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; } hp_taco_device::tape_pos_t hp_taco_device::word_end_pos(const tape_track_t::iterator& it) { return it->first + word_length(it->second); } void hp_taco_device::adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos) { if (it != track.begin()) { --it; if (word_end_pos(it) <= pos) { ++it; } } } // Write a word on current tape track void hp_taco_device::write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length) { tape_track_t& track = current_track(); tape_track_t::iterator it_low = track.lower_bound(start); adjust_it(track , it_low , start); length = word_length(word); tape_pos_t end_pos = start + length; tape_track_t::iterator it_high = track.lower_bound(end_pos); track.erase(it_low , it_high); track.insert(it_high , std::make_pair(start, word)); LOG_0(("WR %04x @ T%u:%u\n" , word , BIT(m_status_reg , STATUS_TRACKB_BIT) , start)); } // Write a gap on current track void hp_taco_device::write_gap(tape_pos_t a , tape_pos_t b) { ensure_a_lt_b(a , b); tape_track_t& track = current_track(); tape_track_t::iterator it_low = track.lower_bound(a); adjust_it(track , it_low , a); tape_track_t::iterator it_high = track.lower_bound(b); track.erase(it_low, it_high); LOG_0(("GAP on T%u:[%u,%u)\n" , BIT(m_status_reg , STATUS_TRACKB_BIT) , a , b)); } bool hp_taco_device::just_gap(tape_pos_t a , tape_pos_t b) { ensure_a_lt_b(a , b); tape_track_t& track = current_track(); tape_track_t::iterator it_low = track.lower_bound(a); tape_track_t::iterator it_high = track.lower_bound(b); adjust_it(track, it_low, a); return it_low == it_high; } hp_taco_device::tape_pos_t hp_taco_device::farthest_end(const tape_track_t::iterator& it) const { if (m_tape_fwd) { return word_end_pos(it); } else { return it->first; } } bool hp_taco_device::next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive) { tape_track_t& track = current_track(); it = track.lower_bound(pos); if (m_tape_fwd) { if (inclusive) { adjust_it(track, it, pos); } return it != track.end(); } else { // Never more than 2 iterations do { if (it == track.begin()) { it = track.end(); return false; } --it; } while (!inclusive && word_end_pos(it) > pos); return true; } } hp_taco_device::adv_res_t hp_taco_device::adv_it(tape_track_t::iterator& it) { tape_track_t& track = current_track(); if (m_tape_fwd) { tape_pos_t prev_pos = word_end_pos(it); ++it; if (it == track.end()) { return ADV_NO_MORE_DATA; } else { adv_res_t res = prev_pos == it->first ? ADV_CONT_DATA : ADV_DISCONT_DATA; return res; } } else { if (it == track.begin()) { it = track.end(); return ADV_NO_MORE_DATA; } else { tape_pos_t prev_pos = it->first; --it; return prev_pos == word_end_pos(it) ? ADV_CONT_DATA : ADV_DISCONT_DATA; } } } attotime hp_taco_device::fetch_next_wr_word(void) { if (m_data_reg_full) { m_next_word = m_data_reg; m_data_reg_full = false; LOG_0(("next %04x (DR)\n" , m_next_word)); } else { // When data register is empty, write checksum word m_next_word = m_checksum_reg; LOG_0(("next %04x (CS)\n" , m_next_word)); } if (m_clear_checksum_reg) { m_checksum_reg = 0; m_clear_checksum_reg = false; } // Update checksum with new word m_checksum_reg += m_next_word; return time_to_distance(word_length(m_next_word)); } attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos) { if (m_rd_it_valid) { word_rd_pos = farthest_end(m_rd_it); return time_to_target(word_rd_pos); } else { return attotime::never; } } hp_taco_device::tape_pos_t hp_taco_device::min_gap_size(void) const { return LONG_GAP(m_cmd_reg) ? LONG_GAP_LENGTH : SHORT_GAP_LENGTH; } /** * Scan for next "n_gaps" gaps * * @param[in,out] pos Start position on input, start of gap on output * @param it Pointer to data word where scan is to start * @param n_gaps Number of gaps to scan * @param min_gap Minimum gap size * * @return true if n_gaps gaps are found */ bool hp_taco_device::next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap) { tape_track_t& track = current_track(); bool done = false; tape_track_t::iterator prev_it; if (m_tape_fwd) { tape_pos_t next_pos; while (1) { if (it == track.end()) { next_pos = TAPE_LENGTH; done = true; } else { next_pos = it->first; } if (((next_pos - pos) >= min_gap && --n_gaps == 0) || done) { break; } adv_res_t adv_res; do { prev_it = it; adv_res = adv_it(it); } while (adv_res == ADV_CONT_DATA); pos = word_end_pos(prev_it); } } else { tape_pos_t next_pos; while (1) { if (it == track.end()) { next_pos = 0; done = true; } else { next_pos = word_end_pos(it); } if (((pos - next_pos) >= min_gap && --n_gaps == 0) || done) { break; } adv_res_t adv_res; do { prev_it = it; adv_res = adv_it(it); } while (adv_res == ADV_CONT_DATA); pos = prev_it->first; } } // Set "pos" where minimum gap size is met pos_offset(pos , min_gap); return n_gaps == 0; } bool hp_taco_device::next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap) { tape_track_t::iterator it; // First align with next data next_data(it, pos, true); // Then scan for n_gaps return next_n_gap(pos, it, n_gaps, min_gap); } void hp_taco_device::clear_tape(void) { for (tape_track_t& track : m_tracks) { track.clear(); } } void hp_taco_device::dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words) { if (n_words) { uint32_t tmp32; uint16_t tmp16; tmp32 = n_words; fwrite(&tmp32 , sizeof(tmp32)); tmp32 = it_start->first; fwrite(&tmp32 , sizeof(tmp32)); for (unsigned i = 0; i < n_words; i++) { tmp16 = it_start->second; fwrite(&tmp16 , sizeof(tmp16)); ++it_start; } } } void hp_taco_device::save_tape(void) { uint32_t tmp32; fseek(0, SEEK_SET); tmp32 = FILE_MAGIC; fwrite(&tmp32 , sizeof(tmp32)); for (const tape_track_t& track : m_tracks) { tape_pos_t next_pos = (tape_pos_t)-1; unsigned n_words = 0; tape_track_t::const_iterator it_start; for (tape_track_t::const_iterator it = track.cbegin(); it != track.cend(); ++it) { if (it->first != next_pos) { dump_sequence(it_start , n_words); it_start = it; n_words = 0; } next_pos = it->first + word_length(it->second); n_words++; } dump_sequence(it_start , n_words); // End of track tmp32 = (uint32_t)-1; fwrite(&tmp32 , sizeof(tmp32)); } } bool hp_taco_device::load_track(tape_track_t& track) { uint32_t tmp32; track.clear(); while (1) { if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { return false; } if (tmp32 == (uint32_t)-1) { return true; } unsigned n_words = tmp32; if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { return false; } tape_pos_t pos = (tape_pos_t)tmp32; for (unsigned i = 0; i < n_words; i++) { uint16_t tmp16; if (fread(&tmp16 , sizeof(tmp16)) != sizeof(tmp16)) { return false; } track.insert(std::make_pair(pos , tmp16)); pos += word_length(tmp16); } } } bool hp_taco_device::load_tape(void) { uint32_t magic; if (fread(&magic , sizeof(magic)) != sizeof(magic) || magic != FILE_MAGIC) { return false; } for (tape_track_t& track : m_tracks) { if (!load_track(track)) { LOG(("load_tape failed")); clear_tape(); return false; } } LOG(("load_tape done\n")); return true; } void hp_taco_device::set_tape_present(bool present) { if (present) { if (is_readonly()) { BIT_SET(m_status_reg, STATUS_WPR_BIT); } else { BIT_CLR(m_status_reg, STATUS_WPR_BIT); } // STATUS_CART_OUT_BIT is reset by CMD_CLEAR } else { BIT_SET(m_status_reg, STATUS_CART_OUT_BIT); BIT_SET(m_status_reg, STATUS_WPR_BIT); } } attotime hp_taco_device::time_to_next_hole(void) const { tape_pos_t pos = next_hole(); if (pos == NULL_TAPE_POS) { return attotime::never; } else { return time_to_target(pos); } } attotime hp_taco_device::time_to_tach_pulses(void) const { return time_to_distance((tape_pos_t)(0x10000U - m_tach_reg) * TAPE_POS_FRACT); } void hp_taco_device::terminate_cmd_now(void) { m_cmd_state = CMD_END; m_tape_timer->adjust(attotime::zero); m_hole_timer->reset(); m_timeout_timer->reset(); } void hp_taco_device::set_data_timeout(bool long_timeout) { attotime timeout = time_to_distance(long_timeout ? PREAMBLE_TIMEOUT : DATA_TIMEOUT); m_timeout_timer->adjust(timeout , 0 , timeout); } void hp_taco_device::cmd_fsm(void) { if (m_cmd_state == CMD_END) { // Command ended m_cmd_state = CMD_IDLE; m_hole_timer->reset(); m_timeout_timer->reset(); irq_w(true); if (AUTO_STOP(m_cmd_reg)) { // Automatic stop after command execution LOG_0(("Tape clamped\n")); m_cmd_state = CMD_STOPPING; m_tape_timer->adjust(time_to_stopping_pos()); } } else if (m_cmd_state == CMD_STOPPING) { // Braking phase ended m_cmd_state = CMD_IDLE; stop_tape(); if (CMD_CODE(m_cmd_reg) == CMD_STOP) { irq_w(true); } } else { attotime cmd_duration = attotime::never; if (m_cmd_state == CMD_INVERTING) { m_tape_fwd = DIR_FWD(m_cmd_reg); m_tape_fast = SPEED_FAST(m_cmd_reg); m_cmd_state = CMD_PH0; } if (m_cmd_state == CMD_PH0) { m_hole_timer->adjust(time_to_next_hole()); } switch (CMD_CODE(m_cmd_reg)) { case CMD_FINAL_GAP: if (m_cmd_state == CMD_PH0) { // PH0 m_rw_pos = m_tape_pos; cmd_duration = time_to_distance(END_GAP_LENGTH); m_cmd_state = CMD_PH1; } else { // PH1 write_gap(m_rw_pos , m_tape_pos); cmd_duration = attotime::zero; m_cmd_state = CMD_END; } break; case CMD_RECORD_WRITE: if (m_cmd_state == CMD_PH0) { // PH0 // Search for preamble first m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); cmd_duration = time_to_rd_next_word(m_rw_pos); // Set T/O for preamble search set_data_timeout(true); m_cmd_state = CMD_PH1; break; } else if (m_cmd_state == CMD_PH1) { // PH1 if (m_rd_it->second == PREAMBLE_WORD) { LOG_0(("Got preamble\n")); m_cmd_state = CMD_PH2; // m_rw_pos already at correct position cmd_duration = fetch_next_wr_word(); m_timeout_timer->reset(); irq_w(true); } else { adv_res_t res = adv_it(m_rd_it); if (res != ADV_NO_MORE_DATA) { cmd_duration = time_to_rd_next_word(m_rw_pos); } // Set T/O for arrival of data words set_data_timeout(false); } break; } // Intentional fall-through on PH2 case CMD_INIT_WRITE: if (m_cmd_state == CMD_PH0) { // PH0 m_next_word = PREAMBLE_WORD; m_rw_pos = m_tape_pos; cmd_duration = time_to_distance(word_length(m_next_word)); m_cmd_state = CMD_PH1; } else { // PH1 & PH2 of CMD_RECORD_WRITE tape_pos_t length; write_word(m_rw_pos , m_next_word , length); pos_offset(m_rw_pos , length); // Just to be sure.. m_tape_pos = m_rw_pos; cmd_duration = fetch_next_wr_word(); irq_w(true); } break; case CMD_SET_TRACK: // PH0 // When b9 is 0, set track A/B // When b9 is 1, ignore command (in TACO chip it has an unknown purpose) if (!AUTO_STOP(m_cmd_reg)) { if (CMD_OPT(m_cmd_reg)) { BIT_SET(m_status_reg, STATUS_TRACKB_BIT); } else { BIT_CLR(m_status_reg, STATUS_TRACKB_BIT); } } cmd_duration = attotime::from_usec(QUICK_CMD_USEC); m_hole_timer->reset(); m_cmd_state = CMD_END; break; case CMD_MOVE: // PH0 // Endless movement: not setting cmd_duration m_cmd_state = CMD_END; break; case CMD_INGAP_MOVE: if (m_cmd_state == CMD_PH0) { // PH0 tape_pos_t target = m_tape_pos; if (next_n_gap(target, 1, min_gap_size())) { LOG_0(("IRG @%d\n" , target)); cmd_duration = time_to_target(target); } m_cmd_state = CMD_PH1; } else { // PH1 cmd_duration = time_to_tach_pulses(); freeze_tach_reg(false); m_cmd_state = CMD_END; } break; case CMD_INDTA_INGAP: case CMD_NOT_INDTA: if (m_cmd_state == CMD_PH0) { // PH0 if (next_data(m_rd_it , m_tape_pos , true)) { cmd_duration = time_to_target(farthest_end(m_rd_it)); } // Set T/O for data set_data_timeout(true); m_cmd_state = CMD_PH1; } else { // PH1 tape_pos_t target = m_tape_pos; if (next_n_gap(target, 1, min_gap_size())) { LOG_0(("End of data @%d\n" , target)); cmd_duration = time_to_target(target); } // Got data, stop T/O m_timeout_timer->reset(); m_cmd_state = CMD_END; } break; case CMD_WRITE_IRG: if (m_cmd_state == CMD_PH0) { // PH0 freeze_tach_reg(false); m_rw_pos = m_tape_pos; cmd_duration = time_to_tach_pulses(); m_cmd_state = CMD_PH1; } else { // PH1 write_gap(m_rw_pos , m_tape_pos); cmd_duration = attotime::zero; m_cmd_state = CMD_END; } break; case CMD_SCAN_RECORDS: if (m_cmd_state == CMD_PH0 || m_cmd_state == CMD_PH2) { // PH0 and PH2 if (m_cmd_state == CMD_PH2) { m_tach_reg++; if (m_tach_reg == 0) { // All gaps found, bail out cmd_duration = attotime::zero; m_cmd_state = CMD_END; break; } } if (next_data(m_rd_it , m_tape_pos , true)) { cmd_duration = time_to_target(farthest_end(m_rd_it)); } // Set T/O for data set_data_timeout(true); m_cmd_state = CMD_PH1; } else if (m_cmd_state == CMD_PH1) { // PH1 tape_pos_t target = m_tape_pos; if (next_n_gap(target, 1, min_gap_size())) { LOG_0(("Gap @%d (%u to go)\n" , target , 0x10000U - m_tach_reg)); cmd_duration = time_to_target(target); } m_timeout_timer->reset(); m_cmd_state = CMD_PH2; } break; case CMD_MOVE_INDTA: if (m_cmd_state == CMD_PH0) { // PH0 freeze_tach_reg(false); cmd_duration = time_to_tach_pulses(); m_cmd_state = CMD_PH1; } else { // PH1 if (next_data(m_rd_it , m_tape_pos , true)) { cmd_duration = time_to_target(farthest_end(m_rd_it)); } // Apparently this cmd doesn't set no-data T/O m_cmd_state = CMD_END; } break; case CMD_UNK_1b: // PH0 // Unknown purpose, but make it a NOP (it's used in "T" test of test ROM) cmd_duration = attotime::from_usec(QUICK_CMD_USEC); m_cmd_state = CMD_END; break; case CMD_MOVE_INGAP: if (m_cmd_state == CMD_PH0) { // PH0 freeze_tach_reg(false); cmd_duration = time_to_tach_pulses(); m_cmd_state = CMD_PH1; } else { // PH1 tape_pos_t target = m_tape_pos; if (next_n_gap(target, 1, min_gap_size())) { LOG_0(("GAP @%d\n" , target)); cmd_duration = time_to_target(target); } m_cmd_state = CMD_END; } break; case CMD_START_READ: if (m_cmd_state == CMD_PH0) { // PH0 // Should also check if tape position has gone too far to read word @ m_rd_it if (!m_rd_it_valid) { // Search for preamble first m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); // Set T/O for preamble search set_data_timeout(true); m_cmd_state = CMD_PH1; } else { // Resume reading from last position, skip preamble search m_cmd_state = CMD_PH2; } } else { // Just to be sure.. m_tape_pos = m_rw_pos; if (m_cmd_state == CMD_PH1) { // PH1 // Any word with at least a 0 will do as preamble. // But anything that's not the correct preamble word (0) will cause a wrong alignment // with word boundaries. TACO will read garbage data in this case (this effect is not simulated here). if (m_rd_it->second != 0xffff) { m_cmd_state = CMD_PH2; LOG_0(("Got preamble %04x\n" , m_rd_it->second)); } } else { // PH2 if (m_irq) { LOG(("Data reg overflow!\n")); } irq_w(true); m_data_reg = m_rd_it->second; if (m_clear_checksum_reg) { m_checksum_reg = 0; m_clear_checksum_reg = false; } m_checksum_reg += m_data_reg; LOG_0(("RD %04x\n" , m_data_reg)); } // Set T/O for arrival of data words set_data_timeout(false); adv_res_t res = adv_it(m_rd_it); LOG_0(("adv_it %d\n" , res)); if (res == ADV_NO_MORE_DATA) { m_rd_it_valid = false; } } cmd_duration = time_to_rd_next_word(m_rw_pos); break; case CMD_DELTA_MOVE_IRG: // PH0 freeze_tach_reg(false); cmd_duration = time_to_tach_pulses(); m_cmd_state = CMD_END; break; case CMD_END_READ: if (m_cmd_state == CMD_PH0) { // PH0 cmd_duration = time_to_rd_next_word(m_rw_pos); set_data_timeout(false); m_cmd_state = CMD_PH1; } else { // PH1 m_tape_pos = m_rw_pos; // Note: checksum is not updated m_data_reg = m_rd_it->second; LOG_0(("Final RD %04x\n" , m_data_reg)); adv_res_t res = adv_it(m_rd_it); if (res == ADV_NO_MORE_DATA) { m_rd_it_valid = false; } cmd_duration = attotime::zero; m_cmd_state = CMD_END; } break; default: break; } m_tape_timer->adjust(cmd_duration); } } void hp_taco_device::start_cmd_exec(uint16_t new_cmd_reg) { LOG_0(("New cmd %02x @ %g cmd %02x st %d\n" , CMD_CODE(new_cmd_reg) , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); update_tape_pos(); unsigned new_cmd_code = CMD_CODE(new_cmd_reg); if (new_cmd_code != CMD_START_READ && new_cmd_code != CMD_END_READ && new_cmd_code != CMD_CLEAR) { m_rd_it_valid = false; } bool started = false; switch (new_cmd_code) { case CMD_INDTA_INGAP: // Errors: CART OUT,FAST SPEED started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); break; case CMD_FINAL_GAP: // Errors: WP,CART OUT started = start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK); break; case CMD_INIT_WRITE: // Errors: WP,CART OUT,fast speed,reverse started = start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK); break; case CMD_STOP: m_cmd_reg = new_cmd_reg; freeze_tach_reg(false); m_hole_timer->reset(); m_timeout_timer->reset(); if (is_braking()) { // Already braking // m_tape_timer already set } else if (m_start_time.is_never()) { // Tape is already stopped LOG_0(("Already stopped\n")); m_tape_timer->adjust(attotime::from_usec(QUICK_CMD_USEC)); } else { // Start braking timer m_cmd_state = CMD_STOPPING; m_tape_timer->adjust(time_to_stopping_pos()); } m_cmd_state = CMD_STOPPING; return; case CMD_SET_TRACK: // Don't know if this command really starts the tape or not (probably it doesn't) started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_MOVE: started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_INGAP_MOVE: // Errors: CART OUT,FAST SPEED started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); break; case CMD_CLEAR: set_error(false); BIT_CLR(m_status_reg, STATUS_HOLE_BIT); BIT_CLR(m_status_reg, STATUS_CART_OUT_BIT); BIT_CLR(m_status_reg, STATUS_WPR_BIT); set_tape_present(is_loaded()); // This is a special command: it doesn't raise IRQ at completion and it // doesn't replace current command return; case CMD_NOT_INDTA: // Errors: CART OUT started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_WRITE_IRG: // Errors: WP,CART OUT started = start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK); break; case CMD_SCAN_RECORDS: // Errors: CART OUT started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_RECORD_WRITE: // Errors: WP,CART OUT,fast speed,reverse started = start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK); break; case CMD_MOVE_INDTA: // Errors: CART OUT,FAST SPEED started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); break; case CMD_UNK_1b: started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_MOVE_INGAP: started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_START_READ: // Yes, you can read tape backwards: test "C" does that! // Because of this DIR_FWD_MASK is not in the "must be 1" mask. started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); break; case CMD_DELTA_MOVE_IRG: started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_END_READ: // This command only makes sense after CMD_START_READ if (CMD_CODE(m_cmd_reg) == CMD_START_READ) { started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); LOG_0(("END_READ %d\n" , m_rd_it_valid)); } break; default: LOG(("Unrecognized command\n")); started = false; break; } if (!started) { set_error(true); m_cmd_state = CMD_IDLE; m_tape_timer->reset(); m_hole_timer->reset(); m_timeout_timer->reset(); } } image_init_result hp_taco_device::internal_load(bool is_create) { device_reset(); if (is_create) { clear_tape(); save_tape(); } else if (!load_tape()) { seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); set_tape_present(false); return image_init_result::FAIL; } m_image_dirty = false; set_tape_present(true); return image_init_result::PASS; } image_init_result hp_taco_device::call_load() { LOG(("call_load\n")); return internal_load(false); } image_init_result hp_taco_device::call_create(int format_type, util::option_resolution *format_options) { LOG(("call_create\n")); return internal_load(true); } void hp_taco_device::call_unload() { LOG(("call_unload dirty=%d\n" , m_image_dirty)); device_reset(); if (m_image_dirty) { save_tape(); m_image_dirty = false; } clear_tape(); set_tape_present(false); } std::string hp_taco_device::call_display() { std::string buffer; // Mostly lifted from cassette_image_device::call_display ;) // Do not show anything if image not loaded or tape not moving if (!exists() || m_start_time.is_never()) { return buffer; } char track = BIT(m_status_reg , STATUS_TRACKB_BIT) ? 'B' : 'A'; char r_w = m_tape_wr ? 'W' : 'R'; char m1; char m2; if (m_tape_fwd) { m1 = '>'; m2 = m_tape_fast ? '>' : ' '; } else { m1 = '<'; m2 = m_tape_fast ? '<' : ' '; } int pos_in = current_tape_pos() / ONE_INCH_POS; buffer = string_format("%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); // Not correct when there are 2 or more instances of TACO return buffer; } const char *hp_taco_device::file_extensions() const { return "hti"; }