summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/machine/hp_taco.cpp
blob: eb94faf3b05c9be481e6264b7c484a6e089d32a9 (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                                      












                                                                                                                             

                                                                                              



                                                                                                                                
 














                                                                                                                                   



                                              


                                                                                             








                                                    
































































                                                                                                 







                                                                                                        





                                                         

                                                             







                                             

                            




                                                        
                                                                                      

                                                                                                                  

                                                                                        













                                                                                                                                         
                                                                                     


                            
                                      
                      
                               
                         
                              
                      
                              
                         
                              

                                                              


           































                                                                                                                                           








                                                           


                                                                                        













                                                            










                                                                                         






                                                                                                                                                                        






                                                                                        
 
                              


                                                                                                             






                                                                                             
 
                              



                                     


























                                                                    



                                    
                               
 

                                              
 




                                                 
 



                                                                                                    
 



                                                      
 





                                                                         
 
                                                             
 
                           



                                       
                             



                                       
                             

 


                                             
                                                


                       


                                   




























                                                        




                                  




                                   

                                      
 

                              
 


                                     



                                                                                             



































































































































































































                                                                                                                                                     

 

                                      




















                                                                  

 

                                      




                                                              



                                          


                                                

 
                                                       
 
                                                                                                       



                                                                          



















                                                    

 
                                                        
 









                                                                        



                                          
























                                                                                                                    

 
                                                                 
 






                                                                 




                                                                            
                                                       
 




                                                                              
 
                             


                                                             
                                                                
 


















                                                                                                                                   

 
                                                                    
 

                                                                                 

 
                                                                
 
                                                                  

 
                                                                                         
 


































                                                                                                                                                                       



                                    

                                                   



                                                                 
                                                                         




                                                                     
                                      
 




                                                                
 
                                  
 
                                                                       



                                                                                         
                                                   



                                                                                                 





                                                              




                                                                                         





                                                                            
 
                                              
 

                                                                                                     




                                                           




                                                                      
 
                                             
 
                                                                                                



                                                          



                                                                      
 
                                            
 
                                         



                                                                                               




                                                        



                                                                                            

















                                                                                 



                                                                            



















                                                                                                                         



                                                 












                                                                                   



                                                                      





                                                                    













                                                                                                                   

















































                                                                                                             



                                                                                       




                                                            



                                     


                                                                    

 
                                                                                            
 














                                                                               

 
                                    
 

























                                                                                                                  

 
                                                    
 
                             
 
                              
 



                                                                                     
 


                                                            
 
                                                         
 


                                                                                     
 
                                                                   
 

                                                                        
 


                                                                                                     
 



                                                                                          

 
                                    
 
















                                                                          



                                                   










                                                                                      



                                                      
                                                   



                                                        
                                                                                              



                                                       












































































































































































































                                                                                                                                                    
 


                                












                                                                                    




                                                                                    

                                                               



                                  







                                                                



                                                   
                             
 
// license:BSD-3-Clause
// copyright-holders:F. Ulivi
/*********************************************************************

    hp_taco.cpp

    HP TApe COntroller (5006-3012)

*********************************************************************/

// 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.
// The main shortcomings of my approach are:
// * I could indentify 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)
//
// 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.

// This is an overview of the TACO/CPU interface.
//
// Reg. | R/W | Content
// =====================
// 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.
// 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.
//
// 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
//  7     RW  Speed of tape (1 = 90 ips, 0 = 22 ips)
//  6     RW  Option bit for various commands
//  5     R   Current track (1 = B)
//  4     R   Gap detected (1)
//  3     R   Write protection (1)
//  2     R   Servo failure (1)
//  1     R   Cartridge out (1)
//  0     R   Hole detected (1)

// 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)
// * 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)
//   - 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
//   - End of tape holes
// * Sector #0 is not used
// * 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:
// 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 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:
// - 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
// - 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
//
// 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.
// * 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).
//
// 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
// * 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"

// 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)

// 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))

// Timers
enum {
		TAPE_TMR_ID,
		HOLE_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 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 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 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)
#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"

// Parts of command register
#define CMD_CODE(reg) \
		(((reg) >> 10) & 0x1f)
#define DIR_FWD(reg) \
		(BIT(reg , 15))
#define SPEED_FAST(reg) \
		(BIT(reg , 7))
#define CMD_OPT(reg) \
		(BIT(reg , 6))
#define UNKNOWN_B9(reg) \
		(BIT(reg , 9))
#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
};

// 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.

// *** Position of tape holes ***
// At beginning of tape:
// *START*
// |<-----24"----->|<---12"--->|<---12"--->|<-----24"----->|
//               O   O       O   O       O   O             O
//               |<->|       |<->|       |<->|
//               0.218"      0.218"      0.218"
// At end of tape:
//                                                     *END*
// |<-----24"----->|<---12"--->|<---12"--->|<-----24"----->|
// O               O           O           O
//
static const hp_taco_device::tape_pos_t tape_holes[] = {
		(hp_taco_device::tape_pos_t)(23.891 * ONE_INCH_POS),    // 24 - 0.218 / 2
		(hp_taco_device::tape_pos_t)(24.109 * ONE_INCH_POS),    // 24 + 0.218 / 2
		(hp_taco_device::tape_pos_t)(35.891 * ONE_INCH_POS),    // 36 - 0.218 / 2
		(hp_taco_device::tape_pos_t)(36.109 * ONE_INCH_POS),    // 36 + 0.218 / 2
		(hp_taco_device::tape_pos_t)(47.891 * ONE_INCH_POS),    // 48 - 0.218 / 2
		(hp_taco_device::tape_pos_t)(48.109 * ONE_INCH_POS),    // 48 + 0.218 / 2
		72 * ONE_INCH_POS,      // 72
		1752 * ONE_INCH_POS,    // 1752
		1776 * ONE_INCH_POS,    // 1776
		1788 * ONE_INCH_POS,    // 1788
		1800 * ONE_INCH_POS     // 1800
};

// Device type definition
const device_type HP_TACO = &device_creator<hp_taco_device>;

// Constructors
hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname)
		: device_t(mconfig, type, name, tag, owner, clock, shortname, __FILE__),
			device_image_interface(mconfig , *this),
			m_irq_handler(*this),
			m_flg_handler(*this),
			m_sts_handler(*this),
			m_tape_pos(TAPE_INIT_POS),
			m_image_dirty(false)
{
		clear_state();
}

hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
		: device_t(mconfig, HP_TACO, "HP TACO", tag, owner, clock, "TACO", __FILE__),
			device_image_interface(mconfig , *this),
			m_irq_handler(*this),
			m_flg_handler(*this),
			m_sts_handler(*this),
			m_tape_pos(TAPE_INIT_POS),
			m_image_dirty(false)
{
		clear_state();
}

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;
		}
}

READ16_MEMBER(hp_taco_device::reg_r)
{
		UINT16 res = 0;

		// Any I/O activity clears IRQ
		irq_w(false);

		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 2:
				// Tachometer register
				res = m_tach_reg;
				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));

		return res;
}

READ_LINE_MEMBER(hp_taco_device::flg_r)
{
		return m_flg;
}

READ_LINE_MEMBER(hp_taco_device::sts_r)
{
		return m_sts;
}

// device_config_complete
void hp_taco_device::device_config_complete()
{
		LOG(("device_config_complete"));
	update_names();
}

// 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);
}

// device_stop
void hp_taco_device::device_stop()
{
}

// device_reset
void hp_taco_device::device_reset()
{
		LOG(("device_reset"));
		clear_state();

		m_irq = false;
		m_flg = true;

		m_irq_handler(false);
		m_flg_handler(true);
		set_error(false);
}

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;
		}
}

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());
}

void hp_taco_device::irq_w(bool state)
{
		if (state != m_irq) {
				m_irq = state;
				m_irq_handler(state);
				LOG_0(("IRQ = %d\n" , state));
		}
}

void hp_taco_device::set_error(bool state)
{
		m_sts = !state;
		m_sts_handler(m_sts);
		LOG_0(("error = %d\n" , state));
}

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;
}

bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const
{
		if (offset == 0) {
				return true;
		}

		if (!m_tape_fwd) {
				offset = -offset;
		}

		pos += offset;

		// In real life tape would unspool..
		if (pos > TAPE_LENGTH) {
				pos = TAPE_LENGTH;
				return false;
		} else if (pos < 0) {
				pos = 0;
				return false;
		} else {
				return true;
		}
}

void hp_taco_device::move_tape_pos(tape_pos_t delta_pos)
{
		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);
		}
}

void hp_taco_device::update_tape_pos(void)
{
		if (m_start_time.is_never()) {
				// Tape not moving
				return;
		}

		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));

		move_tape_pos(delta_tach);

		// 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)
{
		if (a > b) {
				// Ensure A always comes before B
				tape_pos_t tmp;
				tmp = a;
				a = b;
				b = tmp;
		}
}

// 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
{
		if (m_tape_fwd) {
				for (tape_pos_t hole : tape_holes) {
						if (hole > m_tape_pos) {
								LOG_0(("next hole fwd @%u = %u\n" , m_tape_pos , hole));
								return hole;
						}
				}
				// No more holes: will hit end of tape
				return TAPE_LENGTH;
		} else {
				for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) {
						if (tape_holes[ i ] < m_tape_pos) {
								LOG_0(("next hole rev @%u = %u\n" , m_tape_pos , tape_holes[ i ]));
								return tape_holes[ i ];
						}
				}
				// No more holes: will hit start of tape
				return 0;
		}
}

attotime hp_taco_device::time_to_distance(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(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)
{
		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) {
				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;
		}
}

void hp_taco_device::stop_tape(void)
{
		m_start_time = attotime::never;
		m_gap_detect_start = NULL_TAPE_POS;
}

hp_taco_device::tape_track_t& hp_taco_device::current_track(void)
{
		return m_tracks[ BIT(m_status_reg , STATUS_TRACKB_BIT) ];
}

// Return physical length of a 16-bit word on tape
hp_taco_device::tape_pos_t hp_taco_device::word_length(tape_word_t w)
{
		unsigned zeros , ones;

		// pop count of w
		ones = (w & 0x5555) + ((w >> 1) & 0x5555);
		ones = (ones & 0x3333) + ((ones >> 2) & 0x3333);
		ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f);
		ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff);

		zeros = 16 - ones;

		return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN;
}

hp_taco_device::tape_pos_t hp_taco_device::word_end_pos(const tape_track_t::iterator& it)
{
	return it->first + word_length(it->second);
}

void hp_taco_device::adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos)
{
		if (it != track.begin()) {
				it--;
				if (word_end_pos(it) <= pos) {
						it++;
				}
		}
}

// Write a word on current tape track
void hp_taco_device::write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length)
{
		tape_track_t& track = current_track();
		tape_track_t::iterator it_low = track.lower_bound(start);
		adjust_it(track , it_low , start);
		length = word_length(word);
		tape_pos_t end_pos = start + length;
		tape_track_t::iterator it_high = track.lower_bound(end_pos);

		track.erase(it_low , it_high);

		track.insert(it_high , std::make_pair(start, word));
		LOG_0(("WR %04x @ T%u:%u\n" , word , BIT(m_status_reg , STATUS_TRACKB_BIT) , start));
}

// Write a gap on current track
void hp_taco_device::write_gap(tape_pos_t a , tape_pos_t b)
{
		ensure_a_lt_b(a , b);
		tape_track_t& track = current_track();
		tape_track_t::iterator it_low = track.lower_bound(a);
		adjust_it(track , it_low , a);
		tape_track_t::iterator it_high = track.lower_bound(b);

		track.erase(it_low, it_high);

		LOG_0(("GAP on T%u:[%u,%u)\n" , BIT(m_status_reg , STATUS_TRACKB_BIT) , a , b));
}

bool hp_taco_device::just_gap(tape_pos_t a , tape_pos_t b)
{
		ensure_a_lt_b(a , b);
		tape_track_t& track = current_track();
		tape_track_t::iterator it_low = track.lower_bound(a);
		tape_track_t::iterator it_high = track.lower_bound(b);

		adjust_it(track, it_low, a);

		return it_low == it_high;
}

hp_taco_device::tape_pos_t hp_taco_device::farthest_end(const tape_track_t::iterator& it) const
{
		if (m_tape_fwd) {
				return word_end_pos(it);
		} else {
				return it->first;
		}
}

bool hp_taco_device::next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive)
{
		tape_track_t& track = current_track();
		it = track.lower_bound(pos);
		if (m_tape_fwd) {
				if (inclusive) {
						adjust_it(track, it, pos);
				}
				return it != track.end();
		} else {
				// Never more than 2 iterations
				do {
						if (it == track.begin()) {
								it = track.end();
								return false;
						}
						it--;
				} while (!inclusive && word_end_pos(it) > pos);
				return true;
		}
}

hp_taco_device::adv_res_t hp_taco_device::adv_it(tape_track_t::iterator& it)
{
		tape_track_t& track = current_track();
		if (m_tape_fwd) {
				tape_pos_t prev_pos = word_end_pos(it);
				it++;
				if (it == track.end()) {
						return ADV_NO_MORE_DATA;
				} else {
						adv_res_t res = prev_pos == it->first ? ADV_CONT_DATA : ADV_DISCONT_DATA;
						return res;
				}
		} else {
				if (it == track.begin()) {
						it = track.end();
						return ADV_NO_MORE_DATA;
				} else {
						tape_pos_t prev_pos = it->first;
						it--;
						return prev_pos == word_end_pos(it) ? ADV_CONT_DATA : ADV_DISCONT_DATA;
				}
		}
}

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));
}

attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos)
{
		if (m_rd_it_valid) {
				word_rd_pos = farthest_end(m_rd_it);
				return time_to_target(word_rd_pos);
		} else {
				return attotime::never;
		}
}

/**
 * Scan for next "n_gaps" gaps
 *
 * @param[in,out] pos Start position on input, start of gap on output
 * @param it Pointer to data word where scan is to start
 * @param n_gaps Number of gaps to scan
 * @param min_gap Minimum gap size
 *
 * @return true if n_gaps gaps are found
 */
bool hp_taco_device::next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap)
{
		tape_track_t& track = current_track();
		bool done = false;
		tape_track_t::iterator prev_it;

		if (m_tape_fwd) {
				tape_pos_t next_pos;

				while (1) {
						if (it == track.end()) {
								next_pos = TAPE_LENGTH;
								done = true;
						} else {
								next_pos = it->first;
						}
						if (((next_pos - pos) >= min_gap && --n_gaps == 0) || done) {
								break;
						}
						adv_res_t adv_res;
						do {
								prev_it = it;
								adv_res = adv_it(it);
						} while (adv_res == ADV_CONT_DATA);
						pos = word_end_pos(prev_it);
				}
		} else {
				tape_pos_t next_pos;

				while (1) {
						if (it == track.end()) {
								next_pos = 0;
								done = true;
						} else {
								next_pos = word_end_pos(it);
						}
						if (((pos - next_pos) >= min_gap && --n_gaps == 0) || done) {
								break;
						}
						adv_res_t adv_res;
						do {
								prev_it = it;
								adv_res = adv_it(it);
						} while (adv_res == ADV_CONT_DATA);
						pos = prev_it->first;
				}
		}

		// Set "pos" where minimum gap size is met
		pos_offset(pos , min_gap);

		return n_gaps == 0;
}

bool hp_taco_device::next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap)
{
		tape_track_t::iterator it;
		// First align with next data
		next_data(it, pos, true);
		// Then scan for n_gaps
		return next_n_gap(pos, it, n_gaps, min_gap);
}

void hp_taco_device::clear_tape(void)
{
		for (unsigned track_n = 0; track_n < 2; track_n++) {
				m_tracks[ track_n ].clear();
		}
}

void hp_taco_device::dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words)
{
		if (n_words) {
				UINT32 tmp32;
				UINT16 tmp16;

				tmp32 = n_words;
				fwrite(&tmp32 , sizeof(tmp32));
				tmp32 = it_start->first;
				fwrite(&tmp32 , sizeof(tmp32));

				for (unsigned i = 0; i < n_words; i++) {
						tmp16 = it_start->second;
						fwrite(&tmp16 , sizeof(tmp16));
						it_start++;
				}
		}
}

void hp_taco_device::save_tape(void)
{
		UINT32 tmp32;

		fseek(0, SEEK_SET);

		tmp32 = FILE_MAGIC;
		fwrite(&tmp32 , sizeof(tmp32));

		for (unsigned track_n = 0; track_n < 2; track_n++) {
				const tape_track_t& track = m_tracks[ track_n ];
				tape_pos_t next_pos = (tape_pos_t)-1;
				unsigned n_words = 0;
				tape_track_t::const_iterator it_start;
				for (tape_track_t::const_iterator it = track.cbegin(); it != track.cend(); it++) {
						if (it->first != next_pos) {
								dump_sequence(it_start , n_words);
								it_start = it;
								n_words = 0;
						}
						next_pos = it->first + word_length(it->second);
						n_words++;
				}
				dump_sequence(it_start , n_words);
				// End of track
				tmp32 = (UINT32)-1;
				fwrite(&tmp32 , sizeof(tmp32));
		}
}

bool hp_taco_device::load_track(tape_track_t& track)
{
		UINT32 tmp32;

		track.clear();

		while (1) {
				if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) {
						return false;
				}

				if (tmp32 == (UINT32)-1) {
						return true;
				}

				unsigned n_words = tmp32;

				if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) {
						return false;
				}

				tape_pos_t pos = (tape_pos_t)tmp32;

				for (unsigned i = 0; i < n_words; i++) {
						UINT16 tmp16;

						if (fread(&tmp16 , sizeof(tmp16)) != sizeof(tmp16)) {
								return false;
						}

						track.insert(std::make_pair(pos , tmp16));
						pos += word_length(tmp16);
				}
		}
}

bool hp_taco_device::load_tape(void)
{
		UINT32 magic;

		if (fread(&magic , sizeof(magic)) != sizeof(magic) ||
			magic != FILE_MAGIC) {
				return false;
		}

		for (unsigned track_n = 0; track_n < 2; track_n++) {
				if (!load_track(m_tracks[ track_n ])) {
						LOG(("load_tape failed"));
						clear_tape();
						return false;
				}
		}

		LOG(("load_tape done\n"));
		return true;
}

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
		} else {
				BIT_SET(m_status_reg, STATUS_CART_OUT_BIT);
				BIT_SET(m_status_reg, STATUS_WPR_BIT);
		}
}

attotime hp_taco_device::time_to_next_hole(void) const
{
		return time_to_target(next_hole());
}

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)
{
		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;

		default:
				LOG(("Unrecognized command\n"));
				break;
		}

		m_tape_timer->adjust(cmd_duration);
		m_hole_timer->adjust(time_to_hole);
}

bool hp_taco_device::call_load()
{
		LOG(("call_load %d\n" , has_been_created()));
		if (has_been_created()) {
				clear_tape();
				save_tape();
		} else if (!load_tape()) {
				seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format");
				set_tape_present(false);
				return IMAGE_INIT_FAIL;
		}

		m_image_dirty = false;

		set_tape_present(true);
	return IMAGE_INIT_PASS;
}

bool hp_taco_device::call_create(int format_type, option_resolution *format_options)
{
		LOG(("call_create %d\n" , has_been_created()));
		return call_load();
}

void hp_taco_device::call_unload()
{
		LOG(("call_unload dirty=%d\n" , m_image_dirty));
		if (m_image_dirty) {
				save_tape();
				m_image_dirty = false;
		}

		clear_tape();
		set_tape_present(false);
}

const char *hp_taco_device::file_extensions() const
{
		return "hti";
}