diff options
author | 2019-09-09 22:05:41 +0200 | |
---|---|---|
committer | 2019-09-09 16:05:41 -0400 | |
commit | d4e2fbd306bf9f847fed16b9a9112eb6d8f56c8e (patch) | |
tree | b91e639bf1dce0eb05d0b813c22f81aa88686da1 | |
parent | eaae4640aa5b9db02ee3e7e16617c8413fe43daf (diff) |
HP9845: TACO driver re-written (#5601)
* hp9825: fixed a bug in 9825t
* hp9845: TACO driver re-written from scratch, DC100 tape separated into
a new device, various adaptations
* hp9845: "new TACO" renamed to just "TACO"
-rw-r--r-- | scripts/src/machine.lua | 12 | ||||
-rw-r--r-- | scripts/target/mame/mess.lua | 1 | ||||
-rw-r--r-- | src/devices/cpu/hphybrid/hphybrid.cpp | 2 | ||||
-rw-r--r-- | src/devices/machine/1ma6.cpp | 676 | ||||
-rw-r--r-- | src/devices/machine/1ma6.h | 80 | ||||
-rw-r--r-- | src/devices/machine/hp_dc100_tape.cpp | 914 | ||||
-rw-r--r-- | src/devices/machine/hp_dc100_tape.h | 178 | ||||
-rw-r--r-- | src/devices/machine/hp_taco.cpp | 1949 | ||||
-rw-r--r-- | src/devices/machine/hp_taco.h | 105 | ||||
-rw-r--r-- | src/lib/formats/hti_tape.cpp | 118 | ||||
-rw-r--r-- | src/lib/formats/hti_tape.h | 20 | ||||
-rw-r--r-- | src/mame/machine/hp9825_tape.cpp | 719 | ||||
-rw-r--r-- | src/mame/machine/hp9825_tape.h | 81 | ||||
-rw-r--r-- | src/tools/imgtool/modules/hp85_tape.cpp | 36 | ||||
-rw-r--r-- | src/tools/imgtool/modules/hp9845_tape.cpp | 16 |
15 files changed, 2493 insertions, 2414 deletions
diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 164399017a5..726d92510c5 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -1192,6 +1192,18 @@ end --------------------------------------------------- -- +--@src/devices/machine/hp_dc100_tape.h,MACHINES["HP_DC100_TAPE"] = true +--------------------------------------------------- + +if (MACHINES["HP_DC100_TAPE"]~=null) then + files { + MAME_DIR .. "src/devices/machine/hp_dc100_tape.cpp", + MAME_DIR .. "src/devices/machine/hp_dc100_tape.h", + } +end + +--------------------------------------------------- +-- --@src/devices/machine/hp_taco.h,MACHINES["HP_TACO"] = true --------------------------------------------------- diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index 47ffd35785d..441a69a72e8 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -471,6 +471,7 @@ MACHINES["ER2055"] = true MACHINES["F3853"] = true MACHINES["HD63450"] = true MACHINES["HD64610"] = true +MACHINES["HP_DC100_TAPE"] = true MACHINES["HP_TACO"] = true MACHINES["1MA6"] = true MACHINES["1MB5"] = true diff --git a/src/devices/cpu/hphybrid/hphybrid.cpp b/src/devices/cpu/hphybrid/hphybrid.cpp index b9e7da6cc4e..ccd76079081 100644 --- a/src/devices/cpu/hphybrid/hphybrid.cpp +++ b/src/devices/cpu/hphybrid/hphybrid.cpp @@ -404,9 +404,9 @@ bool hp_hybrid_cpu_device::execute_one_bpc(uint16_t opcode , uint16_t& next_pc) case 0x4000: // JSM m_icount -= 5; + next_pc = remove_mae(get_ea(opcode)); m_reg_R = (m_reg_R + 1) & m_addr_mask_low16; WM(AEC_CASE_C , m_reg_R , m_reg_P); - next_pc = remove_mae(get_ea(opcode)); return true; case 0x4800: diff --git a/src/devices/machine/1ma6.cpp b/src/devices/machine/1ma6.cpp index 771fd0d9b1f..479c7b9d6d3 100644 --- a/src/devices/machine/1ma6.cpp +++ b/src/devices/machine/1ma6.cpp @@ -29,8 +29,14 @@ #include "1ma6.h" // Debugging -#define VERBOSE 0 #include "logmacro.h" +#define LOG_DBG_MASK (LOG_GENERAL << 1) +#define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__) +#define LOG_RW_MASK (LOG_DBG_MASK << 1) +#define LOG_RW(...) LOGMASKED(LOG_RW_MASK, __VA_ARGS__) +#undef VERBOSE +//#define VERBOSE (LOG_GENERAL | LOG_DBG_MASK | LOG_RW_MASK) +#define VERBOSE 0 // Device type definition DEFINE_DEVICE_TYPE(HP_1MA6, hp_1ma6_device, "hp_1ma6", "HP 1MA6") @@ -63,19 +69,14 @@ namespace { } // **** Constants **** +constexpr double FAST_SPEED = 60.0; // Fast speed: 60 ips +constexpr double SLOW_SPEED = 10.0; // Slow speed: 10 ips +constexpr double MOVING_THRESHOLD = 2.0; // Tape is moving when speed > 2.0 ips +constexpr double ACCELERATION = 1200.0; // Acceleration when speed set point is changed: 1200 ips^2 // One tachometer tick every 1/32 of inch -static constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / 32; -static constexpr hti_format_t::tape_pos_t TAPE_INIT_POS = hti_format_t::ONE_INCH_POS * 80; -// Slow tape speed (10 ips) -static constexpr unsigned TACH_FREQ_SLOW = hti_format_t::ONE_INCH_POS * 10; -// Fast tape speed (60 ips) -static constexpr unsigned TACH_FREQ_FAST = hti_format_t::ONE_INCH_POS * 60; +constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / 32; // Minimum gap size (totally made up) -static constexpr hti_format_t::tape_pos_t MIN_GAP_SIZE = TACH_TICK_LEN; -// Braking distance at fast speed: 1.5 in (this value assumes the deceleration is 1200 in/s^2, the same as in TACO) -static constexpr hti_format_t::tape_pos_t FAST_BRAKE_DIST = hti_format_t::ONE_INCH_POS * 1.5; -// Braking distance at slow speed: 1/24 in -static constexpr hti_format_t::tape_pos_t SLOW_BRAKE_DIST = hti_format_t::ONE_INCH_POS / 24; +constexpr hti_format_t::tape_pos_t MIN_GAP_SIZE = TACH_TICK_LEN; // Bits in control register enum control_bits : unsigned @@ -110,10 +111,8 @@ enum { }; hp_1ma6_device::hp_1ma6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig , HP_1MA6 , tag , owner , clock), - device_image_interface(mconfig , *this), - m_image(), - m_image_dirty(false) + : device_t(mconfig , HP_1MA6 , tag , owner , clock) + , m_tape(*this , "drive") { clear_state(); } @@ -141,7 +140,7 @@ READ8_MEMBER(hp_1ma6_device::reg_r) switch (offset) { case 0: // Status register - update_tape_pos(); + m_tape->update_speed_pos(); if (m_cmd_state == CMD_IDLE) { BIT_SET(m_status_reg , STS_READY_BIT); } else if (m_cmd_state == CMD_STOPPING || @@ -151,24 +150,15 @@ READ8_MEMBER(hp_1ma6_device::reg_r) } if ((m_control_reg & (BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT))) != 0 && - (!m_cartridge_in || !is_readonly())) { + (m_tape->cart_out_r() || !m_tape->wpr_r())) { BIT_SET(m_status_reg , STS_WRITE_EN_BIT); } else { BIT_CLR(m_status_reg , STS_WRITE_EN_BIT); } // Gap detection - if (m_cmd_state == CMD_IDLE) { + if (m_cmd_state == CMD_IDLE || + m_tape->gap_reached(MIN_GAP_SIZE)) { BIT_SET(m_status_reg , STS_GAP_BIT); - } else if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS) { - if (abs(m_gap_detect_start - m_tape_pos) >= MIN_GAP_SIZE) { - auto tmp = m_tape_pos; - hti_format_t::pos_offset(tmp , is_moving_fwd() , -MIN_GAP_SIZE); - if (m_image.just_gap(current_track() , tmp , m_tape_pos)) { - BIT_SET(m_status_reg , STS_GAP_BIT); - } - } else { - BIT_SET(m_status_reg , STS_GAP_BIT); - } } res = m_status_reg; @@ -177,7 +167,7 @@ READ8_MEMBER(hp_1ma6_device::reg_r) BIT_CLR(m_status_reg , STS_GAP_BIT); BIT_CLR(m_status_reg , STS_TACH_BIT); BIT_CLR(m_status_reg , STS_READY_BIT); - if (m_cartridge_in) { + if (!m_tape->cart_out_r()) { BIT_SET(m_status_reg , STS_CASSETTE_IN_BIT); } break; @@ -191,143 +181,137 @@ READ8_MEMBER(hp_1ma6_device::reg_r) return res; } -void hp_1ma6_device::device_start() +WRITE_LINE_MEMBER(hp_1ma6_device::cart_out_w) { - save_item(NAME(m_data_reg)); - save_item(NAME(m_status_reg)); - save_item(NAME(m_control_reg)); - save_item(NAME(m_tape_pos)); - save_item(NAME(m_prev_pos)); - save_item(NAME(m_start_time)); - save_item(NAME(m_image_dirty)); - 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); + LOG_DBG("cart_out_w %d\n" , state); + if (state) { + // STS_CASSETTE_IN_BIT is set by reading status register + BIT_CLR(m_status_reg , STS_CASSETTE_IN_BIT); + } } -void hp_1ma6_device::device_reset() +WRITE_LINE_MEMBER(hp_1ma6_device::hole_w) { - clear_state(); + if (state) { + LOG_DBG("hole_w\n"); + BIT_SET(m_status_reg , STS_HOLE_BIT); + } +} - m_tape_timer->reset(); - m_hole_timer->reset(); +WRITE_LINE_MEMBER(hp_1ma6_device::tacho_tick_w) +{ + if (state) { + LOG_DBG("tacho_tick_w\n"); + BIT_SET(m_status_reg , STS_TACH_BIT); + } } -void hp_1ma6_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +WRITE_LINE_MEMBER(hp_1ma6_device::motion_w) { - update_tape_pos(); - - switch (id) { - case TAPE_TMR_ID: - { - LOG("TMR %.6f %d %d\n" , machine().time().as_double() , m_tape_pos , m_cmd_state); - attotime cmd_duration = attotime::never; - switch (m_cmd_state) { - case CMD_STOPPING: + if (state) { + LOG_DBG("motion_w @%.6f st=%d\n" , machine().time().as_double() , m_cmd_state); + switch (m_cmd_state) { + case CMD_STOPPING: + if (!m_tape->is_moving()) { m_cmd_state = CMD_IDLE; - stop_tape(); - break; - - case CMD_RD_WAIT_SYNC: - m_tape_pos = m_rw_pos; - LOG("RD WS @ %d = %04x\n" , m_tape_pos , m_rd_it->second); - if (m_rd_it->second != 0xffff) { - // Sync achieved - m_cmd_state = CMD_RD_MSB; - } - cmd_duration = advance_rd_state(); - break; - - case CMD_RD_MSB: - m_tape_pos = m_rw_pos; - m_next_word = m_rd_it->second; - LOG("RD T%u @ %d = %04x\n" , current_track() , m_rw_pos , m_next_word); - m_data_reg = (uint8_t)(m_next_word >> 8); - BIT_SET(m_status_reg , STS_READY_BIT); - // Time to reach LSB: half the length of a 0 word - // Actually it varies basing on MSB content - cmd_duration = time_to_distance(hti_format_t::word_length(0) / 2); - m_cmd_state = CMD_RD_LSB; - break; - - case CMD_RD_LSB: - m_data_reg = (uint8_t)m_next_word; - BIT_SET(m_status_reg , STS_READY_BIT); - m_cmd_state = CMD_RD_MSB; - cmd_duration = advance_rd_state(); - break; - - case CMD_WR_SYNC: - m_tape_pos = m_rw_pos; - if (m_rd_it->second != 0xffff) { - // Got preamble - m_next_word = (hti_format_t::tape_word_t)m_data_reg << 8; - BIT_SET(m_status_reg , STS_READY_BIT); - m_cmd_state = CMD_WR_LSB; - cmd_duration = time_to_distance(hti_format_t::word_length(0) / 2); - } else { - cmd_duration = advance_rd_state(); - } - break; - - case CMD_WR_MSB: - // **** FWD **** - // | m_rw_pos --> m_next_word - // | m_tape_pos --> (start of next word) - // V - // - // **** REV **** - // ^ m_tape_pos --> m_next_word - // | m_rw_pos --> (start of previous word) - // | - { - auto length = hti_format_t::word_length(m_next_word); - if (!is_moving_fwd()) { - hti_format_t::pos_offset(m_rw_pos , false , length); + } + break; + + case CMD_STARTING: + { + hp_dc100_tape_device::tape_op_t op = hp_dc100_tape_device::OP_IDLE; + + switch (m_control_reg & (BIT_MASK(CTL_FAST_BIT) | + BIT_MASK(CTL_WRITE_DATA_BIT) | + BIT_MASK(CTL_WRITE_SYNC_BIT) | + BIT_MASK(CTL_WRITE_GAP_BIT))) { + case 0: + if (m_tape->is_above_threshold()) { + // Start RD + m_cmd_state = CMD_RD_WAIT_SYNC; + op = hp_dc100_tape_device::OP_READ; } - LOG("WR T%u @ %d = %04x\n" , current_track() , m_rw_pos , m_next_word); - if (m_cartridge_in) { - m_image.write_word(current_track() , m_rw_pos , m_next_word , length , is_moving_fwd()); - m_image_dirty = true; + break; + + case BIT_MASK(CTL_FAST_BIT): + case BIT_MASK(CTL_WRITE_GAP_BIT): + case BIT_MASK(CTL_WRITE_GAP_BIT) | BIT_MASK(CTL_WRITE_DATA_BIT): + // Start simple movement + m_cmd_state = CMD_FAST_FWD_REV; + break; + + case BIT_MASK(CTL_WRITE_DATA_BIT): + if (m_tape->is_above_threshold()) { + // Start re-writing + m_cmd_state = CMD_WR_WAIT_SYNC; + // Need to achieve sync first (hence RD op) + op = hp_dc100_tape_device::OP_READ; } - if (is_moving_fwd()) { - hti_format_t::pos_offset(m_rw_pos , true , length); + break; + + case BIT_MASK(CTL_WRITE_SYNC_BIT): + if (m_tape->is_above_threshold()) { + // Start WR + load_wr_word(); + m_cmd_state = CMD_WR_PREAMBLE; + op = hp_dc100_tape_device::OP_WRITE; } - m_tape_pos = m_rw_pos; + break; - m_next_word = (hti_format_t::tape_word_t)m_data_reg << 8; - BIT_SET(m_status_reg , STS_READY_BIT); - m_cmd_state = CMD_WR_LSB; - cmd_duration = time_to_distance(hti_format_t::word_length(0) / 2); - } - break; - - case CMD_WR_LSB: - { - m_next_word = (m_next_word & 0xff00) | m_data_reg; - BIT_SET(m_status_reg , STS_READY_BIT); - m_cmd_state = CMD_WR_MSB; - auto length = hti_format_t::word_length(m_next_word); - auto tmp = m_rw_pos; - hti_format_t::pos_offset(tmp , is_moving_fwd() , length); - cmd_duration = time_to_target(tmp); - } - break; + case BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT): + if (m_tape->is_above_threshold()) { + // Start erasing (gap writing) + m_cmd_state = CMD_WR_GAP; + op = hp_dc100_tape_device::OP_ERASE; + } + break; - default: - break; + default: + break; + } + m_tape->set_op(op); } - m_tape_timer->adjust(cmd_duration); + break; + + default: + break; + } + } +} + +WRITE_LINE_MEMBER(hp_1ma6_device::rd_bit_w) +{ + LOG_RW("RD bit %d (st=%d,sr=%02x,i=%u)\n" , state , m_cmd_state , m_data_sr , m_bit_idx); + switch (m_cmd_state) { + case CMD_RD_WAIT_SYNC: + if (state) { + // Got sync (bit is 1) + LOG_RW("RD synced\n"); + m_cmd_state = CMD_RD; + m_bit_idx = 7; + m_data_sr = 0; } break; - case HOLE_TMR_ID: - BIT_SET(m_status_reg , STS_HOLE_BIT); - m_hole_timer->adjust(time_to_next_hole()); + case CMD_RD: + if (state) { + BIT_SET(m_data_sr , m_bit_idx); + } + if (m_bit_idx) { + m_bit_idx--; + } else { + LOG_RW("RD byte %02x\n" , m_data_sr); + m_data_reg = m_data_sr; + m_bit_idx = 7; + m_data_sr = 0; + BIT_SET(m_status_reg , STS_READY_BIT); + } + break; + + case CMD_WR_WAIT_SYNC: + m_cmd_state = CMD_WR_PREAMBLE; + load_wr_word(); + m_tape->set_op(hp_dc100_tape_device::OP_WRITE); break; default: @@ -335,74 +319,50 @@ void hp_1ma6_device::device_timer(emu_timer &timer, device_timer_id id, int para } } -image_init_result hp_1ma6_device::call_load() +READ_LINE_MEMBER(hp_1ma6_device::wr_bit_r) { - LOG("call_load\n"); - return internal_load(false); + bool bit = m_cmd_state == CMD_WR_PREAMBLE ? false : BIT(m_data_sr , m_bit_idx); + if (m_bit_idx) { + m_bit_idx--; + } else { + if (m_cmd_state == CMD_WR) { + load_wr_word(); + } + m_cmd_state = CMD_WR; + m_bit_idx = 7; + } + LOG_RW("WR bit %d (sr=%02x,i=%u)\n" , bit , m_data_sr , m_bit_idx); + return bit; } -image_init_result hp_1ma6_device::call_create(int format_type, util::option_resolution *format_options) +void hp_1ma6_device::device_add_mconfig(machine_config &config) { - LOG("call_create\n"); - return internal_load(true); + HP_DC100_TAPE(config , m_tape , 0); + m_tape->set_acceleration(ACCELERATION); + m_tape->set_set_points(SLOW_SPEED , FAST_SPEED); + m_tape->set_tick_size(TACH_TICK_LEN); + m_tape->set_bits_per_word(16); + m_tape->set_go_threshold(MOVING_THRESHOLD); + m_tape->cart_out().set(FUNC(hp_1ma6_device::cart_out_w)); + m_tape->hole().set(FUNC(hp_1ma6_device::hole_w)); + m_tape->tacho_tick().set(FUNC(hp_1ma6_device::tacho_tick_w)); + m_tape->motion_event().set(FUNC(hp_1ma6_device::motion_w)); + m_tape->rd_bit().set(FUNC(hp_1ma6_device::rd_bit_w)); + m_tape->wr_bit().set(FUNC(hp_1ma6_device::wr_bit_r)); } -void hp_1ma6_device::call_unload() -{ - LOG("call_unload dirty=%d\n" , m_image_dirty); - - device_reset(); - - if (m_image_dirty) { - io_generic io; - io.file = (device_image_interface *)this; - io.procs = &image_ioprocs; - io.filler = 0; - m_image.save_tape(&io); - m_image_dirty = false; - } - - m_image.clear_tape(); - set_cartridge_in(false); -} - -std::string hp_1ma6_device::call_display() +void hp_1ma6_device::device_start() { - std::string buffer; - // Mostly lifted from cassette_image_device::call_display ;) - // See also hp_taco_device::call_display - - // Do not show anything if image not loaded or tape not moving - if (!exists() || m_start_time.is_never()) { - return buffer; - } - - char track = BIT(m_control_reg , CTL_TRACK_NO_BIT) ? 'B' : 'A'; - char r_w = m_cmd_state == CMD_WR_SYNC || - m_cmd_state == CMD_WR_MSB || - m_cmd_state == CMD_WR_LSB || - m_cmd_state == CMD_WR_GAP ? 'W' : 'R'; - char m1; - char m2; - - if (is_moving_fwd()) { - m1 = '>'; - m2 = BIT(m_control_reg , CTL_FAST_BIT) ? '>' : ' '; - } else { - m1 = '<'; - m2 = BIT(m_control_reg , CTL_FAST_BIT) ? '<' : ' '; - } - - int pos_in = current_tape_pos() / hti_format_t::ONE_INCH_POS; - - buffer = string_format("%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); - - return buffer; + save_item(NAME(m_data_reg)); + save_item(NAME(m_status_reg)); + save_item(NAME(m_control_reg)); + save_item(NAME(m_bit_idx)); + save_item(NAME(m_data_sr)); } -const char *hp_1ma6_device::file_extensions() const +void hp_1ma6_device::device_reset() { - return "hti"; + clear_state(); } void hp_1ma6_device::clear_state() @@ -410,300 +370,40 @@ void hp_1ma6_device::clear_state() m_data_reg = 0; m_status_reg = 0; m_control_reg = 0; - m_tape_pos = TAPE_INIT_POS; - m_prev_pos = TAPE_INIT_POS; - m_start_time = attotime::never; - m_rw_pos = hti_format_t::NULL_TAPE_POS; - m_next_word = 0; - m_rd_it_valid = false; - m_gap_detect_start = hti_format_t::NULL_TAPE_POS; + m_bit_idx = 0; + m_data_sr = 0; m_cmd_state = CMD_IDLE; - set_cartridge_in(false); - set_cartridge_in(is_loaded()); -} - -unsigned hp_1ma6_device::current_track() const -{ - return BIT(m_control_reg , CTL_TRACK_NO_BIT); -} - -unsigned hp_1ma6_device::speed_to_tick_freq(void) const -{ - unsigned freq = BIT(m_control_reg , CTL_FAST_BIT) ? TACH_FREQ_FAST : TACH_FREQ_SLOW; - - if (m_cmd_state == CMD_STOPPING) { - // Braking - freq /= 2; - } - - return freq; -} - -attotime hp_1ma6_device::time_to_distance(hti_format_t::tape_pos_t distance) const -{ - // +1 for rounding - return attotime::from_ticks(distance + 1 , speed_to_tick_freq()); -} - -attotime hp_1ma6_device::time_to_target(hti_format_t::tape_pos_t target) const -{ - return time_to_distance(abs(target - m_tape_pos)); -} - -attotime hp_1ma6_device::time_to_next_hole(void) const -{ - auto pos = hti_format_t::next_hole(m_tape_pos , is_moving_fwd()); - - if (pos == hti_format_t::NULL_TAPE_POS) { - return attotime::never; - } else { - return time_to_target(pos); - } -} - -attotime hp_1ma6_device::time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos) const -{ - if (m_rd_it_valid) { - word_rd_pos = hti_format_t::farthest_end(m_rd_it , is_moving_fwd()); - return time_to_target(word_rd_pos); - } else { - return attotime::never; - } } -hti_format_t::tape_pos_t hp_1ma6_device::current_tape_pos() const +void hp_1ma6_device::load_wr_word() { - if (m_start_time.is_never()) { - // Tape not moving - return m_tape_pos; - } - - attotime delta_time(machine().time() - m_start_time); - hti_format_t::tape_pos_t delta_tach = (hti_format_t::tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); - - auto tape_pos = m_tape_pos; - if (!hti_format_t::pos_offset(tape_pos , is_moving_fwd() , delta_tach)) { - LOG("Tape unspooled!\n"); - } - - return tape_pos; -} - -void hp_1ma6_device::update_tape_pos() -{ - if (m_start_time.is_never()) { - // Tape not moving - return; - } - - m_tape_pos = current_tape_pos(); - m_start_time = machine().time(); - - // Tachometer ticks - if ((m_prev_pos / TACH_TICK_LEN) != (m_tape_pos / TACH_TICK_LEN)) { - BIT_SET(m_status_reg , STS_TACH_BIT); - } - m_prev_pos = m_tape_pos; -} - -bool hp_1ma6_device::is_reading() const -{ - return m_cmd_state >= CMD_RD_WAIT_SYNC && - m_cmd_state <= CMD_RD_LSB; -} - -bool hp_1ma6_device::is_writing() const -{ - return m_cmd_state >= CMD_WR_SYNC && - m_cmd_state <= CMD_WR_GAP; -} - -bool hp_1ma6_device::is_moving_fwd() const -{ - return BIT(m_control_reg , CTL_DIR_FWD_BIT); -} - -void hp_1ma6_device::set_cartridge_in(bool in) -{ - m_cartridge_in = in; - if (!m_cartridge_in) { - BIT_CLR(m_status_reg , STS_CASSETTE_IN_BIT); - } -} - -void hp_1ma6_device::start_tape() -{ - m_start_time = machine().time(); - m_prev_pos = m_tape_pos; - if (!is_writing()) { - m_gap_detect_start = m_tape_pos; - } else { - m_gap_detect_start = hti_format_t::NULL_TAPE_POS; - } - m_hole_timer->adjust(time_to_next_hole()); -} - -void hp_1ma6_device::stop_tape() -{ - m_start_time = attotime::never; - m_gap_detect_start = hti_format_t::NULL_TAPE_POS; - m_hole_timer->reset(); - m_tape_timer->reset(); -} - -attotime hp_1ma6_device::advance_rd_state() -{ - auto res = m_image.adv_it(current_track() , is_moving_fwd() , m_rd_it); - if (res == hti_format_t::ADV_NO_MORE_DATA) { - m_rd_it_valid = false; - } else if (res == hti_format_t::ADV_DISCONT_DATA && is_reading()) { - // Discontinuous data: move back to sync search (probably..) - m_cmd_state = CMD_RD_WAIT_SYNC; - } - return time_to_rd_next_word(m_rw_pos); + m_bit_idx = 7; + m_data_sr = m_data_reg; + LOG_RW("WR byte %02x\n" , m_data_sr); + BIT_SET(m_status_reg , STS_READY_BIT); } void hp_1ma6_device::start_cmd_exec(uint8_t new_ctl_reg) { - if (new_ctl_reg == m_control_reg) { - return; - } - - cmd_state_t new_state; - - if (!BIT(new_ctl_reg , CTL_POWER_UP_BIT)) { - new_state = CMD_IDLE; - } else if (!BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) { - new_state = CMD_STOPPING; - } else switch (new_ctl_reg & (BIT_MASK(CTL_FAST_BIT) | - BIT_MASK(CTL_WRITE_DATA_BIT) | - BIT_MASK(CTL_WRITE_SYNC_BIT) | - BIT_MASK(CTL_WRITE_GAP_BIT))) { - case 0: - new_state = CMD_RD_WAIT_SYNC; - break; - - case BIT_MASK(CTL_FAST_BIT): - case BIT_MASK(CTL_WRITE_GAP_BIT): - case BIT_MASK(CTL_WRITE_GAP_BIT) | BIT_MASK(CTL_WRITE_DATA_BIT): - new_state = CMD_FAST_FWD_REV; - break; - - case BIT_MASK(CTL_WRITE_DATA_BIT): - new_state = CMD_WR_SYNC; - break; - - case BIT_MASK(CTL_WRITE_SYNC_BIT): - new_state = CMD_WR_MSB; - break; - - case BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT): - new_state = CMD_WR_GAP; - break; - - default: - LOG("Unknown command %02x\n" , new_ctl_reg); - return; - } - - update_tape_pos(); - LOG("State %d -> %d pos = %d\n" , m_cmd_state , new_state , m_tape_pos); - - if (m_cmd_state == CMD_WR_GAP) { - // Finish gap writing - LOG("T%u: Gap from %d to %d\n" , current_track() , m_rw_pos , m_tape_pos); - if (m_cartridge_in) { - m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); - m_image_dirty = true; - } - } - - uint8_t saved_dir_speed = m_control_reg & (BIT_MASK(CTL_DIR_FWD_BIT) | BIT_MASK(CTL_FAST_BIT)); - m_control_reg = new_ctl_reg; - attotime cmd_duration = attotime::never; - - switch (new_state) { - case CMD_IDLE: - m_cmd_state = CMD_IDLE; - stop_tape(); - break; - - case CMD_STOPPING: - if (m_cmd_state != CMD_IDLE && - m_cmd_state != CMD_STOPPING) { - m_cmd_state = CMD_STOPPING; - // Keep speed & direction from before the STOP command - m_control_reg = (m_control_reg & ~(BIT_MASK(CTL_DIR_FWD_BIT) | BIT_MASK(CTL_FAST_BIT))) | saved_dir_speed; - cmd_duration = time_to_distance(BIT(m_control_reg , CTL_FAST_BIT) ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); - } - break; - - case CMD_RD_WAIT_SYNC: - m_cmd_state = CMD_RD_WAIT_SYNC; - start_tape(); - m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , is_moving_fwd() , false , m_rd_it); - cmd_duration = time_to_rd_next_word(m_rw_pos); - // TODO: ?? - BIT_CLR(m_status_reg , STS_READY_BIT); - break; - - case CMD_WR_SYNC: - m_cmd_state = CMD_WR_SYNC; - start_tape(); - m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , is_moving_fwd() , false , m_rd_it); - cmd_duration = time_to_rd_next_word(m_rw_pos); - BIT_SET(m_status_reg , STS_READY_BIT); - break; - - case CMD_WR_MSB: - m_cmd_state = CMD_WR_MSB; - start_tape(); - m_rw_pos = m_tape_pos; - // SYNC word: MSB = 00, LSB = data register - m_next_word = (hti_format_t::tape_word_t)m_data_reg; - BIT_SET(m_status_reg , STS_READY_BIT); - cmd_duration = time_to_distance(hti_format_t::word_length(m_next_word)); - break; - - case CMD_WR_GAP: - m_cmd_state = CMD_WR_GAP; - start_tape(); - m_rw_pos = m_tape_pos; - break; - - case CMD_FAST_FWD_REV: - m_cmd_state = CMD_FAST_FWD_REV; - start_tape(); - break; - - default: - break; + if (!BIT(new_ctl_reg , CTL_POWER_UP_BIT) || + !BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) { + m_cmd_state = CMD_STOPPING; + } else { + m_cmd_state = CMD_STARTING; } - m_tape_timer->adjust(cmd_duration); -} + m_tape->set_op(hp_dc100_tape_device::OP_IDLE); + m_tape->set_track_no(BIT(m_control_reg , CTL_TRACK_NO_BIT)); -image_init_result hp_1ma6_device::internal_load(bool is_create) -{ - device_reset(); - - io_generic io; - io.file = (device_image_interface *)this; - io.procs = &image_ioprocs; - io.filler = 0; - if (is_create) { - m_image.clear_tape(); - m_image.save_tape(&io); - } else if (!m_image.load_tape(&io)) { - seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); - set_cartridge_in(false); - return image_init_result::FAIL; + hp_dc100_tape_device::tape_speed_t new_speed = hp_dc100_tape_device::SP_STOP; + if (BIT(new_ctl_reg , CTL_POWER_UP_BIT) && + BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) { + new_speed = BIT(new_ctl_reg , CTL_FAST_BIT) ? hp_dc100_tape_device::SP_FAST : + hp_dc100_tape_device::SP_SLOW; } - m_image_dirty = false; - set_cartridge_in(true); - - return image_init_result::PASS; + m_tape->set_speed_setpoint(new_speed , BIT(new_ctl_reg , CTL_DIR_FWD_BIT)); + motion_w(1); } diff --git a/src/devices/machine/1ma6.h b/src/devices/machine/1ma6.h index 2a227c496c6..d2943857760 100644 --- a/src/devices/machine/1ma6.h +++ b/src/devices/machine/1ma6.h @@ -14,9 +14,9 @@ #pragma once #include "formats/hti_tape.h" +#include "machine/hp_dc100_tape.h" -class hp_1ma6_device : public device_t , - public device_image_interface +class hp_1ma6_device : public device_t { public: // construction/destruction @@ -26,89 +26,49 @@ public: DECLARE_WRITE8_MEMBER(reg_w); DECLARE_READ8_MEMBER(reg_r); + DECLARE_WRITE_LINE_MEMBER(cart_out_w); + DECLARE_WRITE_LINE_MEMBER(hole_w); + DECLARE_WRITE_LINE_MEMBER(tacho_tick_w); + DECLARE_WRITE_LINE_MEMBER(motion_w); + DECLARE_WRITE_LINE_MEMBER(rd_bit_w); + DECLARE_READ_LINE_MEMBER(wr_bit_r); + protected: // device-level overrides + virtual void device_add_mconfig(machine_config &config) override; virtual void device_start() override; virtual void device_reset() override; - virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; - - // device_image_interface overrides - virtual image_init_result call_load() override; - virtual image_init_result call_create(int format_type, util::option_resolution *format_options) override; - virtual void call_unload() override; - virtual std::string 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; } - virtual bool is_creatable() const override { return true; } - virtual bool must_be_loaded() const override { return false; } - virtual bool is_reset_on_load() const override { return false; } - virtual const char *file_extensions() const override; private: + required_device<hp_dc100_tape_device> m_tape; + // Registers uint8_t m_data_reg; uint8_t m_status_reg; uint8_t m_control_reg; - // Tape position & motion - hti_format_t::tape_pos_t m_tape_pos; - hti_format_t::tape_pos_t m_prev_pos; - attotime m_start_time; // Tape moving if != never - - // Timers - emu_timer *m_tape_timer; - emu_timer *m_hole_timer; - - // Content of tape - hti_format_t m_image; - bool m_image_dirty; - // Reading & writing - hti_format_t::tape_pos_t m_rw_pos; - hti_format_t::tape_word_t m_next_word; - hti_format_t::track_iterator_t m_rd_it; - bool m_rd_it_valid; - - // Gap detection - hti_format_t::tape_pos_t m_gap_detect_start; + unsigned m_bit_idx; + uint8_t m_data_sr; // Command FSM state typedef enum { CMD_IDLE, CMD_STOPPING, + CMD_STARTING, CMD_RD_WAIT_SYNC, - CMD_RD_MSB, - CMD_RD_LSB, - CMD_WR_SYNC, - CMD_WR_MSB, - CMD_WR_LSB, + CMD_RD, + CMD_WR_WAIT_SYNC, + CMD_WR_PREAMBLE, + CMD_WR, CMD_WR_GAP, CMD_FAST_FWD_REV } cmd_state_t; cmd_state_t m_cmd_state; - // Cartridge-in status - bool m_cartridge_in; - void clear_state(); - unsigned current_track() const; - unsigned speed_to_tick_freq(void) const; - attotime time_to_distance(hti_format_t::tape_pos_t distance) const; - attotime time_to_target(hti_format_t::tape_pos_t target) const; - attotime time_to_next_hole(void) const; - attotime time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos) const; - hti_format_t::tape_pos_t current_tape_pos() const; - void update_tape_pos(); - bool is_reading() const; - bool is_writing() const; - bool is_moving_fwd() const; - void set_cartridge_in(bool in); - void start_tape(); - void stop_tape(); - attotime advance_rd_state(); + void load_wr_word(); void start_cmd_exec(uint8_t new_ctl_reg); - image_init_result internal_load(bool is_create); }; // device type definition diff --git a/src/devices/machine/hp_dc100_tape.cpp b/src/devices/machine/hp_dc100_tape.cpp new file mode 100644 index 00000000000..807d4305bda --- /dev/null +++ b/src/devices/machine/hp_dc100_tape.cpp @@ -0,0 +1,914 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp_dc100_tape.cpp + + HP DC100 tape cartridge & drive + + This driver emulates the DC100 tape drive as used by HP9825, 9845 + and 85 systems. + + Main features: + - Handles HTI tape images + - Supports 3 speed setpoints (stop, slow & fast) and forward/reverse + tape direction + - Applies a constant acceleration whenever a change of speed setpoint + and/or direction occurs + - Reports through a callback "motion" events such as the reaching/leaving + of setpoint, the crossing of speed thresholds, etc. + - Simulates tachometer ticks + - Handles 3 different tape operations: reading, writing and erasing. + - Simulates the serial reading and writing of bits from/to tape + and their correct timing + - Reports various tape conditions as the presence of cartridge, + of tape holes, of the write-protection switch etc. + - Checks for gaps (erased zones) of arbitrary length + +*********************************************************************/ + +#include "emu.h" +#include "hp_dc100_tape.h" + +// Debugging +#include "logmacro.h" +#define LOG_TMR_MASK (LOG_GENERAL << 1) +#define LOG_TMR(...) LOGMASKED(LOG_TMR_MASK, __VA_ARGS__) +#define LOG_DBG_MASK (LOG_TMR_MASK << 1) +#define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__) +#define LOG_RW_MASK (LOG_DBG_MASK << 1) +#define LOG_RW(...) LOGMASKED(LOG_RW_MASK, __VA_ARGS__) +#undef VERBOSE +//#define VERBOSE (LOG_GENERAL | LOG_TMR_MASK | LOG_DBG_MASK | LOG_RW_MASK) +#define VERBOSE (LOG_GENERAL) + +// Bit manipulation +namespace { + template<typename T> constexpr T BIT_MASK(unsigned n) + { + return (T)1U << n; + } + + template<typename T> void BIT_CLR(T& w , unsigned n) + { + w &= ~BIT_MASK<T>(n); + } + + template<typename T> void BIT_SET(T& w , unsigned n) + { + w |= BIT_MASK<T>(n); + } +} + +// Device type definition +DEFINE_DEVICE_TYPE(HP_DC100_TAPE, hp_dc100_tape_device, "hp_dc100_tape", "HP DC100 tape drive") + +// Timers +enum { + BIT_TMR_ID, + TACHO_TMR_ID, + HOLE_TMR_ID, + MOTION_TMR_ID +}; + +// Constants +constexpr double MOTION_MARGIN = 1e-5; // Margin to ensure motion events have passed when timer expires (10 µs) +constexpr hti_format_t::tape_pos_t TAPE_INIT_POS = 80 * hti_format_t::ONE_INCH_POS; // Initial tape position: 80" from beginning (just past the punched part) + +hp_dc100_tape_device::hp_dc100_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig , HP_DC100_TAPE , tag , owner , clock) + , device_image_interface(mconfig , *this) + , m_cart_out_handler(*this) + , m_hole_handler(*this) + , m_tacho_tick_handler(*this) + , m_motion_handler(*this) + , m_rd_bit_handler(*this) + , m_wr_bit_handler(*this) + , m_image() + , m_image_dirty(false) +{ +} + +image_init_result hp_dc100_tape_device::call_load() +{ + return internal_load(false); +} + +image_init_result hp_dc100_tape_device::call_create(int format_type, util::option_resolution *format_options) +{ + return internal_load(true); +} + +void hp_dc100_tape_device::call_unload() +{ + LOG("call_unload dirty=%d\n" , m_image_dirty); + + device_reset(); + + if (m_image_dirty) { + io_generic io; + io.file = (device_image_interface *)this; + io.procs = &image_ioprocs; + io.filler = 0; + m_image.save_tape(&io); + m_image_dirty = false; + } + + m_image.clear_tape(); + set_tape_present(false); +} + +std::string hp_dc100_tape_device::call_display() +{ + std::string buffer; + // Mostly lifted from cassette_image_device::call_display ;) + + // Do not show anything if image not loaded or tape not moving + if (cart_out_r() || !has_started()) { + return buffer; + } + + char track = m_track ? 'B' : 'A'; + char r_w = m_current_op == OP_WRITE || m_current_op == OP_ERASE ? 'W' : 'R'; + char m1; + char m2; + + if (is_moving_fwd()) { + m1 = '>'; + m2 = m_tape_speed == SP_FAST ? '>' : ' '; + } else { + m1 = '<'; + m2 = m_tape_speed == SP_FAST ? '<' : ' '; + } + + int pos_in = get_approx_pos() / hti_format_t::ONE_INCH_POS; + + buffer = string_format("%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); + + return buffer; +} + +const char *hp_dc100_tape_device::file_extensions() const +{ + return "hti"; +} + +void hp_dc100_tape_device::set_acceleration(double accel) +{ + m_acceleration = accel; +} + +void hp_dc100_tape_device::set_set_points(double slow_sp , double fast_sp) +{ + m_slow_set_point = slow_sp; + m_fast_set_point = fast_sp; +} + +void hp_dc100_tape_device::set_tick_size(hti_format_t::tape_pos_t size) +{ + m_tick_size = size; +} + +void hp_dc100_tape_device::set_bits_per_word(unsigned bits) +{ + m_image.set_bits_per_word(bits); +} + +void hp_dc100_tape_device::set_go_threshold(double threshold) +{ + m_go_threshold = threshold; +} + +void hp_dc100_tape_device::set_track_no(unsigned track) +{ + if (m_track != track) { + LOG_DBG("Setting track %u (op=%d)\n" , track , static_cast<int>(m_current_op)); + auto saved_op = m_current_op; + if (m_current_op != OP_IDLE) { + // Close current op on old track + set_op(OP_IDLE); + } + m_track = track; + if (saved_op != OP_IDLE) { + // Resume op on new track + set_op(saved_op); + } + } +} + +bool hp_dc100_tape_device::set_speed_setpoint(tape_speed_t speed , bool fwd) +{ + if (!m_present) { + return false; + } + + double new_setpoint = compute_set_point(speed, fwd); + + if (m_set_point != new_setpoint) { + update_speed_pos(); + LOG_DBG("Speed SP changed %f->%f %.6f p=%d\n" , m_set_point , new_setpoint , machine().time().as_double() , m_tape_pos); + m_tape_speed = speed; + m_set_point = new_setpoint; + // Speed set point changed, accelerate/decelerate + m_accelerating = true; + if (m_start_time.is_never()) { + // Tape starting now + start_tape(); + // When tape starts m_current_op is always OP_IDLE + m_gap_detect_start = m_tape_pos; + } + adjust_tacho_timer(); + adjust_hole_timer(); + set_motion_timer(); + return true; + } else { + return false; + } +} + +void hp_dc100_tape_device::set_op(tape_op_t op , bool force) +{ + if (!m_present || m_start_time.is_never()) { + return; + } + if (!m_in_set_op && (op != m_current_op || force)) { + LOG_DBG("Op %d->%d (f=%d)\n" , m_current_op , op , force); + m_in_set_op = true; + update_speed_pos(); + auto prev_op = m_current_op; + // Close current operating state + stop_op(); + m_in_set_op = false; + m_current_op = op; + + switch (op) { + case OP_IDLE: + m_gap_detect_start = m_tape_pos; + break; + + case OP_READ: + if (prev_op == OP_WRITE) { + LOG("Starting RD after WR?\n"); + } + if (fabs(m_speed) < m_slow_set_point) { + LOG("Starting RD at %f speed?\n" , m_speed); + } + m_rd_it_valid = m_image.next_data(get_track_no() , m_tape_pos , is_moving_fwd() , false , m_rd_it); + load_rd_word(); + m_gap_detect_start = m_tape_pos; + break; + + case OP_WRITE: + if (m_accelerating || fabs(m_speed) != m_slow_set_point) { + LOG("Starting WR at %f speed (acc=%d)?\n" , m_speed , m_accelerating); + } + if (prev_op == OP_READ) { + // Switching from RD to WR + // Clear out the part of m_rw_word that is going to be written + LOG_RW("Switch RD->WR @%d, idx=%d, w=%04x\n" , m_rw_pos , m_bit_idx , m_rw_word); + if (is_moving_fwd()) { + if (--m_bit_idx >= 0) { + m_rw_word &= 0xffffU << (m_bit_idx + 1); + } else { + m_bit_idx = 15; + m_rw_pos = m_next_bit_pos; + m_rw_word = 0; + } + } else { + if (++m_bit_idx < 16) { + m_rw_word &= 0xffffU >> (16 - m_bit_idx); + } else { + m_bit_idx = 0; + m_rw_pos = m_next_bit_pos; + m_rw_word = 0; + } + } + time_to_distance(m_next_bit_pos - m_tape_pos, m_next_bit_pos, m_bit_timer); + } else { + m_rw_word = 0; + if (is_moving_fwd()) { + m_bit_idx = 15; + } else { + m_bit_idx = 0; + } + m_next_bit_pos = m_rw_pos = m_tape_pos; + m_bit_timer->adjust(attotime::zero); + } + break; + + case OP_ERASE: + LOG_DBG("Start GAP @%d\n" , m_tape_pos); + m_rw_pos = m_tape_pos; + break; + + default: + LOG("Invalid op!\n"); + break; + } + } +} + +void hp_dc100_tape_device::update_speed_pos() +{ + if (m_start_time.is_never()) { + // Tape stopped + return; + } + + attotime delta_time{machine().time() - m_start_time}; + if (delta_time.is_zero()) { + return; + } + m_start_time = machine().time(); + double delta_time_double = delta_time.as_double(); + + // Space in const A phase + double space_const_a; + // Time in const V phase + double time_const_v; + // Report a motion event + bool motion_event = false; + // Direction inverted + bool inverted = false; + + if (m_accelerating) { + auto prev_speed = m_speed; + double time_to_const_v = fabs(m_set_point - m_speed) / m_acceleration; + double acceleration = m_set_point > m_speed ? m_acceleration : -m_acceleration; + bool retrigger_motion = false; + if (delta_time_double < time_to_const_v) { + space_const_a = const_a_space(acceleration , delta_time_double); + m_speed += delta_time_double * acceleration; + time_const_v = 0.0; + } else { + space_const_a = const_a_space(acceleration , time_to_const_v); + time_const_v = delta_time_double - time_to_const_v; + LOG_DBG("Acceleration ends\n"); + m_accelerating = false; + m_speed = m_set_point; + motion_event = true; + retrigger_motion = true; + if (m_speed == 0.0) { + // Tape stops + stop_tape(); + } + } + if ((fabs(prev_speed) - m_slow_set_point) * (fabs(m_speed) - m_slow_set_point) <= 0.0 || + (fabs(prev_speed) - m_go_threshold) * (fabs(m_speed) - m_go_threshold) < 0.0) { + // Slow speed threshold crossed + // In-motion threshold crossed + LOG_DBG("Thr crossed %f->%f\n" , prev_speed , m_speed); + motion_event = true; + retrigger_motion = true; + } + if (prev_speed * m_speed < 0.0) { + // Direction inverted (speed sign flipped) + LOG_DBG("Dir inverted s=%f\n" , m_speed); + inverted = true; + retrigger_motion = true; + } + if (retrigger_motion) { + set_motion_timer(); + } + } else { + space_const_a = 0.0; + time_const_v = delta_time_double; + } + + hti_format_t::tape_pos_t delta_pos = (hti_format_t::tape_pos_t)((space_const_a + m_speed * time_const_v) * hti_format_t::ONE_INCH_POS); + LOG_DBG("dp=%d\n" , delta_pos); + if (!hti_format_t::pos_offset(m_tape_pos , true , delta_pos)) { + LOG("Tape unspooled!\n"); + } + + if (inverted) { + adjust_tacho_timer(); + adjust_hole_timer(); + if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS) { + m_gap_detect_start = m_tape_pos; + } + } + if (motion_event) { + // Must be done here or pos & speed won't be correct + m_motion_handler(1); + } +} + +hti_format_t::tape_pos_t hp_dc100_tape_device::get_approx_pos() const +{ + if (m_start_time.is_never()) { + // Tape not moving + return m_tape_pos; + } + + attotime delta_time{ machine().time() - m_start_time }; + hti_format_t::tape_pos_t delta_pos = (hti_format_t::tape_pos_t)(delta_time.as_double() * m_speed * hti_format_t::ONE_INCH_POS); + auto tape_pos = m_tape_pos; + hti_format_t::pos_offset(tape_pos , true , delta_pos); + return tape_pos; +} + +bool hp_dc100_tape_device::gap_reached(hti_format_t::tape_pos_t min_gap_size) +{ + update_speed_pos(); + + if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS && + abs(m_gap_detect_start - m_tape_pos) >= min_gap_size) { + auto tmp = m_tape_pos; + hti_format_t::pos_offset(tmp , is_moving_fwd() , -min_gap_size); + if (m_image.just_gap(get_track_no() , tmp , m_tape_pos)) { + return true; + } + } + return false; +} + +void hp_dc100_tape_device::time_to_next_gap(hti_format_t::tape_pos_t min_gap_size , bool new_gap , emu_timer *target_timer) +{ + update_speed_pos(); + + bool fwd = is_moving_fwd(); + hti_format_t::tape_pos_t tmp = m_tape_pos; + bool found = true; + if (new_gap) { + hti_format_t::track_iterator_t it; + found = m_image.next_data(get_track_no() , tmp , fwd , true , it); + if (found) { + tmp = hti_format_t::farthest_end(it , !fwd); + } + } + if (found && m_image.next_gap(get_track_no() , tmp , fwd , min_gap_size)) { + hti_format_t::tape_pos_t dummy; + LOG_DBG("TTNG T%u S%d N%d %d->%d\n" , get_track_no() , min_gap_size , new_gap , m_tape_pos , tmp); + time_to_distance(tmp - m_tape_pos, dummy, target_timer); + } else { + LOG_DBG("TTNG T%u S%d N%d %d->X\n" , get_track_no() , min_gap_size , new_gap , m_tape_pos); + target_timer->reset(); + } +} + +void hp_dc100_tape_device::device_start() +{ + m_cart_out_handler.resolve_safe(); + m_hole_handler.resolve_safe(); + m_tacho_tick_handler.resolve_safe(); + m_motion_handler.resolve_safe(); + m_rd_bit_handler.resolve_safe(); + m_wr_bit_handler.resolve_safe(0); + + save_item(NAME(m_acceleration)); + save_item(NAME(m_slow_set_point)); + save_item(NAME(m_fast_set_point)); + save_item(NAME(m_tick_size)); + save_item(NAME(m_go_threshold)); + save_item(NAME(m_tape_pos)); + save_item(NAME(m_set_point)); + save_item(NAME(m_speed)); + save_item(NAME(m_start_time)); + save_item(NAME(m_accelerating)); + save_item(NAME(m_track)); + save_item(NAME(m_in_set_op)); + save_item(NAME(m_present)); + save_item(NAME(m_rd_it_valid)); + save_item(NAME(m_rw_word)); + save_item(NAME(m_bit_idx)); + save_item(NAME(m_rw_pos)); + save_item(NAME(m_gap_detect_start)); + save_item(NAME(m_next_bit_pos)); + save_item(NAME(m_next_tacho_pos)); + save_item(NAME(m_next_hole_pos)); + save_item(NAME(m_image_dirty)); + + m_bit_timer = timer_alloc(BIT_TMR_ID); + m_tacho_timer = timer_alloc(TACHO_TMR_ID); + m_hole_timer = timer_alloc(HOLE_TMR_ID); + m_motion_timer = timer_alloc(MOTION_TMR_ID); +} + +void hp_dc100_tape_device::device_reset() +{ + clear_state(); +} + +void hp_dc100_tape_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + LOG_TMR("%.6f TMR %d p=%d s=%.3f(%.3f) a=%d\n" , machine().time().as_double() , id , m_tape_pos , m_speed , m_set_point , m_accelerating); + update_speed_pos(); + + switch (id) { + case BIT_TMR_ID: + m_tape_pos = m_next_bit_pos; + if (m_current_op == OP_READ) { + bool bit = BIT(m_rd_it->second , m_bit_idx); + m_rd_bit_handler(bit); + if (is_moving_fwd()) { + if (--m_bit_idx >= 0) { + time_to_distance(m_image.bit_length(BIT(m_rd_it->second , m_bit_idx)), m_next_bit_pos, m_bit_timer); + } else { + m_rd_it_valid = m_image.adv_it(get_track_no() , true , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA; + load_rd_word(); + } + } else { + if (++m_bit_idx < 16) { + time_to_distance(-m_image.bit_length(BIT(m_rd_it->second , m_bit_idx)), m_next_bit_pos, m_bit_timer); + } else { + m_rd_it_valid = m_image.adv_it(get_track_no() , false , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA; + load_rd_word(); + } + } + } else if (m_current_op == OP_WRITE) { + bool bit = m_wr_bit_handler(); + hti_format_t::tape_pos_t bit_len = m_image.bit_length(bit); + if (bit) { + BIT_SET(m_rw_word , m_bit_idx); + } + if (is_moving_fwd()) { + if (--m_bit_idx < 0) { + store_wr_word(); + } + } else { + if (++m_bit_idx >= 16) { + store_wr_word(); + } + bit_len = -bit_len; + } + time_to_distance(bit_len, m_next_bit_pos, m_bit_timer); + } + break; + + case TACHO_TMR_ID: + m_tape_pos = m_next_tacho_pos; + m_tacho_tick_handler(1); + adjust_tacho_timer(); + break; + + case HOLE_TMR_ID: + m_tape_pos = m_next_hole_pos; + m_hole_handler(1); + adjust_hole_timer(); + break; + + case MOTION_TMR_ID: + // In itself it does nothing (all work is in update_speed_pos) + break; + + default: + break; + } +} + +void hp_dc100_tape_device::clear_state() +{ + m_tape_pos = TAPE_INIT_POS; + m_tape_speed = SP_STOP; + m_set_point = 0.0; + m_speed = 0.0; + m_start_time = attotime::never; + m_accelerating = false; + m_track = 0; + m_current_op = OP_IDLE; + m_in_set_op = false; + m_present = true; + m_rd_it_valid = false; + m_rw_word = 0; + m_bit_idx = 0; + m_rw_pos = hti_format_t::NULL_TAPE_POS; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; + m_bit_timer->reset(); + m_tacho_timer->reset(); + m_hole_timer->reset(); + m_motion_timer->reset(); + + set_tape_present(false); + set_tape_present(is_loaded()); +} + +image_init_result hp_dc100_tape_device::internal_load(bool is_create) +{ + LOG("load %d\n" , is_create); + + device_reset(); + + io_generic io; + io.file = (device_image_interface *)this; + io.procs = &image_ioprocs; + io.filler = 0; + if (is_create) { + m_image.clear_tape(); + m_image.save_tape(&io); + } else if (!m_image.load_tape(&io)) { + LOG("load failed\n"); + seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); + set_tape_present(false); + return image_init_result::FAIL; + } + LOG("load OK\n"); + + m_image_dirty = false; + + set_tape_present(true); + return image_init_result::PASS; +} + +void hp_dc100_tape_device::set_tape_present(bool present) +{ + if (m_present != present) { + m_present = present; + m_cart_out_handler(!m_present); + } +} + +double hp_dc100_tape_device::compute_set_point(tape_speed_t speed , bool fwd) const +{ + double sp; + + if (speed == SP_SLOW) { + sp = m_slow_set_point; + } else if (speed == SP_FAST) { + sp = m_fast_set_point; + } else { + sp = 0.0; + } + + if (!fwd) { + sp = -sp; + } + + return sp; +} + +void hp_dc100_tape_device::start_tape() +{ + LOG_DBG("Tape started %.6f p=%d\n" , machine().time().as_double() , m_tape_pos); + m_start_time = machine().time(); + m_accelerating = true; + m_speed = 0; +} + +void hp_dc100_tape_device::stop_tape() +{ + LOG_DBG("Tape stops %.6f p=%d\n" , machine().time().as_double() , m_tape_pos); + m_start_time = attotime::never; + m_accelerating = false; + m_speed = 0; + m_tacho_timer->reset(); + m_hole_timer->reset(); + m_motion_timer->reset(); + stop_op(); +} + +double hp_dc100_tape_device::const_a_space(double a , double t) const +{ + // Space traveled in time 't' at constant acceleration 'a' starting with 'm_speed' speed + return t * (m_speed + a / 2 * t); +} + +attotime hp_dc100_tape_device::time_to_threshold(double threshold , bool zero_allowed) const +{ + attotime time{ attotime::never }; + + auto delta_sp = m_set_point - m_speed; + auto delta_t = threshold - m_speed; + LOG_DBG("Dsp=%.6f D+th=%.6f\n" , delta_sp , delta_t); + + if ((delta_sp * delta_t > 0.0 && fabs(delta_t) <= fabs(delta_sp)) || + (zero_allowed && delta_t == 0.0)) { + time = attotime::from_double(fabs(delta_t) / m_acceleration); + LOG_DBG("Time to +th: %.6f\n" , time.as_double()); + } + + delta_t = -threshold - m_speed; + LOG_DBG("Dsp=%.6f D-th=%.6f\n" , delta_sp , delta_t); + if ((delta_sp * delta_t > 0.0 && fabs(delta_t) <= fabs(delta_sp)) || + (zero_allowed && delta_t == 0.0)) { + double tm = fabs(delta_t) / m_acceleration; + if (tm < time.as_double()) { + time = attotime::from_double(tm); + LOG_DBG("Time to -th: %.6f\n" , time.as_double()); + } + } + return time; +} + +void hp_dc100_tape_device::set_motion_timer() +{ + if (!m_accelerating) { + m_motion_timer->reset(); + } else { + // There are 4 possible future "motion" events: + // 1. Slow speed threshold is crossed + // 2. The "in motion" threshold is crossed + // 3. Tape direction reverses + // 4. Set point is reached + // Motion timer is set to expire at the event that occurs first + attotime time{ time_to_threshold(m_slow_set_point , true) }; + + attotime tmp{ time_to_threshold(m_go_threshold , false) }; + if (tmp < time) { + time = tmp; + } + + // Time to the moment when tape inverts its motion + // (i.e. when m_speed crosses 0) + tmp = time_to_threshold(0.0 , false); + if (tmp < time) { + time = tmp; + } + + // Time to reach set point + tmp = attotime::from_double(fabs(m_speed - m_set_point) / m_acceleration); + if (tmp < time) { + time = tmp; + } + + if (time.is_never()) { + // Should never get here + m_motion_timer->reset(); + } else { + // Add margin + time += attotime::from_double(MOTION_MARGIN); + + m_motion_timer->adjust(time); + } + } +} + +void hp_dc100_tape_device::time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const +{ + if (m_start_time.is_never()) { + // If tape is stopped we'll never get there.. + target_timer->reset(); + return; + } + + target_pos = m_tape_pos; + if (!hti_format_t::pos_offset(target_pos , true , distance)) { + // Beyond end of tape + target_timer->reset(); + return; + } + + double space = double(distance) / hti_format_t::ONE_INCH_POS; + double time_const_a; + + if (m_accelerating) { + // Time to reach constant V phase + double time_to_const_v = fabs(m_set_point - m_speed) / m_acceleration; + // Signed acceleration + double acceleration = m_set_point > m_speed ? m_acceleration : -m_acceleration; + // Compute time to cover distance with constant acceleration + // It's the smallest non-negative root of this quadratic equation: + // 1/2*acceleration*t^2+m_speed*t=space + double delta = m_speed * m_speed + 2 * acceleration * space; + bool has_root = delta >= 0.0; + double time_in_const_a = 0.0; + if (has_root) { + double time_in_const_a_pos = (sqrt(delta) - m_speed) / acceleration; + double time_in_const_a_neg = -(sqrt(delta) + m_speed) / acceleration; + LOG_DBG("TTD %.6f %.6f\n" , time_in_const_a_pos , time_in_const_a_neg); + if (time_in_const_a_pos >= 0.0) { + if (time_in_const_a_neg >= 0.0) { + // pos + neg + + time_in_const_a = std::min(time_in_const_a_pos , time_in_const_a_neg); + } else { + // pos + neg - + time_in_const_a = time_in_const_a_pos; + } + } else { + if (time_in_const_a_neg >= 0.0) { + // pos - neg + + time_in_const_a = time_in_const_a_neg; + } else { + // pos - neg - + has_root = false; + } + } + } + LOG_DBG("TTD %d %d %.6f %.6f %.6f\n" , distance , has_root , m_speed , time_to_const_v , time_in_const_a); + if (has_root && time_in_const_a <= time_to_const_v) { + // Entirely in the constant A phase + time_const_a = time_in_const_a; + space = 0.0; + } else { + // Partly in const A & partly in const V + double space_in_const_a = const_a_space(acceleration , time_to_const_v); + space -= space_in_const_a; + time_const_a = time_to_const_v; + } + } else { + // Entirely in const V + time_const_a = 0.0; + } + + // Time in constant V + double time_const_v; + if (space != 0.0) { + if (m_set_point == 0.0) { + target_timer->reset(); + return; + } else { + time_const_v = space / m_set_point; + if (time_const_v < 0.0) { + target_timer->reset(); + return; + } + } + } else { + time_const_v = 0.0; + } + LOG_DBG("TTD %.6f %.6f\n" , time_const_a , time_const_v); + + target_timer->adjust(attotime::from_double(time_const_a + time_const_v)); +} + +void hp_dc100_tape_device::adjust_tacho_timer() +{ + auto tick_fract = m_tape_pos % m_tick_size; + hti_format_t::tape_pos_t dist_to_next; + if (is_moving_fwd()) { + // Distance to next tick in the fwd direction + dist_to_next = m_tick_size - tick_fract; + } else if (tick_fract) { + // Distance to next tick in the rev direction when we're not exactly + // on a tick (tick_fract != 0) + dist_to_next = -tick_fract; + } else { + // Distance to next tick in the rev direction when we're exactly + // on a tick (tick_fract == 0) + dist_to_next = -m_tick_size; + } + LOG_DBG("Next tick @%d (pos=%d)\n" , dist_to_next , m_tape_pos); + time_to_distance(dist_to_next, m_next_tacho_pos, m_tacho_timer); +} + +void hp_dc100_tape_device::adjust_hole_timer() +{ + auto hole_pos = m_image.next_hole(m_tape_pos , is_moving_fwd()); + if (hole_pos == hti_format_t::NULL_TAPE_POS) { + m_hole_timer->reset(); + } else { + time_to_distance(hole_pos - m_tape_pos, m_next_hole_pos, m_hole_timer); + } +} + +void hp_dc100_tape_device::stop_op() +{ + if (m_current_op == OP_WRITE) { + store_wr_word(); + } else if (m_current_op == OP_ERASE) { + LOG_DBG("Wr gap from %d to %d\n" , m_rw_pos , m_tape_pos); + m_image.write_gap(get_track_no() , m_rw_pos , m_tape_pos); + m_image_dirty = true; + } + m_bit_timer->reset(); + m_current_op = OP_IDLE; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; +} + +void hp_dc100_tape_device::load_rd_word() +{ + if (m_rd_it_valid) { + bool fwd = is_moving_fwd(); + if (fwd) { + m_bit_idx = 15; + } else { + m_bit_idx = 0; + } + // This is actually the nearest end (dir is inverted) + m_rw_pos = m_next_bit_pos = hti_format_t::farthest_end(m_rd_it , !fwd); + // Compute end of bit cell + hti_format_t::tape_pos_t bit_len = m_image.bit_length(BIT(m_rd_it->second , m_bit_idx)); + if (!fwd) { + bit_len = -bit_len; + } + time_to_distance(m_next_bit_pos + bit_len - m_tape_pos , m_next_bit_pos , m_bit_timer); + LOG_RW("RD %04x @%d\n" , m_rd_it->second , m_next_bit_pos); + } else { + LOG_RW("End of RD data @%d\n" , m_tape_pos); + stop_op(); + m_gap_detect_start = m_tape_pos; + } +} + +void hp_dc100_tape_device::store_wr_word() +{ + bool fwd = is_moving_fwd(); + if ((fwd && m_bit_idx == 15) || + (!fwd && m_bit_idx == 0)) { + return; + } + hti_format_t::tape_pos_t word_length = m_image.word_length(m_rw_word); + if (!fwd) { + m_rw_pos -= word_length; + } + LOG_RW("WR %04x @%d\n" , m_rw_word , m_rw_pos); + m_image.write_word(get_track_no() , m_rw_pos , m_rw_word , word_length , fwd); + m_image_dirty = true; + m_rw_word = 0; + if (fwd) { + m_bit_idx = 15; + m_rw_pos += word_length; + } else { + m_bit_idx = 0; + } +} diff --git a/src/devices/machine/hp_dc100_tape.h b/src/devices/machine/hp_dc100_tape.h new file mode 100644 index 00000000000..191cd8b6ed2 --- /dev/null +++ b/src/devices/machine/hp_dc100_tape.h @@ -0,0 +1,178 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp_dc100_tape.h + + HP DC100 tape cartridge & drive + +*********************************************************************/ + +#ifndef MAME_MACHINE_HP_DC100_TAPE_H +#define MAME_MACHINE_HP_DC100_TAPE_H + +#pragma once + +#include "formats/hti_tape.h" + +class hp_dc100_tape_device : public device_t, + public device_image_interface +{ +public: + // Construction + hp_dc100_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + // device_image_interface overrides + virtual image_init_result call_load() override; + virtual image_init_result call_create(int format_type, util::option_resolution *format_options) override; + virtual void call_unload() override; + virtual std::string 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; } + virtual bool is_creatable() const override { return true; } + virtual bool must_be_loaded() const override { return false; } + virtual bool is_reset_on_load() const override { return false; } + virtual const char *file_extensions() const override; + + // **** Units **** + // Tape position or distance 1/(968 * 1024) in (see hti_format_t::tape_pos_t) + // Speed in/s + // Acceleration in/s^2 + + // Settings + void set_acceleration(double accel); + void set_set_points(double slow_sp , double fast_sp); + void set_tick_size(hti_format_t::tape_pos_t size); + void set_bits_per_word(unsigned bits); + void set_go_threshold(double threshold); + + // Commands + void set_track_no(unsigned track); + + enum tape_speed_t { + SP_STOP, // Stop + SP_SLOW, // Slow speed + SP_FAST // Fast speed + }; + bool set_speed_setpoint(tape_speed_t speed , bool fwd); + + enum tape_op_t { + OP_IDLE, // No R/W + OP_READ, // Read from tape + OP_WRITE, // Write to tape + OP_ERASE // Erase tape + }; + void set_op(tape_op_t op , bool force = false); + + void update_speed_pos(); + + // State access + hti_format_t::tape_pos_t get_pos() const { return m_tape_pos; } + hti_format_t::tape_pos_t get_approx_pos() const; + double get_speed() const { return m_speed; } + double get_speed_setpoint() const { return m_set_point; } + unsigned get_track_no() const { return m_track; } + tape_op_t get_op() const { return m_current_op; } + bool has_started() const { return !m_start_time.is_never(); } + bool is_moving() const { return fabs(m_speed) >= m_go_threshold; } + bool is_accelerating() const { return m_accelerating; } + bool is_moving_fwd() const { return m_speed == 0.0 ? m_set_point >= 0.0 : m_speed > 0.0; } + bool cart_out_r() const { return !m_present; } + bool wpr_r() const { return !m_present || is_readonly(); } + bool is_above_threshold() const { return fabs(m_speed) >= m_slow_set_point; } + bool gap_reached(hti_format_t::tape_pos_t min_gap_size); + + void time_to_next_gap(hti_format_t::tape_pos_t min_gap_size , bool new_gap , emu_timer *target_timer); + + // Callbacks + // Cartridge is out (1) + auto cart_out() { return m_cart_out_handler.bind(); } + // On a hole (1) + auto hole() { return m_hole_handler.bind(); } + // Tachometer tick (1) + auto tacho_tick() { return m_tacho_tick_handler.bind(); } + // Motion event (1) + // One of the following: + // 1. "In motion" threshold crossed + // 2. Slow speed threshold crossed + // 3. Accelerated phase ended (== set point reached) + auto motion_event() { return m_motion_handler.bind(); } + // Read bit + auto rd_bit() { return m_rd_bit_handler.bind(); } + // Bit to write + auto wr_bit() { return m_wr_bit_handler.bind(); } + +protected: + // device-level overrides + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + +private: + devcb_write_line m_cart_out_handler; + devcb_write_line m_hole_handler; + devcb_write_line m_tacho_tick_handler; + devcb_write_line m_motion_handler; + devcb_write_line m_rd_bit_handler; + devcb_read_line m_wr_bit_handler; + + // Settings + double m_acceleration; + double m_slow_set_point; + double m_fast_set_point; + hti_format_t::tape_pos_t m_tick_size; + double m_go_threshold; + + // State + hti_format_t::tape_pos_t m_tape_pos; + tape_speed_t m_tape_speed; + double m_set_point; + double m_speed; + attotime m_start_time; // Tape moving if != never + bool m_accelerating; + unsigned m_track; + tape_op_t m_current_op; + bool m_in_set_op; + bool m_present; + hti_format_t::track_iterator_t m_rd_it; + bool m_rd_it_valid; + hti_format_t::tape_word_t m_rw_word; + int m_bit_idx; + hti_format_t::tape_pos_t m_rw_pos; // Start position of current R/W word or start position of gap when erasing + hti_format_t::tape_pos_t m_gap_detect_start; + + // Timers + emu_timer *m_bit_timer; + emu_timer *m_tacho_timer; + emu_timer *m_hole_timer; + emu_timer *m_motion_timer; + hti_format_t::tape_pos_t m_next_bit_pos; + hti_format_t::tape_pos_t m_next_tacho_pos; + hti_format_t::tape_pos_t m_next_hole_pos; + + // Image of tape + hti_format_t m_image; + bool m_image_dirty; + + void clear_state(); + image_init_result internal_load(bool is_create); + void set_tape_present(bool present); + double compute_set_point(tape_speed_t speed , bool fwd) const; + void start_tape(); + void stop_tape(); + double const_a_space(double a , double t) const; + attotime time_to_threshold(double threshold , bool zero_allowed) const; + void set_motion_timer(); + void time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const; + void adjust_tacho_timer(); + void adjust_hole_timer(); + void stop_op(); + void load_rd_word(); + void store_wr_word(); +}; + +// device type definition +DECLARE_DEVICE_TYPE(HP_DC100_TAPE, hp_dc100_tape_device) + +#endif // MAME_MACHINE_HP_DC100_TAPE_H diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 1a375a7accd..9c3edf16fc4 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -8,28 +8,37 @@ *********************************************************************/ -// This device has been reverse engineered entirely through documents & study of HP software. -// I had no access to the real device to experiment. -// Available documentation on the internal working of TACO chip is close to nothing. The best -// I could find is [1] (see below) where all that's described is a (too) brief summary of registers and little else. -// In other words, no description of the commands that can be issued to TACO chips. -// So, my main source of information was the careful study of HP software, especially the 9845 system test ROM (09845-66520). -// The second half of this ROM holds a comprehensive set of tape drive tests. -// Another source was the "SIF" utility tools which use a lot of peculiar command sequences. -// The main shortcomings of my approach are: -// * I could identify only those TACO commands that are actually used by the software. I managed -// to identify 17 out of 32 possible commands. The purpose of the rest of commands is anyone's guess. -// * I could only guess the behavior of TACO chips in corner cases (especially behavior in various error/abnormal -// conditions) -// +// This a complete re-write of my previous driver of TACO chip. +// The old driver was entirely based on reverse-engineering of the chip as very little +// documentation was available. Then, in late 2018, a HP internal doc on TACO +// was published by Dyke Shaffer on https://groups.io/g/VintHPcom. This doc, of course, +// was a total game changer for what concerns the TACO emulation for various reasons: +// it comes from HP, it is very detailed and it appears to be an exhaustive description +// of the behavior of the chip. +// It was time to throw out my mostly-correct, hard-won reverse engineered driver and +// re-write it. + // Documentation I used: -// [1] HP, manual 64940-90905, may 80 rev. - Model 64940A tape control & drive service manual -// [2] US patent 4,075,679 describing HP9825 system (this system had a discrete implementation of tape controller). The -// firmware listing was quite useful in identifying sequences of commands (for example how to find a specific sector etc.). -// [3] http://www.hp9845.net site -// [4] April 1978 issue of HP Journal. There is a one-page summary of TACO chip on page 20. -// [5] HP, manual 09845-10201, apr 81 rev. - General Utility Routines. This manual describes the SIF format and related -// utility tools. +// [1] HP, TACO external reference specification, dec 76: *THE* document about TACO +// [2] HP, manual 09845-10201, apr 81 rev. - General Utility Routines. This manual +// describes the SIF format and related utility tools. + +// What's in the new driver: +// - All commands used by test ROM, by system firmware and by SIF utilities are implemented +// according to flow charts in [1] +// - Handling of error conditions, R/W bits, tachometer ticks and gaps according to [1] +// What's not in: +// - Commands that are not used at all by the software I analyzed, especially those that +// R/W tapes in HP9825 format. They could be added easily, though. +// - Accurate execution times of commands +// - Handling of FLG signal: the emulated chip always reports itself as ready for writing. +// - Read threshold is ignored. Real tapes could be read with either a low or high threshold. +// Where I filled the gaps in [1]: +// - Command 0D (001101) is not documented at all but it's used by test ROM and system fw. +// I carried this command over from my old TACO driver: it's implemented as NOP + IRQ. +// - [1] is not very clear about the gap size that's used to detect the so-called "gap in read" +// error condition. I use a 0.132" gap (GIR_GAP_LENGTH) because it's the minimum that +// allows the SIF utilities to run correctly. // This is an overview of the TACO/CPU interface. // @@ -37,23 +46,21 @@ // ===================== // 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 +// R6 | R/W | Tachometer register. Writing it sets a pulse counter that counts up on either tachometer pulses or gaps, depending // | | on command. When the counter rolls over from 0xffff to 0 it typically ends the command. // | | Current counter value is returned when reading from this register. // R7 | R | Checksum register. Reading it clears it next time the checksum is updated. -// R7 | W | Timing register. It controls somehow the encoding and decoding of bits. For now I completely ignore it because its -// | | content it's totally unknown to me. It seems safe to do so, anyway. I can see that it's always set to 0x661d before -// | | writing to tape and to 0x0635 before reading (0x061d is set by SIF utilities). +// R7 | W | Threshold register. It sets the duration of bits when reading and writing. It +// | | also controls the pre-compensation when writing. This driver ignores it. // // Format of TACO command/status register (R5) // Bit R/W Content // =============== // 15 RW Tape direction (1 = forward) -// 14..10 RW Command (see the "enum" below) -// 9 RW Automatic stopping of tape at command completion (when 1) -// 8 RW Minimum size of gaps (1 = 1.5", 0 = 0.066") +// 14..9 RW Command (see the "enum" below) +// 8 RW Minimum size of gaps (1 = 1.5", 0 = 0.017") // 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). +// 6 RW MOD bit. Most of the commands 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) @@ -63,1296 +70,1048 @@ // Here's a summary of the on-tape format of HP9845 systems. // * A tape has two independent tracks (A & B). -// * Each track holds 426 sectors. -// * Each sector has an header and 256 bytes of payload (see below) -// * Sectors are separated by gaps of uniform magnetization called IRGs (Inter-Record Gaps) +// * Each track holds 426 records. +// * Each record has an header and 256 bytes of payload (see below) +// * Records are separated by gaps of uniform magnetization called IRG (Inter-Record Gap) +// or IFG (Inter-File Gap), depending on the length. In HP9845 all records are separated +// by IRGs except records 0 & 1, which are separated by 1 IFG. // * The basic unit of data I/O are 16-bit words // * Bits are encoded by different distances between magnetic flux reversals // * The structure of tracks is: // - Begin of tape holes // - The deadzone: 350x 0xffff words // - 1" of IRG -// - Sector #0 (track A) or #426 (track B) -// - 1" of IRG (2.5" on track A) -// - Sector #1 (track A) or #427 (track B) +// - Record #0 (track A) or #426 (track B) +// - 1" of IRG (2.5" of IFG on track A) +// - Record #1 (track A) or #427 (track B) // - 1" of IRG -// - Sector #2 (track A) or #428 (track B) -// - ...and so on up to sector #425/#851 -// - 6" of final gap -// - Non-recorded tape +// - Record #2 (track A) or #428 (track B) +// - ...and so on up to record #425/#851 +// - EVD gap (End of Valid Data): at least 6" // - End of tape holes // * Even though the tape format is not SIF (HP's own Standard Interchange Format), it is // clearly based on SIF itself. The whole tape content is stored according to SIF, // specifically it is "encapsulated" inside file #1. -// * Sector #0 is not used. It serves as SIF "file identifier record" (i.e. it identifies -// that the rest of tape is inside file #1 from SIF point of view). -// * Sectors #1 and #2 hold the first copy of tape directory -// * Sectors #3 and #4 hold the second/backup copy of tape directory -// * User data are stored starting from sector #5 +// * Record #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). The IFG between +// record #0 and #1 is placed according to SIF specification. +// * Records #1 and #2 hold the first copy of tape directory +// * Records #3 and #4 hold the second/backup copy of tape directory +// * User data are stored starting from record #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. +// spanning more than 1 record always occupy a single block of contiguous records. // -// 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. +// A record is structured like this (see description of SIF in [2], pg 655 and following): +// Word 0: Invisible preamble word: it's made of a string of 0s terminated by a single 1. +// It can be longer than 16 bits. This word is used to synchronize with word +// boundary. It's always written as 0x0001. Record re-writing appears to add +// a few 0s at the beginning so that preamble looks longer than 16 bits. +// This is caused by re-writing starting just after reading a few bits of the +// previous preamble in order to synchronize with record. This driver typically +// leaves 2 bits of the previous preamble so that the new one seems to be 18 +// bit long (17x 0s and a 1). // 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 2: Record word: record number and "free field pattern". +// Free field pattern is 4-bit long and it's used as a kind of source +// ID. As far as I can tell, HP9845 systems set it to 0, HP85 sets it to 1. +// Word 3: Length word: bytes available and used in record // Word 4: Checksum (sum of words 1..3) // Words 5..132: Payload // Word 133: Checksum (sum of words 5..132) // -// Physical encoding of words is borrowed from 9825 as I wasn't able -// to gather any info on the actual encoding of TACO chips. -// This is how 9825 encodes words on tape: +// This is how TACO encodes words on tape: // - the unit of encoding are 16-bit words // - each word is encoded from MSB to LSB -// - each word has an extra invisible "1" encoded at the end (a kind of "stop" bit) // - tape is read/written at slow speed only (21.98 ips) // - a 0 is encoded with a distance between flux reversals of 1/35200 s // (giving a maximum density of about 1600 reversals per inch) // - a 1 is encoded with a distance that's 1.75 times that of a 0 +// - when reading, word boundary is recovered by looking for the "1" that terminates the +// preamble (see above) // -// This driver is based on the following model of the actual TACO/tape system: -// * Tape immediately reaches working speed (no spin-up time) -// * Change of speed is immediate as well -// * Time & distance to stop and invert direction of tape are modeled, though. Firmware is upset by -// a tape with null braking/inversion time/distance. -// * Speed of tape is exceptionally accurate. Real tape was controlled by a closed loop -// with something like 1% accuracy on speed. -// * Storage is modeled by one "map" data structure per track. Each map maps the tape position -// to the 16-bit word stored at that position. Gaps are modeled by lack of data in the map. -// There is no model of the physical encoding of bits (except to compute how long each word -// is on tape). -// * Read threshold is ignored. Real tapes could be read with either a low or high threshold. -// * "Flag" bit is used as a busy/ready signal in real TACO. Here I assumed the device is -// always ready, so Flag is always active. -// * I tried to fill the (many) gaps on chip behavior with "sensible" solutions. I could only -// validate my solutions by running the original firmware in MAME, though (no real hw at hand). +// HP9825 encodes words in a slightly different way: each word has a 17th "1" at the end. +// This is added to gain some time to process read words. It carries no information and +// it's discarded when reading the tape. TACO has a special version of read & write commands +// to handle HP9825 encoding. // -// TODOs/issues: -// * Commands 00 & 10 seem to do the same things. My bet is that they differ in some subtle way that -// is not stimulated by all the tape software I used for R.E. Maybe it's just the length of the -// no-data timeout they have. -// * Find more info on TACO chips (does anyone with a working 9845 or access to internal HP docs want to -// help me here, please?) +// Acknowledgments: +// Dyke Shaffer for publishing the HP-internal TACO document. // #include "emu.h" #include "hp_taco.h" -#include "ui/uimain.h" // Debugging -#define VERBOSE 1 -#define LOG(x) do { if (VERBOSE) logerror x; } while (0) -#define VERBOSE_0 0 -#define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) +#include "logmacro.h" +#define LOG_DBG_MASK (LOG_GENERAL << 1) +#define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__) +#define LOG_RW_MASK (LOG_DBG_MASK << 1) +#define LOG_RW(...) LOGMASKED(LOG_RW_MASK, __VA_ARGS__) +#define LOG_REG_MASK (LOG_RW_MASK << 1) +#define LOG_REG(...) LOGMASKED(LOG_REG_MASK, __VA_ARGS__) +#undef VERBOSE +//#define VERBOSE (LOG_GENERAL | LOG_DBG_MASK | LOG_RW_MASK | LOG_REG_MASK) +#define VERBOSE (LOG_GENERAL) + +// Bit manipulation +namespace { + template<typename T> constexpr T BIT_MASK(unsigned n) + { + return (T)1U << n; + } + + template<typename T> void BIT_CLR(T& w , unsigned n) + { + w &= ~BIT_MASK<T>(n); + } -// Macros to clear/set single bits -#define BIT_MASK(n) (1U << (n)) -#define BIT_CLR(w , n) ((w) &= ~BIT_MASK(n)) -#define BIT_SET(w , n) ((w) |= BIT_MASK(n)) + template<typename T> void BIT_SET(T& w , unsigned n) + { + w |= BIT_MASK<T>(n); + } +} // Timers enum { - TAPE_TMR_ID, - HOLE_TMR_ID, - TIMEOUT_TMR_ID + GAP_TMR_ID, + EVD_TMR_ID, + ERROR_TMR_ID }; -// Constants -#define CMD_REG_MASK 0xffc0 // Command register mask -#define STATUS_REG_MASK 0x003f // Status register mask -#define TACH_TICKS_PER_INCH 968 // Tachometer pulses per inch of tape movement -#define TAPE_POS_FRACT 1024 // 10 bits of fractional part in tape_pos_t -#define 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_INIT_POS (80 * hti_format_t::ONE_INCH_POS) // Initial tape position: 80" from beginning (just past the punched part) -#define QUICK_CMD_USEC 25 // usec for "quick" command execution (totally made up) -#define FAST_BRAKE_DIST 3350450 // Braking distance at fast speed (~3.38 in) -// There are 2 braking distances here: The first one (commented out) is the theoretical value, the second one -// is the value that works. Ideally we would always be using the first value but there is a kind of race -// condition when loading the memory test from the exerciser that freezes the system. So, the second (shorter) -// value is used to avoid this condition. -//#define SLOW_BRAKE_DIST 197883 // Braking distance at slow speed (~0.2 in) -#define SLOW_BRAKE_DIST 71000 // Braking distance at slow speed (~0.07 in) -#define PREAMBLE_WORD 0 // Value of preamble word -#define END_GAP_LENGTH (6 * hti_format_t::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 ((hti_format_t::tape_pos_t)(0.066 * hti_format_t::ONE_INCH_POS)) // Minimum length of short gaps: 0.066" ([1], pg 8-10) -#define LONG_GAP_LENGTH ((hti_format_t::tape_pos_t)(1.5 * hti_format_t::ONE_INCH_POS)) // Minimum length of long gaps: 1.5" ([1], pg 8-10) -#define PREAMBLE_TIMEOUT ((hti_format_t::tape_pos_t)(2.6 * hti_format_t::ONE_INCH_POS)) // Min. length of gap making preamble search time out (totally made up) -#define DATA_TIMEOUT ((hti_format_t::tape_pos_t)(0.066 * hti_format_t::ONE_INCH_POS)) // Min. length of gap that will cause data reading to time out (totally made up) - -// Parts of command register -#define CMD_CODE(reg) \ - (((reg) >> 10) & 0x1f) -#define DIR_FWD(reg) \ - (BIT(reg , 15)) -#define AUTO_STOP(reg) \ - (BIT(reg , 9)) -#define LONG_GAP(reg) \ - (BIT(reg , 8)) -#define SPEED_FAST(reg) \ - (BIT(reg , 7)) -#define CMD_OPT(reg) \ - (BIT(reg , 6)) -#define DIR_FWD_MASK BIT_MASK(15) // Direction = forward -#define SPEED_FAST_MASK BIT_MASK(7) // Speed = fast +// **** Constants **** +constexpr unsigned TACH_FREQ_FAST = 87196; // Tachometer pulse frequency for fast speed (90.08 ips) +constexpr unsigned TACH_FREQ_SLOW = 21276; // Tachometer pulse frequency for slow speed (21.98 ips) +constexpr unsigned TACH_TICKS_PER_INCH = 968; // Tachometer pulses per inch of tape movement +constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / TACH_TICKS_PER_INCH; // One tachometer tick every 1/968 of inch +constexpr double FAST_SPEED = double(TACH_FREQ_FAST) / double(TACH_TICKS_PER_INCH); // Fast speed: 90.08 ips +constexpr double SLOW_SPEED = double(TACH_FREQ_SLOW) / double(TACH_TICKS_PER_INCH); // Slow speed: 21.98 ips +constexpr double MOVING_THRESHOLD = 2.0; // Tape is moving when speed > 2.0 ips +constexpr double ACCELERATION = 1200.0; // Acceleration when speed set point is changed: 1200 ips^2 +constexpr uint16_t PREAMBLE_WORD = 0x0001; // Value of preamble word +constexpr unsigned ERROR_IRQ_PERIOD = 2048; // Clocks between repetitions of IRQ during error condition +constexpr hti_format_t::tape_pos_t SHORT_GAP_LENGTH = 16 * TACH_TICK_LEN; // Minimum length of short gaps: 0.017" +constexpr hti_format_t::tape_pos_t LONG_GAP_LENGTH = 1452 * TACH_TICK_LEN; // Minimum length of long gaps: 1.5" +constexpr hti_format_t::tape_pos_t EVD_GAP_LENGTH = 5548 * TACH_TICK_LEN; // End of valid data gap: 5.73" +constexpr hti_format_t::tape_pos_t GIR_GAP_LENGTH = 128 * TACH_TICK_LEN; // Minimum length of gaps for Gap In Read error: 0.132" (made up) + +// Bits in command/status register +enum cmd_status_bits : unsigned { + CMD_ST_FWD = 15, // Forward direction (1) + CMD_ST_G5 = 14, // Command bit G5 + CMD_ST_G4 = 13, // Command bit G4 + CMD_ST_G3 = 12, // Command bit G3 + CMD_ST_G2 = 11, // Command bit G2 + CMD_ST_G1 = 10, // Command bit G1 + CMD_ST_G0 = 9, // Command bit G0 + CMD_ST_FGAP = 8, // File gap (1) + CMD_ST_FST = 7, // Fast speed (1) + CMD_ST_MOD = 6, // MOD bit + CMD_ST_TRB = 5, // Track B (1) + CMD_ST_GAP = 4, // Gap detected (1) + CMD_ST_WPR = 3, // Write protection (1) + CMD_ST_ESTS = 2, // Servo failure (1) + CMD_ST_CART_OUT = 1,// Cartridge out (1) + CMD_ST_HOLE = 0 // Hole detected (1) +}; + +// Command register mask +constexpr uint16_t CMD_REG_MASK = + BIT_MASK<uint16_t>(CMD_ST_FWD) | + BIT_MASK<uint16_t>(CMD_ST_G5) | + BIT_MASK<uint16_t>(CMD_ST_G4) | + BIT_MASK<uint16_t>(CMD_ST_G3) | + BIT_MASK<uint16_t>(CMD_ST_G2) | + BIT_MASK<uint16_t>(CMD_ST_G1) | + BIT_MASK<uint16_t>(CMD_ST_G0) | + BIT_MASK<uint16_t>(CMD_ST_FGAP) | + BIT_MASK<uint16_t>(CMD_ST_FST) | + BIT_MASK<uint16_t>(CMD_ST_MOD); + +// Status register mask +constexpr uint16_t STATUS_REG_MASK = + BIT_MASK<uint16_t>(CMD_ST_TRB) | + BIT_MASK<uint16_t>(CMD_ST_GAP) | + BIT_MASK<uint16_t>(CMD_ST_WPR) | + BIT_MASK<uint16_t>(CMD_ST_ESTS) | + BIT_MASK<uint16_t>(CMD_ST_CART_OUT) | + BIT_MASK<uint16_t>(CMD_ST_HOLE); // Commands -enum { - CMD_INDTA_INGAP, // 00: scan for data first then for gap (see also cmd 10) - CMD_UNK_01, // 01: unknown - CMD_FINAL_GAP, // 02: write final gap - CMD_INIT_WRITE, // 03: write words for tape formatting - CMD_STOP, // 04: stop - CMD_UNK_05, // 05: unknown - CMD_SET_TRACK, // 06: set A/B track - CMD_UNK_07, // 07: unknown - CMD_UNK_08, // 08: unknown - CMD_UNK_09, // 09: unknown - CMD_MOVE, // 0a: move tape - CMD_UNK_0b, // 0b: unknown - CMD_INGAP_MOVE, // 0c: scan for gap then move a bit further (used to gain some margin when inverting tape movement) - CMD_UNK_0d, // 0d: unknown - CMD_CLEAR, // 0e: clear errors/unlatch status bits - CMD_UNK_0f, // 0f: unknown - CMD_NOT_INDTA, // 10: scan for end of data (at the moment it's the same as cmd 00) - CMD_UNK_11, // 11: unknown - CMD_UNK_12, // 12: unknown - CMD_UNK_13, // 13: unknown - CMD_UNK_14, // 14: unknown - CMD_UNK_15, // 15: unknown - CMD_WRITE_IRG, // 16: write inter-record gap - CMD_UNK_17, // 17: unknown - CMD_SCAN_RECORDS, // 18: scan records (count IRGs) - CMD_RECORD_WRITE, // 19: write record words - CMD_MOVE_INDTA, // 1a: move then scan for data - CMD_UNK_1b, // 1b: unknown (for now it seems harmless to handle it as NOP) - CMD_MOVE_INGAP, // 1c: move tape a given distance then scan for gap (as cmd 0c but in reverse order) - CMD_START_READ, // 1d: start record reading - CMD_DELTA_MOVE_IRG, // 1e: move tape a given distance, detect gaps in parallel - CMD_END_READ // 1f: stop reading +enum cmd_t : uint8_t { + // GGGGGG + // 543210 * = not emulated + CMD_INT_ON_GAP = 0b000000, // Interrupt on new gap + CMD_ERASE = 0b000100, // Erase tape + CMD_WR_REVERSAL = 0b000101, // Write flux reversal (*) + CMD_WR = 0b000110, // Write + CMD_WR_9825 = 0b000111, // Write 9825 (*) + CMD_STOP = 0b001000, // Stop + CMD_STOP_INT = 0b001001, // Stop & interrupt + CMD_SET_TRACK = 0b001100, // Set track + CMD_UNK_0D = 0b001101, // Unknown (looks like a kind of NOP + IRQ) + CMD_MOVE = 0b010100, // Move tape + CMD_OPP_DIR_N_TACH = 0b011000, // Opposite direction on N tach + CMD_RD_12UPD = 0b011010, // Read 12% update (*) + CMD_RD_9825_12UPD = 0b011011, // Read 9825 12% update (*) + CMD_CLEAR_ST = 0b011100, // Clear status + CMD_RD_CSUM_12UPD = 0b011110, // Read checksum 12% update (*) + CMD_RD_9825_CSUM12 = 0b011111, // Read checksum 9825 12% update (*) + CMD_STOP_IN_GAP = 0b100000, // Stop in gap + CMD_STOP_IN_GAP1 = 0b100001, // Stop in gap (int when stopped) + CMD_RD_NO_UPDATE = 0b101010, // Read no update (*) + CMD_RD_9815 = 0b101011, // Read 9815 (*) + CMD_WR_GAP_N_TACH = 0b101100, // Write gap of N tach + CMD_INT_N_GAP = 0b110000, // Interrupt on N gap + CMD_WR_SYNC = 0b110010, // Write synchronous + CMD_WR_SYNC_9825 = 0b110011, // Write synchronous 9825 (*) + CMD_INT_N_TACH_22 = 0b110100, // Interrupt on N tach after 22 ips + CMD_WR_CSUM = 0b110110, // Write checksum + CMD_WR_CSUM_9825 = 0b110111, // Write checksum 9825 (*) + CMD_INT_STOP_N_TACH = 0b111000, // Interrupt and stop on N tach + CMD_INT_STOP_N_TACH1= 0b111001, // Interrupt and stop on N tach (int when stopped) + CMD_RD_6UPD = 0b111010, // Read 6% update + CMD_RD_9825_6UPD = 0b111011, // Read 9825 6% update (*) + CMD_INT_N_TACH = 0b111100, // Interrupt on N tach + CMD_RD_CSUM_6UPD = 0b111110, // Read checksum 6% update + CMD_RD_9825_CSUM6 = 0b111111 // Read checksum 9825 6% update (*) }; -// Bits of status register -#define STATUS_HOLE_BIT 0 // Hole detected -#define STATUS_CART_OUT_BIT 1 // Cartridge out -#define STATUS_SFAIL_BIT 2 // Servo failure -#define STATUS_WPR_BIT 3 // Write protection -#define STATUS_GAP_BIT 4 // Gap detected -#define STATUS_TRACKB_BIT 5 // Track B selected -#define STATUS_CART_OUT_MASK BIT_MASK(STATUS_CART_OUT_BIT) // Cartridge out -#define STATUS_WPR_MASK BIT_MASK(STATUS_WPR_BIT) // Write protection -#define STATUS_ERR_MASK (STATUS_CART_OUT_MASK) // Mask of errors in status reg. - // Device type definition DEFINE_DEVICE_TYPE(HP_TACO, hp_taco_device, "hp_taco", "HP TACO") // Constructors hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) - : device_t(mconfig, type, tag, owner, clock), - device_image_interface(mconfig , *this), - m_irq_handler(*this), - m_flg_handler(*this), - m_sts_handler(*this), - m_image(), - m_image_dirty(false) + : device_t(mconfig, type, tag, owner, clock) + , m_tape(*this , "drive") + , m_irq_handler(*this) + , m_flg_handler(*this) + , m_sts_handler(*this) { - clear_state(); } hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : hp_taco_device(mconfig, HP_TACO, tag, owner, clock) + : hp_taco_device(mconfig, HP_TACO, tag, owner, clock) { } WRITE16_MEMBER(hp_taco_device::reg_w) { - LOG_0(("wr R%u = %04x\n", 4 + offset , data)); - - // Any I/O activity clears IRQ - irq_w(false); - - switch (offset) { - case 0: - // Data register - m_data_reg = data; - m_data_reg_full = true; - break; - - case 1: - // Command register - start_cmd_exec(data & CMD_REG_MASK); - break; - - case 2: - // Tachometer register - m_tach_reg = data; - freeze_tach_reg(true); - break; - - case 3: - // Timing register - m_timing_reg = data; - break; - } + LOG_REG("wr R%u = %04x\n", 4 + offset , data); + + // Any I/O activity clears IRQ + irq_w(false); + + switch (offset) { + case 0: + // Data register + m_data_reg = data; + break; + + case 1: + // Command register + start_cmd_exec(data & CMD_REG_MASK); + break; + + case 2: + // Tachometer register + m_tach_reg = data; + break; + + case 3: + // Threshold register + m_threshold_reg = data; + break; + } } READ16_MEMBER(hp_taco_device::reg_r) { - uint16_t res = 0; + uint16_t res = 0; - // Any I/O activity clears IRQ - irq_w(false); + // Any I/O activity clears IRQ + irq_w(false); - switch (offset) { - case 0: - // Data register - res = m_data_reg; - break; + switch (offset) { + case 0: + // Data register + res = m_data_reg; + break; - case 1: - // Command & status register - res = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK); - break; + case 1: + // Command & status register + if (m_tape->gap_reached(SHORT_GAP_LENGTH)) { + BIT_SET(m_status_reg , CMD_ST_GAP); + } else { + BIT_CLR(m_status_reg , CMD_ST_GAP); + } + res = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK); + break; - case 2: - // Tachometer register - update_tach_reg(); - res = m_tach_reg; - break; + case 2: + // Tachometer register + res = m_tach_reg; + break; - case 3: - // Checksum register: it clears when read - res = m_checksum_reg; - m_clear_checksum_reg = true; - break; - } + case 3: + // Checksum register: it clears when read + res = m_checksum_reg; + m_checksum_reg = 0; + break; + } - LOG_0(("rd R%u = %04x\n", 4 + offset , res)); + LOG_REG("rd R%u = %04x\n", 4 + offset , res); - return res; + return res; } READ_LINE_MEMBER(hp_taco_device::flg_r) { - return m_flg; + return m_flg; } READ_LINE_MEMBER(hp_taco_device::sts_r) { - return m_sts; + return m_sts; } -// device_start -void hp_taco_device::device_start() -{ - LOG(("device_start")); - m_irq_handler.resolve_safe(); - m_flg_handler.resolve_safe(); - m_sts_handler.resolve_safe(); - - save_item(NAME(m_data_reg)); - save_item(NAME(m_data_reg_full)); - save_item(NAME(m_cmd_reg)); - //save_item(NAME(m_cmd_state)); - save_item(NAME(m_status_reg)); - save_item(NAME(m_tach_reg)); - save_item(NAME(m_tach_reg_ref)); - save_item(NAME(m_tach_reg_frozen)); - save_item(NAME(m_checksum_reg)); - save_item(NAME(m_clear_checksum_reg)); - save_item(NAME(m_timing_reg)); - save_item(NAME(m_irq)); - save_item(NAME(m_flg)); - save_item(NAME(m_sts)); - save_item(NAME(m_tape_pos)); - save_item(NAME(m_start_time)); - save_item(NAME(m_tape_fwd)); - save_item(NAME(m_tape_fast)); - save_item(NAME(m_image_dirty)); - save_item(NAME(m_tape_wr)); - save_item(NAME(m_rw_pos)); - save_item(NAME(m_next_word)); - save_item(NAME(m_rd_it_valid)); - save_item(NAME(m_gap_detect_start)); - - m_tape_timer = timer_alloc(TAPE_TMR_ID); - m_hole_timer = timer_alloc(HOLE_TMR_ID); - m_timeout_timer = timer_alloc(TIMEOUT_TMR_ID); -} - -// device_stop -void hp_taco_device::device_stop() +WRITE_LINE_MEMBER(hp_taco_device::cart_out_w) { + LOG_DBG("cart_out_w %d\n" , state); + set_tape_present(!state); + if (state && m_cmd_state != CMD_IDLE) { + set_error(true , false); + m_cmd_state = CMD_IDLE; + } } -// device_reset -void hp_taco_device::device_reset() +WRITE_LINE_MEMBER(hp_taco_device::hole_w) { - LOG(("device_reset")); - clear_state(); - - m_irq = false; - m_flg = true; - - m_irq_handler(false); - m_flg_handler(true); - set_error(false); - - m_tape_timer->reset(); - m_hole_timer->reset(); - m_timeout_timer->reset(); + if (state) { + LOG_DBG("hole_w\n"); + if (m_cmd_state != CMD_IDLE && m_cmd_state != CMD_STOPPING && + (!is_double_hole_cmd(m_cmd_reg) || BIT(m_status_reg , CMD_ST_HOLE))) { + set_error(true , false); + } + BIT_SET(m_status_reg , CMD_ST_HOLE); + } } -void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +WRITE_LINE_MEMBER(hp_taco_device::tacho_tick_w) { - update_tape_pos(); - - switch (id) { - case TAPE_TMR_ID: - LOG_0(("Tape tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); - - cmd_fsm(); + if (state) { + LOG_DBG("tacho_tick_w\n"); + if (m_cmd_state == CMD_STOPPING) { + m_tach_reg++; + } else if (m_cmd_state != CMD_IDLE) { + switch (get_cmd(m_cmd_reg)) { + case CMD_OPP_DIR_N_TACH: + if (m_cmd_state == CMD_PH0) { + m_tach_reg++; + if (m_tach_reg == 0) { + // Toggle FWD bit + m_cmd_reg ^= BIT_MASK<uint16_t>(CMD_ST_FWD); + send_go(); + m_cmd_state = CMD_PH1; + } + } break; - case HOLE_TMR_ID: - LOG_0(("Hole tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); - - BIT_SET(m_status_reg , STATUS_HOLE_BIT); - - if (m_cmd_state >= CMD_PH0 && m_cmd_state <= CMD_END) { - switch (CMD_CODE(m_cmd_reg)) { - case CMD_FINAL_GAP: - case CMD_WRITE_IRG: - m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); - m_rw_pos = m_tape_pos; - break; - - case CMD_INIT_WRITE: - case CMD_RECORD_WRITE: - case CMD_MOVE_INGAP: - m_hole_timer->adjust(time_to_next_hole()); - // No IRQ at holes - return; - - case CMD_DELTA_MOVE_IRG: - // Hit hole before end of programmed pulses - terminate_cmd_now(); - update_tach_reg(); - freeze_tach_reg(true); - return; - - case CMD_INDTA_INGAP: - case CMD_INGAP_MOVE: - case CMD_NOT_INDTA: - case CMD_SCAN_RECORDS: - // Commands are terminated at first hole (and failure is reported) - terminate_cmd_now(); - set_error(true); - return; - - case CMD_START_READ: - case CMD_END_READ: - // Commands report failure at first hole - set_error(true); - break; - - default: - // Other cmds: default processing (update tape pos, set IRQ, schedule timer for next hole) - break; + case CMD_STOP_IN_GAP: + case CMD_STOP_IN_GAP1: + case CMD_INT_STOP_N_TACH: + case CMD_INT_STOP_N_TACH1: + if (m_cmd_state == CMD_PH2) { + m_tach_reg++; + if (m_tach_reg == 0) { + if (!BIT(m_cmd_reg , CMD_ST_G0)) { + irq_w(true); } - - irq_w(true); + send_stop(); + } } - m_hole_timer->adjust(time_to_next_hole()); break; - case TIMEOUT_TMR_ID: - LOG_0(("T/O tmr @%g cmd %02x st %d\n" , machine().time().as_double() , CMD_CODE(m_cmd_reg) , m_cmd_state)); - switch (CMD_CODE(m_cmd_reg)) { - case CMD_START_READ: - if (m_cmd_state == CMD_PH1) { - irq_w(true); + case CMD_INT_N_TACH_22: + if (m_cmd_state != CMD_PH1) { + break; + } + // Falls through! + + case CMD_WR_GAP_N_TACH: + case CMD_INT_N_TACH: + m_tach_reg++; + if (m_tach_reg == 0) { + irq_and_end(); } break; default: - // Most commands are terminated with failure on data T/O - terminate_cmd_now(); break; } - set_error(true); - break; - - default: - break; } + } } -void hp_taco_device::clear_state(void) +WRITE_LINE_MEMBER(hp_taco_device::motion_w) { - m_data_reg = 0; - m_data_reg_full = false; - m_cmd_reg = 0; - m_status_reg = 0; - m_tach_reg = 0; - m_tach_reg_ref = m_tape_pos; - m_tach_reg_frozen = true; - m_checksum_reg = 0; - m_clear_checksum_reg = false; - m_timing_reg = 0; - m_cmd_state = CMD_IDLE; - m_tape_pos = TAPE_INIT_POS; - m_start_time = attotime::never; - m_tape_fwd = false; - m_tape_fast = false; - // m_image_dirty is not touched - m_tape_wr = false; - m_rw_pos = 0; - m_next_word = 0; - m_rd_it_valid = false; - m_gap_detect_start = hti_format_t::NULL_TAPE_POS; - - set_tape_present(false); - set_tape_present(is_loaded()); + if (state) { + cmd_fsm(); + } } -void hp_taco_device::irq_w(bool state) +WRITE_LINE_MEMBER(hp_taco_device::rd_bit_w) { - if (state != m_irq) { - m_irq = state; - m_irq_handler(state); - LOG_0(("IRQ = %d\n" , state)); + LOG_RW("RD bit %d (st=%d,w=%04x,i=%u)\n" , state , m_cmd_state , m_working_reg , m_bit_idx); + if (m_cmd_state != CMD_IDLE) { + switch (get_cmd(m_cmd_reg)) { + case CMD_RD_6UPD: + case CMD_RD_CSUM_6UPD: + if (m_cmd_state == CMD_PH1) { + if (m_bit_idx < 8) { + m_bit_idx++; + } else if (state) { + // Synchronized + LOG_RW("RD synced!\n"); + m_cmd_state = CMD_PH2; + m_bit_idx = 15; + m_working_reg = 0; + } + } else if (m_cmd_state == CMD_PH2) { + if (state) { + BIT_SET(m_working_reg , m_bit_idx); + } + if (m_bit_idx) { + m_bit_idx--; + } else { + m_data_reg = m_working_reg; + m_bit_idx = 15; + m_working_reg = 0; + if (!BIT(m_cmd_reg , CMD_ST_G2)) { + update_checksum(m_data_reg); + } + LOG_RW("RD word %04x csum=%04x\n" , m_data_reg , m_checksum_reg); + irq_w(true); + } + } + break; + + case CMD_WR_SYNC: + case CMD_WR_CSUM: + if (m_cmd_state == CMD_PH1) { + LOG_RW("WR synced\n"); + m_cmd_state = CMD_PH2; + m_bit_idx = 15; + start_wr(); + } + break; + + default: + break; } + } } -void hp_taco_device::set_error(bool state) +READ_LINE_MEMBER(hp_taco_device::wr_bit_r) { - m_sts = !state; - m_sts_handler(m_sts); - LOG_0(("error = %d\n" , state)); + bool bit = false; + if (is_cmd_wr(m_cmd_reg) && m_cmd_state == CMD_PH2) { + bit = BIT(m_working_reg , m_bit_idx); + if (m_bit_idx) { + m_bit_idx--; + } else { + m_bit_idx = 15; + m_working_reg = m_data_reg; + if (get_cmd(m_cmd_reg) != CMD_WR_CSUM) { + update_checksum(m_data_reg); + } + LOG_RW("WR word %04x csum=%04x\n" , m_working_reg , m_checksum_reg); + m_data_reg = m_checksum_reg; + irq_w(true); + } + } + LOG_RW("WR bit %d (w=%04x,i=%u)\n" , bit , m_working_reg , m_bit_idx); + return bit; } -bool hp_taco_device::is_braking(void) const +void hp_taco_device::device_add_mconfig(machine_config &config) { - return m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING; + HP_DC100_TAPE(config , m_tape , 0); + m_tape->set_acceleration(ACCELERATION); + m_tape->set_set_points(SLOW_SPEED , FAST_SPEED); + m_tape->set_tick_size(TACH_TICK_LEN); + m_tape->set_bits_per_word(16); + m_tape->set_go_threshold(MOVING_THRESHOLD); + m_tape->cart_out().set(FUNC(hp_taco_device::cart_out_w)); + m_tape->hole().set(FUNC(hp_taco_device::hole_w)); + m_tape->tacho_tick().set(FUNC(hp_taco_device::tacho_tick_w)); + m_tape->motion_event().set(FUNC(hp_taco_device::motion_w)); + m_tape->rd_bit().set(FUNC(hp_taco_device::rd_bit_w)); + m_tape->wr_bit().set(FUNC(hp_taco_device::wr_bit_r)); } -unsigned hp_taco_device::speed_to_tick_freq(void) const +void hp_taco_device::device_start() { - 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); + LOG("device_start\n"); + 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_cmd_reg)); + save_item(NAME(m_status_reg)); + save_item(NAME(m_tach_reg)); + save_item(NAME(m_checksum_reg)); + save_item(NAME(m_threshold_reg)); + save_item(NAME(m_irq)); + save_item(NAME(m_flg)); + save_item(NAME(m_sts)); + save_item(NAME(m_error)); + save_item(NAME(m_gap_in_read)); + save_item(NAME(m_working_reg)); + save_item(NAME(m_bit_idx)); + + m_gap_timer = timer_alloc(GAP_TMR_ID); + m_evd_timer = timer_alloc(EVD_TMR_ID); + m_error_timer = timer_alloc(ERROR_TMR_ID); } -hti_format_t::tape_pos_t hp_taco_device::current_tape_pos(void) const +void hp_taco_device::device_reset() { - if (m_start_time.is_never()) { - // Tape not moving - return m_tape_pos; - } + LOG("device_reset\n"); + clear_state(); - 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? - hti_format_t::tape_pos_t delta_tach = (hti_format_t::tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); - LOG_0(("delta_tach = %u\n" , delta_tach)); + m_irq = false; + m_flg = true; + m_sts = true; - auto tape_pos = m_tape_pos; - if (!hti_format_t::pos_offset(tape_pos , m_tape_fwd , delta_tach)) { - LOG(("Tape unspooled!\n")); - } + m_irq_handler(false); + m_flg_handler(true); + m_sts_handler(true); + set_error(false , false); - return tape_pos; + m_gap_timer->reset(); + m_evd_timer->reset(); + m_error_timer->reset(); } -void hp_taco_device::update_tape_pos(void) +void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { - if (m_start_time.is_never()) { - // Tape not moving - return; - } + switch (id) { + case GAP_TMR_ID: + m_tape->update_speed_pos(); + LOG_DBG("Gap tmr @%g pos=%d cmd %02x st %d\n" , machine().time().as_double() , m_tape->get_pos() , get_cmd(m_cmd_reg) , m_cmd_state); + + switch (get_cmd(m_cmd_reg)) { + case CMD_INT_ON_GAP: + if (m_cmd_state == CMD_PH1) { + irq_and_end(); + } + break; - m_tape_pos = current_tape_pos(); - m_start_time = machine().time(); - LOG_0(("Tape pos = %u\n" , m_tape_pos)); + case CMD_STOP_IN_GAP: + case CMD_STOP_IN_GAP1: + if (m_cmd_state == CMD_PH1) { + // Count 256 ticks + m_tach_reg = 0xff00; + m_cmd_state = CMD_PH2; + } + break; - // Gap detection - bool gap_detected = false; - if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= min_gap_size()) { - auto tmp = m_tape_pos; - hti_format_t::pos_offset(tmp , m_tape_fwd , -min_gap_size()); - gap_detected = m_image.just_gap(current_track() , tmp , m_tape_pos); - } - if (gap_detected) { - BIT_SET(m_status_reg, STATUS_GAP_BIT); - } else { - BIT_CLR(m_status_reg, STATUS_GAP_BIT); - } - // Tach register update - update_tach_reg(); -} + case CMD_INT_N_GAP: + if (m_cmd_state == CMD_PH1) { + m_tach_reg++; + if (m_tach_reg == 0) { + irq_and_end(); + } else { + set_gap_timer(); + } + } + break; -void hp_taco_device::update_tach_reg(void) -{ - if (m_tach_reg_frozen) { - LOG_0(("Tach reg frozen\n")); - return; + case CMD_RD_6UPD: + case CMD_RD_CSUM_6UPD: + // Gap in read error + set_error(false , true); + break; + + default: + break; } + break; - auto pos = current_tape_pos(); - hti_format_t::tape_pos_t pos_int = pos / TAPE_POS_FRACT; - hti_format_t::tape_pos_t ref_int = m_tach_reg_ref / TAPE_POS_FRACT; - uint16_t reg_value = (uint16_t)(abs(pos_int - ref_int) + m_tach_reg); + case EVD_TMR_ID: + m_tape->update_speed_pos(); + LOG_DBG("EVD tmr @%g pos=%d cmd %02x st %d\n" , machine().time().as_double() , m_tape->get_pos() , get_cmd(m_cmd_reg) , m_cmd_state); + set_error(true , false); + break; - LOG_0(("Tach = %04x @ pos = %d, ref_pos = %d\n" , reg_value , pos , m_tach_reg_ref)); + case ERROR_TMR_ID: + LOG_DBG("Error tmr\n"); + irq_w(true); + break; - m_tach_reg = reg_value; - m_tach_reg_ref = pos; + default: + break; + } } -void hp_taco_device::freeze_tach_reg(bool freeze) +void hp_taco_device::clear_state() { - if (freeze) { - m_tach_reg_frozen = true; - } else { - m_tach_reg_frozen = false; - m_tach_reg_ref = current_tape_pos(); - } + m_data_reg = 0; + m_cmd_reg = 0; + m_status_reg = 0; + m_tach_reg = 0; + m_checksum_reg = 0; + m_threshold_reg = 0; + m_cmd_state = CMD_IDLE; + m_working_reg = 0; + m_bit_idx = 0; + set_tape_present(false); + set_tape_present(!m_tape->cart_out_r()); } -attotime hp_taco_device::time_to_distance(hti_format_t::tape_pos_t distance) const -{ - // +1 for rounding - return attotime::from_ticks(distance + 1 , speed_to_tick_freq()); -} - -attotime hp_taco_device::time_to_target(hti_format_t::tape_pos_t target) const +void hp_taco_device::irq_w(bool state) { - return time_to_distance(abs(target - m_tape_pos)); + if (state != m_irq) { + m_irq = state; + m_irq_handler(state); + LOG_DBG("IRQ = %d\n" , state); + } } -attotime hp_taco_device::time_to_stopping_pos(void) const +void hp_taco_device::sts_w(bool state) { - return time_to_distance(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); + if (state != m_sts) { + m_sts = state; + m_sts_handler(state); + LOG_DBG("STS = %d\n" , state); + } } -bool hp_taco_device::start_tape_cmd(uint16_t cmd_reg , uint16_t must_be_1 , uint16_t must_be_0) +void hp_taco_device::set_error(bool error , bool gap_in_read) { - m_cmd_reg = cmd_reg; - - uint16_t to_be_tested = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK); - // Bits in STATUS_ERR_MASK must always be 0 - must_be_0 |= STATUS_ERR_MASK; - - // It's not an error if the error state is already set (sts false) - if (((to_be_tested & (must_be_1 | must_be_0)) ^ must_be_1) != 0) { - return false; - } else { - bool prev_tape_wr = m_tape_wr; - bool prev_tape_fwd = m_tape_fwd; - bool prev_tape_fast = m_tape_fast; - bool prev_tape_braking = is_braking(); - bool not_moving = m_start_time.is_never(); - - m_start_time = machine().time(); - m_tape_wr = (must_be_0 & STATUS_WPR_MASK) != 0; - m_tape_fwd = DIR_FWD(m_cmd_reg); - m_tape_fast = SPEED_FAST(m_cmd_reg); - - if (m_tape_wr) { - // Write command: disable gap detector - m_gap_detect_start = hti_format_t::NULL_TAPE_POS; - BIT_CLR(m_status_reg, STATUS_GAP_BIT); - m_image_dirty = true; - } else if (not_moving || prev_tape_braking || prev_tape_wr != m_tape_wr || prev_tape_fwd != m_tape_fwd || prev_tape_fast != m_tape_fast) { - // Tape started or re-started right now, switched from writing to reading, direction changed or speed changed: (re)start gap detector - m_gap_detect_start = m_tape_pos; - BIT_CLR(m_status_reg, STATUS_GAP_BIT); - } + LOG_DBG("Error %d %d\n" , error , gap_in_read); - if (!not_moving && prev_tape_fwd != m_tape_fwd) { - // Tape direction inverted, stop tape before executing command - m_tape_fwd = prev_tape_fwd; - m_tape_fast = prev_tape_fast; - m_cmd_state = CMD_INVERTING; - LOG_0(("Direction reversed! fwd = %d fast = %d\n" , m_tape_fwd , m_tape_fast)); - if (!prev_tape_braking) { - m_tape_timer->adjust(time_to_stopping_pos()); - } - } else { - // No change in direction, immediate execution - m_cmd_state = CMD_PH0; - m_tape_timer->adjust(attotime::zero); - } + m_error = error; + m_gap_in_read = gap_in_read; - m_hole_timer->reset(); - m_timeout_timer->reset(); - return true; + if (m_error || m_gap_in_read) { + end_cmd(); + m_tape->set_op(hp_dc100_tape_device::OP_IDLE); + if (m_cmd_state != CMD_STOPPING) { + m_cmd_state = CMD_IDLE; + } + sts_w(false); + m_error_timer->adjust(attotime::zero , 0 , clocks_to_attotime(ERROR_IRQ_PERIOD)); + if (m_error) { + send_stop(); } + } else { + m_error_timer->reset(); + } } -void hp_taco_device::stop_tape(void) +hti_format_t::tape_pos_t hp_taco_device::min_gap_size() const { - m_start_time = attotime::never; - m_gap_detect_start = hti_format_t::NULL_TAPE_POS; + return BIT(m_cmd_reg , CMD_ST_FGAP) ? LONG_GAP_LENGTH : SHORT_GAP_LENGTH; } -unsigned hp_taco_device::current_track(void) +void hp_taco_device::set_gap_timer() { - return BIT(m_status_reg , STATUS_TRACKB_BIT); + m_tape->time_to_next_gap(min_gap_size() , true , m_gap_timer); } -attotime hp_taco_device::fetch_next_wr_word(void) +void hp_taco_device::set_evd_timer() { - 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(hti_format_t::word_length(m_next_word)); -} - -attotime hp_taco_device::time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos) -{ - if (m_rd_it_valid) { - word_rd_pos = hti_format_t::farthest_end(m_rd_it , m_tape_fwd); - return time_to_target(word_rd_pos); - } else { - return attotime::never; - } -} - -hti_format_t::tape_pos_t hp_taco_device::min_gap_size(void) const -{ - return LONG_GAP(m_cmd_reg) ? LONG_GAP_LENGTH : SHORT_GAP_LENGTH; + m_tape->time_to_next_gap(EVD_GAP_LENGTH , false , m_evd_timer); } void hp_taco_device::set_tape_present(bool present) { - if (present) { - if (is_readonly()) { - BIT_SET(m_status_reg, STATUS_WPR_BIT); - } else { - BIT_CLR(m_status_reg, STATUS_WPR_BIT); - } - // STATUS_CART_OUT_BIT is reset by CMD_CLEAR + if (present) { + if (m_tape->wpr_r()) { + BIT_SET(m_status_reg, CMD_ST_WPR); } else { - BIT_SET(m_status_reg, STATUS_CART_OUT_BIT); - BIT_SET(m_status_reg, STATUS_WPR_BIT); + BIT_CLR(m_status_reg, CMD_ST_WPR); } + // CMD_ST_CART_OUT is reset by CMD_CLEAR_ST + } else { + BIT_SET(m_status_reg, CMD_ST_CART_OUT); + BIT_SET(m_status_reg, CMD_ST_WPR); + } } -attotime hp_taco_device::time_to_next_hole(void) const +void hp_taco_device::send_go() { - auto pos = hti_format_t::next_hole(m_tape_pos , m_tape_fwd); + hp_dc100_tape_device::tape_speed_t speed; - if (pos == hti_format_t::NULL_TAPE_POS) { - return attotime::never; + if (BIT(m_cmd_reg , CMD_ST_FST)) { + speed = hp_dc100_tape_device::SP_FAST; } else { - return time_to_target(pos); + speed = hp_dc100_tape_device::SP_SLOW; } + m_tape->set_speed_setpoint(speed , BIT(m_cmd_reg , CMD_ST_FWD)); } -attotime hp_taco_device::time_to_tach_pulses(void) const +void hp_taco_device::send_stop() { - return time_to_distance((hti_format_t::tape_pos_t)(0x10000U - m_tach_reg) * TAPE_POS_FRACT); + m_tape->set_op(hp_dc100_tape_device::OP_IDLE); + if (m_tape->set_speed_setpoint(hp_dc100_tape_device::SP_STOP , false)) { + m_cmd_state = CMD_STOPPING; + } } -void hp_taco_device::terminate_cmd_now(void) +void hp_taco_device::end_cmd() { - m_cmd_state = CMD_END; - m_tape_timer->adjust(attotime::zero); - m_hole_timer->reset(); - m_timeout_timer->reset(); + m_gap_timer->reset(); + m_evd_timer->reset(); } -void hp_taco_device::set_data_timeout(bool long_timeout) +void hp_taco_device::irq_and_end() { - attotime timeout = time_to_distance(long_timeout ? PREAMBLE_TIMEOUT : DATA_TIMEOUT); - m_timeout_timer->adjust(timeout , 0 , timeout); + irq_w(true); + end_cmd(); + m_cmd_state = CMD_IDLE; } -void hp_taco_device::cmd_fsm(void) +bool hp_taco_device::is_at_slow_speed() const { - if (m_cmd_state == CMD_END) { - // Command ended - m_cmd_state = CMD_IDLE; - m_hole_timer->reset(); - m_timeout_timer->reset(); - irq_w(true); - if (AUTO_STOP(m_cmd_reg)) { - // Automatic stop after command execution - LOG_0(("Tape clamped\n")); - m_cmd_state = CMD_STOPPING; - m_tape_timer->adjust(time_to_stopping_pos()); - } - } else if (m_cmd_state == CMD_STOPPING) { - // Braking phase ended - m_cmd_state = CMD_IDLE; - stop_tape(); - if (CMD_CODE(m_cmd_reg) == CMD_STOP) { - irq_w(true); - } - } else { - attotime cmd_duration = attotime::never; - - if (m_cmd_state == CMD_INVERTING) { - m_tape_fwd = DIR_FWD(m_cmd_reg); - m_tape_fast = SPEED_FAST(m_cmd_reg); - m_cmd_state = CMD_PH0; - } - - if (m_cmd_state == CMD_PH0) { - m_hole_timer->adjust(time_to_next_hole()); - } - - switch (CMD_CODE(m_cmd_reg)) { - case CMD_FINAL_GAP: - if (m_cmd_state == CMD_PH0) { - // PH0 - m_rw_pos = m_tape_pos; - cmd_duration = time_to_distance(END_GAP_LENGTH); - m_cmd_state = CMD_PH1; - } else { - // PH1 - m_image.write_gap(current_track() , 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 = m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , false , m_rd_it); - cmd_duration = time_to_rd_next_word(m_rw_pos); - // Set T/O for preamble search - set_data_timeout(true); - m_cmd_state = CMD_PH1; - break; - } else if (m_cmd_state == CMD_PH1) { - // PH1 - if (m_rd_it->second == PREAMBLE_WORD) { - LOG_0(("Got preamble\n")); - m_cmd_state = CMD_PH2; - // m_rw_pos already at correct position - cmd_duration = fetch_next_wr_word(); - m_timeout_timer->reset(); - irq_w(true); - } else { - auto res = m_image.adv_it(current_track() , m_tape_fwd , m_rd_it); - if (res != hti_format_t::ADV_NO_MORE_DATA) { - cmd_duration = time_to_rd_next_word(m_rw_pos); - } - // Set T/O for arrival of data words - set_data_timeout(false); - } - break; - } - // Intentional fall-through on PH2 - - case CMD_INIT_WRITE: - if (m_cmd_state == CMD_PH0) { - // PH0 - m_next_word = PREAMBLE_WORD; - m_rw_pos = m_tape_pos; - cmd_duration = time_to_distance(hti_format_t::word_length(m_next_word)); - m_cmd_state = CMD_PH1; - } else { - // PH1 & PH2 of CMD_RECORD_WRITE - hti_format_t::tape_pos_t length; - m_image.write_word(current_track() , m_rw_pos , m_next_word , length); - hti_format_t::pos_offset(m_rw_pos , m_tape_fwd , 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 - auto target = m_tape_pos; - if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { - LOG_0(("IRG @%d\n" , target)); - cmd_duration = time_to_target(target); - } - m_cmd_state = CMD_PH1; - } else { - // PH1 - cmd_duration = time_to_tach_pulses(); - freeze_tach_reg(false); - m_cmd_state = CMD_END; - } - break; - - case CMD_INDTA_INGAP: - case CMD_NOT_INDTA: - if (m_cmd_state == CMD_PH0) { - // PH0 - if (m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , true , m_rd_it)) { - cmd_duration = time_to_target(hti_format_t::farthest_end(m_rd_it , m_tape_fwd)); - } - // Set T/O for data - set_data_timeout(true); - m_cmd_state = CMD_PH1; - } else { - // PH1 - auto target = m_tape_pos; - if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { - LOG_0(("End of data @%d\n" , target)); - cmd_duration = time_to_target(target); - } - // Got data, stop T/O - m_timeout_timer->reset(); - m_cmd_state = CMD_END; - } - break; - - case CMD_WRITE_IRG: - if (m_cmd_state == CMD_PH0) { - // PH0 - freeze_tach_reg(false); - m_rw_pos = m_tape_pos; - cmd_duration = time_to_tach_pulses(); - m_cmd_state = CMD_PH1; - } else { - // PH1 - m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); - cmd_duration = attotime::zero; - m_cmd_state = CMD_END; - } - break; - - case CMD_SCAN_RECORDS: - if (m_cmd_state == CMD_PH0 || m_cmd_state == CMD_PH2) { - // PH0 and PH2 - if (m_cmd_state == CMD_PH2) { - m_tach_reg++; - if (m_tach_reg == 0) { - // All gaps found, bail out - cmd_duration = attotime::zero; - m_cmd_state = CMD_END; - break; - } - } - if (m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , true , m_rd_it)) { - cmd_duration = time_to_target(hti_format_t::farthest_end(m_rd_it , m_tape_fwd)); - } - // Set T/O for data - set_data_timeout(true); - m_cmd_state = CMD_PH1; - } else if (m_cmd_state == CMD_PH1) { - // PH1 - auto target = m_tape_pos; - if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { - LOG_0(("Gap @%d (%u to go)\n" , target , 0x10000U - m_tach_reg)); - cmd_duration = time_to_target(target); - } - m_timeout_timer->reset(); - m_cmd_state = CMD_PH2; - } - break; - - case CMD_MOVE_INDTA: - if (m_cmd_state == CMD_PH0) { - // PH0 - freeze_tach_reg(false); - cmd_duration = time_to_tach_pulses(); - m_cmd_state = CMD_PH1; - } else { - // PH1 - if (m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , true , m_rd_it)) { - cmd_duration = time_to_target(hti_format_t::farthest_end(m_rd_it , m_tape_fwd)); - } - // Apparently this cmd doesn't set no-data T/O - m_cmd_state = CMD_END; - } - break; - - case CMD_UNK_1b: - // PH0 - // Unknown purpose, but make it a NOP (it's used in "T" test of test ROM) - cmd_duration = attotime::from_usec(QUICK_CMD_USEC); - m_cmd_state = CMD_END; - break; - - case CMD_MOVE_INGAP: - if (m_cmd_state == CMD_PH0) { - // PH0 - freeze_tach_reg(false); - cmd_duration = time_to_tach_pulses(); - m_cmd_state = CMD_PH1; - } else { - // PH1 - auto target = m_tape_pos; - if (m_image.next_gap(current_track() , target, m_tape_fwd, 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 = m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , false , m_rd_it); - // Set T/O for preamble search - set_data_timeout(true); - - m_cmd_state = CMD_PH1; - } else { - // Resume reading from last position, skip preamble search - m_cmd_state = CMD_PH2; - } - } else { - // Just to be sure.. - m_tape_pos = m_rw_pos; - - if (m_cmd_state == CMD_PH1) { - // PH1 - // Any word with at least a 0 will do as preamble. - // But anything that's not the correct preamble word (0) will cause a wrong alignment - // with word boundaries. TACO will read garbage data in this case (this effect is not simulated here). - if (m_rd_it->second != 0xffff) { - m_cmd_state = CMD_PH2; - LOG_0(("Got preamble %04x\n" , m_rd_it->second)); - } - } else { - // PH2 - if (m_irq) { - LOG(("Data reg overflow!\n")); - } - irq_w(true); - m_data_reg = m_rd_it->second; - if (m_clear_checksum_reg) { - m_checksum_reg = 0; - m_clear_checksum_reg = false; - } - m_checksum_reg += m_data_reg; - LOG_0(("RD %04x\n" , m_data_reg)); - } - // Set T/O for arrival of data words - set_data_timeout(false); - auto res = m_image.adv_it(current_track() , m_tape_fwd , m_rd_it); - LOG_0(("adv_it %d\n" , res)); - if (res == hti_format_t::ADV_NO_MORE_DATA) { - m_rd_it_valid = false; - } - } - cmd_duration = time_to_rd_next_word(m_rw_pos); - break; - - case CMD_DELTA_MOVE_IRG: - // PH0 - freeze_tach_reg(false); - cmd_duration = time_to_tach_pulses(); - m_cmd_state = CMD_END; - break; - - case CMD_END_READ: - if (m_cmd_state == CMD_PH0) { - // PH0 - cmd_duration = time_to_rd_next_word(m_rw_pos); - set_data_timeout(false); - m_cmd_state = CMD_PH1; - } else { - // PH1 - m_tape_pos = m_rw_pos; - // Note: checksum is not updated - m_data_reg = m_rd_it->second; - LOG_0(("Final RD %04x\n" , m_data_reg)); - auto res = m_image.adv_it(current_track() , m_tape_fwd , m_rd_it); - if (res == hti_format_t::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); - } + return !m_tape->is_accelerating() && fabs(m_tape->get_speed()) == SLOW_SPEED; } -void hp_taco_device::start_cmd_exec(uint16_t new_cmd_reg) +void hp_taco_device::start_rd() { - 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)); + if (m_tape->get_op() != hp_dc100_tape_device::OP_READ) { + m_tape->set_op(hp_dc100_tape_device::OP_READ); + // TODO: check + m_tape->time_to_next_gap(GIR_GAP_LENGTH , true , m_gap_timer); + set_evd_timer(); + } +} - update_tape_pos(); +void hp_taco_device::start_wr() +{ + m_tape->set_op(hp_dc100_tape_device::OP_WRITE); + m_gap_timer->reset(); + m_evd_timer->reset(); +} - unsigned new_cmd_code = CMD_CODE(new_cmd_reg); +void hp_taco_device::update_checksum(uint16_t data) +{ + // Update checksum with data + m_checksum_reg += data; +} - if (new_cmd_code != CMD_START_READ && - new_cmd_code != CMD_END_READ && - new_cmd_code != CMD_CLEAR) { - m_rd_it_valid = false; +void hp_taco_device::cmd_fsm() +{ + auto prev_state = m_cmd_state; + if (m_cmd_state == CMD_STOPPING) { + if (!m_tape->is_moving()) { + LOG_DBG("Tape stopped\n"); + m_cmd_state = CMD_IDLE; + auto cmd = get_cmd(m_cmd_reg); + if (cmd == CMD_STOP_INT || + cmd == CMD_STOP_IN_GAP1 || + cmd == CMD_INT_STOP_N_TACH1) { + irq_w(true); + } } + } else if (m_cmd_state != CMD_IDLE) { + switch (get_cmd(m_cmd_reg)) { + case CMD_INT_ON_GAP: + if (m_cmd_state == CMD_PH0 && is_at_slow_speed()) { + m_cmd_state = CMD_PH1; + set_gap_timer(); + set_evd_timer(); + } + break; - bool started = false; - - switch (new_cmd_code) { - case CMD_INDTA_INGAP: - // Errors: CART OUT,FAST SPEED - started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); - break; - - case CMD_FINAL_GAP: - // Errors: WP,CART OUT - started = start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK); - break; - - case CMD_INIT_WRITE: - // Errors: WP,CART OUT,fast speed,reverse - started = start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK); - break; - - case CMD_STOP: - m_cmd_reg = new_cmd_reg; - freeze_tach_reg(false); - m_hole_timer->reset(); - m_timeout_timer->reset(); - if (is_braking()) { - // Already braking - // m_tape_timer already set - } else if (m_start_time.is_never()) { - // Tape is already stopped - LOG_0(("Already stopped\n")); - m_tape_timer->adjust(attotime::from_usec(QUICK_CMD_USEC)); - } else { - // Start braking timer - m_cmd_state = CMD_STOPPING; - m_tape_timer->adjust(time_to_stopping_pos()); - } - m_cmd_state = CMD_STOPPING; - return; - - case CMD_SET_TRACK: - // Don't know if this command really starts the tape or not (probably it doesn't) - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; - + case CMD_ERASE: case CMD_MOVE: - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; + // Stay in PH0 until next command + break; - case CMD_INGAP_MOVE: - // Errors: CART OUT,FAST SPEED - started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); - break; + case CMD_WR: + if (m_cmd_state == CMD_PH0 && is_at_slow_speed()) { + m_cmd_state = CMD_PH2; + m_bit_idx = 15; + start_wr(); + } + 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_SET_TRACK: + m_cmd_state = CMD_IDLE; + break; - case CMD_NOT_INDTA: - // Errors: CART OUT - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; + case CMD_UNK_0D: + irq_and_end(); + break; - case CMD_WRITE_IRG: - // Errors: WP,CART OUT - started = start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK); - break; + case CMD_OPP_DIR_N_TACH: + if (m_cmd_state == CMD_PH1 && is_at_slow_speed()) { + irq_and_end(); + } + break; - case CMD_SCAN_RECORDS: - // Errors: CART OUT - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; + case CMD_STOP_IN_GAP: + case CMD_STOP_IN_GAP1: + if (m_cmd_state == CMD_PH0 && !m_tape->is_accelerating()) { + m_cmd_state = CMD_PH1; + set_gap_timer(); + } + break; - case CMD_RECORD_WRITE: - // Errors: WP,CART OUT,fast speed,reverse - started = start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK); - break; + case CMD_WR_GAP_N_TACH: + break; - case CMD_MOVE_INDTA: - // Errors: CART OUT,FAST SPEED - started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); - break; + case CMD_INT_N_GAP: + if (m_cmd_state == CMD_PH0 && !m_tape->is_accelerating()) { + m_cmd_state = CMD_PH1; + set_gap_timer(); + set_evd_timer(); + } + break; - case CMD_UNK_1b: - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; + case CMD_WR_SYNC: + case CMD_WR_CSUM: + if (m_cmd_state == CMD_PH0 && is_at_slow_speed()) { + m_cmd_state = CMD_PH1; + start_rd(); + } + break; - case CMD_MOVE_INGAP: - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; + case CMD_INT_N_TACH_22: + if (m_cmd_state == CMD_PH0 && is_at_slow_speed()) { + m_cmd_state = CMD_PH1; + } + break; - case CMD_START_READ: - // Yes, you can read tape backwards: test "C" does that! - // Because of this DIR_FWD_MASK is not in the "must be 1" mask. - started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); - break; + case CMD_INT_STOP_N_TACH: + case CMD_INT_STOP_N_TACH1: + if (m_cmd_state == CMD_PH0) { + // PH2 to use common code with CMD_STOP_IN_GAP in tacho_tick_w + m_cmd_state = CMD_PH2; + } + break; - case CMD_DELTA_MOVE_IRG: - started = start_tape_cmd(new_cmd_reg , 0 , 0); - break; + case CMD_RD_6UPD: + case CMD_RD_CSUM_6UPD: + if (m_cmd_state == CMD_PH0 && is_at_slow_speed()) { + m_cmd_state = CMD_PH1; + start_rd(); + m_bit_idx = 0; + } + break; - case CMD_END_READ: - // This command only makes sense after CMD_START_READ - if (CMD_CODE(m_cmd_reg) == CMD_START_READ) { - started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); - LOG_0(("END_READ %d\n" , m_rd_it_valid)); - } - break; + case CMD_INT_N_TACH: + break; default: - LOG(("Unrecognized command\n")); - started = false; - break; - } - - if (!started) { - set_error(true); - m_cmd_state = CMD_IDLE; - m_tape_timer->reset(); - m_hole_timer->reset(); - m_timeout_timer->reset(); + break; } + } + if (prev_state != m_cmd_state) { + LOG_DBG("FSM st %d->%d\n" , prev_state , m_cmd_state); + } } -image_init_result hp_taco_device::internal_load(bool is_create) +uint8_t hp_taco_device::get_cmd(uint16_t cmd_reg) { - device_reset(); - - io_generic io; - io.file = (device_image_interface *)this; - io.procs = &image_ioprocs; - io.filler = 0; - if (is_create) { - m_image.clear_tape(); - m_image.save_tape(&io); - } else if (!m_image.load_tape(&io)) { - seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); - set_tape_present(false); - return image_init_result::FAIL; - } + return uint8_t((cmd_reg & (BIT_MASK<uint16_t>(CMD_ST_G5) | BIT_MASK<uint16_t>(CMD_ST_G4) | + BIT_MASK<uint16_t>(CMD_ST_G3) | BIT_MASK<uint16_t>(CMD_ST_G2) | + BIT_MASK<uint16_t>(CMD_ST_G1) | BIT_MASK<uint16_t>(CMD_ST_G0))) >> CMD_ST_G0); +} - m_image_dirty = false; +bool hp_taco_device::is_cmd_rd_wr(uint16_t cmd_reg) +{ + return BIT(cmd_reg , CMD_ST_G1); +} - set_tape_present(true); - return image_init_result::PASS; +bool hp_taco_device::is_cmd_rd(uint16_t cmd_reg) +{ + return is_cmd_rd_wr(cmd_reg) && BIT(cmd_reg , CMD_ST_G3); } -image_init_result hp_taco_device::call_load() +bool hp_taco_device::is_cmd_wr(uint16_t cmd_reg) { - LOG(("call_load\n")); - return internal_load(false); + return is_cmd_rd_wr(cmd_reg) && !BIT(cmd_reg , CMD_ST_G3); } -image_init_result hp_taco_device::call_create(int format_type, util::option_resolution *format_options) +bool hp_taco_device::is_double_hole_cmd(uint16_t cmd_reg) { - LOG(("call_create\n")); - return internal_load(true); + return is_cmd_rd_wr(cmd_reg) || get_cmd(cmd_reg) == CMD_ERASE || get_cmd(cmd_reg) == CMD_WR_GAP_N_TACH; } -void hp_taco_device::call_unload() +void hp_taco_device::start_cmd_exec(uint16_t new_cmd_reg) { - LOG(("call_unload dirty=%d\n" , m_image_dirty)); + LOG_DBG("New cmd %02x D=%d S=%d @ %g cmd %02x st %d\n" , get_cmd(new_cmd_reg) , BIT(new_cmd_reg , CMD_ST_FWD) , BIT(new_cmd_reg , CMD_ST_FST) , machine().time().as_double() , get_cmd(m_cmd_reg) , m_cmd_state); - device_reset(); + m_tape->update_speed_pos(); - if (m_image_dirty) { - io_generic io; - io.file = (device_image_interface *)this; - io.procs = &image_ioprocs; - io.filler = 0; - m_image.save_tape(&io); - m_image_dirty = false; - } + unsigned new_cmd_code = get_cmd(new_cmd_reg); - m_image.clear_tape(); - set_tape_present(false); -} + if (new_cmd_code == CMD_CLEAR_ST) { + // Clear status + set_error(false , false); + sts_w(true); + if (!m_tape->cart_out_r()) { + BIT_CLR(m_status_reg, CMD_ST_CART_OUT); + } + BIT_CLR(m_status_reg, CMD_ST_HOLE); + if (m_cmd_state != CMD_STOPPING) { + m_cmd_state = CMD_IDLE; + } + } else { + bool start_tape = true; + bool clear_timers = true; + hp_dc100_tape_device::tape_op_t op = hp_dc100_tape_device::OP_IDLE; -std::string hp_taco_device::call_display() -{ - std::string buffer; - // Mostly lifted from cassette_image_device::call_display ;) + if (m_gap_in_read) { + set_error(false , false); + } - // Do not show anything if image not loaded or tape not moving - if (!exists() || m_start_time.is_never()) { - return buffer; - } + m_cmd_state = CMD_PH0; + uint16_t prev_cmd_reg = m_cmd_reg; + m_cmd_reg = new_cmd_reg; - char track = BIT(m_status_reg , STATUS_TRACKB_BIT) ? 'B' : 'A'; - char r_w = m_tape_wr ? 'W' : 'R'; - char m1; - char m2; + switch (new_cmd_code) { + case CMD_INT_ON_GAP: + // 1. Wait for tape to reach 22 ips + // 2. Wait to reach a new gap + // 3. Int & end + break; - if (m_tape_fwd) { - m1 = '>'; - m2 = m_tape_fast ? '>' : ' '; - } else { - m1 = '<'; - m2 = m_tape_fast ? '<' : ' '; - } + case CMD_ERASE: + // 1. Start erase op + op = hp_dc100_tape_device::OP_ERASE; + break; + + case CMD_WR: + case CMD_WR_SYNC: + case CMD_WR_CSUM: + // 1. Wait for tape to reach 22 ips + // 2. Wait for data to begin (not for CMD_WR) + // 3. Write words on tape + if (is_cmd_rd_wr(prev_cmd_reg)) { + // Skip synchronization (steps 1 & 2) + m_cmd_state = CMD_PH2; + start_wr(); + op = m_tape->get_op(); + } else { + m_working_reg = PREAMBLE_WORD; + } + break; - int pos_in = current_tape_pos() / hti_format_t::ONE_INCH_POS; + case CMD_STOP: + case CMD_STOP_INT: + // 1. Send stop to tape + // 2. Wait for tape to stop, keep counting tach ticks + // 3. When tape has stopped, int. (CMD_STOP_INT only) + send_stop(); + m_cmd_state = CMD_STOPPING; + start_tape = false; + break; - buffer = string_format("%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); + case CMD_SET_TRACK: + // 1. Set A/B track + if (BIT(new_cmd_reg , CMD_ST_MOD)) { + BIT_SET(m_status_reg , CMD_ST_TRB); + m_tape->set_track_no(1); + } else { + BIT_CLR(m_status_reg , CMD_ST_TRB); + m_tape->set_track_no(0); + } + start_tape = false; + break; - // Not correct when there are 2 or more instances of TACO - return buffer; -} + case CMD_UNK_0D: + // 1. Int & end + start_tape = false; + break; -const char *hp_taco_device::file_extensions() const -{ - return "hti"; + case CMD_MOVE: + // 1. Start tape + break; + + case CMD_OPP_DIR_N_TACH: + // 1. Count tach ticks until tacho reg rolls over to 0 + // 2. Toggle FWD bit + // 3. Wait for tape to reach 22 ips + // 4. Int & end + break; + + case CMD_STOP_IN_GAP: + case CMD_STOP_IN_GAP1: + // 1. Wait for tape to reach set point + // 2. Wait to reach a new gap + // 3. Wait for 256 tacho ticks + // 4. Stop tape + break; + + case CMD_WR_GAP_N_TACH: + // 1. Erase tape until tacho reg overflows + // 2. Int & end + op = hp_dc100_tape_device::OP_ERASE; + break; + + case CMD_INT_N_GAP: + // 1. Wait for tape to reach set point + // 2. Wait for N gaps + // 3. Int & end + break; + + case CMD_INT_N_TACH_22: + // 1. Wait for tape to reach 22 ips + // 2. Count tach ticks until tacho reg rolls over to 0 + // 3. Int & end + break; + + case CMD_INT_STOP_N_TACH: + case CMD_INT_STOP_N_TACH1: + // 1. Count tach ticks until tacho reg rolls over to 0 + // 2. Stop tape + break; + + case CMD_RD_6UPD: + case CMD_RD_CSUM_6UPD: + // 1. Wait for tape to reach 22 ips + // 2. Wait for preamble + // 3. Read words + if (is_cmd_rd_wr(prev_cmd_reg)) { + // Skip synchronization (steps 1 & 2) + m_cmd_state = CMD_PH2; + start_rd(); + clear_timers = false; + op = m_tape->get_op(); + } + break; + + case CMD_INT_N_TACH: + // 1. Count tach ticks until tacho reg rolls over to 0 + // 2. Int & end + break; + + default: + LOG("Unrecognized command %x\n" , new_cmd_code); + start_tape = false; + break; + } + + if (start_tape) { + if (m_error || + (BIT(m_status_reg , CMD_ST_HOLE) && !is_double_hole_cmd(new_cmd_reg)) || + BIT(m_status_reg , CMD_ST_CART_OUT)) { + set_error(true , false); + return; + } else { + send_go(); + } + } + if (clear_timers) { + m_gap_timer->reset(); + m_evd_timer->reset(); + } + m_tape->set_op(op); + cmd_fsm(); + } } diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index bb20bd464d3..b052ba77a8f 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -14,9 +14,9 @@ #pragma once #include "formats/hti_tape.h" +#include "machine/hp_dc100_tape.h" -class hp_taco_device : public device_t , - public device_image_interface +class hp_taco_device : public device_t { public: // construction/destruction @@ -35,113 +35,86 @@ public: DECLARE_READ_LINE_MEMBER(flg_r); DECLARE_READ_LINE_MEMBER(sts_r); - // device_image_interface overrides - virtual image_init_result call_load() override; - virtual image_init_result call_create(int format_type, util::option_resolution *format_options) override; - virtual void call_unload() override; - virtual std::string 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; } - virtual bool is_creatable() const override { return true; } - virtual bool must_be_loaded() const override { return false; } - virtual bool is_reset_on_load() const override { return false; } - virtual const char *file_extensions() const override; + DECLARE_WRITE_LINE_MEMBER(cart_out_w); + DECLARE_WRITE_LINE_MEMBER(hole_w); + DECLARE_WRITE_LINE_MEMBER(tacho_tick_w); + DECLARE_WRITE_LINE_MEMBER(motion_w); + DECLARE_WRITE_LINE_MEMBER(rd_bit_w); + DECLARE_READ_LINE_MEMBER(wr_bit_r); protected: hp_taco_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); // device-level overrides + virtual void device_add_mconfig(machine_config &config) override; virtual void device_start() override; - virtual void device_stop() override; virtual void device_reset() override; virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; private: + required_device<hp_dc100_tape_device> m_tape; + devcb_write_line m_irq_handler; devcb_write_line m_flg_handler; devcb_write_line m_sts_handler; // Registers uint16_t m_data_reg; - bool m_data_reg_full; uint16_t m_cmd_reg; uint16_t m_status_reg; uint16_t m_tach_reg; - hti_format_t::tape_pos_t m_tach_reg_ref; - bool m_tach_reg_frozen; uint16_t m_checksum_reg; - bool m_clear_checksum_reg; - uint16_t m_timing_reg; + uint16_t m_threshold_reg; // State bool m_irq; bool m_flg; bool m_sts; + bool m_error; + bool m_gap_in_read; // Command FSM state typedef enum { CMD_IDLE, - CMD_INVERTING, CMD_PH0, CMD_PH1, CMD_PH2, - CMD_END, CMD_STOPPING } cmd_state_t; cmd_state_t m_cmd_state; - // Tape position & motion - hti_format_t::tape_pos_t m_tape_pos; - attotime m_start_time; // Tape moving if != never - bool m_tape_fwd; - bool m_tape_fast; - // Timers - emu_timer *m_tape_timer; - emu_timer *m_hole_timer; - emu_timer *m_timeout_timer; - - // Content of tape - hti_format_t m_image; - bool m_image_dirty; + emu_timer *m_gap_timer; + emu_timer *m_evd_timer; + emu_timer *m_error_timer; // Reading & writing - bool m_tape_wr; - hti_format_t::tape_pos_t m_rw_pos; - uint16_t m_next_word; - hti_format_t::track_iterator_t m_rd_it; - bool m_rd_it_valid; - - // Gap detection - hti_format_t::tape_pos_t m_gap_detect_start; + uint16_t m_working_reg; + unsigned m_bit_idx; - void clear_state(void); + void clear_state(); void irq_w(bool state); - void set_error(bool state); - bool is_braking(void) const; - unsigned speed_to_tick_freq(void) const; - hti_format_t::tape_pos_t current_tape_pos(void) const; - void update_tape_pos(void); - void update_tach_reg(void); - void freeze_tach_reg(bool freeze); - attotime time_to_distance(hti_format_t::tape_pos_t distance) const; - attotime time_to_target(hti_format_t::tape_pos_t target) const; - attotime time_to_stopping_pos(void) const; - bool start_tape_cmd(uint16_t cmd_reg , uint16_t must_be_1 , uint16_t must_be_0); - void stop_tape(void); - unsigned current_track(void); - attotime fetch_next_wr_word(void); - attotime time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos); - hti_format_t::tape_pos_t min_gap_size(void) const; + void sts_w(bool state); + void set_error(bool error , bool gap_in_read); + hti_format_t::tape_pos_t min_gap_size() const; + void set_gap_timer(); + void set_evd_timer(); 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 set_data_timeout(bool long_timeout); - void cmd_fsm(void); + void send_go(); + void send_stop(); + void end_cmd(); + void irq_and_end(); + bool is_at_slow_speed() const; + void start_rd(); + void start_wr(); + void update_checksum(uint16_t data); + void cmd_fsm(); + static uint8_t get_cmd(uint16_t cmd_reg); + static bool is_cmd_rd_wr(uint16_t cmd_reg); + static bool is_cmd_rd(uint16_t cmd_reg); + static bool is_cmd_wr(uint16_t cmd_reg); + static bool is_double_hole_cmd(uint16_t cmd_reg); void start_cmd_exec(uint16_t new_cmd_reg); - image_init_result internal_load(bool is_create); }; // device type definition diff --git a/src/lib/formats/hti_tape.cpp b/src/lib/formats/hti_tape.cpp index 64fec17ec08..29bab6cd51c 100644 --- a/src/lib/formats/hti_tape.cpp +++ b/src/lib/formats/hti_tape.cpp @@ -11,7 +11,8 @@ #include "imageutl.h" -static constexpr uint32_t FILE_MAGIC = 0x5441434f; // Magic value at start of image file: "TACO" +static constexpr uint32_t OLD_FILE_MAGIC = 0x5441434f; // Magic value at start of old-format image file: "TACO" +static constexpr uint32_t FILE_MAGIC = 0x48544930; // Magic value at start of image file: "HTI0" // *** Position of tape holes *** // At beginning of tape: @@ -40,6 +41,7 @@ static const hti_format_t::tape_pos_t tape_holes[] = { }; hti_format_t::hti_format_t() + : m_bits_per_word(16) { clear_tape(); } @@ -49,14 +51,15 @@ bool hti_format_t::load_tape(io_generic *io) uint8_t tmp[ 4 ]; io_generic_read(io, tmp, 0, 4); - if (pick_integer_be(tmp , 0 , 4) != FILE_MAGIC) { + auto magic = pick_integer_be(tmp , 0 , 4); + if (magic != FILE_MAGIC && magic != OLD_FILE_MAGIC) { return false; } uint64_t offset = 4; for (tape_track_t& track : m_tracks) { - if (!load_track(io , offset , track)) { + if (!load_track(io , offset , track , magic == OLD_FILE_MAGIC)) { clear_tape(); return false; } @@ -114,7 +117,7 @@ hti_format_t::tape_pos_t hti_format_t::word_length(tape_word_t w) zeros = 16 - ones; - return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; + return zeros * ZERO_BIT_LEN + ones * ONE_BIT_LEN; } hti_format_t::tape_pos_t hti_format_t::farthest_end(const track_iterator_t& it , bool forward) @@ -187,7 +190,7 @@ void hti_format_t::write_word(unsigned track_no , tape_pos_t start , tape_word_t // as the record expands & contracts when re-written with different content. // Without this fix, a gap could form in the slack big enough to cause // false gap detections. - if (forward && it_high != track.end() && (it_high->first - end_pos) >= (ZERO_BIT_LEN * 16 + ONE_BIT_LEN)) { + if (forward && it_high != track.end() && (it_high->first - end_pos) >= (ZERO_BIT_LEN * 16)) { track.insert(it_high, std::make_pair(end_pos, 0)); it_high--; } @@ -264,6 +267,50 @@ hti_format_t::adv_res_t hti_format_t::adv_it(unsigned track_no , bool forward , } } +bool hti_format_t::sync_with_record(unsigned track_no , track_iterator_t& it , unsigned& bit_idx) +{ + while ((it->second & (1U << bit_idx)) == 0) { + if (bit_idx) { + bit_idx--; + } else { + bit_idx = 15; + auto res = adv_it(track_no, true, it); + if (res != ADV_CONT_DATA) { + return false; + } + } + } + if (bit_idx) { + bit_idx--; + } else { + bit_idx = 15; + } + return true; +} + +hti_format_t::adv_res_t hti_format_t::next_word(unsigned track_no , track_iterator_t& it , unsigned& bit_idx , tape_word_t& word) +{ + if (bit_idx == 15) { + auto res = adv_it(track_no, true, it); + if (res == ADV_NO_MORE_DATA) { + return res; + } + word = it->second; + return res; + } else { + word = it->second << (15 - bit_idx); + auto res = adv_it(track_no, true, it); + if (res == ADV_DISCONT_DATA) { + bit_idx = 15; + it--; + } else if (res == ADV_CONT_DATA) { + word |= (it->second >> (bit_idx + 1)); + } + + return res; + } +} + bool hti_format_t::next_gap(unsigned track_no , tape_pos_t& pos , bool forward , tape_pos_t min_gap) { tape_track_t::iterator it; @@ -323,10 +370,12 @@ bool hti_format_t::next_gap(unsigned track_no , tape_pos_t& pos , bool forward , return n_gaps == 0; } -bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& track) +bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& track , bool old_format) { uint8_t tmp[ 4 ]; uint32_t tmp32; + tape_pos_t delta_pos = 0; + tape_pos_t last_word_end = 0; track.clear(); @@ -350,7 +399,10 @@ bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& tmp32 = pick_integer_le(tmp, 0, 4); - tape_pos_t pos = (tape_pos_t)tmp32; + tape_pos_t pos = (tape_pos_t)tmp32 + delta_pos; + + tape_word_t word_accum = 0; + unsigned bits_in_accum = 0; for (unsigned i = 0; i < n_words; i++) { uint16_t tmp16; @@ -359,8 +411,56 @@ bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& offset += 2; tmp16 = pick_integer_le(tmp, 0, 2); - track.insert(std::make_pair(pos , tmp16)); - pos += word_length(tmp16); + if (!old_format) { + track.insert(std::make_pair(pos , tmp16)); + pos += word_length(tmp16); + } else if (m_bits_per_word == 16) { + // Convert HP9845 & HP85 old format + // Basically, in old format each word had 17 bits (an implicit 1 + // was added at the end). In new format we just keep the 16 bits + // and don't add the 17th bit. + if (i == 0 && tmp16 == 0 && (pos - last_word_end) > 16384) { + // This mysterious heuristic is meant to turn the first + // word of a record into a proper preamble word (from 0 to 1) + // provided this is actually at the beginning of a new record + // (enough distance from end of last record) + tmp16 = 1; + } + track.insert(std::make_pair(pos, tmp16)); + pos += word_length(tmp16); + last_word_end = pos; + delta_pos -= ONE_BIT_LEN; + } else { + // Convert HP9825 old format + // In moving from old to new format we make the 17th bit at the + // end of each word explicit + word_accum |= (tmp16 >> bits_in_accum); + // Avoid storing overlapping words + if (pos >= last_word_end) { + track.insert(std::make_pair(pos, word_accum)); + } + pos += word_length(word_accum); + last_word_end = pos; + if (bits_in_accum == 0) { + word_accum = 0; + } else { + word_accum = tmp16 << (16 - bits_in_accum); + } + word_accum |= (1U << (15 - bits_in_accum)); + if (++bits_in_accum >= 16) { + track.insert(std::make_pair(pos, word_accum)); + pos += word_length(word_accum); + last_word_end = pos; + word_accum = 0; + bits_in_accum = 0; + } + } + } + if (bits_in_accum) { + track.insert(std::make_pair(pos, word_accum)); + tape_pos_t shift = (tape_pos_t)(16 - bits_in_accum) * ZERO_BIT_LEN; + delta_pos += shift; + last_word_end = pos + word_length(word_accum); } } } diff --git a/src/lib/formats/hti_tape.h b/src/lib/formats/hti_tape.h index 93a8cbac43e..72eb85d55cc 100644 --- a/src/lib/formats/hti_tape.h +++ b/src/lib/formats/hti_tape.h @@ -4,8 +4,8 @@ "HTI" format - Format of images of DC-100 tape cassettes as used in HP 9845 - and HP 85 systems. + Format of images of DC-100 tape cassettes as used in HP 9825, + HP 9845 and HP 85 systems. *********************************************************************/ #ifndef MAME_FORMATS_HTI_TAPE_H @@ -49,10 +49,16 @@ public: // Iterator to access words on tape typedef tape_track_t::iterator track_iterator_t; + // Set no. of bits per word (needed when loading old format) + void set_bits_per_word(unsigned bits) { m_bits_per_word = bits; } + bool load_tape(io_generic *io); void save_tape(io_generic *io); void clear_tape(); + // Return physical length of a bit on tape + static constexpr tape_pos_t bit_length(bool bit) { return bit ? ONE_BIT_LEN : ZERO_BIT_LEN; } + // Return physical length of a 16-bit word on tape static tape_pos_t word_length(tape_word_t w); @@ -84,14 +90,22 @@ public: // Advance an iterator to next word of data adv_res_t adv_it(unsigned track_no , bool forward , track_iterator_t& it); + // Sync with the preamble of a record + bool sync_with_record(unsigned track_no , track_iterator_t& it , unsigned& bit_idx); + + // Get a data word from record, after syncing + adv_res_t next_word(unsigned track_no , track_iterator_t& it , unsigned& bit_idx , tape_word_t& word); + // Scan for beginning of next gap in a given direction bool next_gap(unsigned track_no , tape_pos_t& pos , bool forward , tape_pos_t min_gap); private: // Content of tape tracks tape_track_t m_tracks[ 2 ]; + // Bits per word in old format + unsigned m_bits_per_word; - static bool load_track(io_generic *io , uint64_t& offset , tape_track_t& track); + bool load_track(io_generic *io , uint64_t& offset , tape_track_t& track , bool old_format); static void dump_sequence(io_generic *io , uint64_t& offset , tape_track_t::const_iterator it_start , unsigned n_words); static tape_pos_t word_end_pos(const track_iterator_t& it); diff --git a/src/mame/machine/hp9825_tape.cpp b/src/mame/machine/hp9825_tape.cpp index f2f12135ef1..dbfbd22929e 100644 --- a/src/mame/machine/hp9825_tape.cpp +++ b/src/mame/machine/hp9825_tape.cpp @@ -16,11 +16,10 @@ #include "logmacro.h" #define LOG_REG_MASK (LOG_GENERAL << 1) #define LOG_REG(...) LOGMASKED(LOG_REG_MASK, __VA_ARGS__) -#define LOG_TMR_MASK (LOG_REG_MASK << 1) -#define LOG_TMR(...) LOGMASKED(LOG_TMR_MASK, __VA_ARGS__) -#define LOG_DBG_MASK (LOG_TMR_MASK << 1) +#define LOG_DBG_MASK (LOG_REG_MASK << 1) #define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__) #undef VERBOSE +//#define VERBOSE (LOG_GENERAL | LOG_REG_MASK | LOG_DBG_MASK) #define VERBOSE (LOG_GENERAL) // Bit manipulation @@ -44,11 +43,9 @@ namespace { // Constants constexpr double FAST_SPEED = 90.0; // Fast speed: 90 ips constexpr double SLOW_SPEED = 22.0; // Slow speed: 22 ips -constexpr double MIN_RD_SPEED = 22.0; // Minimum speed to read data off the tape constexpr double MOVING_THRESHOLD = 2.0; // Tape is moving (from MVG bit POV) when speed > 2.0 ips constexpr double ACCELERATION = 1200.0; // Acceleration when speed set point is changed: 1200 ips^2 constexpr unsigned TACH_TICKS_PER_INCH = 483; // Tachometer pulses per inch -constexpr double INVERSION_MARGIN = 1e-5; // Margin to ensure speed is away from 0 when motion is inverted (10 µs) constexpr hti_format_t::tape_pos_t TACH_TICK_LENGTH = hti_format_t::ONE_INCH_POS / TACH_TICKS_PER_INCH; // Length of each tach tick // Bits in command register @@ -74,33 +71,36 @@ enum : unsigned { STAT_REG_EOT_BIT = 0 // End of tape (1) }; -// Timers -enum { - BIT_TMR_ID, - TACHO_TMR_ID, - HOLE_TMR_ID, - INV_TMR_ID -}; - // Device type definition DEFINE_DEVICE_TYPE(HP9825_TAPE, hp9825_tape_device, "hp9825_tape", "HP9825 tape sub-system") hp9825_tape_device::hp9825_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig , HP9825_TAPE , tag , owner , clock) - , device_image_interface(mconfig , *this) , m_flg_handler(*this) , m_sts_handler(*this) , m_dmar_handler(*this) , m_led_handler(*this) + , m_tape(*this , "drive") , m_short_gap_timer(*this , "short_tmr") , m_long_gap_timer(*this , "long_tmr") - , m_image() - , m_image_dirty(false) { } void hp9825_tape_device::device_add_mconfig(machine_config &config) { + HP_DC100_TAPE(config , m_tape , 0); + m_tape->set_acceleration(ACCELERATION); + m_tape->set_set_points(SLOW_SPEED , FAST_SPEED); + m_tape->set_tick_size(TACH_TICK_LENGTH); + m_tape->set_bits_per_word(17); + m_tape->set_go_threshold(MOVING_THRESHOLD); + m_tape->cart_out().set(FUNC(hp9825_tape_device::cart_out_w)); + m_tape->hole().set(FUNC(hp9825_tape_device::hole_w)); + m_tape->tacho_tick().set(FUNC(hp9825_tape_device::tacho_tick_w)); + m_tape->motion_event().set(FUNC(hp9825_tape_device::motion_w)); + m_tape->rd_bit().set(FUNC(hp9825_tape_device::rd_bit_w)); + m_tape->wr_bit().set(FUNC(hp9825_tape_device::wr_bit_r)); + TTL74123(config , m_short_gap_timer , 0); m_short_gap_timer->set_connection_type(TTL74123_NOT_GROUNDED_NO_DIODE); m_short_gap_timer->set_resistor_value(RES_K(37.9)); @@ -124,11 +124,6 @@ void hp9825_tape_device::device_start() m_dmar_handler.resolve_safe(); m_led_handler.resolve_safe(); - m_bit_timer = timer_alloc(BIT_TMR_ID); - m_tacho_timer = timer_alloc(TACHO_TMR_ID); - m_hole_timer = timer_alloc(HOLE_TMR_ID); - m_inv_timer = timer_alloc(INV_TMR_ID); - save_item(NAME(m_cmd_reg)); save_item(NAME(m_stat_reg)); save_item(NAME(m_flg)); @@ -140,24 +135,10 @@ void hp9825_tape_device::device_start() save_item(NAME(m_dma_req)); save_item(NAME(m_in_gap)); save_item(NAME(m_no_go)); - save_item(NAME(m_present)); save_item(NAME(m_valid_bits)); save_item(NAME(m_trans_cnt)); save_item(NAME(m_short_gap_out)); save_item(NAME(m_long_gap_out)); - save_item(NAME(m_image_dirty)); - save_item(NAME(m_tape_pos)); - save_item(NAME(m_next_bit_pos)); - save_item(NAME(m_next_tacho_pos)); - save_item(NAME(m_next_hole_pos)); - save_item(NAME(m_speed)); - save_item(NAME(m_start_time)); - save_item(NAME(m_accelerating)); - save_item(NAME(m_rw_stat)); - save_item(NAME(m_rd_it_valid)); - save_item(NAME(m_rw_word)); - save_item(NAME(m_bit_idx)); - save_item(NAME(m_gap_start)); } void hp9825_tape_device::device_reset() @@ -181,13 +162,6 @@ void hp9825_tape_device::clear_state() m_no_go = false; m_valid_bits = false; m_trans_cnt = 0; - m_tape_pos = 80 * hti_format_t::ONE_INCH_POS; - m_speed = 0.0; - m_start_time = attotime::never; - m_accelerating = false; - m_rw_stat = RW_IDLE; - m_rd_it_valid = false; - m_gap_start = hti_format_t::NULL_TAPE_POS; m_short_gap_timer->b_w(0); m_long_gap_timer->a_w(1); @@ -197,152 +171,6 @@ void hp9825_tape_device::clear_state() m_sts_handler(true); m_dmar_handler(false); m_led_handler(false); - - m_bit_timer->reset(); - m_tacho_timer->reset(); - m_hole_timer->reset(); - m_inv_timer->reset(); - - set_tape_present(false); - set_tape_present(is_loaded()); -} - -void hp9825_tape_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) -{ - LOG_TMR("%.6f TMR %d s=%.3f p=%d a=%d\n" , machine().time().as_double() , id , m_speed , m_tape_pos , m_accelerating); - update_speed_pos(); - - switch (id) { - case BIT_TMR_ID: - m_tape_pos = m_next_bit_pos; - if (m_rw_stat == RW_READING) { - // Reading - // Tape pos here is aligned with the beginning of bit cell. It'd be more correct - // to align with end of cell, though. This solution is a lot simpler and - // it's basically harmless. - - // 17th bit is sync (always 1) - bool bit = m_bit_idx == 16 ? true : BIT(m_rd_it->second , 15 - m_bit_idx); - rd_bit(bit); - if (is_moving_fwd()) { - m_bit_idx++; - if (m_bit_idx >= 17) { - m_rd_it_valid = m_image.adv_it(current_track() , true , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA; - load_rd_word(); - } else { - time_to_distance(bit_size(bit), m_next_bit_pos, m_bit_timer); - } - } else { - if (m_bit_idx > 0) { - m_bit_idx--; - time_to_distance(-bit_size(bit), m_next_bit_pos, m_bit_timer); - } else { - m_rd_it_valid = m_image.adv_it(current_track() , false , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA; - load_rd_word(); - } - } - } else if (m_rw_stat == RW_WRITING) { - // Writing - // Tape pos is aligned with beginning of bit cell - bool bit = m_data_in; - if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) { - set_flg(true); - } - wr_bit(bit); - time_to_distance(bit_size(bit), m_next_bit_pos, m_bit_timer); - } - break; - - case TACHO_TMR_ID: - m_tape_pos = m_next_tacho_pos; - if (!BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) { - set_flg(true); - } - adjust_tacho_timer(); - break; - - case HOLE_TMR_ID: - m_tape_pos = m_next_hole_pos; - BIT_SET(m_stat_reg , STAT_REG_EOT_BIT); - update_sts(); - adjust_hole_timer(); - break; - - case INV_TMR_ID: - // In itself it does nothing (all work is in update_speed_pos) - break; - - default: - break; - } - LOG_TMR("%.6f TMR %d s=%.3f p=%d a=%d\n" , machine().time().as_double() , id , m_speed , m_tape_pos , m_accelerating); -} - -image_init_result hp9825_tape_device::internal_load(bool is_create) -{ - LOG("load %d\n" , is_create); - - device_reset(); - - io_generic io; - io.file = (device_image_interface *)this; - io.procs = &image_ioprocs; - io.filler = 0; - if (is_create) { - m_image.clear_tape(); - m_image.save_tape(&io); - } else if (!m_image.load_tape(&io)) { - LOG("load failed\n"); - seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); - set_tape_present(false); - return image_init_result::FAIL; - } - LOG("load OK\n"); - - m_image_dirty = false; - - set_tape_present(true); - return image_init_result::PASS; -} - -image_init_result hp9825_tape_device::call_load() -{ - return internal_load(false); -} - -image_init_result hp9825_tape_device::call_create(int format_type, util::option_resolution *format_options) -{ - return internal_load(true); -} - -void hp9825_tape_device::call_unload() -{ - LOG("call_unload dirty=%d\n" , m_image_dirty); - - device_reset(); - - if (m_image_dirty) { - io_generic io; - io.file = (device_image_interface *)this; - io.procs = &image_ioprocs; - io.filler = 0; - m_image.save_tape(&io); - m_image_dirty = false; - } - - m_image.clear_tape(); - set_tape_present(false); -} - -std::string hp9825_tape_device::call_display() -{ - // TODO: - return std::string(); -} - -const char *hp9825_tape_device::file_extensions() const -{ - return "hti"; } READ16_MEMBER(hp9825_tape_device::tape_r) @@ -398,16 +226,16 @@ WRITE16_MEMBER(hp9825_tape_device::tape_w) case 1: // R5: write command { - double old_set_point = get_speed_set_point(); auto old_cmd_reg = m_cmd_reg; m_cmd_reg = data; - check_for_speed_change(old_set_point); + check_for_speed_change(); // Direction bit is mirrored (inverted) in status register if (BIT(m_cmd_reg , CMD_REG_DIR_BIT)) { BIT_CLR(m_stat_reg , STAT_REG_DIR_BIT); } else { BIT_SET(m_stat_reg , STAT_REG_DIR_BIT); } + m_tape->set_track_no(!BIT(m_cmd_reg , CMD_REG_TRACK_SEL_BIT)); if (BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) { // DMA disabled m_search_complete = false; @@ -418,7 +246,7 @@ WRITE16_MEMBER(hp9825_tape_device::tape_w) if ((old_cmd_reg ^ m_cmd_reg) & (BIT_MASK<uint8_t>(CMD_REG_WR_GATE_BIT) | BIT_MASK<uint8_t>(CMD_REG_THRESHOLD_BIT))) { // Something changed in Wr gate or threshold bit, start rd/wr - update_speed_pos(); + m_tape->update_speed_pos(); start_rd_wr(); } } @@ -436,7 +264,7 @@ WRITE16_MEMBER(hp9825_tape_device::tape_w) case 3: // R7: reset status - if (m_present) { + if (!m_tape->cart_out_r()) { BIT_CLR(m_stat_reg , STAT_REG_COUT_BIT); update_sts(); } @@ -485,6 +313,92 @@ void hp9825_tape_device::set_flg(bool state) } } +WRITE_LINE_MEMBER(hp9825_tape_device::cart_out_w) +{ + LOG_DBG("cart_out_w %d\n" , state); + if (state) { + // STAT_REG_COUT_BIT is cleared by a write to R7 + BIT_SET(m_stat_reg, STAT_REG_COUT_BIT); + } + + if (m_tape->wpr_r()) { + BIT_SET(m_stat_reg, STAT_REG_WPR_BIT); + } else { + BIT_CLR(m_stat_reg, STAT_REG_WPR_BIT); + } + + update_sts(); +} + +WRITE_LINE_MEMBER(hp9825_tape_device::hole_w) +{ + if (state) { + LOG_DBG("hole_w\n"); + BIT_SET(m_stat_reg , STAT_REG_EOT_BIT); + update_sts(); + } +} + +WRITE_LINE_MEMBER(hp9825_tape_device::tacho_tick_w) +{ + if (state) { + LOG_DBG("tacho_tick_w\n"); + if (!BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) { + set_flg(true); + } + } +} + +WRITE_LINE_MEMBER(hp9825_tape_device::motion_w) +{ + if (state) { + LOG_DBG("motion_w\n"); + // Update MVG bit + if (m_tape->is_moving()) { + if (!BIT(m_stat_reg , STAT_REG_MVG_BIT)) { + BIT_SET(m_stat_reg , STAT_REG_MVG_BIT); + m_led_handler(true); + } + } else { + if (BIT(m_stat_reg , STAT_REG_MVG_BIT)) { + BIT_CLR(m_stat_reg , STAT_REG_MVG_BIT); + m_led_handler(false); + } + } + start_rd_wr(); + } +} + +WRITE_LINE_MEMBER(hp9825_tape_device::rd_bit_w) +{ + m_short_gap_timer->b_w(1); + m_short_gap_timer->b_w(0); + m_long_gap_timer->b_w(1); + m_long_gap_timer->b_w(0); + m_data_out = m_valid_bits && state; + if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) { + set_flg(true); + } + m_trans_cnt++; + LOG_DBG("TC %u IG %d VB %d\n" , m_trans_cnt , m_in_gap , m_valid_bits); + if ((m_trans_cnt & 0x0c) == 0x0c) { + m_valid_bits = true; + } + if (BIT(m_trans_cnt , 2) && m_in_gap) { + m_in_gap = false; + m_long_gap_timer->a_w(m_in_gap); + update_sts(); + } +} + +READ_LINE_MEMBER(hp9825_tape_device::wr_bit_r) +{ + if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) { + set_flg(true); + } + return m_data_in; +} + void hp9825_tape_device::update_sts() { // Inputs to STS computation: @@ -494,7 +408,6 @@ void hp9825_tape_device::update_sts() // STAT_REG_COUT_BIT // m_search_complete // m_in_gap - auto prev_set_point = get_speed_set_point(); auto prev_exception = m_exception; m_exception = @@ -503,7 +416,7 @@ void hp9825_tape_device::update_sts() m_search_complete; if (prev_exception != m_exception) { - check_for_speed_change(prev_set_point); + check_for_speed_change(); } m_no_go = m_exception && !BIT(m_cmd_reg , CMD_REG_MOTOR_BIT); @@ -526,23 +439,6 @@ void hp9825_tape_device::update_dmar() m_dmar_handler(m_dma_req && !BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)); } -void hp9825_tape_device::set_tape_present(bool present) -{ - m_present = present; - if (present) { - if (is_readonly()) { - BIT_SET(m_stat_reg, STAT_REG_WPR_BIT); - } else { - BIT_CLR(m_stat_reg, STAT_REG_WPR_BIT); - } - // STAT_REG_COUT_BIT is cleared by a write to R7 - } else { - BIT_SET(m_stat_reg, STAT_REG_COUT_BIT); - BIT_SET(m_stat_reg, STAT_REG_WPR_BIT); - update_sts(); - } -} - bool hp9825_tape_device::is_moving_fwd() const { return BIT(m_cmd_reg , CMD_REG_DIR_BIT); @@ -553,412 +449,37 @@ bool hp9825_tape_device::is_speed_fast() const return !BIT(m_cmd_reg , CMD_REG_SPEED_BIT); } -bool hp9825_tape_device::is_actual_dir_fwd() const +void hp9825_tape_device::check_for_speed_change() { - // Actual direction can be different from commanded direction in accelerated phases (e.g. when tape - // direction is turned around) - return m_speed == 0.0 ? is_moving_fwd() : m_speed > 0.0; -} + hp_dc100_tape_device::tape_speed_t new_speed; -double hp9825_tape_device::get_speed_set_point() const -{ if (m_exception || BIT(m_cmd_reg , CMD_REG_MOTOR_BIT)) { // Stop - return 0.0; + new_speed = hp_dc100_tape_device::SP_STOP; } else { - double c; - if (is_speed_fast()) { - // HS - c = FAST_SPEED; - } else { - // LS - c = SLOW_SPEED; - } - if (!is_moving_fwd()) { - // Reverse - c = -c; - } - return c; + new_speed = is_speed_fast() ? hp_dc100_tape_device::SP_FAST : + hp_dc100_tape_device::SP_SLOW; } -} - -void hp9825_tape_device::start_tape() -{ - LOG_DBG("Tape started %.6f p=%d\n" , machine().time().as_double() , m_tape_pos); - m_start_time = machine().time(); - m_accelerating = true; - m_speed = 0; -} -void hp9825_tape_device::stop_tape() -{ - LOG_DBG("Tape stops %.6f p=%d\n" , machine().time().as_double() , m_tape_pos); - m_start_time = attotime::never; - m_accelerating = false; - m_speed = 0; - m_tacho_timer->reset(); - m_hole_timer->reset(); - m_inv_timer->reset(); - stop_rd_wr(); -} + bool changed = m_tape->set_speed_setpoint(new_speed , is_moving_fwd()); -void hp9825_tape_device::check_for_speed_change(double prev_set_point) -{ - double set_point = get_speed_set_point(); - if (prev_set_point != set_point) { - update_speed_pos(); - LOG_DBG("Speed SP changed %f->%f %.6f p=%d\n" , prev_set_point , set_point , machine().time().as_double() , m_tape_pos); - // Speed set point changed, accelerate/decelerate - m_accelerating = true; - if (m_start_time.is_never()) { - // Tape starting now - start_tape(); - } + if (changed) { start_rd_wr(true); - adjust_tacho_timer(); - adjust_hole_timer(); - set_inv_timer(); - } -} - -void hp9825_tape_device::update_speed_pos() -{ - if (m_start_time.is_never()) { - // Tape stopped - return; - } - - attotime delta_time{machine().time() - m_start_time}; - if (delta_time.is_zero()) { - return; - } - m_start_time = machine().time(); - double delta_time_double = delta_time.as_double(); - - // Space in const A phase - double space_const_a; - // Time in const V phase - double time_const_v; - // Do R/W start/stop - bool rw_start_stop = false; - - if (m_accelerating) { - double set_point = get_speed_set_point(); - double time_to_const_v = fabs(set_point - m_speed) / ACCELERATION; - double acceleration = set_point > m_speed ? ACCELERATION : -ACCELERATION; - if (delta_time_double < time_to_const_v) { - space_const_a = const_a_space(acceleration , delta_time_double); - auto prev_speed = m_speed; - m_speed += delta_time_double * acceleration; - rw_start_stop = (fabs(prev_speed) >= MIN_RD_SPEED) != (fabs(m_speed) >= MIN_RD_SPEED); - if (prev_speed * m_speed < 0.0) { - // Direction inverted (speed sign flipped) - LOG_DBG("Dir inverted s=%f\n" , m_speed); - adjust_tacho_timer(); - adjust_hole_timer(); - } - set_inv_timer(); - time_const_v = 0.0; - } else { - space_const_a = const_a_space(acceleration , time_to_const_v); - time_const_v = delta_time_double - time_to_const_v; - LOG_DBG("Acceleration ends\n"); - m_accelerating = false; - m_speed = set_point; - m_inv_timer->reset(); - if (m_speed == 0.0) { - // Tape stops - stop_tape(); - } else { - rw_start_stop = true; - } - } - } else { - space_const_a = 0.0; - time_const_v = delta_time_double; - } - - // Update MVG bit - if (fabs(m_speed) >= MOVING_THRESHOLD) { - if (!BIT(m_stat_reg , STAT_REG_MVG_BIT)) { - BIT_SET(m_stat_reg , STAT_REG_MVG_BIT); - m_led_handler(true); - } - } else { - if (BIT(m_stat_reg , STAT_REG_MVG_BIT)) { - BIT_CLR(m_stat_reg , STAT_REG_MVG_BIT); - m_led_handler(false); - } - } - - hti_format_t::tape_pos_t delta_pos = (hti_format_t::tape_pos_t)((space_const_a + m_speed * time_const_v) * hti_format_t::ONE_INCH_POS); - LOG_DBG("dp=%d\n" , delta_pos); - if (!hti_format_t::pos_offset(m_tape_pos , true , delta_pos)) { - LOG("Tape unspooled!\n"); - } - - if (rw_start_stop) { - start_rd_wr(); - } -} - -void hp9825_tape_device::set_inv_timer() -{ - // Set inversion timer to go off at the moment the tape inverts its motion - // (i.e. when m_speed crosses 0) - if (m_speed == 0.0 || (get_speed_set_point() * m_speed) > 0.0) { - LOG_DBG("Inv tmr reset\n"); - // No inversion in sight - m_inv_timer->reset(); - } else { - // INVERSION_MARGIN is added to ensure that m_speed has already cleared the - // 0-crossing when inv timer goes off - LOG_DBG("Inv tmr set %.6f %f\n" , machine().time().as_double() , m_speed); - m_inv_timer->adjust(attotime::from_double(fabs(m_speed) / ACCELERATION + INVERSION_MARGIN)); - } -} - -void hp9825_tape_device::time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const -{ - if (m_start_time.is_never()) { - // If tape is stopped we'll never get there.. - target_timer->reset(); - return; - } - - target_pos = m_tape_pos; - if (!hti_format_t::pos_offset(target_pos , true , distance)) { - // Beyond end of tape - target_timer->reset(); - return; - } - - double space = double(distance) / hti_format_t::ONE_INCH_POS; - double set_point = get_speed_set_point(); - double time_const_a; - - if (m_accelerating) { - // Time to reach constant V phase - double time_to_const_v = fabs(set_point - m_speed) / ACCELERATION; - // Signed acceleration - double acceleration = set_point > m_speed ? ACCELERATION : -ACCELERATION; - // Compute time to cover distance with constant acceleration - // It's the smallest non-negative root of this quadratic equation: - // 1/2*acceleration*t^2+m_speed*t=space - double delta = m_speed * m_speed + 2 * acceleration * space; - bool has_root = delta >= 0.0; - double time_in_const_a = 0.0; - if (has_root) { - double time_in_const_a_pos = (sqrt(delta) - m_speed) / acceleration; - double time_in_const_a_neg = -(sqrt(delta) + m_speed) / acceleration; - LOG_DBG("TTD %.6f %.6f\n" , time_in_const_a_pos , time_in_const_a_neg); - if (time_in_const_a_pos >= 0.0) { - if (time_in_const_a_neg >= 0.0) { - // pos + neg + - time_in_const_a = std::min(time_in_const_a_pos , time_in_const_a_neg); - } else { - // pos + neg - - time_in_const_a = time_in_const_a_pos; - } - } else { - if (time_in_const_a_neg >= 0.0) { - // pos - neg + - time_in_const_a = time_in_const_a_neg; - } else { - // pos - neg - - has_root = false; - } - } - } - LOG_DBG("TTD %d %d %.6f %.6f %.6f\n" , distance , has_root , m_speed , time_to_const_v , time_in_const_a); - if (has_root && time_in_const_a <= time_to_const_v) { - // Entirely in the constant A phase - time_const_a = time_in_const_a; - space = 0.0; - } else { - // Partly in const A & partly in const V - double space_in_const_a = const_a_space(acceleration , time_to_const_v); - space -= space_in_const_a; - time_const_a = time_to_const_v; - } - } else { - // Entirely in const V - time_const_a = 0.0; - } - - // Time in constant V - double time_const_v; - if (space != 0.0) { - if (set_point == 0.0) { - target_timer->reset(); - return; - } else { - time_const_v = space / set_point; - if (time_const_v < 0.0) { - target_timer->reset(); - return; - } - } - } else { - time_const_v = 0.0; - } - LOG_DBG("TTD %.6f %.6f\n" , time_const_a , time_const_v); - - target_timer->adjust(attotime::from_double(time_const_a + time_const_v)); -} - -double hp9825_tape_device::const_a_space(double a , double t) const -{ - // Space traveled in time 't' at constant acceleration 'a' starting with 'm_speed' speed - return t * (m_speed + a / 2 * t); -} - -hti_format_t::tape_pos_t hp9825_tape_device::get_next_hole() const -{ - return hti_format_t::next_hole(m_tape_pos , is_actual_dir_fwd()) - m_tape_pos; -} - -void hp9825_tape_device::adjust_tacho_timer() -{ - hti_format_t::tape_pos_t tick = TACH_TICK_LENGTH; - if (!is_actual_dir_fwd()) { - tick = -tick; } - time_to_distance(tick , m_next_tacho_pos , m_tacho_timer); -} - -void hp9825_tape_device::adjust_hole_timer() -{ - time_to_distance(get_next_hole() , m_next_hole_pos , m_hole_timer); -} - -unsigned hp9825_tape_device::current_track() const -{ - return !BIT(m_cmd_reg , CMD_REG_TRACK_SEL_BIT); -} - -constexpr hti_format_t::tape_pos_t hp9825_tape_device::bit_size(bool bit) -{ - return bit ? hti_format_t::ONE_BIT_LEN : hti_format_t::ZERO_BIT_LEN; } void hp9825_tape_device::start_rd_wr(bool recalc) { - if (fabs(m_speed) >= MIN_RD_SPEED && BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT)) { + if (m_tape->is_above_threshold() && BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT)) { // Reading - if (m_rw_stat != RW_READING || recalc) { - stop_rd_wr(); - LOG_DBG("Start RD @%d s=%f t=%u\n" , m_tape_pos , m_speed , current_track()); - m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , is_actual_dir_fwd() , false , m_rd_it); - m_rw_stat = RW_READING; - load_rd_word(); - } - } else if (!m_accelerating && !BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT) && BIT(m_cmd_reg , CMD_REG_THRESHOLD_BIT)) { + m_tape->set_op(hp_dc100_tape_device::OP_READ , recalc); + } else if (!m_tape->is_accelerating() && !BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT) && BIT(m_cmd_reg , CMD_REG_THRESHOLD_BIT)) { // Data writing - if (m_rw_stat != RW_WRITING) { - stop_rd_wr(); - // Start WR, only LS FWD is allowed - if (m_speed == SLOW_SPEED) { - LOG_DBG("Start WR @%d\n" , m_tape_pos); - // Looking for sync - m_bit_idx = 17; - m_rw_word = 0; - time_to_distance(bit_size(true) , m_next_bit_pos , m_bit_timer); - m_rw_stat = RW_WRITING; - } else { - LOG("Starting WR s=%f ???\n" , m_speed); - } - } + m_tape->set_op(hp_dc100_tape_device::OP_WRITE); } else if (!BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT) && !BIT(m_cmd_reg , CMD_REG_THRESHOLD_BIT)) { // Gap writing - if (m_rw_stat != RW_WRITING_GAP) { - stop_rd_wr(); - LOG_DBG("Start GAP @%d\n" , m_tape_pos); - m_gap_start = m_tape_pos; - m_rw_stat = RW_WRITING_GAP; - } + m_tape->set_op(hp_dc100_tape_device::OP_ERASE); } else { - stop_rd_wr(); - } -} - -void hp9825_tape_device::load_rd_word() -{ - if (m_rd_it_valid) { - if (is_moving_fwd()) { - m_bit_idx = 0; - } else { - m_bit_idx = 16; - } - // This is actually the nearest end (dir is inverted) - m_next_bit_pos = hti_format_t::farthest_end(m_rd_it , !is_actual_dir_fwd()); - LOG_DBG("Valid np=%d\n" , m_next_bit_pos); - time_to_distance(m_next_bit_pos - m_tape_pos , m_next_bit_pos , m_bit_timer); - } else { - LOG_DBG("Invalid\n"); - stop_rd_wr(); - } -} - -void hp9825_tape_device::rd_bit(bool bit) -{ - m_short_gap_timer->b_w(1); - m_short_gap_timer->b_w(0); - m_long_gap_timer->b_w(1); - m_long_gap_timer->b_w(0); - m_data_out = m_valid_bits && bit; - if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) { - set_flg(true); - } - m_trans_cnt++; - LOG_DBG("TC %u IG %d VB %d\n" , m_trans_cnt , m_in_gap , m_valid_bits); - if ((m_trans_cnt & 0x0c) == 0x0c) { - m_valid_bits = true; - } - if (BIT(m_trans_cnt , 2) && m_in_gap) { - m_in_gap = false; - m_long_gap_timer->a_w(m_in_gap); - update_sts(); + m_tape->set_op(hp_dc100_tape_device::OP_IDLE); } } - -void hp9825_tape_device::wr_bit(bool bit) -{ - LOG_DBG("WR %d idx=%u\n" , bit , m_bit_idx); - m_rw_word <<= 1; - m_rw_word |= bit; - - // A bit of heuristic here to achieve synchronization (i.e. find word boundary in bit stream) - if (m_bit_idx >= 17 && m_bit_idx < (17 + 16)) { - m_bit_idx++; - } else if (m_bit_idx == (17 + 16) && (m_rw_word & 3) == 3) { - m_bit_idx = 16; - } - if (m_bit_idx < 16) { - m_bit_idx++; - } else if (m_bit_idx == 16) { - // Write word - if (!BIT(m_rw_word , 0)) { - LOG_DBG("Sync lost! w=%05x\n" , m_rw_word & 0x1ffff); - } else { - hti_format_t::tape_word_t w = (hti_format_t::tape_word_t)(m_rw_word >> 1); - hti_format_t::tape_pos_t wr_pos = m_tape_pos - hti_format_t::word_length(w) + hti_format_t::ONE_BIT_LEN; - LOG_DBG("Wr word %04x @%d\n" , w , wr_pos); - m_image.write_word(current_track() , wr_pos , w , wr_pos); - m_image_dirty = true; - m_bit_idx = 0; - } - } -} - -void hp9825_tape_device::stop_rd_wr() -{ - if (m_rw_stat == RW_WRITING_GAP) { - LOG_DBG("Wr gap from %d to %d\n" , m_gap_start , m_tape_pos); - m_image.write_gap(current_track() , m_gap_start , m_tape_pos); - m_image_dirty = true; - } - m_bit_timer->reset(); - m_rw_stat = RW_IDLE; -} - diff --git a/src/mame/machine/hp9825_tape.h b/src/mame/machine/hp9825_tape.h index c2e13ef1898..7605db758cb 100644 --- a/src/mame/machine/hp9825_tape.h +++ b/src/mame/machine/hp9825_tape.h @@ -14,9 +14,10 @@ #pragma once #include "formats/hti_tape.h" +#include "machine/hp_dc100_tape.h" #include "machine/74123.h" -class hp9825_tape_device : public device_t , public device_image_interface +class hp9825_tape_device : public device_t { public: // construction/destruction @@ -26,20 +27,6 @@ public: virtual void device_add_mconfig(machine_config &config) override; virtual void device_start() override; virtual void device_reset() override; - virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; - - // device_image_interface overrides - virtual image_init_result call_load() override; - virtual image_init_result call_create(int format_type, util::option_resolution *format_options) override; - virtual void call_unload() override; - virtual std::string 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; } - virtual bool is_creatable() const override { return true; } - virtual bool must_be_loaded() const override { return false; } - virtual bool is_reset_on_load() const override { return false; } - virtual const char *file_extensions() const override; DECLARE_READ16_MEMBER(tape_r); DECLARE_WRITE16_MEMBER(tape_w); @@ -52,12 +39,20 @@ public: DECLARE_WRITE_LINE_MEMBER(short_gap_w); DECLARE_WRITE_LINE_MEMBER(long_gap_w); + DECLARE_WRITE_LINE_MEMBER(cart_out_w); + DECLARE_WRITE_LINE_MEMBER(hole_w); + DECLARE_WRITE_LINE_MEMBER(tacho_tick_w); + DECLARE_WRITE_LINE_MEMBER(motion_w); + DECLARE_WRITE_LINE_MEMBER(rd_bit_w); + DECLARE_READ_LINE_MEMBER(wr_bit_r); + private: devcb_write_line m_flg_handler; devcb_write_line m_sts_handler; devcb_write_line m_dmar_handler; devcb_write_line m_led_handler; + required_device<hp_dc100_tape_device> m_tape; required_device<ttl74123_device> m_short_gap_timer; // U43a required_device<ttl74123_device> m_long_gap_timer; // U43b @@ -75,73 +70,19 @@ private: bool m_dma_req; // U9-9 bool m_in_gap; // U39-4 bool m_no_go; // U6-3 - bool m_present; bool m_valid_bits; // U39-5 uint8_t m_trans_cnt; // U42 bool m_short_gap_out; // U43-13 bool m_long_gap_out; // U43-5 - // Timers - emu_timer *m_bit_timer; - emu_timer *m_tacho_timer; - emu_timer *m_hole_timer; - emu_timer *m_inv_timer; - - // Image of tape - hti_format_t m_image; - bool m_image_dirty; - - // Tape motion - hti_format_t::tape_pos_t m_tape_pos; - hti_format_t::tape_pos_t m_next_bit_pos; - hti_format_t::tape_pos_t m_next_tacho_pos; - hti_format_t::tape_pos_t m_next_hole_pos; - double m_speed; - attotime m_start_time; // Tape moving if != never - bool m_accelerating; - - // R/W - enum { - RW_IDLE, - RW_READING, - RW_WRITING, - RW_WRITING_GAP - }; - - int m_rw_stat; - hti_format_t::track_iterator_t m_rd_it; - bool m_rd_it_valid; - uint32_t m_rw_word; // Need 17 bits because of sync bit - unsigned m_bit_idx; // 0 is MSB, 15 is LSB, 16 is sync bit, >16 means "looking for sync" - hti_format_t::tape_pos_t m_gap_start; - void clear_state(); - image_init_result internal_load(bool is_create); void set_flg(bool state); void update_sts(); void update_dmar(); - void set_tape_present(bool present); bool is_moving_fwd() const; bool is_speed_fast() const; - bool is_actual_dir_fwd() const; - double get_speed_set_point() const; - void start_tape(); - void stop_tape(); - void check_for_speed_change(double prev_set_point); - void update_speed_pos(); - void set_inv_timer(); - void time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const; - double const_a_space(double a , double t) const; - hti_format_t::tape_pos_t get_next_hole() const; - void adjust_tacho_timer(); - void adjust_hole_timer(); - unsigned current_track() const; - static constexpr hti_format_t::tape_pos_t bit_size(bool bit); + void check_for_speed_change(); void start_rd_wr(bool recalc = false); - void load_rd_word(); - void rd_bit(bool bit); - void wr_bit(bool bit); - void stop_rd_wr(); }; // device type definition diff --git a/src/tools/imgtool/modules/hp85_tape.cpp b/src/tools/imgtool/modules/hp85_tape.cpp index 599a2a43a92..dada87e290a 100644 --- a/src/tools/imgtool/modules/hp85_tape.cpp +++ b/src/tools/imgtool/modules/hp85_tape.cpp @@ -43,8 +43,8 @@ static constexpr tape_pos_t FMT_IFG_SIZE = 2.5 * hti_format_t::ONE_INCH_POS; // Formatted size of IRGs: 1" static constexpr tape_pos_t FMT_IRG_SIZE = hti_format_t::ONE_INCH_POS; -// Formatted size of records: 2.85" -static constexpr tape_pos_t FMT_REC_SIZE = 2.85 * hti_format_t::ONE_INCH_POS; +// Formatted size of records: 2.67" +static constexpr tape_pos_t FMT_REC_SIZE = 2.67 * hti_format_t::ONE_INCH_POS; // Starting position on tracks: 74" from beginning of tape static constexpr tape_pos_t TRACK_START = 74 * hti_format_t::ONE_INCH_POS; @@ -145,6 +145,7 @@ typedef struct { tape_image_85::tape_image_85(void) : dirty(false) { + image.set_bits_per_word(16); } void tape_image_85::format_img(void) @@ -263,22 +264,24 @@ bool tape_image_85::load_sif_file(file_no_t file_no , sif_file_t& out) if (!image.next_data(track , pos , true , false , it)) { break; } - // 0 Sync word - // 1 File word - // 2 Record word - // 3 Length word - // 4 Checksum - tape_word_t hdr[ 5 ]; - auto res = hti_format_t::ADV_CONT_DATA; - for (unsigned i = 0; i < 5; i++) { + unsigned bit_idx = 15; + if (!image.sync_with_record(track , it , bit_idx)) { + // Couldn't align + return false; + } + + // 0 File word + // 1 Record word + // 2 Length word + // 3 Checksum + tape_word_t hdr[ 4 ]; + for (unsigned i = 0; i < 4; i++) { + auto res = image.next_word(track , it , bit_idx , hdr[ i ]); if (res != hti_format_t::ADV_CONT_DATA) { return false; } - hdr[ i ] = it->second; - res = image.adv_it(track , true , it); } - if (hdr[ 0 ] != SYNC_WORD || - checksum(&hdr[ 1 ] , 3) != hdr[ 4 ]) { + if (checksum(&hdr[ 0 ] , 3) != hdr[ 3 ]) { return false; } @@ -287,7 +290,7 @@ bool tape_image_85::load_sif_file(file_no_t file_no , sif_file_t& out) bool hdr_has_body; unsigned hdr_body_len; - if (!dec_rec_header(&hdr[ 1 ] , hdr_file_no , hdr_rec_no , hdr_has_body , hdr_body_len)) { + if (!dec_rec_header(&hdr[ 0 ] , hdr_file_no , hdr_rec_no , hdr_has_body , hdr_body_len)) { return false; } @@ -304,11 +307,10 @@ bool tape_image_85::load_sif_file(file_no_t file_no , sif_file_t& out) tape_word_t body[ MAX_RECORD_SIZE / 2 + 1 ]; unsigned word_no = (hdr_body_len + 1) / 2 + 1; for (unsigned i = 0; i < word_no; i++) { + auto res = image.next_word(track , it , bit_idx , body[ i ]); if (res != hti_format_t::ADV_CONT_DATA) { return false; } - body[ i ] = it->second; - res = image.adv_it(track , true , it); } if (checksum(&body[ 0 ] , word_no - 1) != body[ word_no - 1 ]) { return false; diff --git a/src/tools/imgtool/modules/hp9845_tape.cpp b/src/tools/imgtool/modules/hp9845_tape.cpp index b7edf371f88..d3416deaefd 100644 --- a/src/tools/imgtool/modules/hp9845_tape.cpp +++ b/src/tools/imgtool/modules/hp9845_tape.cpp @@ -148,10 +148,10 @@ #define BYTES_AVAILABLE_MASK 0xff00 // Mask of "bytes available" field #define BYTES_USED 0x00ff // "bytes used" field = 256 #define BYTES_USED_MASK 0x00ff // Mask of "bytes used" field -#define FORMAT_SECT_SIZE ((tape_pos_t)(2.85 * hti_format_t::ONE_INCH_POS)) // Size of sectors including padding: 2.85" -#define PREAMBLE_WORD 0 // Value of preamble word +#define FORMAT_SECT_SIZE ((tape_pos_t)(2.67 * hti_format_t::ONE_INCH_POS)) // Size of sectors including padding: 2.67" +#define PREAMBLE_WORD 0x0001 // Value of preamble word #define WORDS_PER_HEADER_N_SECTOR (WORDS_PER_SECTOR + 5) -#define MIN_IRG_SIZE ((tape_pos_t)(0.066 * hti_format_t::ONE_INCH_POS)) // Minimum size of IRG gaps: 0.066" +#define MIN_IRG_SIZE ((tape_pos_t)(16 * 1024)) // Minimum size of IRG gaps: 0.017" // File types #define BKUP_FILETYPE 0 @@ -324,6 +324,7 @@ static const struct io_procs my_stream_procs = { imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream) { hti_format_t inp_image; + inp_image.set_bits_per_word(16); io_generic io; io.file = (void *)stream; @@ -357,16 +358,19 @@ imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream) } if (state == 1) { // Extract record data - if (it->second != PREAMBLE_WORD) { + + // The top 8 bits are ignored by TACO when aligning with preamble + unsigned bit_idx = 7; + if (!inp_image.sync_with_record(track , it , bit_idx)) { + // Couldn't align return IMGTOOLERR_CORRUPTIMAGE; } tape_word_t buffer[ WORDS_PER_HEADER_N_SECTOR ]; for (unsigned i = 0; i < WORDS_PER_HEADER_N_SECTOR; i++) { - auto res = inp_image.adv_it(track , true , it); + auto res = inp_image.next_word(track , it , bit_idx , buffer[ i ]); if (res != hti_format_t::ADV_CONT_DATA) { return IMGTOOLERR_CORRUPTIMAGE; } - buffer[ i ] = it->second; } if (buffer[ 3 ] != checksum(&buffer[ 0 ], 3) || buffer[ 4 + WORDS_PER_SECTOR ] != checksum(&buffer[ 4 ], WORDS_PER_SECTOR)) { |