hp9845: Introduced no-data timeouts throughout TACO device

This commit is contained in:
fulivi 2016-07-04 15:34:45 +02:00
parent 13d8279e49
commit ae64748f91
2 changed files with 99 additions and 67 deletions

View File

@ -131,9 +131,9 @@
// validate my solutions by running the original firmware in MAME, though (no real hw at hand). // validate my solutions by running the original firmware in MAME, though (no real hw at hand).
// //
// TODOs/issues: // TODOs/issues:
// * It seems like all commands expecting to read data off the tape have a kind of timeout. More // * Commands 00 & 10 seem to do the same things. My bet is that they differ in some subtle way that
// investigation is needed. // is not stimulated by all the tape software I used for R.E. Maybe it's just the length of the
// * Emulation of CMD_NOT_INDTA is not entirely ok, SIF utilities fail when using this command. // 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 // * Find more info on TACO chips (does anyone with a working 9845 or access to internal HP docs want to
// help me here, please?) // help me here, please?)
// //
@ -144,7 +144,7 @@
// Debugging // Debugging
#define VERBOSE 1 #define VERBOSE 1
#define LOG(x) do { if (VERBOSE) logerror x; } while (0) #define LOG(x) do { if (VERBOSE) logerror x; } while (0)
#define VERBOSE_0 0 #define VERBOSE_0 1
#define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) #define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0)
// Macros to clear/set single bits // Macros to clear/set single bits
@ -155,7 +155,8 @@
// Timers // Timers
enum { enum {
TAPE_TMR_ID, TAPE_TMR_ID,
HOLE_TMR_ID HOLE_TMR_ID,
TIMEOUT_TMR_ID
}; };
// Constants // Constants
@ -182,7 +183,8 @@ enum {
#define SHORT_GAP_LENGTH ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Minimum length of short gaps: 0.066" ([1], pg 8-10) #define SHORT_GAP_LENGTH ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Minimum length of short gaps: 0.066" ([1], pg 8-10)
#define LONG_GAP_LENGTH ((tape_pos_t)(1.5 * ONE_INCH_POS)) // Minimum length of long gaps: 1.5" ([1], pg 8-10) #define LONG_GAP_LENGTH ((tape_pos_t)(1.5 * ONE_INCH_POS)) // Minimum length of long gaps: 1.5" ([1], pg 8-10)
#define NULL_TAPE_POS ((tape_pos_t)-1) // Special value for invalid/unknown tape position #define NULL_TAPE_POS ((tape_pos_t)-1) // Special value for invalid/unknown tape position
#define NO_DATA_GAP (17 * ONE_BIT_LEN) // Minimum gap size to detect end of data: length of longest word (0xffff) #define PREAMBLE_TIMEOUT ((tape_pos_t)(2.6 * ONE_INCH_POS)) // Min. length of gap making preamble search time out (totally made up)
#define DATA_TIMEOUT ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Min. length of gap that will cause data reading to time out (totally made up)
#define FILE_MAGIC 0x4f434154 // Magic value at start of image file: "TACO" #define FILE_MAGIC 0x4f434154 // Magic value at start of image file: "TACO"
// Parts of command register // Parts of command register
@ -203,7 +205,7 @@ enum {
// Commands // Commands
enum { enum {
CMD_INDTA_INGAP, // 00: scan for data first then for gap CMD_INDTA_INGAP, // 00: scan for data first then for gap (see also cmd 10)
CMD_UNK_01, // 01: unknown CMD_UNK_01, // 01: unknown
CMD_FINAL_GAP, // 02: write final gap CMD_FINAL_GAP, // 02: write final gap
CMD_INIT_WRITE, // 03: write words for tape formatting CMD_INIT_WRITE, // 03: write words for tape formatting
@ -219,7 +221,7 @@ enum {
CMD_UNK_0d, // 0d: unknown CMD_UNK_0d, // 0d: unknown
CMD_CLEAR, // 0e: clear errors/unlatch status bits CMD_CLEAR, // 0e: clear errors/unlatch status bits
CMD_UNK_0f, // 0f: unknown CMD_UNK_0f, // 0f: unknown
CMD_NOT_INDTA, // 10: scan for end of data 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_11, // 11: unknown
CMD_UNK_12, // 12: unknown CMD_UNK_12, // 12: unknown
CMD_UNK_13, // 13: unknown CMD_UNK_13, // 13: unknown
@ -422,6 +424,7 @@ void hp_taco_device::device_start()
m_tape_timer = timer_alloc(TAPE_TMR_ID); m_tape_timer = timer_alloc(TAPE_TMR_ID);
m_hole_timer = timer_alloc(HOLE_TMR_ID); m_hole_timer = timer_alloc(HOLE_TMR_ID);
m_timeout_timer = timer_alloc(TIMEOUT_TMR_ID);
} }
// device_stop // device_stop
@ -467,12 +470,6 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para
m_rw_pos = m_tape_pos; m_rw_pos = m_tape_pos;
break; break;
case CMD_SCAN_RECORDS:
// Cmds 18 is terminated at first hole
terminate_cmd_now();
// No reloading of hole timer
return;
case CMD_MOVE_INGAP: case CMD_MOVE_INGAP:
m_hole_timer->adjust(time_to_next_hole()); m_hole_timer->adjust(time_to_next_hole());
// No IRQ at holes // No IRQ at holes
@ -485,8 +482,18 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para
freeze_tach_reg(true); freeze_tach_reg(true);
return; 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_START_READ:
case CMD_END_READ: case CMD_END_READ:
// Commands report failure at first hole
set_error(true); set_error(true);
break; break;
@ -500,6 +507,23 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para
m_hole_timer->adjust(time_to_next_hole()); m_hole_timer->adjust(time_to_next_hole());
break; 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);
}
break;
default:
// Most commands are terminated with failure on data T/O
terminate_cmd_now();
break;
}
set_error(true);
break;
default: default:
break; break;
} }
@ -684,7 +708,7 @@ hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const
} }
} }
// No more holes: will hit end of tape // No more holes: will hit end of tape
return TAPE_LENGTH; return NULL_TAPE_POS;
} else { } else {
for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) { for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) {
if (tape_holes[ i ] < m_tape_pos) { if (tape_holes[ i ] < m_tape_pos) {
@ -693,7 +717,7 @@ hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const
} }
} }
// No more holes: will hit start of tape // No more holes: will hit start of tape
return 0; return NULL_TAPE_POS;
} }
} }
@ -749,13 +773,15 @@ bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 m
BIT_CLR(m_status_reg, STATUS_GAP_BIT); BIT_CLR(m_status_reg, STATUS_GAP_BIT);
} }
if (!not_moving && !prev_tape_braking && prev_tape_fwd != m_tape_fwd) { if (!not_moving && prev_tape_fwd != m_tape_fwd) {
// Tape direction inverted, stop tape before executing command // Tape direction inverted, stop tape before executing command
m_tape_fwd = prev_tape_fwd; m_tape_fwd = prev_tape_fwd;
m_tape_fast = prev_tape_fast; m_tape_fast = prev_tape_fast;
m_cmd_state = CMD_INVERTING; m_cmd_state = CMD_INVERTING;
LOG_0(("Direction reversed! fwd = %d fast = %d\n" , m_tape_fwd , m_tape_fast)); 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()); m_tape_timer->adjust(time_to_stopping_pos());
}
} else { } else {
// No change in direction, immediate execution // No change in direction, immediate execution
m_cmd_state = CMD_PH0; m_cmd_state = CMD_PH0;
@ -763,7 +789,7 @@ bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 m
} }
m_hole_timer->reset(); m_hole_timer->reset();
m_timeout_timer->reset();
return true; return true;
} }
} }
@ -1146,7 +1172,13 @@ void hp_taco_device::set_tape_present(bool present)
attotime hp_taco_device::time_to_next_hole(void) const attotime hp_taco_device::time_to_next_hole(void) const
{ {
return time_to_target(next_hole()); tape_pos_t pos = next_hole();
if (pos == NULL_TAPE_POS) {
return attotime::never;
} else {
return time_to_target(pos);
}
} }
attotime hp_taco_device::time_to_tach_pulses(void) const attotime hp_taco_device::time_to_tach_pulses(void) const
@ -1159,6 +1191,13 @@ void hp_taco_device::terminate_cmd_now(void)
m_cmd_state = CMD_END; m_cmd_state = CMD_END;
m_tape_timer->adjust(attotime::zero); m_tape_timer->adjust(attotime::zero);
m_hole_timer->reset(); m_hole_timer->reset();
m_timeout_timer->reset();
}
void hp_taco_device::set_data_timeout(bool long_timeout)
{
attotime timeout = time_to_distance(long_timeout ? PREAMBLE_TIMEOUT : DATA_TIMEOUT);
m_timeout_timer->adjust(timeout , 0 , timeout);
} }
void hp_taco_device::cmd_fsm(void) void hp_taco_device::cmd_fsm(void)
@ -1167,6 +1206,7 @@ void hp_taco_device::cmd_fsm(void)
// Command ended // Command ended
m_cmd_state = CMD_IDLE; m_cmd_state = CMD_IDLE;
m_hole_timer->reset(); m_hole_timer->reset();
m_timeout_timer->reset();
irq_w(true); irq_w(true);
if (AUTO_STOP(m_cmd_reg)) { if (AUTO_STOP(m_cmd_reg)) {
// Automatic stop after command execution // Automatic stop after command execution
@ -1195,23 +1235,6 @@ void hp_taco_device::cmd_fsm(void)
} }
switch (CMD_CODE(m_cmd_reg)) { switch (CMD_CODE(m_cmd_reg)) {
case CMD_INDTA_INGAP:
if (m_cmd_state == CMD_PH0) {
// PH0
if (next_data(m_rd_it , m_tape_pos , true)) {
cmd_duration = time_to_target(farthest_end(m_rd_it));
}
m_cmd_state = CMD_PH1;
} else {
// PH1
tape_pos_t target = m_tape_pos;
if (next_n_gap(target, 1, min_gap_size())) {
cmd_duration = time_to_target(target);
}
m_cmd_state = CMD_END;
}
break;
case CMD_FINAL_GAP: case CMD_FINAL_GAP:
if (m_cmd_state == CMD_PH0) { if (m_cmd_state == CMD_PH0) {
// PH0 // PH0
@ -1232,6 +1255,8 @@ void hp_taco_device::cmd_fsm(void)
// Search for preamble first // Search for preamble first
m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false);
cmd_duration = time_to_rd_next_word(m_rw_pos); 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; m_cmd_state = CMD_PH1;
break; break;
} else if (m_cmd_state == CMD_PH1) { } else if (m_cmd_state == CMD_PH1) {
@ -1241,12 +1266,15 @@ void hp_taco_device::cmd_fsm(void)
m_cmd_state = CMD_PH2; m_cmd_state = CMD_PH2;
// m_rw_pos already at correct position // m_rw_pos already at correct position
cmd_duration = fetch_next_wr_word(); cmd_duration = fetch_next_wr_word();
m_timeout_timer->reset();
irq_w(true); irq_w(true);
} else { } else {
adv_res_t res = adv_it(m_rd_it); adv_res_t res = adv_it(m_rd_it);
if (res != ADV_NO_MORE_DATA) { if (res != ADV_NO_MORE_DATA) {
cmd_duration = time_to_rd_next_word(m_rw_pos); cmd_duration = time_to_rd_next_word(m_rw_pos);
} }
// Set T/O for arrival of data words
set_data_timeout(false);
} }
break; break;
} }
@ -1310,20 +1338,25 @@ void hp_taco_device::cmd_fsm(void)
} }
break; break;
case CMD_INDTA_INGAP:
case CMD_NOT_INDTA: case CMD_NOT_INDTA:
if (m_cmd_state == CMD_PH0) { if (m_cmd_state == CMD_PH0) {
// PH0 // PH0
if (next_data(m_rd_it , m_tape_pos , true)) { if (next_data(m_rd_it , m_tape_pos , true)) {
cmd_duration = time_to_target(farthest_end(m_rd_it)); cmd_duration = time_to_target(farthest_end(m_rd_it));
} }
// Set T/O for data
set_data_timeout(true);
m_cmd_state = CMD_PH1; m_cmd_state = CMD_PH1;
} else { } else {
// PH1 // PH1
tape_pos_t target = m_tape_pos; tape_pos_t target = m_tape_pos;
if (next_n_gap(target, 1, NO_DATA_GAP)) { if (next_n_gap(target, 1, min_gap_size())) {
LOG_0(("End of data @%d\n" , target)); LOG_0(("End of data @%d\n" , target));
cmd_duration = time_to_target(target); cmd_duration = time_to_target(target);
} }
// Got data, stop T/O
m_timeout_timer->reset();
m_cmd_state = CMD_END; m_cmd_state = CMD_END;
} }
break; break;
@ -1344,16 +1377,20 @@ void hp_taco_device::cmd_fsm(void)
break; break;
case CMD_SCAN_RECORDS: case CMD_SCAN_RECORDS:
// It's probably more correct to implement this to alternate between next data and next gap by using different
// FSM states. It times out sooner if the record being searched for doesn't exist on tape. With the current
// implementation it has to wait until a hole is reached at either end of the tape.
if (m_cmd_state == CMD_PH0) { if (m_cmd_state == CMD_PH0) {
// PH0 // PH0
if (next_data(m_rd_it , m_tape_pos , true)) { if (next_data(m_rd_it , m_tape_pos , true)) {
cmd_duration = time_to_target(farthest_end(m_rd_it)); cmd_duration = time_to_target(farthest_end(m_rd_it));
} }
// Set T/O for data
set_data_timeout(true);
m_cmd_state = CMD_PH1; m_cmd_state = CMD_PH1;
} else { } else {
// PH1 // PH1
tape_pos_t target = m_tape_pos; tape_pos_t target = m_tape_pos;
// b8 seems to select size of gaps
// Tach. register is incremented at each gap. Command ends when this register goes positive (b15 = 0). // Tach. register is incremented at each gap. Command ends when this register goes positive (b15 = 0).
unsigned n_gaps; unsigned n_gaps;
if (BIT(m_tach_reg , 15)) { if (BIT(m_tach_reg , 15)) {
@ -1367,6 +1404,7 @@ void hp_taco_device::cmd_fsm(void)
LOG_0(("%u gaps @%d\n" , n_gaps, target)); LOG_0(("%u gaps @%d\n" , n_gaps, target));
cmd_duration = time_to_target(target); cmd_duration = time_to_target(target);
} }
m_timeout_timer->reset();
m_cmd_state = CMD_END; m_cmd_state = CMD_END;
} }
break; break;
@ -1382,6 +1420,8 @@ void hp_taco_device::cmd_fsm(void)
if (next_data(m_rd_it , m_tape_pos , true)) { if (next_data(m_rd_it , m_tape_pos , true)) {
cmd_duration = time_to_target(farthest_end(m_rd_it)); cmd_duration = time_to_target(farthest_end(m_rd_it));
} }
// Set T/O for data
set_data_timeout(true);
m_cmd_state = CMD_END; m_cmd_state = CMD_END;
} }
break; break;
@ -1417,23 +1457,18 @@ void hp_taco_device::cmd_fsm(void)
if (!m_rd_it_valid) { if (!m_rd_it_valid) {
// Search for preamble first // Search for preamble first
m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false);
// Set T/O for preamble search
set_data_timeout(true);
m_cmd_state = CMD_PH1; m_cmd_state = CMD_PH1;
} else { } else {
// Resume reading from last position, skip preamble search // Resume reading from last position, skip preamble search
m_cmd_state = CMD_PH2; m_cmd_state = CMD_PH2;
} }
cmd_duration = time_to_rd_next_word(m_rw_pos);
} else { } else {
// Just to be sure.. // Just to be sure..
m_tape_pos = m_rw_pos; m_tape_pos = m_rw_pos;
if (m_cmd_state == CMD_PH3) {
// PH3: delayed setting of error condition
set_error(true);
m_cmd_state = CMD_PH2;
}
if (m_cmd_state == CMD_PH1) { if (m_cmd_state == CMD_PH1) {
// PH1 // PH1
// Any word with at least a 0 will do as preamble. // Any word with at least a 0 will do as preamble.
@ -1445,6 +1480,9 @@ void hp_taco_device::cmd_fsm(void)
} }
} else { } else {
// PH2 // PH2
if (m_irq) {
LOG(("Data reg overflow!\n"));
}
irq_w(true); irq_w(true);
m_data_reg = m_rd_it->second; m_data_reg = m_rd_it->second;
if (m_clear_checksum_reg) { if (m_clear_checksum_reg) {
@ -1454,24 +1492,15 @@ void hp_taco_device::cmd_fsm(void)
m_checksum_reg += m_data_reg; m_checksum_reg += m_data_reg;
LOG_0(("RD %04x\n" , m_data_reg)); LOG_0(("RD %04x\n" , m_data_reg));
} }
// Set T/O for arrival of data words
set_data_timeout(false);
adv_res_t res = adv_it(m_rd_it); adv_res_t res = adv_it(m_rd_it);
LOG_0(("adv_it %d\n" , res)); LOG_0(("adv_it %d\n" , res));
if (res == ADV_NO_MORE_DATA) { if (res == ADV_NO_MORE_DATA) {
m_rd_it_valid = false; m_rd_it_valid = false;
} else { }
}
cmd_duration = time_to_rd_next_word(m_rw_pos); cmd_duration = time_to_rd_next_word(m_rw_pos);
if (res == ADV_DISCONT_DATA) {
// Wild guess: TACO sets error flag when it stumbles on a gap between words
if (m_cmd_state == CMD_PH2 && abs(m_tape_pos - m_rw_pos) > ((tape_pos_t)(0.25 * ONE_INCH_POS))) {
m_cmd_state = CMD_PH3;
} else {
// Hit a gap, restart preamble search
// TODO: is this ok?
m_cmd_state = CMD_PH1;
}
}
}
}
break; break;
case CMD_DELTA_MOVE_IRG: case CMD_DELTA_MOVE_IRG:
@ -1485,6 +1514,7 @@ void hp_taco_device::cmd_fsm(void)
if (m_cmd_state == CMD_PH0) { if (m_cmd_state == CMD_PH0) {
// PH0 // PH0
cmd_duration = time_to_rd_next_word(m_rw_pos); cmd_duration = time_to_rd_next_word(m_rw_pos);
set_data_timeout(false);
m_cmd_state = CMD_PH1; m_cmd_state = CMD_PH1;
} else { } else {
// PH1 // PH1
@ -1545,7 +1575,8 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
m_cmd_reg = new_cmd_reg; m_cmd_reg = new_cmd_reg;
freeze_tach_reg(false); freeze_tach_reg(false);
m_hole_timer->reset(); m_hole_timer->reset();
if (m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING) { m_timeout_timer->reset();
if (is_braking()) {
// Already braking // Already braking
// m_tape_timer already set // m_tape_timer already set
} else if (m_start_time.is_never()) { } else if (m_start_time.is_never()) {
@ -1585,8 +1616,8 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
return; return;
case CMD_NOT_INDTA: case CMD_NOT_INDTA:
// Errors: CART OUT,FAST SPEED // Errors: CART OUT
started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); started = start_tape_cmd(new_cmd_reg , 0 , 0);
break; break;
case CMD_WRITE_IRG: case CMD_WRITE_IRG:
@ -1629,8 +1660,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
case CMD_END_READ: case CMD_END_READ:
// This command only makes sense after CMD_START_READ // This command only makes sense after CMD_START_READ
if (CMD_CODE(m_cmd_reg) == CMD_START_READ && if (CMD_CODE(m_cmd_reg) == CMD_START_READ) {
(m_cmd_state == CMD_PH2 || m_cmd_state == CMD_PH3)) {
started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK); started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK);
LOG_0(("END_READ %d\n" , m_rd_it_valid)); LOG_0(("END_READ %d\n" , m_rd_it_valid));
} }
@ -1647,6 +1677,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
m_cmd_state = CMD_IDLE; m_cmd_state = CMD_IDLE;
m_tape_timer->reset(); m_tape_timer->reset();
m_hole_timer->reset(); m_hole_timer->reset();
m_timeout_timer->reset();
} }
} }

View File

@ -103,7 +103,6 @@ private:
CMD_PH0, CMD_PH0,
CMD_PH1, CMD_PH1,
CMD_PH2, CMD_PH2,
CMD_PH3,
CMD_END, CMD_END,
CMD_STOPPING CMD_STOPPING
} cmd_state_t; } cmd_state_t;
@ -118,6 +117,7 @@ private:
// Timers // Timers
emu_timer *m_tape_timer; emu_timer *m_tape_timer;
emu_timer *m_hole_timer; emu_timer *m_hole_timer;
emu_timer *m_timeout_timer;
// Content of tape tracks // Content of tape tracks
tape_track_t m_tracks[ 2 ]; tape_track_t m_tracks[ 2 ];
@ -180,6 +180,7 @@ private:
attotime time_to_next_hole(void) const; attotime time_to_next_hole(void) const;
attotime time_to_tach_pulses(void) const; attotime time_to_tach_pulses(void) const;
void terminate_cmd_now(void); void terminate_cmd_now(void);
void set_data_timeout(bool long_timeout);
void cmd_fsm(void); void cmd_fsm(void);
void start_cmd_exec(UINT16 new_cmd_reg); void start_cmd_exec(UINT16 new_cmd_reg);
}; };