From b1f5519497381943690c53aab1adb69171bf7b7f Mon Sep 17 00:00:00 2001 From: fulivi Date: Mon, 29 Feb 2016 14:30:17 +0100 Subject: [PATCH 1/6] hphybrid: interrupt vector fetching fixed. One day I'll eventually get it right.. --- src/devices/cpu/hphybrid/hphybrid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/cpu/hphybrid/hphybrid.cpp b/src/devices/cpu/hphybrid/hphybrid.cpp index 44c29698cb1..fd7870d319f 100644 --- a/src/devices/cpu/hphybrid/hphybrid.cpp +++ b/src/devices/cpu/hphybrid/hphybrid.cpp @@ -1459,7 +1459,7 @@ UINT32 hp_5061_3001_cpu_device::add_mae(aec_cases_t aec_case , UINT16 addr) bool top_half = BIT(addr , 15) != 0; // Detect accesses to top half of base page - if (aec_case == AEC_CASE_C && (addr & 0xfe00) == 0xfe00) { + if ((aec_case == AEC_CASE_C || aec_case == AEC_CASE_I) && (addr & 0xfe00) == 0xfe00) { aec_case = AEC_CASE_B; } From fec7ca20757bd4b049a4ad084bffff6f6156d34a Mon Sep 17 00:00:00 2001 From: fulivi Date: Sun, 6 Mar 2016 16:29:43 +0100 Subject: [PATCH 2/6] hp9845: call_display and a different approach to stopping tape added to TACO driver. --- src/devices/machine/hp_taco.cpp | 987 +++++++++++++++++--------------- src/devices/machine/hp_taco.h | 161 +++--- 2 files changed, 597 insertions(+), 551 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index eb94faf3b05..664279199e7 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -134,11 +134,12 @@ // #include "emu.h" #include "hp_taco.h" +#include "ui/ui.h" // Debugging #define VERBOSE 1 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) -#define VERBOSE_0 0 +#define VERBOSE_0 1 #define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) // Macros to clear/set single bits @@ -160,13 +161,13 @@ enum { #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 -#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 @@ -377,35 +378,36 @@ void hp_taco_device::device_config_complete() // 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(); + 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)); + 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_tape_stopping)); + 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_tape_timer = timer_alloc(TAPE_TMR_ID); + m_hole_timer = timer_alloc(HOLE_TMR_ID); } // device_stop @@ -429,227 +431,227 @@ void hp_taco_device::device_reset() 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(); - } + update_tape_pos(); - switch (id) { - case TAPE_TMR_ID: - LOG_0(("Tape tmr @%g\n" , machine().time().as_double())); + switch (id) { + case TAPE_TMR_ID: + LOG_0(("Tape tmr @%g\n" , machine().time().as_double())); - tape_pos_t length; + 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; + 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_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_STOP: + stop_tape(); + m_tape_stopping = false; + 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_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_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_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_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_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_START_READ: + { + bool set_intr = true; + // Just to be sure.. + m_tape_pos = m_rw_pos; - 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; + 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; - default: - // Other commands: just raise irq - break; - } - irq_w(true); - 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; - case HOLE_TMR_ID: - LOG_0(("Hole tmr @%g\n" , machine().time().as_double())); + default: + // Other commands: just raise irq + break; + } + irq_w(true); + break; - BIT_SET(m_status_reg , STATUS_HOLE_BIT); + case HOLE_TMR_ID: + LOG_0(("Hole tmr @%g\n" , machine().time().as_double())); - 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; + BIT_SET(m_status_reg , STATUS_HOLE_BIT); - 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; + 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_DELTA_MOVE_IRG: - // TODO: update r6 - m_hole_timer->adjust(time_to_next_hole()); - // No IRQ at holes - return; + 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_START_READ: - case CMD_END_READ: - set_error(true); - break; + case CMD_DELTA_MOVE_IRG: + // TODO: update r6 + m_hole_timer->adjust(time_to_next_hole()); + // No IRQ at holes + return; - default: - // Other cmds: default processing (update tape pos, set IRQ, schedule timer for next hole) - break; - } + case CMD_START_READ: + case CMD_END_READ: + set_error(true); + break; - irq_w(true); - m_hole_timer->adjust(time_to_next_hole()); - break; + default: + // Other cmds: default processing (update tape pos, set IRQ, schedule timer for next hole) + break; + } - default: - 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; + 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_tape_stopping = 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()); + set_tape_present(false); + set_tape_present(is_loaded()); } void hp_taco_device::irq_w(bool state) @@ -670,7 +672,9 @@ void hp_taco_device::set_error(bool 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; + return m_tape_stopping ? + (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 @@ -697,47 +701,50 @@ bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const } } -void hp_taco_device::move_tape_pos(tape_pos_t delta_pos) +hp_taco_device::tape_pos_t hp_taco_device::current_tape_pos(void) const { - 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); - } + 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; - } + 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)); + tape_pos_t tape_start_pos = m_tape_pos; + m_tape_pos = current_tape_pos(); + m_start_time = machine().time(); + LOG_0(("Tape pos = %u\n" , m_tape_pos)); - 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); - } + if (any_hole(tape_start_pos , m_tape_pos)) { + // Crossed one or more holes + BIT_SET(m_status_reg , STATUS_HOLE_BIT); + } + // 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) @@ -802,41 +809,43 @@ attotime hp_taco_device::time_to_target(tape_pos_t target) const bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0) { - m_cmd_reg = cmd_reg; + 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; + 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(); + // 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 prev_tape_stopping = m_tape_stopping; + 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); + 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); + m_tape_stopping = false; + // 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; - } + 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_stopping || 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); + } + return true; + } } void hp_taco_device::stop_tape(void) @@ -1218,211 +1227,211 @@ attotime hp_taco_device::time_to_tach_pulses(void) const void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) { - LOG(("Cmd = %02x\n" , CMD_CODE(new_cmd_reg))); + LOG(("Cmd = %02x\n" , CMD_CODE(new_cmd_reg))); - update_tape_pos(); + update_tape_pos(); - attotime cmd_duration = attotime::never; - attotime time_to_hole = attotime::never; + attotime cmd_duration = attotime::never; + attotime time_to_hole = attotime::never; - unsigned new_cmd_code = CMD_CODE(new_cmd_reg); + 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; - } + 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; + 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_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_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_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_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_STOP: + if (!m_tape_stopping) { + if (m_start_time.is_never()) { + // Tape is already stopped + cmd_duration = attotime::from_usec(QUICK_CMD_USEC); + } else { + // Start braking timer + m_tape_stopping = true; + cmd_duration = time_to_distance(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); + } + m_cmd_reg = new_cmd_reg; + } else { + 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_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_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_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_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_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_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_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_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_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); - } + 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; + 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; + 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; - } + default: + LOG(("Unrecognized command\n")); + break; + } - m_tape_timer->adjust(cmd_duration); - m_hole_timer->adjust(time_to_hole); + m_tape_timer->adjust(cmd_duration); + m_hole_timer->adjust(time_to_hole); } bool hp_taco_device::call_load() @@ -1461,6 +1470,42 @@ void hp_taco_device::call_unload() set_tape_present(false); } +void hp_taco_device::call_display() +{ + // 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; + } + + char buffer[ 64 ]; + + 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; + + snprintf(buffer , sizeof(buffer) , "%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); + + float x, y; + x = 0.2f; + y = 0.5f; + y *= device().machine().ui().get_line_height() + 2.0f * UI_BOX_TB_BORDER; + + device().machine().ui().draw_text_box(&device().machine().render().ui_container(), buffer, JUSTIFY_LEFT, x, y, UI_BACKGROUND_COLOR); +} + const char *hp_taco_device::file_extensions() const { return "hti"; diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index 1695b7ce706..bcfb6bdcb7b 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -47,6 +47,7 @@ public: virtual bool call_load() override; virtual bool call_create(int format_type, option_resolution *format_options) override; virtual void call_unload() override; + virtual void call_display() override; virtual iodevice_t image_type() const override { return IO_MAGTAPE; } virtual bool is_readable() const override { return true; } virtual bool is_writeable() const override { return true; } @@ -71,96 +72,96 @@ protected: virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; private: - // Storage of tracks: mapping from a tape position to word stored there - typedef std::map tape_track_t; + // Storage of tracks: mapping from a tape position to word stored there + typedef std::map tape_track_t; - devcb_write_line m_irq_handler; - devcb_write_line m_flg_handler; - devcb_write_line m_sts_handler; + devcb_write_line m_irq_handler; + devcb_write_line m_flg_handler; + devcb_write_line m_sts_handler; - // Registers - UINT16 m_data_reg; - bool m_data_reg_full; - UINT16 m_cmd_reg; - UINT16 m_status_reg; - UINT16 m_tach_reg; - UINT16 m_checksum_reg; - UINT16 m_timing_reg; + // Registers + UINT16 m_data_reg; + bool m_data_reg_full; + UINT16 m_cmd_reg; + UINT16 m_status_reg; + UINT16 m_tach_reg; + UINT16 m_checksum_reg; + UINT16 m_timing_reg; - // State - bool m_irq; - bool m_flg; - bool m_sts; - UINT8 m_cmd_state; + // State + bool m_irq; + bool m_flg; + bool m_sts; + UINT8 m_cmd_state; - // Tape position & motion - tape_pos_t m_tape_pos; - attotime m_start_time; // Tape moving if != never - bool m_tape_fwd; - bool m_tape_fast; + // Tape position & motion + tape_pos_t m_tape_pos; + attotime m_start_time; // Tape moving if != never + bool m_tape_fwd; + bool m_tape_fast; + bool m_tape_stopping; - // Timers - emu_timer *m_tape_timer; - emu_timer *m_hole_timer; + // Timers + emu_timer *m_tape_timer; + emu_timer *m_hole_timer; - // Content of tape tracks - tape_track_t m_tracks[ 2 ]; - bool m_image_dirty; + // Content of tape tracks + tape_track_t m_tracks[ 2 ]; + bool m_image_dirty; - // Reading & writing - bool m_tape_wr; - tape_pos_t m_rw_pos; - UINT16 m_next_word; - tape_track_t::iterator m_rd_it; - bool m_rd_it_valid; + // Reading & writing + bool m_tape_wr; + tape_pos_t m_rw_pos; + UINT16 m_next_word; + tape_track_t::iterator m_rd_it; + bool m_rd_it_valid; - // Gap detection - tape_pos_t m_gap_detect_start; + // Gap detection + tape_pos_t m_gap_detect_start; - typedef enum { - ADV_NO_MORE_DATA, - ADV_CONT_DATA, - ADV_DISCONT_DATA - } adv_res_t; + typedef enum { + ADV_NO_MORE_DATA, + ADV_CONT_DATA, + ADV_DISCONT_DATA + } adv_res_t; - void clear_state(void); - void irq_w(bool state); - void set_error(bool state); - unsigned speed_to_tick_freq(void) const; - bool pos_offset(tape_pos_t& pos , tape_pos_t offset) const; - void move_tape_pos(tape_pos_t delta_pos); - void update_tape_pos(void); - static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); - static bool any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b); - tape_pos_t next_hole(void) const; - attotime time_to_distance(tape_pos_t distance) const; - attotime time_to_target(tape_pos_t target) const; - bool start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0); - void start_tape(UINT16 cmd_reg); - void stop_tape(void); - tape_track_t& current_track(void); - static tape_pos_t word_length(tape_word_t w); - static tape_pos_t word_end_pos(const tape_track_t::iterator& it); - static void adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos); - void write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length); - void write_gap(tape_pos_t a , tape_pos_t b); - bool just_gap(tape_pos_t a , tape_pos_t b); - tape_pos_t farthest_end(const tape_track_t::iterator& it) const; - bool next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive); - adv_res_t adv_it(tape_track_t::iterator& it); - attotime fetch_next_wr_word(void); - attotime time_to_rd_next_word(tape_pos_t& word_rd_pos); - bool next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap); - bool next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap); - void clear_tape(void); - void dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words); - void save_tape(void); - bool load_track(tape_track_t& track); - bool load_tape(void); - void set_tape_present(bool present); - attotime time_to_next_hole(void) const; - attotime time_to_tach_pulses(void) const; - void start_cmd_exec(UINT16 new_cmd_reg); + void clear_state(void); + void irq_w(bool state); + void set_error(bool state); + unsigned speed_to_tick_freq(void) const; + bool pos_offset(tape_pos_t& pos , tape_pos_t offset) const; + tape_pos_t current_tape_pos(void) const; + void update_tape_pos(void); + static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); + static bool any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b); + tape_pos_t next_hole(void) const; + attotime time_to_distance(tape_pos_t distance) const; + attotime time_to_target(tape_pos_t target) const; + bool start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0); + void stop_tape(void); + tape_track_t& current_track(void); + static tape_pos_t word_length(tape_word_t w); + static tape_pos_t word_end_pos(const tape_track_t::iterator& it); + static void adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos); + void write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length); + void write_gap(tape_pos_t a , tape_pos_t b); + bool just_gap(tape_pos_t a , tape_pos_t b); + tape_pos_t farthest_end(const tape_track_t::iterator& it) const; + bool next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive); + adv_res_t adv_it(tape_track_t::iterator& it); + attotime fetch_next_wr_word(void); + attotime time_to_rd_next_word(tape_pos_t& word_rd_pos); + bool next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap); + bool next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap); + void clear_tape(void); + void dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words); + void save_tape(void); + bool load_track(tape_track_t& track); + bool load_tape(void); + void set_tape_present(bool present); + attotime time_to_next_hole(void) const; + attotime time_to_tach_pulses(void) const; + void start_cmd_exec(UINT16 new_cmd_reg); }; // device type definition From 241b732ea75d8bc98bd8d0f48fb3ded3c911ad20 Mon Sep 17 00:00:00 2001 From: fulivi Date: Sun, 6 Mar 2016 16:32:29 +0100 Subject: [PATCH 3/6] hp9845: alpha video test "B" fixed --- src/mame/drivers/hp9845.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/mame/drivers/hp9845.cpp b/src/mame/drivers/hp9845.cpp index 1387432ea85..4890b0ad92e 100644 --- a/src/mame/drivers/hp9845.cpp +++ b/src/mame/drivers/hp9845.cpp @@ -44,7 +44,10 @@ // Base address of video buffer #define VIDEO_BUFFER_BASE 0x17000 -#define MAX_WORD_PER_ROW 600 +// For test "B" of alpha video to succeed this must be < 234 +// Basically "B" test is designed to intentionally prevent line buffer to be filled so that display is blanked +// from 2nd row on. This in turn prevents "BAD" text to be visible on screen. +#define MAX_WORD_PER_ROW 220 #define VIDEO_CHAR_WIDTH 9 #define VIDEO_CHAR_HEIGHT 15 @@ -412,10 +415,14 @@ void hp9845b_state::video_render_buff(unsigned line_in_row, bool buff_idx) m_video_blanked = true; } - if (m_video_blanked) { - // TODO: blank scanline - } else { const rgb_t *palette = m_palette->palette()->entry_list_raw(); + + if (m_video_blanked) { + // Blank scanline + for (unsigned i = 0; i < (80 * 9); i++) { + m_bitmap.pix32(m_video_scanline , i) = palette[ 0 ]; + } + } else { bool cursor_line = line_in_row == 12; bool ul_line = line_in_row == 14; bool cursor_blink = BIT(m_video_frame , 3); From 0b8eec6c4584dcabfd0b270c8d9075a7e63200fb Mon Sep 17 00:00:00 2001 From: fulivi Date: Thu, 17 Mar 2016 16:49:51 +0100 Subject: [PATCH 4/6] hp9845: Changed cmd 1c on TACO --- src/devices/machine/hp_taco.cpp | 104 ++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 664279199e7..51b0ada7ed2 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -16,7 +16,7 @@ // 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 +// * 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) @@ -51,7 +51,7 @@ // 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 +// 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) @@ -128,7 +128,6 @@ // * 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?) // @@ -172,7 +171,8 @@ enum { #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 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 long gaps: 1.5" ([1], pg 8-10) #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" @@ -193,38 +193,38 @@ enum { // 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 + 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_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 @@ -444,7 +444,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para 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)) { + if (next_n_gap(target, 1, SHORT_GAP_LENGTH)) { m_tape_timer->adjust(time_to_target(target)); } return; @@ -500,7 +500,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para 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)) { + if (next_n_gap(target, 0x10000U - m_tach_reg, SHORT_GAP_LENGTH)) { LOG_0(("%u gaps @%d\n" , 0x10000U - m_tach_reg, target)); m_tape_timer->adjust(time_to_target(target)); } @@ -522,7 +522,19 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para // m_cmd_state == 1 -> IRQ & cmd end break; - case CMD_DELTA_MOVE_HOLE: + case CMD_MOVE_INGAP: + if (m_cmd_state == 0) { + m_cmd_state = 1; + tape_pos_t target = m_tape_pos; + if (next_n_gap(target, 1, SHORT_GAP_LENGTH)) { + LOG_0(("GAP @%d\n" , target)); + m_tape_timer->adjust(time_to_target(target)); + } + // No IRQ + return; + } + break; + case CMD_DELTA_MOVE_IRG: // Interrupt at end of movement m_hole_timer->reset(); @@ -596,13 +608,13 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para break; case CMD_SCAN_RECORDS: - case CMD_DELTA_MOVE_HOLE: - // Cmds 18 & 1c are terminated at first hole + // Cmds 18 is terminated at first hole m_tape_timer->reset(); irq_w(true); // No reloading of hole timer return; + case CMD_MOVE_INGAP: case CMD_DELTA_MOVE_IRG: // TODO: update r6 m_hole_timer->adjust(time_to_next_hole()); @@ -735,9 +747,9 @@ void hp_taco_device::update_tape_pos(void) } // 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) { + if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= SHORT_GAP_LENGTH) { tape_pos_t tmp = m_tape_pos; - pos_offset(tmp , -MIN_IRG_LENGTH); + pos_offset(tmp , -SHORT_GAP_LENGTH); gap_detected = just_gap(tmp , m_tape_pos); } if (gap_detected) { @@ -1336,7 +1348,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) 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)) { + if (next_n_gap(target, 1, SHORT_GAP_LENGTH)) { LOG_0(("IRG @%d\n" , target)); cmd_duration = time_to_target(target); } @@ -1391,9 +1403,11 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) } break; - case CMD_DELTA_MOVE_HOLE: + case CMD_MOVE_INGAP: + // Apparently cmd 1c uses b9 for some unknown purpose (to set the gap length?) case CMD_DELTA_MOVE_IRG: if (start_tape_cmd(new_cmd_reg , 0 , 0)) { + m_cmd_state = 0; cmd_duration = time_to_tach_pulses(); time_to_hole = time_to_next_hole(); } From e740ed13ec3b7f42f0f2bf771f521a5c01ea8fec Mon Sep 17 00:00:00 2001 From: fulivi Date: Sun, 20 Mar 2016 16:09:20 +0100 Subject: [PATCH 5/6] hp9845: updating of tachometer register added in TACO driver --- src/devices/machine/hp_taco.cpp | 99 ++++++++++++++++++++++++--------- src/devices/machine/hp_taco.h | 4 ++ 2 files changed, 76 insertions(+), 27 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 51b0ada7ed2..730e7c52921 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -294,33 +294,34 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, d WRITE16_MEMBER(hp_taco_device::reg_w) { - LOG_0(("wr R%u = %04x\n", 4 + offset , data)); + LOG_0(("wr R%u = %04x\n", 4 + offset , data)); - // Any I/O activity clears IRQ - irq_w(false); + // 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; + 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 1: + // Command register + start_cmd_exec(data & CMD_REG_MASK); + break; - case 2: - // Tachometer register - m_tach_reg = data; - break; + case 2: + // Tachometer register + m_tach_reg = data; + freeze_tach_reg(true); + break; - case 3: - // Timing register - m_timing_reg = data; - break; - } + case 3: + // Timing register + m_timing_reg = data; + break; + } } READ16_MEMBER(hp_taco_device::reg_r) @@ -341,10 +342,11 @@ READ16_MEMBER(hp_taco_device::reg_r) res = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK); break; - case 2: - // Tachometer register - res = m_tach_reg; - break; + case 2: + // Tachometer register + update_tach_reg(); + res = m_tach_reg; + break; case 3: // Checksum register: it clears when read @@ -389,6 +391,8 @@ void hp_taco_device::device_start() 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_timing_reg)); save_item(NAME(m_irq)); @@ -486,6 +490,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para if (m_cmd_state == 0) { m_cmd_state = 1; m_tape_timer->adjust(time_to_tach_pulses()); + freeze_tach_reg(false); return; } break; @@ -616,7 +621,6 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para case CMD_MOVE_INGAP: case CMD_DELTA_MOVE_IRG: - // TODO: update r6 m_hole_timer->adjust(time_to_next_hole()); // No IRQ at holes return; @@ -647,6 +651,8 @@ void hp_taco_device::clear_state(void) 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_timing_reg = 0; m_cmd_state = 0; @@ -715,6 +721,11 @@ bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const 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? @@ -757,6 +768,37 @@ void hp_taco_device::update_tape_pos(void) } 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 reg_value = (UINT16)(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) @@ -1359,6 +1401,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) case CMD_WRITE_IRG: // Errors: WP,CART OUT if (start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK)) { + freeze_tach_reg(false); m_rw_pos = m_tape_pos; cmd_duration = time_to_tach_pulses(); time_to_hole = time_to_next_hole(); @@ -1391,6 +1434,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) // Errors: CART OUT,FAST SPEED if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { m_cmd_state = 0; + freeze_tach_reg(false); cmd_duration = time_to_tach_pulses(); // Holes detected? } @@ -1408,6 +1452,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) case CMD_DELTA_MOVE_IRG: if (start_tape_cmd(new_cmd_reg , 0 , 0)) { m_cmd_state = 0; + freeze_tach_reg(false); cmd_duration = time_to_tach_pulses(); time_to_hole = time_to_next_hole(); } diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index bcfb6bdcb7b..c50861dbe57 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -85,6 +85,8 @@ private: UINT16 m_cmd_reg; UINT16 m_status_reg; UINT16 m_tach_reg; + tape_pos_t m_tach_reg_ref; + bool m_tach_reg_frozen; UINT16 m_checksum_reg; UINT16 m_timing_reg; @@ -132,6 +134,8 @@ private: bool pos_offset(tape_pos_t& pos , tape_pos_t offset) const; tape_pos_t current_tape_pos(void) const; void update_tape_pos(void); + void update_tach_reg(void); + void freeze_tach_reg(bool freeze); static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); static bool any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b); tape_pos_t next_hole(void) const; From 874aac03cfd357e5a3682f0bca01f08a180173f8 Mon Sep 17 00:00:00 2001 From: fulivi Date: Wed, 6 Apr 2016 12:26:00 +0200 Subject: [PATCH 6/6] hp9845: big improvement to TACO driver after study of SIF utilities --- src/devices/machine/hp_taco.cpp | 951 +++++++++++++++++++------------- src/devices/machine/hp_taco.h | 22 +- 2 files changed, 573 insertions(+), 400 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 730e7c52921..d6a1fa27805 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -15,6 +15,7 @@ // 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. @@ -27,6 +28,8 @@ // 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. // @@ -35,21 +38,20 @@ // 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. +// | | 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. +// | | 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 ? 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 +// 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) @@ -79,19 +81,23 @@ // - 6" of final gap // - Non-recorded tape // - End of tape holes -// * Sector #0 is not used +// * 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: +// 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: Format/sector in use and other unidentified bits. -// Word 2: Sector number -// Word 3: Sector length and other bits +// 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) @@ -101,7 +107,7 @@ // 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 +// - 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) @@ -109,9 +115,9 @@ // // 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. +// * 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 @@ -125,9 +131,9 @@ // 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 +// * It seems like all commands expecting to read data off the tape have a kind of timeout. More +// investigation is needed. +// * Emulation of CMD_NOT_INDTA is not entirely ok, SIF utilities fail when using this command. // * Find more info on TACO chips (does anyone with a working 9845 or access to internal HP docs want to // help me here, please?) // @@ -138,7 +144,7 @@ // Debugging #define VERBOSE 1 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) -#define VERBOSE_0 1 +#define VERBOSE_0 0 #define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) // Macros to clear/set single bits @@ -166,13 +172,15 @@ enum { #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 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) #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" +// 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 long gaps: 1.5" ([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 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" @@ -181,13 +189,15 @@ enum { #define CMD_CODE(reg) \ (((reg) >> 10) & 0x1f) #define DIR_FWD(reg) \ - (BIT(reg , 15)) + (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 UNKNOWN_B9(reg) \ - (BIT(reg , 9)) + (BIT(reg , 6)) #define DIR_FWD_MASK BIT_MASK(15) // Direction = forward #define SPEED_FAST_MASK BIT_MASK(7) // Speed = fast @@ -348,12 +358,12 @@ READ16_MEMBER(hp_taco_device::reg_r) res = m_tach_reg; break; - case 3: - // Checksum register: it clears when read - res = m_checksum_reg; - m_checksum_reg = 0; - 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)); @@ -388,12 +398,13 @@ void hp_taco_device::device_start() 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_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)); @@ -402,7 +413,6 @@ void hp_taco_device::device_start() save_item(NAME(m_start_time)); save_item(NAME(m_tape_fwd)); save_item(NAME(m_tape_fast)); - save_item(NAME(m_tape_stopping)); save_item(NAME(m_image_dirty)); save_item(NAME(m_tape_wr)); save_item(NAME(m_rw_pos)); @@ -439,203 +449,54 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para switch (id) { case TAPE_TMR_ID: - LOG_0(("Tape tmr @%g\n" , machine().time().as_double())); + LOG_0(("Tape tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); - 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, SHORT_GAP_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: - stop_tape(); - m_tape_stopping = false; - break; - - case CMD_INGAP_MOVE: - if (m_cmd_state == 0) { - m_cmd_state = 1; - m_tape_timer->adjust(time_to_tach_pulses()); - freeze_tach_reg(false); - 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, SHORT_GAP_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_MOVE_INGAP: - if (m_cmd_state == 0) { - m_cmd_state = 1; - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, SHORT_GAP_LENGTH)) { - LOG_0(("GAP @%d\n" , target)); - m_tape_timer->adjust(time_to_target(target)); - } - // No IRQ - return; - } - break; - - 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); + cmd_fsm(); break; case HOLE_TMR_ID: - LOG_0(("Hole tmr @%g\n" , machine().time().as_double())); + 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); - 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; + 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_SCAN_RECORDS: + // Cmds 18 is terminated at first hole + terminate_cmd_now(); + // No reloading of hole timer + return; + + 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_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; + } - case CMD_SCAN_RECORDS: - // Cmds 18 is terminated at first hole - m_tape_timer->reset(); irq_w(true); - // No reloading of hole timer - return; - - case CMD_MOVE_INGAP: - case CMD_DELTA_MOVE_IRG: - 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; @@ -654,13 +515,13 @@ void hp_taco_device::clear_state(void) 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 = 0; + m_cmd_state = CMD_IDLE; // 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_tape_stopping = false; // m_image_dirty is not touched m_tape_wr = false; m_rw_pos = 0; @@ -688,9 +549,14 @@ void hp_taco_device::set_error(bool state) 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 m_tape_stopping ? + 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); } @@ -747,20 +613,15 @@ void hp_taco_device::update_tape_pos(void) return; } - tape_pos_t tape_start_pos = m_tape_pos; m_tape_pos = current_tape_pos(); 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); - } // Gap detection bool gap_detected = false; - if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= SHORT_GAP_LENGTH) { + 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 , -SHORT_GAP_LENGTH); + pos_offset(tmp , -min_gap_size()); gap_detected = just_gap(tmp , m_tape_pos); } if (gap_detected) { @@ -812,20 +673,6 @@ void hp_taco_device::ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b) } } -// 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 { @@ -861,6 +708,11 @@ 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 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0) { m_cmd_reg = cmd_reg; @@ -871,20 +723,18 @@ bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 m // 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 prev_tape_stopping = m_tape_stopping; + 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); - m_tape_stopping = false; // TODO: remove? BIT_CLR(m_status_reg, STATUS_HOLE_BIT); @@ -893,11 +743,27 @@ bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 m 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_stopping || prev_tape_wr != m_tape_wr || prev_tape_fwd != m_tape_fwd || prev_tape_fast != m_tape_fast) { + } 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_braking && 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)); + 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(); + return true; } } @@ -1043,19 +909,23 @@ hp_taco_device::adv_res_t hp_taco_device::adv_it(tape_track_t::iterator& it) 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; + 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)); + return time_to_distance(word_length(m_next_word)); } attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos) @@ -1068,6 +938,11 @@ attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos) } } +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 * @@ -1279,15 +1154,367 @@ 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(); +} + +void hp_taco_device::cmd_fsm(void) +{ + if (m_cmd_state == CMD_END) { + // Command ended + m_cmd_state = CMD_IDLE; + m_hole_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_INDTA_INGAP: + 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)); + } + m_cmd_state = CMD_PH1; + } else { + // PH1 + tape_pos_t target = m_tape_pos; + if (next_n_gap(target, 1, min_gap_size())) { + cmd_duration = time_to_target(target); + } + m_cmd_state = CMD_END; + } + break; + + 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); + 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(); + 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); + } + } + 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_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)); + } + m_cmd_state = CMD_PH1; + } else { + // PH1 + 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); + } + 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) { + // PH0 + if (next_data(m_rd_it , m_tape_pos , true)) { + cmd_duration = time_to_target(farthest_end(m_rd_it)); + } + m_cmd_state = CMD_PH1; + } else { + // PH1 + tape_pos_t target = m_tape_pos; + // b8 seems to select size of gaps + // Tach. register is incremented at each gap. Command ends when this register goes positive (b15 = 0). + unsigned n_gaps; + if (BIT(m_tach_reg , 15)) { + n_gaps = 0x10000U - m_tach_reg; + m_tach_reg = 0; + } else { + n_gaps = 1; + m_tach_reg++; + } + if (next_n_gap(target, n_gaps, min_gap_size())) { + LOG_0(("%u gaps @%d\n" , n_gaps, target)); + cmd_duration = time_to_target(target); + } + m_cmd_state = CMD_END; + } + 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)); + } + 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); + m_cmd_state = CMD_PH1; + } else { + // Resume reading from last position, skip preamble search + m_cmd_state = CMD_PH2; + } + + cmd_duration = time_to_rd_next_word(m_rw_pos); + } else { + // Just to be sure.. + m_tape_pos = m_rw_pos; + + if (m_cmd_state == CMD_PH3) { + // PH3: delayed setting of error condition + set_error(true); + m_cmd_state = CMD_PH2; + } + + 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 + 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)); + } + 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 { + cmd_duration = time_to_rd_next_word(m_rw_pos); + if (res == ADV_DISCONT_DATA) { + // Wild guess: TACO sets error flag when it stumbles on a gap between words + if (m_cmd_state == CMD_PH2 && abs(m_tape_pos - m_rw_pos) > ((tape_pos_t)(0.25 * ONE_INCH_POS))) { + m_cmd_state = CMD_PH3; + } else { + // Hit a gap, restart preamble search + // TODO: is this ok? + m_cmd_state = CMD_PH1; + } + } + } + } + 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); + 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 new_cmd_reg) { - LOG(("Cmd = %02x\n" , CMD_CODE(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(); - 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 && @@ -1296,24 +1523,55 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) m_rd_it_valid = false; } + bool started = 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)); - } - } + started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); 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(); + 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(); + if (m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING) { + // 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: @@ -1328,169 +1586,68 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) 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 (!m_tape_stopping) { - if (m_start_time.is_never()) { - // Tape is already stopped - cmd_duration = attotime::from_usec(QUICK_CMD_USEC); - } else { - // Start braking timer - m_tape_stopping = true; - cmd_duration = time_to_distance(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); - } - m_cmd_reg = new_cmd_reg; - } else { - 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, SHORT_GAP_LENGTH)) { - LOG_0(("IRG @%d\n" , target)); - cmd_duration = time_to_target(target); - } - // Holes detected? - } + started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); break; case CMD_WRITE_IRG: // Errors: WP,CART OUT - if (start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK)) { - freeze_tach_reg(false); - m_rw_pos = m_tape_pos; - cmd_duration = time_to_tach_pulses(); - time_to_hole = time_to_next_hole(); - } + started = start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK); 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(); - } + started = start_tape_cmd(new_cmd_reg , 0 , 0); 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? - } + 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 - if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { - m_cmd_state = 0; - freeze_tach_reg(false); - cmd_duration = time_to_tach_pulses(); - // Holes detected? - } + started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); 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); - } + started = start_tape_cmd(new_cmd_reg , 0 , 0); break; case CMD_MOVE_INGAP: - // Apparently cmd 1c uses b9 for some unknown purpose (to set the gap length?) - case CMD_DELTA_MOVE_IRG: - if (start_tape_cmd(new_cmd_reg , 0 , 0)) { - m_cmd_state = 0; - freeze_tach_reg(false); - cmd_duration = time_to_tach_pulses(); - time_to_hole = time_to_next_hole(); - } + 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. - 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); - } + started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); + break; - cmd_duration = time_to_rd_next_word(m_rw_pos); - time_to_hole = time_to_next_hole(); - } + 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 && m_cmd_state == 1 && - start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) { + if (CMD_CODE(m_cmd_reg) == CMD_START_READ && + (m_cmd_state == CMD_PH2 || m_cmd_state == CMD_PH3)) { + started = 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")); + started = false; break; } - m_tape_timer->adjust(cmd_duration); - m_hole_timer->adjust(time_to_hole); + if (!started) { + set_error(true); + m_cmd_state = CMD_IDLE; + m_tape_timer->reset(); + m_hole_timer->reset(); + } } bool hp_taco_device::call_load() diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index c50861dbe57..288b1db3218 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -88,20 +88,32 @@ private: tape_pos_t m_tach_reg_ref; bool m_tach_reg_frozen; UINT16 m_checksum_reg; + bool m_clear_checksum_reg; UINT16 m_timing_reg; // State bool m_irq; bool m_flg; bool m_sts; - UINT8 m_cmd_state; + + // Command FSM state + typedef enum { + CMD_IDLE, + CMD_INVERTING, + CMD_PH0, + CMD_PH1, + CMD_PH2, + CMD_PH3, + CMD_END, + CMD_STOPPING + } cmd_state_t; + cmd_state_t m_cmd_state; // Tape position & motion tape_pos_t m_tape_pos; attotime m_start_time; // Tape moving if != never bool m_tape_fwd; bool m_tape_fast; - bool m_tape_stopping; // Timers emu_timer *m_tape_timer; @@ -130,6 +142,7 @@ private: void clear_state(void); void irq_w(bool state); void set_error(bool state); + bool is_braking(void) const; unsigned speed_to_tick_freq(void) const; bool pos_offset(tape_pos_t& pos , tape_pos_t offset) const; tape_pos_t current_tape_pos(void) const; @@ -137,10 +150,10 @@ private: void update_tach_reg(void); void freeze_tach_reg(bool freeze); static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); - static bool any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b); tape_pos_t next_hole(void) const; attotime time_to_distance(tape_pos_t distance) const; attotime time_to_target(tape_pos_t target) const; + attotime time_to_stopping_pos(void) const; bool start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0); void stop_tape(void); tape_track_t& current_track(void); @@ -155,6 +168,7 @@ private: adv_res_t adv_it(tape_track_t::iterator& it); attotime fetch_next_wr_word(void); attotime time_to_rd_next_word(tape_pos_t& word_rd_pos); + tape_pos_t min_gap_size(void) const; bool next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap); bool next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap); void clear_tape(void); @@ -165,6 +179,8 @@ private: void set_tape_present(bool present); attotime time_to_next_hole(void) const; attotime time_to_tach_pulses(void) const; + void terminate_cmd_now(void); + void cmd_fsm(void); void start_cmd_exec(UINT16 new_cmd_reg); };