// license:BSD-3-Clause // copyright-holders:F. Ulivi /********************************************************************* hp9825_tape.cpp HP9825 tape sub-system *********************************************************************/ #include "emu.h" #include "hp9825_tape.h" #include "machine/rescap.h" // Debugging #include "logmacro.h" #define LOG_REG_MASK (LOG_GENERAL << 1) #define LOG_REG(...) LOGMASKED(LOG_REG_MASK, __VA_ARGS__) #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 namespace { template constexpr T BIT_MASK(unsigned n) { return (T)1U << n; } template void BIT_CLR(T& w , unsigned n) { w &= ~BIT_MASK(n); } template void BIT_SET(T& w , unsigned n) { w |= BIT_MASK(n); } } // Constants constexpr double FAST_SPEED = 90.0; // Fast speed: 90 ips constexpr double SLOW_SPEED = 22.0; // Slow speed: 22 ips 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 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 enum : unsigned { CMD_REG_MOTOR_BIT = 7, // Motor on (0) CMD_REG_WR_GATE_BIT = 6, // Write gate (0) CMD_REG_SPEED_BIT = 5, // Tape speed (1 = slow) CMD_REG_DIR_BIT = 4, // Tape direction (1 = fwd) CMD_REG_FLG_SEL_BIT = 3, // FLG selection (0 = tacho pulses, 1 = bit clock) CMD_REG_THRESHOLD_BIT = 2, // Threshold selection CMD_REG_DMA_EN_BIT = 1, // DMA enable (0) CMD_REG_TRACK_SEL_BIT = 0 // Track selection (1 = A) }; // Bits in status register enum : unsigned { STAT_REG_WPR_BIT = 7, // Write protected (1) STAT_REG_DIR_BIT = 6, // Tape direction (1 = rev) STAT_REG_MVG_BIT = 5, // Tape moving (1) STAT_REG_GAP_BIT = 4, // Gap (1) or data (0) STAT_REG_COUT_BIT = 2, // Cartridge out (1) STAT_REG_SVF_BIT = 1, // Servo failure (1) STAT_REG_EOT_BIT = 0 // End of tape (1) }; // 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) , 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") { } 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)); m_short_gap_timer->set_capacitor_value(CAP_N(10)); m_short_gap_timer->set_a_pin_value(0); m_short_gap_timer->set_clear_pin_value(1); m_short_gap_timer->out_cb().set(FUNC(hp9825_tape_device::short_gap_w)); TTL74123(config , m_long_gap_timer , 0); m_long_gap_timer->set_connection_type(TTL74123_NOT_GROUNDED_NO_DIODE); m_long_gap_timer->set_resistor_value(RES_K(28.7)); m_long_gap_timer->set_capacitor_value(CAP_U(0.22)); m_long_gap_timer->set_clear_pin_value(1); m_long_gap_timer->out_cb().set(FUNC(hp9825_tape_device::long_gap_w)); } void hp9825_tape_device::device_start() { m_flg_handler.resolve_safe(); m_sts_handler.resolve_safe(); m_dmar_handler.resolve_safe(); m_led_handler.resolve_safe(); save_item(NAME(m_cmd_reg)); save_item(NAME(m_stat_reg)); save_item(NAME(m_flg)); save_item(NAME(m_sts)); save_item(NAME(m_data_out)); save_item(NAME(m_data_in)); save_item(NAME(m_exception)); save_item(NAME(m_search_complete)); save_item(NAME(m_dma_req)); save_item(NAME(m_in_gap)); save_item(NAME(m_no_go)); 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)); } void hp9825_tape_device::device_reset() { clear_state(); } void hp9825_tape_device::clear_state() { m_cmd_reg = ~0; m_stat_reg = 0; // Not actually reset in real hw m_flg = false; m_sts = true; m_data_out = false; m_data_in = false; m_exception = false; m_search_complete = false; m_dma_req = false; m_in_gap = true; m_no_go = false; m_valid_bits = false; m_trans_cnt = 0; m_short_gap_timer->b_w(0); m_long_gap_timer->a_w(1); m_long_gap_timer->b_w(0); m_flg_handler(false); m_sts_handler(true); m_dmar_handler(false); m_led_handler(false); } READ16_MEMBER(hp9825_tape_device::tape_r) { uint16_t res = 0; switch (offset) { case 0: // R4: read data out if (m_data_out) { BIT_SET(res , 0); } if (!m_no_go) { set_flg(false); } break; case 1: // R5: read status res = m_stat_reg; break; case 2: // R6: clear EOT BIT_CLR(m_stat_reg , STAT_REG_EOT_BIT); update_sts(); break; default: logerror("Reading @ offset %u\n" , offset); break; } LOG_REG("R R%u=%02x\n" , offset + 4 , res); return res; } WRITE16_MEMBER(hp9825_tape_device::tape_w) { LOG_REG("W R%u=%02x\n" , offset + 4 , data); switch (offset) { case 0: // R4: write data in m_data_in = BIT(data , 0); m_dma_req = false; update_dmar(); if (!m_no_go) { set_flg(false); } break; case 1: // R5: write command { auto old_cmd_reg = m_cmd_reg; m_cmd_reg = data; 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; } update_sts(); update_dmar(); if ((old_cmd_reg ^ m_cmd_reg) & (BIT_MASK(CMD_REG_WR_GATE_BIT) | BIT_MASK(CMD_REG_THRESHOLD_BIT))) { // Something changed in Wr gate or threshold bit, start rd/wr m_tape->update_speed_pos(); start_rd_wr(); } } break; case 2: // R6: clear DMA if (!BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) { m_search_complete = true; update_sts(); } m_dma_req = false; update_dmar(); break; case 3: // R7: reset status if (!m_tape->cart_out_r()) { BIT_CLR(m_stat_reg , STAT_REG_COUT_BIT); update_sts(); } break; default: logerror("Writing @ offset %u\n" , offset); break; } } WRITE_LINE_MEMBER(hp9825_tape_device::short_gap_w) { LOG_DBG("Short gap %d\n" , state); m_short_gap_out = state; if (!m_short_gap_out) { m_valid_bits = false; m_data_out = false; m_trans_cnt = 0; m_in_gap = true; m_long_gap_timer->a_w(m_in_gap); update_sts(); } } WRITE_LINE_MEMBER(hp9825_tape_device::long_gap_w) { LOG_DBG("Long gap %d\n" , state); if (m_long_gap_out && !state && !BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) { m_dma_req = true; update_dmar(); } m_long_gap_out = state; if (m_long_gap_out) { BIT_CLR(m_stat_reg , STAT_REG_GAP_BIT); } else { BIT_SET(m_stat_reg , STAT_REG_GAP_BIT); } } void hp9825_tape_device::set_flg(bool state) { if (state != m_flg) { m_flg = state; m_flg_handler(m_flg); } } 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: // CMD_REG_MOTOR_BIT // CMD_REG_DMA_EN_BIT // STAT_REG_EOT_BIT // STAT_REG_COUT_BIT // m_search_complete // m_in_gap auto prev_exception = m_exception; m_exception = BIT(m_stat_reg , STAT_REG_EOT_BIT) || BIT(m_stat_reg , STAT_REG_COUT_BIT) || m_search_complete; if (prev_exception != m_exception) { check_for_speed_change(); } m_no_go = m_exception && !BIT(m_cmd_reg , CMD_REG_MOTOR_BIT); // U6-6 bool sts_2 = m_in_gap && BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT); bool new_sts = m_no_go || sts_2; if (new_sts != m_sts) { m_sts = new_sts; m_sts_handler(m_sts); } if (m_no_go) { set_flg(true); } } void hp9825_tape_device::update_dmar() { m_dmar_handler(m_dma_req && !BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)); } bool hp9825_tape_device::is_moving_fwd() const { return BIT(m_cmd_reg , CMD_REG_DIR_BIT); } bool hp9825_tape_device::is_speed_fast() const { return !BIT(m_cmd_reg , CMD_REG_SPEED_BIT); } void hp9825_tape_device::check_for_speed_change() { hp_dc100_tape_device::tape_speed_t new_speed; if (m_exception || BIT(m_cmd_reg , CMD_REG_MOTOR_BIT)) { // Stop new_speed = hp_dc100_tape_device::SP_STOP; } else { new_speed = is_speed_fast() ? hp_dc100_tape_device::SP_FAST : hp_dc100_tape_device::SP_SLOW; } bool changed = m_tape->set_speed_setpoint(new_speed , is_moving_fwd()); if (changed) { start_rd_wr(true); } } void hp9825_tape_device::start_rd_wr(bool recalc) { if (m_tape->is_above_threshold() && BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT)) { // Reading 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 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 m_tape->set_op(hp_dc100_tape_device::OP_ERASE); } else { m_tape->set_op(hp_dc100_tape_device::OP_IDLE); } }