summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/formats/hti_tape.cpp2
-rw-r--r--src/lib/formats/hti_tape.h6
-rw-r--r--src/mame/drivers/hp9825.cpp77
-rw-r--r--src/mame/machine/hp9825_tape.cpp963
-rw-r--r--src/mame/machine/hp9825_tape.h150
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