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).
//
// TODOs/issues:
// * It seems like all commands expecting to read data off the tape have a kind of timeout. More
// investigation is needed.
// * Emulation of CMD_NOT_INDTA is not entirely ok, SIF utilities fail when using this command.
// * 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?)
//
@ -144,7 +144,7 @@
// Debugging
#define VERBOSE 1
#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)
// Macros to clear/set single bits
@ -155,7 +155,8 @@
// Timers
enum {
TAPE_TMR_ID,
HOLE_TMR_ID
HOLE_TMR_ID,
TIMEOUT_TMR_ID
};
// 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 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 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"
// Parts of command register
@ -203,7 +205,7 @@ enum {
// Commands
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_FINAL_GAP, // 02: write final gap
CMD_INIT_WRITE, // 03: write words for tape formatting
@ -219,7 +221,7 @@ enum {
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
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
@ -422,6 +424,7 @@ void hp_taco_device::device_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
@ -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;
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:
m_hole_timer->adjust(time_to_next_hole());
// No IRQ at holes
@ -485,10 +482,20 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para
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:
set_error(true);
break;
// 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)
@ -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());
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:
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
return TAPE_LENGTH;
return NULL_TAPE_POS;
} else {
for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) {
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
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);
}
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
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));
m_tape_timer->adjust(time_to_stopping_pos());
if (!prev_tape_braking) {
m_tape_timer->adjust(time_to_stopping_pos());
}
} else {
// No change in direction, immediate execution
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_timeout_timer->reset();
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
{
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
@ -1159,6 +1191,13 @@ void hp_taco_device::terminate_cmd_now(void)
m_cmd_state = CMD_END;
m_tape_timer->adjust(attotime::zero);
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)
@ -1167,6 +1206,7 @@ void hp_taco_device::cmd_fsm(void)
// 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
@ -1195,23 +1235,6 @@ void hp_taco_device::cmd_fsm(void)
}
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:
if (m_cmd_state == CMD_PH0) {
// PH0
@ -1232,6 +1255,8 @@ void hp_taco_device::cmd_fsm(void)
// Search for preamble first
m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false);
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) {
@ -1241,12 +1266,15 @@ void hp_taco_device::cmd_fsm(void)
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 {
adv_res_t res = adv_it(m_rd_it);
if (res != 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;
}
@ -1310,20 +1338,25 @@ void hp_taco_device::cmd_fsm(void)
}
break;
case CMD_INDTA_INGAP:
case CMD_NOT_INDTA:
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));
}
// Set T/O for data
set_data_timeout(true);
m_cmd_state = CMD_PH1;
} else {
// PH1
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));
cmd_duration = time_to_target(target);
}
// Got data, stop T/O
m_timeout_timer->reset();
m_cmd_state = CMD_END;
}
break;
@ -1344,16 +1377,20 @@ void hp_taco_device::cmd_fsm(void)
break;
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) {
// PH0
if (next_data(m_rd_it , m_tape_pos , true)) {
cmd_duration = time_to_target(farthest_end(m_rd_it));
}
// Set T/O for data
set_data_timeout(true);
m_cmd_state = CMD_PH1;
} else {
// PH1
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).
unsigned n_gaps;
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));
cmd_duration = time_to_target(target);
}
m_timeout_timer->reset();
m_cmd_state = CMD_END;
}
break;
@ -1382,6 +1420,8 @@ void hp_taco_device::cmd_fsm(void)
if (next_data(m_rd_it , m_tape_pos , true)) {
cmd_duration = time_to_target(farthest_end(m_rd_it));
}
// Set T/O for data
set_data_timeout(true);
m_cmd_state = CMD_END;
}
break;
@ -1417,23 +1457,18 @@ void hp_taco_device::cmd_fsm(void)
if (!m_rd_it_valid) {
// Search for preamble first
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;
} else {
// Resume reading from last position, skip preamble search
m_cmd_state = CMD_PH2;
}
cmd_duration = time_to_rd_next_word(m_rw_pos);
} else {
// Just to be sure..
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) {
// PH1
// Any word with at least a 0 will do as preamble.
@ -1445,6 +1480,9 @@ void hp_taco_device::cmd_fsm(void)
}
} 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) {
@ -1454,24 +1492,15 @@ void hp_taco_device::cmd_fsm(void)
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);
adv_res_t res = adv_it(m_rd_it);
LOG_0(("adv_it %d\n" , res));
if (res == ADV_NO_MORE_DATA) {
m_rd_it_valid = false;
} else {
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;
}
}
}
}
cmd_duration = time_to_rd_next_word(m_rw_pos);
break;
case CMD_DELTA_MOVE_IRG:
@ -1485,6 +1514,7 @@ void hp_taco_device::cmd_fsm(void)
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
@ -1545,7 +1575,8 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
m_cmd_reg = new_cmd_reg;
freeze_tach_reg(false);
m_hole_timer->reset();
if (m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING) {
m_timeout_timer->reset();
if (is_braking()) {
// Already braking
// m_tape_timer already set
} else if (m_start_time.is_never()) {
@ -1585,8 +1616,8 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
return;
case CMD_NOT_INDTA:
// Errors: CART OUT,FAST SPEED
started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK);
// Errors: CART OUT
started = start_tape_cmd(new_cmd_reg , 0 , 0);
break;
case CMD_WRITE_IRG:
@ -1629,8 +1660,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
case CMD_END_READ:
// This command only makes sense after CMD_START_READ
if (CMD_CODE(m_cmd_reg) == CMD_START_READ &&
(m_cmd_state == CMD_PH2 || m_cmd_state == CMD_PH3)) {
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));
}
@ -1647,6 +1677,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
m_cmd_state = CMD_IDLE;
m_tape_timer->reset();
m_hole_timer->reset();
m_timeout_timer->reset();
}
}

View File

@ -103,7 +103,6 @@ private:
CMD_PH0,
CMD_PH1,
CMD_PH2,
CMD_PH3,
CMD_END,
CMD_STOPPING
} cmd_state_t;
@ -118,6 +117,7 @@ private:
// Timers
emu_timer *m_tape_timer;
emu_timer *m_hole_timer;
emu_timer *m_timeout_timer;
// Content of tape tracks
tape_track_t m_tracks[ 2 ];
@ -180,6 +180,7 @@ private:
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 start_cmd_exec(UINT16 new_cmd_reg);
};