diff options
Diffstat (limited to 'src/devices/machine/hp_taco.cpp')
-rw-r--r-- | src/devices/machine/hp_taco.cpp | 1467 |
1 files changed, 0 insertions, 1467 deletions
diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp deleted file mode 100644 index 76203d31ee3..00000000000 --- a/src/devices/machine/hp_taco.cpp +++ /dev/null @@ -1,1467 +0,0 @@ -// 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. -// The main shortcomings of my approach are: -// * I could indentify 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. - -// 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. It's not clear to me -// | | what value could be read from this register, if it's just the same value that was written last time or the internal -// | | counter or something else entirely. -// R7 | R | Checksum register. Reading it clears it. -// 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. -// -// 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 ? Drive ON according to [1], the actual use seems to be selection of gap length -// 8 RW ? Size of gaps according to [1], N/U in my opinion -// 7 RW Speed of tape (1 = 90 ips, 0 = 22 ips) -// 6 RW Option bit for various commands -// 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 -// * Sector #0 is not used -// * 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: -// 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: Format/sector in use and other unidentified bits. -// Word 2: Sector number -// Word 3: Sector length and other bits -// 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 -// - 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) -// * Inversion of tape direction and change of speed are immediate as well -// * Time & distance to stop the tape are modeled, though. Firmware is upset by -// a tape with null braking 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: -// * Some code cleanup -// * Handling of tape holes seems to be wrong: test "C" of test ROM only works partially -// * Find out what is read from register R6 -// * Handle device_image_interface::call_display to show state of tape -// * 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" - -// 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 -}; - -// 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 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 -#define FAST_BRAKE_MSEC 73 // Braking time from fast speed to stop (2 ips) in msec (deceleration is 1200 in/s^2) -#define SLOW_BRAKE_MSEC 17 // Braking time from slow speed to stop in msec -#define FAST_BRAKE_DIST 3350450 // Braking distance at fast speed (~3.38 in) -#define SLOW_BRAKE_DIST 197883 // Braking distance at slow speed (~0.2 in) -#define PREAMBLE_WORD 0 // Value of preamble word -#define END_GAP_LENGTH (6 * ONE_INCH_POS) // Length of final gap: 6" -#define MIN_IRG_LENGTH ((tape_pos_t)(0.2 * ONE_INCH_POS)) // Minimum length of IRGs: 0.2" (from 9825, not sure about value in TACO) -#define NULL_TAPE_POS ((tape_pos_t)-1) // Special value for invalid/unknown tape position -#define NO_DATA_GAP (17 * ONE_BIT_LEN) // Minimum gap size to detect end of data: length of longest word (0xffff) -#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 SPEED_FAST(reg) \ - (BIT(reg , 7)) -#define CMD_OPT(reg) \ - (BIT(reg , 6)) -#define UNKNOWN_B9(reg) \ - (BIT(reg , 9)) -#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 - 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 - 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_DELTA_MOVE_HOLE, // 1c: move tape a given distance, intr at end or first hole found (whichever comes first) - 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<hp_taco_device>; - -// Constructors -hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 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_tape_pos(TAPE_INIT_POS), - m_image_dirty(false) -{ - clear_state(); -} - -hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) - : device_t(mconfig, HP_TACO, "HP TACO", tag, owner, clock, "TACO", __FILE__), - device_image_interface(mconfig , *this), - m_irq_handler(*this), - m_flg_handler(*this), - m_sts_handler(*this), - m_tape_pos(TAPE_INIT_POS), - 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; - break; - - case 3: - // Timing register - m_timing_reg = data; - break; - } -} - -READ16_MEMBER(hp_taco_device::reg_r) -{ - UINT16 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 - res = m_tach_reg; - break; - - case 3: - // Checksum register: it clears when read - res = m_checksum_reg; - m_checksum_reg = 0; - 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_config_complete -void hp_taco_device::device_config_complete() -{ - LOG(("device_config_complete")); - update_names(); -} - -// 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_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); -} - -// 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); -} - -void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) -{ - if (CMD_CODE(m_cmd_reg) != CMD_STOP) { - update_tape_pos(); - } - - switch (id) { - case TAPE_TMR_ID: - LOG_0(("Tape tmr @%g\n" , machine().time().as_double())); - - tape_pos_t length; - - switch (CMD_CODE(m_cmd_reg)) { - case CMD_INDTA_INGAP: - if (m_cmd_state == 0) { - m_cmd_state = 1; - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, MIN_IRG_LENGTH)) { - m_tape_timer->adjust(time_to_target(target)); - } - return; - } - break; - - case CMD_RECORD_WRITE: - if (m_cmd_state == 0) { - if (m_rd_it->second == PREAMBLE_WORD) { - LOG_0(("Got preamble\n")); - m_cmd_state = 1; - // m_rw_pos already at correct position - m_tape_timer->adjust(fetch_next_wr_word()); - break; - } else { - adv_res_t res = adv_it(m_rd_it); - if (res != ADV_NO_MORE_DATA) { - m_tape_timer->adjust(time_to_rd_next_word(m_rw_pos)); - } - // No IRQ - return; - } - } - // Intentional fall-through - case CMD_INIT_WRITE: - 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; - m_tape_timer->adjust(fetch_next_wr_word()); - break; - - case CMD_STOP: - move_tape_pos(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); - stop_tape(); - break; - - case CMD_INGAP_MOVE: - if (m_cmd_state == 0) { - m_cmd_state = 1; - m_tape_timer->adjust(time_to_tach_pulses()); - return; - } - break; - - case CMD_FINAL_GAP: - case CMD_WRITE_IRG: - write_gap(m_rw_pos , m_tape_pos); - m_hole_timer->reset(); - break; - - case CMD_SCAN_RECORDS: - if (m_cmd_state == 0) { - m_cmd_state = 1; - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 0x10000U - m_tach_reg, MIN_IRG_LENGTH)) { - LOG_0(("%u gaps @%d\n" , 0x10000U - m_tach_reg, target)); - m_tape_timer->adjust(time_to_target(target)); - } - return; - } else { - m_hole_timer->reset(); - } - break; - - case CMD_MOVE_INDTA: - if (m_cmd_state == 0) { - if (next_data(m_rd_it , m_tape_pos , true)) { - m_cmd_state = 1; - m_tape_timer->adjust(time_to_target(farthest_end(m_rd_it))); - } - // No IRQ - return; - } - // m_cmd_state == 1 -> IRQ & cmd end - break; - - case CMD_DELTA_MOVE_HOLE: - case CMD_DELTA_MOVE_IRG: - // Interrupt at end of movement - m_hole_timer->reset(); - break; - - case CMD_START_READ: - { - bool set_intr = true; - // Just to be sure.. - m_tape_pos = m_rw_pos; - if (m_cmd_state == 0) { - set_intr = false; - if (m_rd_it->second == PREAMBLE_WORD) { - m_cmd_state = 1; - } - LOG_0(("Got preamble\n")); - } else { - m_data_reg = m_rd_it->second; - m_checksum_reg += m_data_reg; - LOG_0(("RD %04x\n" , m_data_reg)); - } - 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; - } else { - if (res == ADV_DISCONT_DATA) { - // Hit a gap, restart preamble search - m_cmd_state = 0; - } - m_tape_timer->adjust(time_to_rd_next_word(m_rw_pos)); - } - if (!set_intr) { - return; - } - } - break; - - case CMD_END_READ: - { - 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; - } - m_hole_timer->reset(); - } - break; - - default: - // Other commands: just raise irq - break; - } - irq_w(true); - break; - - case HOLE_TMR_ID: - LOG_0(("Hole tmr @%g\n" , machine().time().as_double())); - - BIT_SET(m_status_reg , STATUS_HOLE_BIT); - - 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_SCAN_RECORDS: - case CMD_DELTA_MOVE_HOLE: - // Cmds 18 & 1c are terminated at first hole - m_tape_timer->reset(); - irq_w(true); - // No reloading of hole timer - return; - - case CMD_DELTA_MOVE_IRG: - // TODO: update r6 - m_hole_timer->adjust(time_to_next_hole()); - // No IRQ at holes - return; - - case CMD_START_READ: - case CMD_END_READ: - 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; - - 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_checksum_reg = 0; - m_timing_reg = 0; - m_cmd_state = 0; - // m_tape_pos is not reset, tape stays where it is - 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)); -} - -unsigned hp_taco_device::speed_to_tick_freq(void) const -{ - return 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; - } -} - -void hp_taco_device::move_tape_pos(tape_pos_t delta_pos) -{ - tape_pos_t tape_start_pos = m_tape_pos; - if (!pos_offset(m_tape_pos , delta_pos)) { - LOG(("Tape unspooled!\n")); - } - m_start_time = machine().time(); - LOG_0(("Tape pos = %u\n" , m_tape_pos)); - if (any_hole(tape_start_pos , m_tape_pos)) { - // Crossed one or more holes - BIT_SET(m_status_reg , STATUS_HOLE_BIT); - } -} - -void hp_taco_device::update_tape_pos(void) -{ - if (m_start_time.is_never()) { - // Tape not moving - return; - } - - 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)); - - move_tape_pos(delta_tach); - - // Gap detection - bool gap_detected = false; - if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= MIN_IRG_LENGTH) { - tape_pos_t tmp = m_tape_pos; - pos_offset(tmp , -MIN_IRG_LENGTH); - 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); - } -} - -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; - } -} - -// Is there any hole in a given section of tape? -bool hp_taco_device::any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b) -{ - ensure_a_lt_b(tape_pos_a , tape_pos_b); - - for (tape_pos_t hole : tape_holes) { - if (tape_pos_a < hole && tape_pos_b >= hole) { - return true; - } - } - - return false; -} - -// 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 TAPE_LENGTH; - } 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 0; - } -} - -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)); -} - -bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0) -{ - m_cmd_reg = cmd_reg; - - UINT16 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) { - set_error(true); - 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 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); - // TODO: remove? - BIT_CLR(m_status_reg, STATUS_HOLE_BIT); - - 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_wr != m_tape_wr || prev_tape_fwd != m_tape_fwd || prev_tape_fast != m_tape_fast) { - // Tape 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); - } - 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)); - } - // 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; - } -} - -/** - * 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 (unsigned track_n = 0; track_n < 2; track_n++) { - m_tracks[ track_n ].clear(); - } -} - -void hp_taco_device::dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words) -{ - if (n_words) { - UINT32 tmp32; - UINT16 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 tmp32; - - fseek(0, SEEK_SET); - - tmp32 = FILE_MAGIC; - fwrite(&tmp32 , sizeof(tmp32)); - - for (unsigned track_n = 0; track_n < 2; track_n++) { - const tape_track_t& track = m_tracks[ track_n ]; - 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)-1; - fwrite(&tmp32 , sizeof(tmp32)); - } -} - -bool hp_taco_device::load_track(tape_track_t& track) -{ - UINT32 tmp32; - - track.clear(); - - while (1) { - if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { - return false; - } - - if (tmp32 == (UINT32)-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 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 magic; - - if (fread(&magic , sizeof(magic)) != sizeof(magic) || - magic != FILE_MAGIC) { - return false; - } - - for (unsigned track_n = 0; track_n < 2; track_n++) { - if (!load_track(m_tracks[ track_n ])) { - 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 -{ - return time_to_target(next_hole()); -} - -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::start_cmd_exec(UINT16 new_cmd_reg) -{ - LOG(("Cmd = %02x\n" , CMD_CODE(new_cmd_reg))); - - update_tape_pos(); - - attotime cmd_duration = attotime::never; - attotime time_to_hole = attotime::never; - - 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; - } - - switch (new_cmd_code) { - case CMD_INDTA_INGAP: - // Errors: CART OUT,FAST SPEED - if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - m_cmd_state = 0; - if (next_data(m_rd_it , m_tape_pos , true)) { - cmd_duration = time_to_target(farthest_end(m_rd_it)); - } - } - break; - - case CMD_FINAL_GAP: - // Errors: WP,CART OUT - if (start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK)) { - m_rw_pos = m_tape_pos; - cmd_duration = time_to_distance(END_GAP_LENGTH); - time_to_hole = time_to_next_hole(); - } - 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,FAST SPEED - if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, NO_DATA_GAP)) { - LOG_0(("End of data @%d\n" , target)); - cmd_duration = time_to_target(target); - } - // Holes detected? - } - break; - - case CMD_INIT_WRITE: - // Errors: WP,CART OUT,fast speed,reverse - if (start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK)) { - m_next_word = PREAMBLE_WORD; - m_rw_pos = m_tape_pos; - cmd_duration = time_to_distance(word_length(m_next_word)); - } - break; - - case CMD_STOP: - if (CMD_CODE(m_cmd_reg) != CMD_STOP) { - if (m_start_time.is_never()) { - // Tape is already stopped - cmd_duration = attotime::from_usec(QUICK_CMD_USEC); - } else { - // Start braking timer - cmd_duration = attotime::from_msec(m_tape_fast ? FAST_BRAKE_MSEC : SLOW_BRAKE_MSEC); - } - m_cmd_reg = new_cmd_reg; - } else { - // TODO: check if ok - return; - } - break; - - case CMD_SET_TRACK: - // Don't know if this command really starts the tape or not (probably it doesn't) - if (start_tape_cmd(new_cmd_reg , 0 , 0)) { - // When b9 is 0, set track A/B - // When b9 is 1, ignore command (in TACO chip it has an unknown purpose) - if (!UNKNOWN_B9(new_cmd_reg)) { - if (CMD_OPT(new_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); - } - break; - - case CMD_MOVE: - if (start_tape_cmd(new_cmd_reg , 0 , 0)) { - time_to_hole = time_to_next_hole(); - } - break; - - case CMD_INGAP_MOVE: - // Errors: CART OUT,FAST SPEED - if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - m_cmd_state = 0; - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, MIN_IRG_LENGTH)) { - LOG_0(("IRG @%d\n" , target)); - cmd_duration = time_to_target(target); - } - // Holes detected? - } - break; - - case CMD_WRITE_IRG: - // Errors: WP,CART OUT - if (start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK)) { - m_rw_pos = m_tape_pos; - cmd_duration = time_to_tach_pulses(); - time_to_hole = time_to_next_hole(); - } - break; - - case CMD_SCAN_RECORDS: - // Errors: CART OUT - if (start_tape_cmd(new_cmd_reg , 0 , 0)) { - m_cmd_state = 0; - if (next_data(m_rd_it , m_tape_pos , true)) { - cmd_duration = time_to_target(farthest_end(m_rd_it)); - } - time_to_hole = time_to_next_hole(); - } - break; - - case CMD_RECORD_WRITE: - // Errors: WP,CART OUT,fast speed,reverse - if (start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK)) { - // Search for preamble first - m_cmd_state = 0; - m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); - cmd_duration = time_to_rd_next_word(m_rw_pos); - // Holes detected? - } - break; - - case CMD_MOVE_INDTA: - // Errors: CART OUT,FAST SPEED - if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - m_cmd_state = 0; - cmd_duration = time_to_tach_pulses(); - // Holes detected? - } - break; - - case CMD_UNK_1b: - if (start_tape_cmd(new_cmd_reg , 0 , 0)) { - // Unknown purpose, but make it a NOP (it's used in "T" test of test ROM) - cmd_duration = attotime::from_usec(QUICK_CMD_USEC); - } - break; - - case CMD_DELTA_MOVE_HOLE: - case CMD_DELTA_MOVE_IRG: - if (start_tape_cmd(new_cmd_reg , 0 , 0)) { - cmd_duration = time_to_tach_pulses(); - time_to_hole = time_to_next_hole(); - } - 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. - if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - // TODO: check anche m_rw_pos sforato - if (!m_rd_it_valid) { - // Search for preamble first - m_cmd_state = 0; - m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); - } - - cmd_duration = time_to_rd_next_word(m_rw_pos); - time_to_hole = time_to_next_hole(); - } - break; - - case CMD_END_READ: - // This command only makes sense after CMD_START_READ - if (CMD_CODE(m_cmd_reg) == CMD_START_READ && m_cmd_state == 1 && - start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - LOG_0(("END_READ %d\n" , m_rd_it_valid)); - cmd_duration = time_to_rd_next_word(m_rw_pos); - time_to_hole = time_to_next_hole(); - } - break; - - default: - LOG(("Unrecognized command\n")); - break; - } - - m_tape_timer->adjust(cmd_duration); - m_hole_timer->adjust(time_to_hole); -} - -bool hp_taco_device::call_load() -{ - LOG(("call_load %d\n" , has_been_created())); - if (has_been_created()) { - clear_tape(); - save_tape(); - } else if (!load_tape()) { - seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); - set_tape_present(false); - return IMAGE_INIT_FAIL; - } - - m_image_dirty = false; - - set_tape_present(true); - return IMAGE_INIT_PASS; -} - -bool hp_taco_device::call_create(int format_type, option_resolution *format_options) -{ - LOG(("call_create %d\n" , has_been_created())); - return call_load(); -} - -void hp_taco_device::call_unload() -{ - LOG(("call_unload dirty=%d\n" , m_image_dirty)); - if (m_image_dirty) { - save_tape(); - m_image_dirty = false; - } - - clear_tape(); - set_tape_present(false); -} - -const char *hp_taco_device::file_extensions() const -{ - return "hti"; -} |