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