diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/formats/hti_tape.cpp | 2 | ||||
-rw-r--r-- | src/lib/formats/hti_tape.h | 6 | ||||
-rw-r--r-- | src/mame/drivers/hp9825.cpp | 77 | ||||
-rw-r--r-- | src/mame/machine/hp9825_tape.cpp | 963 | ||||
-rw-r--r-- | src/mame/machine/hp9825_tape.h | 150 |
5 files changed, 1190 insertions, 8 deletions
diff --git a/src/lib/formats/hti_tape.cpp b/src/lib/formats/hti_tape.cpp index 92f28228a59..f046ea0c130 100644 --- a/src/lib/formats/hti_tape.cpp +++ b/src/lib/formats/hti_tape.cpp @@ -10,8 +10,6 @@ #include "imageutl.h" static constexpr uint32_t FILE_MAGIC = 0x5441434f; // Magic value at start of image file: "TACO" -static constexpr hti_format_t::tape_pos_t ZERO_BIT_LEN = 619; // Length of 0 bits at slow tape speed: 1/(35200 Hz) -static constexpr hti_format_t::tape_pos_t ONE_BIT_LEN = 1083; // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN // *** Position of tape holes *** // At beginning of tape: diff --git a/src/lib/formats/hti_tape.h b/src/lib/formats/hti_tape.h index 3366b48f697..4c5e5ed3279 100644 --- a/src/lib/formats/hti_tape.h +++ b/src/lib/formats/hti_tape.h @@ -33,6 +33,12 @@ public: // Tape length: 140 ft of usable tape + 72" of punched tape at either end static constexpr tape_pos_t TAPE_LENGTH = (140 * 12 + 72 * 2) * ONE_INCH_POS; + // Length of 0 bits at slow tape speed: 1/(35200 Hz) + static constexpr tape_pos_t ZERO_BIT_LEN = 619; + + // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN + static constexpr tape_pos_t ONE_BIT_LEN = 1083; + // Words stored on tape typedef uint16_t tape_word_t; diff --git a/src/mame/drivers/hp9825.cpp b/src/mame/drivers/hp9825.cpp index 251b256c9bf..b04967f41c1 100644 --- a/src/mame/drivers/hp9825.cpp +++ b/src/mame/drivers/hp9825.cpp @@ -13,11 +13,11 @@ // - 12 kw of system ROM // - Keyboard (SHIFT LOCK & RESET not implemented) // - Display & run light +// - DC100 tape drive // What's not yet in: // - Internal & external expansion ROMs // - Configurable RAM size // - Printer -// - DC100 tape drive // - I/O expansion slots: 98034 & 98035 modules from hp9845 emulation can be used here, too // - Beeper // @@ -27,6 +27,7 @@ #include "emu.h" #include "cpu/hphybrid/hphybrid.h" #include "machine/timer.h" +#include "machine/hp9825_tape.h" #include "hp9825.lh" // CPU clock (generated by a trimmered RC oscillator) @@ -35,6 +36,10 @@ constexpr unsigned MAIN_CLOCK = 6000000; // KDP chip clock constexpr unsigned KDP_CLOCK = MAIN_CLOCK / 4; +// Peripheral Addresses (PA) +constexpr uint8_t KDP_PA = 0; +constexpr uint8_t TAPE_PA = 1; + // Bit manipulation namespace { template<typename T> constexpr T BIT_MASK(unsigned n) @@ -60,6 +65,7 @@ public: : driver_device(mconfig, type, tag) , m_cpu(*this , "cpu") , m_cursor_timer(*this , "cursor_timer") + , m_tape(*this , "tape") , m_io_key(*this , "KEY%u" , 0) , m_shift_key(*this , "KEY_SHIFT") , m_display(*this , "char_%u_%u" , 0U , 0U) @@ -72,6 +78,7 @@ public: private: required_device<hp_09825_67907_cpu_device> m_cpu; required_device<timer_device> m_cursor_timer; + required_device<hp9825_tape_device> m_tape; required_ioport_array<4> m_io_key; required_ioport m_shift_key; output_finder<32 , 7> m_display; @@ -89,6 +96,10 @@ private: unsigned m_autorepeat_cnt; uint8_t m_irl_pending; uint8_t m_irh_pending; + // FLG/STS handling + uint8_t m_pa; + uint16_t m_flg_status; + uint16_t m_sts_status; virtual void machine_start() override; virtual void machine_reset() override; @@ -109,6 +120,11 @@ private: IRQ_CALLBACK_MEMBER(irq_callback); void update_irq(); void set_irq(uint8_t sc , int state); + + DECLARE_WRITE8_MEMBER(pa_w); + void update_flg_sts(); + void set_sts(uint8_t sc , int state); + void set_flg(uint8_t sc , int state); }; void hp9825_state::machine_start() @@ -139,13 +155,17 @@ void hp9825_state::machine_reset() m_irl_pending = 0; m_irh_pending = 0; update_irq(); + m_pa = 0; + m_flg_status = 0; + m_sts_status = 0; } void hp9825_state::cpu_io_map(address_map &map) { map.unmap_value_low(); - map(HP_MAKE_IOADDR(0 , 0) , HP_MAKE_IOADDR(0 , 0)).rw(FUNC(hp9825_state::kb_scancode_r) , FUNC(hp9825_state::disp_w)); - map(HP_MAKE_IOADDR(0 , 1) , HP_MAKE_IOADDR(0 , 1)).rw(FUNC(hp9825_state::kdp_status_r) , FUNC(hp9825_state::kdp_control_w)); + map(HP_MAKE_IOADDR(KDP_PA , 0) , HP_MAKE_IOADDR(KDP_PA , 0)).rw(FUNC(hp9825_state::kb_scancode_r) , FUNC(hp9825_state::disp_w)); + map(HP_MAKE_IOADDR(KDP_PA , 1) , HP_MAKE_IOADDR(KDP_PA , 1)).rw(FUNC(hp9825_state::kdp_status_r) , FUNC(hp9825_state::kdp_control_w)); + map(HP_MAKE_IOADDR(TAPE_PA , 0) , HP_MAKE_IOADDR(TAPE_PA , 3)).rw(m_tape , FUNC(hp9825_tape_device::tape_r) , FUNC(hp9825_tape_device::tape_w)); // TODO: } @@ -163,7 +183,7 @@ READ16_MEMBER(hp9825_state::kb_scancode_r) if (m_shift_key->read()) { BIT_SET(res , 7); } - set_irq(0 , false); + set_irq(KDP_PA , false); return res; } @@ -417,7 +437,7 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9825_state::kb_scan) if (max_seq_len) { m_scancode = max_seq_idx; m_key_pressed = true; - set_irq(0 , true); + set_irq(KDP_PA , true); } } @@ -431,7 +451,7 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9825_state::kb_scan) } if (m_autorepeating && BIT(~prev_cnt & m_autorepeat_cnt , 3)) { // Repeat key every time bit 3 of autorepeat counter goes 0->1 - set_irq(0 , true); + set_irq(KDP_PA , true); } } else { m_autorepeating = false; @@ -488,6 +508,44 @@ void hp9825_state::set_irq(uint8_t sc , int state) update_irq(); } +WRITE8_MEMBER(hp9825_state::pa_w) +{ + m_pa = data; + update_flg_sts(); +} + +void hp9825_state::update_flg_sts() +{ + bool sts = BIT(m_sts_status , m_pa); + bool flg = BIT(m_flg_status , m_pa); + m_cpu->status_w(sts); + m_cpu->flag_w(flg); +} + +void hp9825_state::set_sts(uint8_t sc , int state) +{ + if (state) { + BIT_SET(m_sts_status, sc); + } else { + BIT_CLR(m_sts_status, sc); + } + if (sc == m_pa) { + update_flg_sts(); + } +} + +void hp9825_state::set_flg(uint8_t sc , int state) +{ + if (state) { + BIT_SET(m_flg_status, sc); + } else { + BIT_CLR(m_flg_status, sc); + } + if (sc == m_pa) { + update_flg_sts(); + } +} + MACHINE_CONFIG_START(hp9825_state::hp9825b) HP_09825_67907(config , m_cpu , MAIN_CLOCK); // Just guessing... settings borrowed from hp9845 @@ -496,12 +554,19 @@ MACHINE_CONFIG_START(hp9825_state::hp9825b) m_cpu->set_addrmap(AS_PROGRAM , &hp9825_state::cpu_mem_map); m_cpu->set_addrmap(AS_IO , &hp9825_state::cpu_io_map); m_cpu->set_irq_acknowledge_callback(FUNC(hp9825_state::irq_callback)); + m_cpu->pa_changed_cb().set(FUNC(hp9825_state::pa_w)); TIMER(config , m_cursor_timer , 0).configure_generic(timer_device::expired_delegate(FUNC(hp9825_state::cursor_blink) , this)); // Keyboard scan timer. A scan of the whole keyboard should take 2^14 KDP clocks. TIMER(config , "kb_timer" , 0).configure_periodic(timer_device::expired_delegate(FUNC(hp9825_state::kb_scan) , this) , attotime::from_ticks(16384 , KDP_CLOCK)); + // Tape drive + HP9825_TAPE(config , m_tape , 0); + m_tape->flg().set([this , sc = TAPE_PA](int state) { set_flg(sc , state); }); + m_tape->sts().set([this , sc = TAPE_PA](int state) { set_sts(sc , state); }); + m_tape->dmar().set(m_cpu , FUNC(hp_09825_67907_cpu_device::dmar_w)); + config.set_default_layout(layout_hp9825); MACHINE_CONFIG_END diff --git a/src/mame/machine/hp9825_tape.cpp b/src/mame/machine/hp9825_tape.cpp new file mode 100644 index 00000000000..63234e2f3aa --- /dev/null +++ b/src/mame/machine/hp9825_tape.cpp @@ -0,0 +1,963 @@ +// 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_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(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__) +#undef VERBOSE +#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); + } +} + +// 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 +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) +}; + +// 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_short_gap_timer(*this , "short_tmr") + , m_long_gap_timer(*this , "long_tmr") + , m_image() + , m_image_dirty(false) +{ +} + +MACHINE_CONFIG_START(hp9825_tape_device::device_add_mconfig) + 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)); +MACHINE_CONFIG_END + +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(); + + 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)); + 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_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() +{ + 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_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); + m_long_gap_timer->b_w(0); + + m_flg_handler(false); + 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) +{ + 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 + { + 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); + // 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); + } + 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<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(); + 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_present) { + 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); + } +} + +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_set_point = get_speed_set_point(); + 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(prev_set_point); + } + + 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)); +} + +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); +} + +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 +{ + // 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; +} + +double hp9825_tape_device::get_speed_set_point() const +{ + if (m_exception || BIT(m_cmd_reg , CMD_REG_MOTOR_BIT)) { + // Stop + return 0.0; + } 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; + } +} + +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(); +} + +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(); + } + 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)) { + // 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)) { + // 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); + } + } + } 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; + } + } 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(); + } +} + +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 new file mode 100644 index 00000000000..48accb2e88f --- /dev/null +++ b/src/mame/machine/hp9825_tape.h @@ -0,0 +1,150 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp9825_tape.h + + HP9825 tape sub-system + +*********************************************************************/ + +#ifndef MAME_MACHINE_HP9825_TAPE_H +#define MAME_MACHINE_HP9825_TAPE_H + +#pragma once + +#include "formats/hti_tape.h" +#include "machine/74123.h" + +class hp9825_tape_device : public device_t , public device_image_interface +{ +public: + // construction/destruction + hp9825_tape_device(const machine_config &mconfig, 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_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); + + auto flg() { return m_flg_handler.bind(); } + auto sts() { return m_sts_handler.bind(); } + auto dmar() { return m_dmar_handler.bind(); } + auto led() { return m_led_handler.bind(); } + + DECLARE_WRITE_LINE_MEMBER(short_gap_w); + DECLARE_WRITE_LINE_MEMBER(long_gap_w); + +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<ttl74123_device> m_short_gap_timer; // U43a + required_device<ttl74123_device> m_long_gap_timer; // U43b + + // Registers + uint8_t m_cmd_reg; + uint8_t m_stat_reg; + + // State + bool m_flg; + bool m_sts; + bool m_data_out; // U38-9 + bool m_data_in; // U13-6 + bool m_exception; // U4-6 + bool m_search_complete; // U9-6 + 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 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 +DECLARE_DEVICE_TYPE(HP9825_TAPE, hp9825_tape_device) + +#endif // MAME_MACHINE_HP9825_TAPE_H |