// license:BSD-3-Clause // copyright-holders:F. Ulivi /********************************************************************* 1ma6.cpp HP-85 tape controller (1MA6-0001) *Status of tape tests of service ROM* Test Code Description Status ================================== I Write protect OK P Status test OK Q Speed test OK R Hole detection Fails with "HOLE D" error (*1) S Write test OK T Read test OK U Record test OK (*2) *1 Hole test fails because it depends on the diameter of the holes being correct (this driver doesn't emulate it). *2 Record test is buggy (byte @74ab in service ROM should be 0x54). Test succeeds if this bug is corrected first. *********************************************************************/ #include "emu.h" #include "1ma6.h" // Debugging #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") // Bit manipulation namespace { static constexpr unsigned BIT_MASK(unsigned n) { return 1U << n; } template void BIT_CLR(T& w , unsigned n) { w &= ~(T)BIT_MASK(n); } template void BIT_SET(T& w , unsigned n) { w |= (T)BIT_MASK(n); } template void COPY_BIT(bool bit , T& w , unsigned n) { if (bit) { BIT_SET(w , n); } else { BIT_CLR(w , n); } } } // **** 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 constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / 32; // Minimum gap size (totally made up) constexpr hti_format_t::tape_pos_t MIN_GAP_SIZE = TACH_TICK_LEN; // Bits in control register enum control_bits : unsigned { CTL_TRACK_NO_BIT = 0, // Track selection CTL_POWER_UP_BIT = 1, // Tape controller power up CTL_MOTOR_ON_BIT = 2, // Motor control CTL_DIR_FWD_BIT = 3, // Tape direction = forward CTL_FAST_BIT = 4, // Speed = fast CTL_WRITE_DATA_BIT = 5, // Write data CTL_WRITE_SYNC_BIT = 6, // Write SYNC CTL_WRITE_GAP_BIT = 7 // Write gap }; // Bits in status register enum status_bits : unsigned { STS_CASSETTE_IN_BIT = 0, // Cassette in STS_STALL_BIT = 1, // Tape stalled STS_ILIM_BIT = 2, // Overcurrent STS_WRITE_EN_BIT = 3, // Write enabled STS_HOLE_BIT = 4, // Hole detected STS_GAP_BIT = 5, // Gap detected STS_TACH_BIT = 6, // Tachometer tick STS_READY_BIT = 7 // Ready }; // Timers enum { TAPE_TMR_ID, HOLE_TMR_ID }; 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) , m_tape(*this , "drive") { clear_state(); } void hp_1ma6_device::reg_w(offs_t offset, uint8_t data) { LOG("WR %u=%02x\n" , offset , data); switch(offset) { case 0: // Control register start_cmd_exec(data); break; case 1: // Data register m_data_reg = data; break; } } uint8_t hp_1ma6_device::reg_r(offs_t offset) { uint8_t res = 0; switch (offset) { case 0: // Status register 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 || m_cmd_state == CMD_FAST_FWD_REV || m_cmd_state == CMD_WR_GAP) { BIT_CLR(m_status_reg , STS_READY_BIT); } if ((m_control_reg & (BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT))) != 0 && (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 || m_tape->gap_reached(MIN_GAP_SIZE)) { BIT_SET(m_status_reg , STS_GAP_BIT); } res = m_status_reg; // Clear latching bits BIT_CLR(m_status_reg , STS_HOLE_BIT); 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_tape->cart_out_r()) { BIT_SET(m_status_reg , STS_CASSETTE_IN_BIT); } break; case 1: // Data register res = m_data_reg; break; } LOG("RD %u=%02x\n" , offset , res); return res; } void hp_1ma6_device::cart_out_w(int state) { 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::hole_w(int state) { if (state) { LOG_DBG("hole_w\n"); BIT_SET(m_status_reg , STS_HOLE_BIT); } } void hp_1ma6_device::tacho_tick_w(int state) { if (state) { LOG_DBG("tacho_tick_w\n"); BIT_SET(m_status_reg , STS_TACH_BIT); } } void hp_1ma6_device::motion_w(int state) { 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; } 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; } 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; } 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; } 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; } m_tape->set_op(op); } break; default: break; } } } void hp_1ma6_device::rd_bit_w(int state) { 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 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: break; } } int hp_1ma6_device::wr_bit_r() { 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; } void hp_1ma6_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_LEN); m_tape->set_image_format(hti_format_t::HTI_DELTA_MOD_16_BITS); 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::device_start() { 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)); } void hp_1ma6_device::device_reset() { clear_state(); } void hp_1ma6_device::clear_state() { m_data_reg = 0; m_status_reg = 0; m_control_reg = 0; m_bit_idx = 0; m_data_sr = 0; m_cmd_state = CMD_IDLE; } void hp_1ma6_device::load_wr_word() { 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) { m_control_reg = new_ctl_reg; 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->set_op(hp_dc100_tape_device::OP_IDLE); m_tape->set_track_no(BIT(m_control_reg , CTL_TRACK_NO_BIT)); 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_tape->set_speed_setpoint(new_speed , BIT(new_ctl_reg , CTL_DIR_FWD_BIT)); motion_w(1); }