summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/hp_taco.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/hp_taco.cpp')
-rw-r--r--src/devices/machine/hp_taco.cpp1551
1 files changed, 906 insertions, 645 deletions
diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp
index eb94faf3b05..d6a1fa27805 100644
--- a/src/devices/machine/hp_taco.cpp
+++ b/src/devices/machine/hp_taco.cpp
@@ -15,8 +15,9 @@
// 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 indentify only those TACO commands that are actually used by the software. I managed
+// * 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)
@@ -27,6 +28,8 @@
// 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.
// This is an overview of the TACO/CPU interface.
//
@@ -35,23 +38,22 @@
// 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
-// | | on command. When the counter rolls over from 0xffff to 0 it typically ends the command. It's not clear to me
-// | | what value could be read from this register, if it's just the same value that was written last time or the internal
-// | | counter or something else entirely.
-// R7 | R | Checksum register. Reading it clears it.
+// | | 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.
+// | | writing to tape and to 0x0635 before reading (0x061d is set by SIF utilities).
//
// 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 ? Drive ON according to [1], the actual use seems to be selection of gap length
-// 8 RW ? Size of gaps according to [1], N/U in my opinion
+// 9 RW Automatic stopping of tape at command completion (when 1)
+// 8 RW Minimum size of gaps (1 = 1.5", 0 = 0.066")
// 7 RW Speed of tape (1 = 90 ips, 0 = 22 ips)
-// 6 RW Option bit for various commands
+// 6 RW Option bit for various commands. Most of them 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)
@@ -79,19 +81,23 @@
// - 6" of final gap
// - Non-recorded tape
// - End of tape holes
-// * Sector #0 is not used
+// * 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
// * 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.
//
-// A sector is structured like this:
+// 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.
-// Word 1: Format/sector in use and other unidentified bits.
-// Word 2: Sector number
-// Word 3: Sector length and other bits
+// 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 4: Checksum (sum of words 1..3)
// Words 5..132: Payload
// Word 133: Checksum (sum of words 5..132)
@@ -101,7 +107,7 @@
// This is how 9825 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
+// - 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)
@@ -109,9 +115,9 @@
//
// This driver is based on the following model of the actual TACO/tape system:
// * Tape immediately reaches working speed (no spin-up time)
-// * Inversion of tape direction and change of speed are immediate as well
-// * Time & distance to stop the tape are modeled, though. Firmware is upset by
-// a tape with null braking time/distance.
+// * 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
@@ -125,15 +131,15 @@
// validate my solutions by running the original firmware in MAME, though (no real hw at hand).
//
// TODOs/issues:
-// * Some code cleanup
-// * Handling of tape holes seems to be wrong: test "C" of test ROM only works partially
-// * Find out what is read from register R6
-// * Handle device_image_interface::call_display to show state of tape
+// * 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.
// * Find more info on TACO chips (does anyone with a working 9845 or access to internal HP docs want to
// help me here, please?)
//
#include "emu.h"
#include "hp_taco.h"
+#include "ui/ui.h"
// Debugging
#define VERBOSE 1
@@ -160,18 +166,21 @@ enum {
#define ONE_INCH_POS (TACH_TICKS_PER_INCH * TAPE_POS_FRACT) // Value in tape_pos_t representing 1 inch of tape
#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_LENGTH ((140 * 12 + 72 * 2) * ONE_INCH_POS) // Tape length: 140 ft of usable tape + 72" of punched tape at either end
#define TAPE_INIT_POS (80 * ONE_INCH_POS) // Initial tape position: 80" from beginning (just past the punched part)
#define ZERO_BIT_LEN 619 // Length of 0 bits at slow tape speed: 1/(35200 Hz)
#define ONE_BIT_LEN 1083 // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN
-#define QUICK_CMD_USEC 25 // usec for "quick" command execution
-#define FAST_BRAKE_MSEC 73 // Braking time from fast speed to stop (2 ips) in msec (deceleration is 1200 in/s^2)
-#define SLOW_BRAKE_MSEC 17 // Braking time from slow speed to stop in msec
+#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)
#define SLOW_BRAKE_DIST 197883 // Braking distance at slow speed (~0.2 in)
#define PREAMBLE_WORD 0 // Value of preamble word
#define END_GAP_LENGTH (6 * ONE_INCH_POS) // Length of final gap: 6"
-#define MIN_IRG_LENGTH ((tape_pos_t)(0.2 * ONE_INCH_POS)) // Minimum length of IRGs: 0.2" (from 9825, not sure about value in TACO)
+// 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 ((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 FILE_MAGIC 0x4f434154 // Magic value at start of image file: "TACO"
@@ -180,50 +189,52 @@ enum {
#define CMD_CODE(reg) \
(((reg) >> 10) & 0x1f)
#define DIR_FWD(reg) \
- (BIT(reg , 15))
+ (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 UNKNOWN_B9(reg) \
- (BIT(reg , 9))
+ (BIT(reg , 6))
#define DIR_FWD_MASK BIT_MASK(15) // Direction = forward
#define SPEED_FAST_MASK BIT_MASK(7) // Speed = fast
// Commands
enum {
- CMD_INDTA_INGAP, // 00: scan for data first then for gap
- 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
- 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_DELTA_MOVE_HOLE, // 1c: move tape a given distance, intr at end or first hole found (whichever comes first)
- 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
+ CMD_INDTA_INGAP, // 00: scan for data first then for gap
+ 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
+ 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
};
// Bits of status register
@@ -293,33 +304,34 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, d
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;
- break;
-
- case 3:
- // Timing register
- m_timing_reg = data;
- break;
- }
+ 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;
+ }
}
READ16_MEMBER(hp_taco_device::reg_r)
@@ -340,17 +352,18 @@ READ16_MEMBER(hp_taco_device::reg_r)
res = (m_cmd_reg & CMD_REG_MASK) | (m_status_reg & STATUS_REG_MASK);
break;
- case 2:
- // Tachometer register
- res = m_tach_reg;
- break;
+ case 2:
+ // Tachometer register
+ update_tach_reg();
+ res = m_tach_reg;
+ break;
- case 3:
- // Checksum register: it clears when read
- res = m_checksum_reg;
- m_checksum_reg = 0;
- break;
- }
+ case 3:
+ // Checksum register: it clears when read
+ res = m_checksum_reg;
+ m_clear_checksum_reg = true;
+ break;
+ }
LOG_0(("rd R%u = %04x\n", 4 + offset , res));
@@ -377,35 +390,38 @@ void hp_taco_device::device_config_complete()
// 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_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);
+ 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);
}
// device_stop
@@ -429,227 +445,92 @@ void hp_taco_device::device_reset()
void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
- if (CMD_CODE(m_cmd_reg) != CMD_STOP) {
- update_tape_pos();
- }
-
- switch (id) {
- case TAPE_TMR_ID:
- LOG_0(("Tape tmr @%g\n" , machine().time().as_double()));
-
- tape_pos_t length;
-
- switch (CMD_CODE(m_cmd_reg)) {
- case CMD_INDTA_INGAP:
- if (m_cmd_state == 0) {
- m_cmd_state = 1;
- tape_pos_t target = m_tape_pos;
- if (next_n_gap(target, 1, MIN_IRG_LENGTH)) {
- m_tape_timer->adjust(time_to_target(target));
- }
- return;
- }
- break;
-
- case CMD_RECORD_WRITE:
- if (m_cmd_state == 0) {
- if (m_rd_it->second == PREAMBLE_WORD) {
- LOG_0(("Got preamble\n"));
- m_cmd_state = 1;
- // m_rw_pos already at correct position
- m_tape_timer->adjust(fetch_next_wr_word());
- break;
- } else {
- adv_res_t res = adv_it(m_rd_it);
- if (res != ADV_NO_MORE_DATA) {
- m_tape_timer->adjust(time_to_rd_next_word(m_rw_pos));
- }
- // No IRQ
- return;
- }
- }
- // Intentional fall-through
- case CMD_INIT_WRITE:
- write_word(m_rw_pos , m_next_word , length);
- pos_offset(m_rw_pos , length);
- // Just to be sure..
- m_tape_pos = m_rw_pos;
- m_tape_timer->adjust(fetch_next_wr_word());
- break;
-
- case CMD_STOP:
- move_tape_pos(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST);
- stop_tape();
- break;
-
- case CMD_INGAP_MOVE:
- if (m_cmd_state == 0) {
- m_cmd_state = 1;
- m_tape_timer->adjust(time_to_tach_pulses());
- return;
- }
- break;
-
- case CMD_FINAL_GAP:
- case CMD_WRITE_IRG:
- write_gap(m_rw_pos , m_tape_pos);
- m_hole_timer->reset();
- break;
-
- case CMD_SCAN_RECORDS:
- if (m_cmd_state == 0) {
- m_cmd_state = 1;
- tape_pos_t target = m_tape_pos;
- if (next_n_gap(target, 0x10000U - m_tach_reg, MIN_IRG_LENGTH)) {
- LOG_0(("%u gaps @%d\n" , 0x10000U - m_tach_reg, target));
- m_tape_timer->adjust(time_to_target(target));
- }
- return;
- } else {
- m_hole_timer->reset();
- }
- break;
-
- case CMD_MOVE_INDTA:
- if (m_cmd_state == 0) {
- if (next_data(m_rd_it , m_tape_pos , true)) {
- m_cmd_state = 1;
- m_tape_timer->adjust(time_to_target(farthest_end(m_rd_it)));
- }
- // No IRQ
- return;
- }
- // m_cmd_state == 1 -> IRQ & cmd end
- break;
-
- case CMD_DELTA_MOVE_HOLE:
- case CMD_DELTA_MOVE_IRG:
- // Interrupt at end of movement
- m_hole_timer->reset();
- break;
-
- case CMD_START_READ:
- {
- bool set_intr = true;
- // Just to be sure..
- m_tape_pos = m_rw_pos;
- if (m_cmd_state == 0) {
- set_intr = false;
- if (m_rd_it->second == PREAMBLE_WORD) {
- m_cmd_state = 1;
- }
- LOG_0(("Got preamble\n"));
- } else {
- m_data_reg = m_rd_it->second;
- m_checksum_reg += m_data_reg;
- LOG_0(("RD %04x\n" , m_data_reg));
- }
- 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 {
- if (res == ADV_DISCONT_DATA) {
- // Hit a gap, restart preamble search
- m_cmd_state = 0;
- }
- m_tape_timer->adjust(time_to_rd_next_word(m_rw_pos));
- }
- if (!set_intr) {
- return;
- }
- }
- break;
-
- case CMD_END_READ:
- {
- 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));
- adv_res_t res = adv_it(m_rd_it);
- if (res == ADV_NO_MORE_DATA) {
- m_rd_it_valid = false;
- }
- m_hole_timer->reset();
- }
- break;
-
- default:
- // Other commands: just raise irq
- break;
- }
- irq_w(true);
- break;
-
- case HOLE_TMR_ID:
- LOG_0(("Hole tmr @%g\n" , machine().time().as_double()));
-
- BIT_SET(m_status_reg , STATUS_HOLE_BIT);
-
- switch (CMD_CODE(m_cmd_reg)) {
- case CMD_FINAL_GAP:
- case CMD_WRITE_IRG:
- write_gap(m_rw_pos , m_tape_pos);
- m_rw_pos = m_tape_pos;
- break;
-
- case CMD_SCAN_RECORDS:
- case CMD_DELTA_MOVE_HOLE:
- // Cmds 18 & 1c are terminated at first hole
- m_tape_timer->reset();
- irq_w(true);
- // No reloading of hole timer
- return;
-
- case CMD_DELTA_MOVE_IRG:
- // TODO: update r6
- m_hole_timer->adjust(time_to_next_hole());
- // No IRQ at holes
- return;
-
- case CMD_START_READ:
- case CMD_END_READ:
- set_error(true);
- break;
-
- default:
- // Other cmds: default processing (update tape pos, set IRQ, schedule timer for next hole)
- break;
- }
-
- irq_w(true);
- m_hole_timer->adjust(time_to_next_hole());
- break;
-
- default:
- break;
- }
+ 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();
+ 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:
+ write_gap(m_rw_pos , m_tape_pos);
+ 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
+ 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_START_READ:
+ case CMD_END_READ:
+ set_error(true);
+ break;
+
+ default:
+ // Other cmds: default processing (update tape pos, set IRQ, schedule timer for next hole)
+ break;
+ }
+
+ irq_w(true);
+ }
+ m_hole_timer->adjust(time_to_next_hole());
+ break;
+
+ default:
+ break;
+ }
}
void hp_taco_device::clear_state(void)
{
- m_data_reg = 0;
- m_data_reg_full = false;
- m_cmd_reg = 0;
- m_status_reg = 0;
- m_tach_reg = 0;
- m_checksum_reg = 0;
- m_timing_reg = 0;
- m_cmd_state = 0;
- // m_tape_pos is not reset, tape stays where it is
- 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 = NULL_TAPE_POS;
-
- set_tape_present(false);
- set_tape_present(is_loaded());
+ 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 is not reset, tape stays where it is
+ 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 = NULL_TAPE_POS;
+
+ set_tape_present(false);
+ set_tape_present(is_loaded());
}
void hp_taco_device::irq_w(bool state)
@@ -668,9 +549,16 @@ void hp_taco_device::set_error(bool state)
LOG_0(("error = %d\n" , state));
}
+bool hp_taco_device::is_braking(void) const
+{
+ return m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING;
+}
+
unsigned hp_taco_device::speed_to_tick_freq(void) const
{
- return m_tape_fast ? TACH_FREQ_FAST * TAPE_POS_FRACT : TACH_FREQ_SLOW * TAPE_POS_FRACT;
+ 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);
}
bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const
@@ -697,47 +585,81 @@ bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const
}
}
-void hp_taco_device::move_tape_pos(tape_pos_t delta_pos)
+hp_taco_device::tape_pos_t hp_taco_device::current_tape_pos(void) const
{
- tape_pos_t tape_start_pos = m_tape_pos;
- if (!pos_offset(m_tape_pos , delta_pos)) {
- LOG(("Tape unspooled!\n"));
- }
- m_start_time = machine().time();
- LOG_0(("Tape pos = %u\n" , m_tape_pos));
- if (any_hole(tape_start_pos , m_tape_pos)) {
- // Crossed one or more holes
- BIT_SET(m_status_reg , STATUS_HOLE_BIT);
- }
+ if (m_start_time.is_never()) {
+ // Tape not moving
+ return m_tape_pos;
+ }
+
+ 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?
+ tape_pos_t delta_tach = (tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq()));
+ LOG_0(("delta_tach = %u\n" , delta_tach));
+
+ tape_pos_t tape_pos = m_tape_pos;
+ if (!pos_offset(tape_pos , delta_tach)) {
+ LOG(("Tape unspooled!\n"));
+ }
+
+ return tape_pos;
}
void hp_taco_device::update_tape_pos(void)
{
- if (m_start_time.is_never()) {
- // Tape not moving
- return;
- }
+ if (m_start_time.is_never()) {
+ // Tape not moving
+ return;
+ }
+
+ m_tape_pos = current_tape_pos();
+ m_start_time = machine().time();
+ LOG_0(("Tape pos = %u\n" , m_tape_pos));
+
+ // Gap detection
+ bool gap_detected = false;
+ if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= min_gap_size()) {
+ tape_pos_t tmp = m_tape_pos;
+ pos_offset(tmp , -min_gap_size());
+ gap_detected = just_gap(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();
+}
- 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?
- tape_pos_t delta_tach = (tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq()));
- LOG_0(("delta_tach = %u\n" , delta_tach));
+void hp_taco_device::update_tach_reg(void)
+{
+ if (m_tach_reg_frozen) {
+ LOG_0(("Tach reg frozen\n"));
+ return;
+ }
- move_tape_pos(delta_tach);
+ tape_pos_t pos = current_tape_pos();
+ tape_pos_t pos_int = pos / TAPE_POS_FRACT;
+ tape_pos_t ref_int = m_tach_reg_ref / TAPE_POS_FRACT;
+ UINT16 reg_value = (UINT16)(abs(pos_int - ref_int) + m_tach_reg);
+
+ LOG_0(("Tach = %04x @ pos = %d, ref_pos = %d\n" , reg_value , pos , m_tach_reg_ref));
+
+ m_tach_reg = reg_value;
+ m_tach_reg_ref = pos;
+}
+
+void hp_taco_device::freeze_tach_reg(bool freeze)
+{
+ if (freeze) {
+ m_tach_reg_frozen = true;
+ } else {
+ m_tach_reg_frozen = false;
+ m_tach_reg_ref = current_tape_pos();
+ }
- // Gap detection
- bool gap_detected = false;
- if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= MIN_IRG_LENGTH) {
- tape_pos_t tmp = m_tape_pos;
- pos_offset(tmp , -MIN_IRG_LENGTH);
- gap_detected = just_gap(tmp , m_tape_pos);
- }
- if (gap_detected) {
- BIT_SET(m_status_reg, STATUS_GAP_BIT);
- } else {
- BIT_CLR(m_status_reg, STATUS_GAP_BIT);
- }
}
void hp_taco_device::ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b)
@@ -751,20 +673,6 @@ void hp_taco_device::ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b)
}
}
-// Is there any hole in a given section of tape?
-bool hp_taco_device::any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b)
-{
- ensure_a_lt_b(tape_pos_a , tape_pos_b);
-
- for (tape_pos_t hole : tape_holes) {
- if (tape_pos_a < hole && tape_pos_b >= hole) {
- return true;
- }
- }
-
- return false;
-}
-
// Position of next hole tape will reach in a given direction
hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const
{
@@ -800,43 +708,64 @@ attotime hp_taco_device::time_to_target(tape_pos_t target) const
return time_to_distance(abs(target - m_tape_pos));
}
-bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0)
+attotime hp_taco_device::time_to_stopping_pos(void) const
{
- m_cmd_reg = cmd_reg;
-
- UINT16 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;
+ return time_to_distance(m_tape_fast ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST);
+}
- // 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) {
- set_error(true);
- 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 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);
- // TODO: remove?
- BIT_CLR(m_status_reg, STATUS_HOLE_BIT);
-
- if (m_tape_wr) {
- // Write command: disable gap detector
- m_gap_detect_start = NULL_TAPE_POS;
- BIT_CLR(m_status_reg, STATUS_GAP_BIT);
- m_image_dirty = true;
- } else if (not_moving || prev_tape_wr != m_tape_wr || prev_tape_fwd != m_tape_fwd || prev_tape_fast != m_tape_fast) {
- // Tape 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);
- }
- return true;
- }
+bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0)
+{
+ m_cmd_reg = cmd_reg;
+
+ UINT16 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);
+ // TODO: remove?
+ BIT_CLR(m_status_reg, STATUS_HOLE_BIT);
+
+ if (m_tape_wr) {
+ // Write command: disable gap detector
+ m_gap_detect_start = 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);
+ }
+
+ if (!not_moving && !prev_tape_braking && 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());
+ } else {
+ // No change in direction, immediate execution
+ m_cmd_state = CMD_PH0;
+ m_tape_timer->adjust(attotime::zero);
+ }
+
+ m_hole_timer->reset();
+
+ return true;
+ }
}
void hp_taco_device::stop_tape(void)
@@ -980,19 +909,23 @@ hp_taco_device::adv_res_t hp_taco_device::adv_it(tape_track_t::iterator& it)
attotime hp_taco_device::fetch_next_wr_word(void)
{
- 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));
- }
- // Update checksum with new word
- m_checksum_reg += m_next_word;
-
- return time_to_distance(word_length(m_next_word));
+ 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(word_length(m_next_word));
}
attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos)
@@ -1005,6 +938,11 @@ attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos)
}
}
+hp_taco_device::tape_pos_t hp_taco_device::min_gap_size(void) const
+{
+ return LONG_GAP(m_cmd_reg) ? LONG_GAP_LENGTH : SHORT_GAP_LENGTH;
+}
+
/**
* Scan for next "n_gaps" gaps
*
@@ -1216,213 +1154,500 @@ attotime hp_taco_device::time_to_tach_pulses(void) const
return time_to_distance((tape_pos_t)(0x10000U - m_tach_reg) * TAPE_POS_FRACT);
}
-void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
+void hp_taco_device::terminate_cmd_now(void)
{
- LOG(("Cmd = %02x\n" , CMD_CODE(new_cmd_reg)));
-
- update_tape_pos();
-
- attotime cmd_duration = attotime::never;
- attotime time_to_hole = attotime::never;
-
- unsigned new_cmd_code = CMD_CODE(new_cmd_reg);
-
- if (new_cmd_code != CMD_START_READ &&
- new_cmd_code != CMD_END_READ &&
- new_cmd_code != CMD_CLEAR) {
- m_rd_it_valid = false;
- }
-
- switch (new_cmd_code) {
- case CMD_INDTA_INGAP:
- // Errors: CART OUT,FAST SPEED
- if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) {
- m_cmd_state = 0;
- if (next_data(m_rd_it , m_tape_pos , true)) {
- cmd_duration = time_to_target(farthest_end(m_rd_it));
- }
- }
- break;
-
- case CMD_FINAL_GAP:
- // Errors: WP,CART OUT
- if (start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK)) {
- m_rw_pos = m_tape_pos;
- cmd_duration = time_to_distance(END_GAP_LENGTH);
- time_to_hole = time_to_next_hole();
- }
- 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_NOT_INDTA:
- // Errors: CART OUT,FAST SPEED
- if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) {
- tape_pos_t target = m_tape_pos;
- if (next_n_gap(target, 1, NO_DATA_GAP)) {
- LOG_0(("End of data @%d\n" , target));
- cmd_duration = time_to_target(target);
- }
- // Holes detected?
- }
- break;
-
- case CMD_INIT_WRITE:
- // Errors: WP,CART OUT,fast speed,reverse
- if (start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK)) {
- m_next_word = PREAMBLE_WORD;
- m_rw_pos = m_tape_pos;
- cmd_duration = time_to_distance(word_length(m_next_word));
- }
- break;
-
- case CMD_STOP:
- if (CMD_CODE(m_cmd_reg) != CMD_STOP) {
- if (m_start_time.is_never()) {
- // Tape is already stopped
- cmd_duration = attotime::from_usec(QUICK_CMD_USEC);
- } else {
- // Start braking timer
- cmd_duration = attotime::from_msec(m_tape_fast ? FAST_BRAKE_MSEC : SLOW_BRAKE_MSEC);
- }
- m_cmd_reg = new_cmd_reg;
- } else {
- // TODO: check if ok
- return;
- }
- break;
-
- case CMD_SET_TRACK:
- // Don't know if this command really starts the tape or not (probably it doesn't)
- if (start_tape_cmd(new_cmd_reg , 0 , 0)) {
- // When b9 is 0, set track A/B
- // When b9 is 1, ignore command (in TACO chip it has an unknown purpose)
- if (!UNKNOWN_B9(new_cmd_reg)) {
- if (CMD_OPT(new_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);
- }
- break;
-
- case CMD_MOVE:
- if (start_tape_cmd(new_cmd_reg , 0 , 0)) {
- time_to_hole = time_to_next_hole();
- }
- break;
-
- case CMD_INGAP_MOVE:
- // Errors: CART OUT,FAST SPEED
- if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) {
- m_cmd_state = 0;
- tape_pos_t target = m_tape_pos;
- if (next_n_gap(target, 1, MIN_IRG_LENGTH)) {
- LOG_0(("IRG @%d\n" , target));
- cmd_duration = time_to_target(target);
- }
- // Holes detected?
- }
- break;
-
- case CMD_WRITE_IRG:
- // Errors: WP,CART OUT
- if (start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK)) {
- m_rw_pos = m_tape_pos;
- cmd_duration = time_to_tach_pulses();
- time_to_hole = time_to_next_hole();
- }
- break;
-
- case CMD_SCAN_RECORDS:
- // Errors: CART OUT
- if (start_tape_cmd(new_cmd_reg , 0 , 0)) {
- m_cmd_state = 0;
- if (next_data(m_rd_it , m_tape_pos , true)) {
- cmd_duration = time_to_target(farthest_end(m_rd_it));
- }
- time_to_hole = time_to_next_hole();
- }
- break;
-
- case CMD_RECORD_WRITE:
- // Errors: WP,CART OUT,fast speed,reverse
- if (start_tape_cmd(new_cmd_reg , DIR_FWD_MASK , STATUS_WPR_MASK | SPEED_FAST_MASK)) {
- // Search for preamble first
- m_cmd_state = 0;
- m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false);
- cmd_duration = time_to_rd_next_word(m_rw_pos);
- // Holes detected?
- }
- break;
-
- case CMD_MOVE_INDTA:
- // Errors: CART OUT,FAST SPEED
- if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) {
- m_cmd_state = 0;
- cmd_duration = time_to_tach_pulses();
- // Holes detected?
- }
- break;
-
- case CMD_UNK_1b:
- if (start_tape_cmd(new_cmd_reg , 0 , 0)) {
- // Unknown purpose, but make it a NOP (it's used in "T" test of test ROM)
- cmd_duration = attotime::from_usec(QUICK_CMD_USEC);
- }
- break;
-
- case CMD_DELTA_MOVE_HOLE:
- case CMD_DELTA_MOVE_IRG:
- if (start_tape_cmd(new_cmd_reg , 0 , 0)) {
- cmd_duration = time_to_tach_pulses();
- time_to_hole = time_to_next_hole();
- }
- 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.
- if (start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) {
- // TODO: check anche m_rw_pos sforato
- if (!m_rd_it_valid) {
- // Search for preamble first
- m_cmd_state = 0;
- m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false);
- }
-
- cmd_duration = time_to_rd_next_word(m_rw_pos);
- time_to_hole = time_to_next_hole();
- }
- break;
-
- 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 == 1 &&
- start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK)) {
- LOG_0(("END_READ %d\n" , m_rd_it_valid));
- cmd_duration = time_to_rd_next_word(m_rw_pos);
- time_to_hole = time_to_next_hole();
- }
- break;
+ m_cmd_state = CMD_END;
+ m_tape_timer->adjust(attotime::zero);
+ m_hole_timer->reset();
+}
- default:
- LOG(("Unrecognized command\n"));
- break;
- }
+void hp_taco_device::cmd_fsm(void)
+{
+ if (m_cmd_state == CMD_END) {
+ // Command ended
+ m_cmd_state = CMD_IDLE;
+ m_hole_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_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
+ m_rw_pos = m_tape_pos;
+ cmd_duration = time_to_distance(END_GAP_LENGTH);
+ m_cmd_state = CMD_PH1;
+ } else {
+ // PH1
+ write_gap(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 = next_data(m_rd_it , m_tape_pos , false);
+ cmd_duration = time_to_rd_next_word(m_rw_pos);
+ 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();
+ 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);
+ }
+ }
+ 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(word_length(m_next_word));
+ m_cmd_state = CMD_PH1;
+ } else {
+ // PH1 & PH2 of CMD_RECORD_WRITE
+ tape_pos_t length;
+ write_word(m_rw_pos , m_next_word , length);
+ pos_offset(m_rw_pos , 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
+ tape_pos_t target = m_tape_pos;
+ if (next_n_gap(target, 1, 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_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));
+ }
+ m_cmd_state = CMD_PH1;
+ } else {
+ // PH1
+ tape_pos_t target = m_tape_pos;
+ if (next_n_gap(target, 1, NO_DATA_GAP)) {
+ LOG_0(("End of data @%d\n" , target));
+ cmd_duration = time_to_target(target);
+ }
+ 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
+ write_gap(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) {
+ // 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;
+ // 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)) {
+ n_gaps = 0x10000U - m_tach_reg;
+ m_tach_reg = 0;
+ } else {
+ n_gaps = 1;
+ m_tach_reg++;
+ }
+ if (next_n_gap(target, n_gaps, min_gap_size())) {
+ LOG_0(("%u gaps @%d\n" , n_gaps, target));
+ cmd_duration = time_to_target(target);
+ }
+ m_cmd_state = CMD_END;
+ }
+ 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 (next_data(m_rd_it , m_tape_pos , true)) {
+ cmd_duration = time_to_target(farthest_end(m_rd_it));
+ }
+ 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
+ tape_pos_t target = m_tape_pos;
+ if (next_n_gap(target, 1, 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 = next_data(m_rd_it , m_tape_pos , false);
+ 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.
+ // 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
+ 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));
+ }
+ 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;
+ }
+ }
+ }
+ }
+ 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);
+ 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));
+ adv_res_t res = adv_it(m_rd_it);
+ if (res == 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);
+ }
+}
- m_tape_timer->adjust(cmd_duration);
- m_hole_timer->adjust(time_to_hole);
+void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg)
+{
+ 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));
+
+ update_tape_pos();
+
+ unsigned new_cmd_code = CMD_CODE(new_cmd_reg);
+
+ if (new_cmd_code != CMD_START_READ &&
+ new_cmd_code != CMD_END_READ &&
+ new_cmd_code != CMD_CLEAR) {
+ m_rd_it_valid = false;
+ }
+
+ 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();
+ if (m_cmd_state == CMD_INVERTING || m_cmd_state == CMD_STOPPING) {
+ // 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_MOVE:
+ started = start_tape_cmd(new_cmd_reg , 0 , 0);
+ break;
+
+ case CMD_INGAP_MOVE:
+ // Errors: CART OUT,FAST SPEED
+ started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK);
+ 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_NOT_INDTA:
+ // Errors: CART OUT,FAST SPEED
+ started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK);
+ break;
+
+ case CMD_WRITE_IRG:
+ // Errors: WP,CART OUT
+ started = start_tape_cmd(new_cmd_reg , 0 , STATUS_WPR_MASK);
+ break;
+
+ case CMD_SCAN_RECORDS:
+ // Errors: CART OUT
+ started = start_tape_cmd(new_cmd_reg , 0 , 0);
+ 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_MOVE_INDTA:
+ // Errors: CART OUT,FAST SPEED
+ started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK);
+ break;
+
+ case CMD_UNK_1b:
+ started = start_tape_cmd(new_cmd_reg , 0 , 0);
+ break;
+
+ case CMD_MOVE_INGAP:
+ started = start_tape_cmd(new_cmd_reg , 0 , 0);
+ 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_DELTA_MOVE_IRG:
+ started = start_tape_cmd(new_cmd_reg , 0 , 0);
+ break;
+
+ 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)) {
+ started = start_tape_cmd(new_cmd_reg , 0 , SPEED_FAST_MASK);
+ LOG_0(("END_READ %d\n" , m_rd_it_valid));
+ }
+ 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();
+ }
}
bool hp_taco_device::call_load()
@@ -1461,6 +1686,42 @@ void hp_taco_device::call_unload()
set_tape_present(false);
}
+void hp_taco_device::call_display()
+{
+ // Mostly lifted from cassette_image_device::call_display ;)
+
+ // Do not show anything if image not loaded or tape not moving
+ if (!exists() || m_start_time.is_never()) {
+ return;
+ }
+
+ char buffer[ 64 ];
+
+ char track = BIT(m_status_reg , STATUS_TRACKB_BIT) ? 'B' : 'A';
+ char r_w = m_tape_wr ? 'W' : 'R';
+ char m1;
+ char m2;
+
+ if (m_tape_fwd) {
+ m1 = '>';
+ m2 = m_tape_fast ? '>' : ' ';
+ } else {
+ m1 = '<';
+ m2 = m_tape_fast ? '<' : ' ';
+ }
+
+ int pos_in = current_tape_pos() / ONE_INCH_POS;
+
+ snprintf(buffer , sizeof(buffer) , "%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in);
+
+ float x, y;
+ x = 0.2f;
+ y = 0.5f;
+ y *= device().machine().ui().get_line_height() + 2.0f * UI_BOX_TB_BORDER;
+
+ device().machine().ui().draw_text_box(&device().machine().render().ui_container(), buffer, JUSTIFY_LEFT, x, y, UI_BACKGROUND_COLOR);
+}
+
const char *hp_taco_device::file_extensions() const
{
return "hti";