From 408c6351cf0b34d2d1ab518451d1239429c08cee Mon Sep 17 00:00:00 2001 From: fulivi Date: Mon, 11 Jan 2016 13:26:13 +0100 Subject: [PATCH 1/7] hp9845: initial version of HP TACO driver (only basic tape movement is working) --- scripts/src/machine.lua | 12 + scripts/target/mame/mess.lua | 1 + src/devices/machine/hp_taco.cpp | 516 ++++++++++++++++++++++++++++++++ src/devices/machine/hp_taco.h | 93 ++++++ src/mame/drivers/hp9845.cpp | 69 ++++- 5 files changed, 681 insertions(+), 10 deletions(-) create mode 100644 src/devices/machine/hp_taco.cpp create mode 100644 src/devices/machine/hp_taco.h diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 5a97d22d015..fbc923b3073 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -771,6 +771,18 @@ if (MACHINES["HD64610"]~=null) then } end +--------------------------------------------------- +-- +--@src/devices/machine/hp_taco.h,MACHINES["HP_TACO"] = true +--------------------------------------------------- + +if (MACHINES["HP_TACO"]~=null) then + files { + MAME_DIR .. "src/devices/machine/hp_taco.cpp", + MAME_DIR .. "src/devices/machine/hp_taco.h", + } +end + --------------------------------------------------- -- --@src/devices/machine/i2cmem.h,MACHINES["I2CMEM"] = true diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index cb9a150224a..80be3e7c76c 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -398,6 +398,7 @@ MACHINES["ER2055"] = true MACHINES["F3853"] = true MACHINES["HD63450"] = true MACHINES["HD64610"] = true +MACHINES["HP_TACO"] = true MACHINES["I2CMEM"] = true MACHINES["I80130"] = true MACHINES["I8089"] = true diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp new file mode 100644 index 00000000000..1597c5c5733 --- /dev/null +++ b/src/devices/machine/hp_taco.cpp @@ -0,0 +1,516 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp_taco.cpp + + HP TApe COntroller (5006-3012) + +*********************************************************************/ + +// 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) + +// Format of TACO command/status register (R5) +// Bit R/W Content +// =============== +// 15 RW Tape direction (1 = forward) +// 14..10 RW Command +// 9 RW ? Drive ON according to [1], doesn't match usage of firmware +// 8 RW ? Size of gaps according to [1] +// 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) + +// TODO: R6 è modificato durante il conteggio impulsi? Viene azzerato alla lettura? + +#include "emu.h" +#include "hp_taco.h" + +// Debugging +#define VERBOSE 1 +#define LOG(x) do { if (VERBOSE) 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 +}; + +// Constants +#define CMD_REG_MASK 0xffc0 // Command register mask +#define STATUS_REG_MASK 0x003f // Status register mask +#define STATUS_ERR_MASK 0x0002 // Mask of errors in status reg. +#define TACH_TICKS_PER_INCH 968 // Tachometer pulses per inch of tape movement +#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) * TACH_TICKS_PER_INCH) // Tape length (in tachometer pulses): 140 ft of usable tape + 72" of punched tape at either end +#define TAPE_INIT_POS (80 * TACH_TICKS_PER_INCH) // Initial tape position: 80" from beginning (just past the punched part) +#define QUICK_CMD_USEC 10 // usec for "quick" command execution + +// 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)) + +// Commands +enum { + CMD_ALIGN_0, // 00: header alignment (?) + 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_UNK_0c, // 0c: unknown* + CMD_UNK_0d, // 0d: unknown + CMD_CLEAR, // 0e: clear errors/unlatch status bits + CMD_UNK_0f, // 0f: unknown + CMD_ALIGN_PREAMBLE, // 10: align to end of preamble (?) + 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_UNK_MOVE, // 1a: some kind of tape movement + CMD_UNK_1b, // 1b: unknown + CMD_DELTA_MOVE_REC, // 1c: move tape a given distance (optionally stop at 1st record) (?) + CMD_START_READ, // 1d: start record reading + CMD_DELTA_MOVE_IRG, // 1e: move tape a given distance (optionally stop at 1st IRG) + 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 + +// *** 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 * TACH_TICKS_PER_INCH), // 24 - 0.218 / 2 + (hp_taco_device::tape_pos_t)(24.109 * TACH_TICKS_PER_INCH), // 24 + 0.218 / 2 + (hp_taco_device::tape_pos_t)(35.891 * TACH_TICKS_PER_INCH), // 36 - 0.218 / 2 + (hp_taco_device::tape_pos_t)(36.109 * TACH_TICKS_PER_INCH), // 36 + 0.218 / 2 + (hp_taco_device::tape_pos_t)(47.891 * TACH_TICKS_PER_INCH), // 48 - 0.218 / 2 + (hp_taco_device::tape_pos_t)(48.109 * TACH_TICKS_PER_INCH), // 48 + 0.218 / 2 + 72 * TACH_TICKS_PER_INCH, // 72 + 1752 * TACH_TICKS_PER_INCH, // 1752 + 1776 * TACH_TICKS_PER_INCH, // 1776 + 1788 * TACH_TICKS_PER_INCH, // 1788 + 1800 * TACH_TICKS_PER_INCH // 1800 +}; + +// Device type definition +const device_type HP_TACO = &device_creator; + +// 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__), + m_irq_handler(*this), + m_flg_handler(*this), + m_sts_handler(*this), + m_data_reg(0), + m_cmd_reg(0), + m_status_reg(0), + m_tach_reg(0), + m_checksum_reg(0), + m_timing_reg(0), + m_tape_pos(TAPE_INIT_POS) +{ +} + +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__), + m_irq_handler(*this), + m_flg_handler(*this), + m_sts_handler(*this), + m_data_reg(0), + m_cmd_reg(0), + m_status_reg(0), + m_tach_reg(0), + m_checksum_reg(0), + m_timing_reg(0), + m_tape_pos(TAPE_INIT_POS) +{ +} + +WRITE16_MEMBER(hp_taco_device::reg_w) +{ + LOG(("wr R%u = %04x\n", 4 + offset , data)); + + // Any I/O activity clears IRQ + irq_w(false); + + switch (offset) { + case 0: + // Data register + m_data_reg = data; + break; + + case 1: + // Command register + start_cmd_exec(data & CMD_REG_MASK); + break; + + case 2: + // Tachometer register + m_tach_reg = data; + break; + + case 3: + // 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 + res = m_checksum_reg; + m_checksum_reg = 0; + break; + } + + LOG(("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_start +void hp_taco_device::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_cmd_reg)); + 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)); + + m_tape_timer = timer_alloc(TAPE_TMR_ID); +} + +// device_reset +void hp_taco_device::device_reset() +{ + m_data_reg = 0; + m_cmd_reg = 0; + m_status_reg = 0; + m_tach_reg = 0; + m_checksum_reg = 0; + m_timing_reg = 0; + m_start_time = attotime::never; + + 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) +{ + switch (id) { + case TAPE_TMR_ID: + LOG(("Tape tmr @%g\n" , machine().time().as_double())); + switch (CMD_CODE(m_cmd_reg)) { + case CMD_MOVE: + // Generate an interrupt each time a hole is crossed (tape doesn't stop) + update_tape_pos(); + m_tape_timer->adjust(time_to_target(next_hole(DIR_FWD(m_cmd_reg)) , SPEED_FAST(m_cmd_reg))); + BIT_SET(m_status_reg, STATUS_HOLE_BIT); + break; + + case CMD_DELTA_MOVE_REC: + case CMD_DELTA_MOVE_IRG: + // Interrupt & stop at end of movement + stop_tape(); + break; + + default: + // Other commands: just raise irq + break; + } + irq_w(true); + break; + + default: + break; + } +} + +void hp_taco_device::irq_w(bool state) +{ + if (state != m_irq) { + m_irq = state; + m_irq_handler(state); + LOG(("IRQ = %d\n" , state)); + } +} + +void hp_taco_device::set_error(bool state) +{ + m_sts = !state; + m_sts_handler(m_sts); + LOG(("error = %d\n" , state)); +} + +bool hp_taco_device::check_for_errors(void) +{ + // Is it an error when "status" flag is already reporting an error? Dunno... + if ((m_status_reg & STATUS_ERR_MASK) != 0) { + set_error(true); + return true; + } else { + return false; + } +} + +unsigned hp_taco_device::speed_to_tick_freq(bool fast) +{ + return fast ? TACH_FREQ_FAST : TACH_FREQ_SLOW; +} + +void hp_taco_device::update_tape_pos(void) +{ + attotime delta_time(machine().time() - m_start_time); + m_start_time = machine().time(); + LOG(("delta_time = %g\n" , delta_time.as_double())); + // How many tachometer ticks has the tape moved? + unsigned delta_tach = (unsigned)(delta_time.as_ticks(speed_to_tick_freq(SPEED_FAST(m_cmd_reg)))); + LOG(("delta_tach = %u\n" , delta_tach)); + + if (DIR_FWD(m_cmd_reg)) { + // Forward + m_tape_pos += delta_tach; + + // In real life tape would unspool.. + if (m_tape_pos > TAPE_LENGTH) { + m_tape_pos = TAPE_LENGTH; + LOG(("Tape unspooled at the end!\n")); + } + } else { + // Reverse + if (delta_tach >= m_tape_pos) { + m_tape_pos = 0; + LOG(("Tape unspooled at the start!\n")); + } else { + m_tape_pos -= delta_tach; + } + } + LOG(("Tape pos = %u\n" , m_tape_pos)); +} + +// 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) +{ + if (tape_pos_a > tape_pos_b) { + // Ensure A always comes before B + tape_pos_t tmp; + tmp = tape_pos_a; + tape_pos_a = tape_pos_b; + tape_pos_b = tmp; + } + + 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(bool fwd) const +{ + if (fwd) { + for (tape_pos_t hole : tape_holes) { + if (hole > m_tape_pos) { + LOG(("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(("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, bool fast) +{ + // +1 for rounding + return attotime::from_ticks(distance + 1 , speed_to_tick_freq(fast)); +} + +attotime hp_taco_device::time_to_target(tape_pos_t target, bool fast) const +{ + return time_to_distance(abs(target - m_tape_pos), fast); +} + +void hp_taco_device::start_tape(void) +{ + m_start_time = machine().time(); + BIT_CLR(m_status_reg, STATUS_HOLE_BIT); +} + +void hp_taco_device::stop_tape(void) +{ + if (!m_start_time.is_never()) { + tape_pos_t tape_start_pos = m_tape_pos; + update_tape_pos(); + if (any_hole(tape_start_pos , m_tape_pos)) { + // Crossed one or more holes + BIT_SET(m_status_reg , STATUS_HOLE_BIT); + } + m_start_time = attotime::never; + } + m_tape_timer->reset(); +} + +void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) +{ + LOG(("Cmd = %02x\n" , CMD_CODE(new_cmd_reg))); + + attotime cmd_duration = attotime::never; + + // Should irq be raised anyway when already in error condition? Here we do nothing. + + switch (CMD_CODE(new_cmd_reg)) { + case CMD_CLEAR: + set_error(false); + BIT_CLR(m_status_reg, STATUS_HOLE_BIT); + // This is a special command: it doesn't raise IRQ at completion and it + // doesn't replace the current command, if any. + return; + + case CMD_STOP: + stop_tape(); + cmd_duration = attotime::from_usec(QUICK_CMD_USEC); + break; + + case CMD_SET_TRACK: + if (!check_for_errors()) { + 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: + stop_tape(); + if (!check_for_errors()) { + start_tape(); + cmd_duration = time_to_target(next_hole(DIR_FWD(new_cmd_reg)) , SPEED_FAST(new_cmd_reg)); + } + break; + + case CMD_DELTA_MOVE_REC: + case CMD_DELTA_MOVE_IRG: + // TODO: record/irg detection + stop_tape(); + if (!check_for_errors()) { + start_tape(); + cmd_duration = time_to_distance(0x10000U - m_tach_reg , SPEED_FAST(new_cmd_reg)); + } + break; + + default: + LOG(("Unrecognized command\n")); + return; + } + + m_tape_timer->adjust(cmd_duration); + m_cmd_reg = new_cmd_reg; +} diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h new file mode 100644 index 00000000000..e279d045d10 --- /dev/null +++ b/src/devices/machine/hp_taco.h @@ -0,0 +1,93 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + hp_taco.h + + HP TApe COntroller (5006-3012) + +*********************************************************************/ + +#ifndef __HP_TACO_H__ +#define __HP_TACO_H__ + +#define MCFG_TACO_IRQ_HANDLER(_devcb) \ + devcb = &hp_taco_device::set_irq_handler(*device , DEVCB_##_devcb); + +#define MCFG_TACO_FLG_HANDLER(_devcb) \ + devcb = &hp_taco_device::set_flg_handler(*device , DEVCB_##_devcb); + +#define MCFG_TACO_STS_HANDLER(_devcb) \ + devcb = &hp_taco_device::set_sts_handler(*device , DEVCB_##_devcb); + +class hp_taco_device : public device_t +{ +public: + // construction/destruction + hp_taco_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname); + hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + // static configuration helpers + template static devcb_base &set_irq_handler(device_t &device, _Object object) { return downcast(device).m_irq_handler.set_callback(object); } + template static devcb_base &set_flg_handler(device_t &device, _Object object) { return downcast(device).m_flg_handler.set_callback(object); } + template static devcb_base &set_sts_handler(device_t &device, _Object object) { return downcast(device).m_sts_handler.set_callback(object); } + + // Register read/write + DECLARE_WRITE16_MEMBER(reg_w); + DECLARE_READ16_MEMBER(reg_r); + + // Flag & status read + DECLARE_READ_LINE_MEMBER(flg_r); + DECLARE_READ_LINE_MEMBER(sts_r); + + typedef UINT32 tape_pos_t; + +protected: + // device-level overrides + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + +private: + devcb_write_line m_irq_handler; + devcb_write_line m_flg_handler; + devcb_write_line m_sts_handler; + + // Registers + UINT16 m_data_reg; + UINT16 m_cmd_reg; + UINT16 m_status_reg; + UINT16 m_tach_reg; + UINT16 m_checksum_reg; + UINT16 m_timing_reg; + + // State + bool m_irq; + bool m_flg; + bool m_sts; + + // Tape position + tape_pos_t m_tape_pos; + attotime m_start_time; + + // Timers + emu_timer *m_tape_timer; + + void irq_w(bool state); + void set_error(bool state); + bool check_for_errors(void); + static unsigned speed_to_tick_freq(bool fast); + void update_tape_pos(void); + static bool any_hole(UINT32 tape_pos_a , UINT32 tape_pos_b); + UINT32 next_hole(bool fwd) const; + static attotime time_to_distance(UINT32 distance, bool fast); + attotime time_to_target(UINT32 target, bool fast) const; + void start_tape(void); + void stop_tape(void); + void start_cmd_exec(UINT16 new_cmd_reg); +}; + +// device type definition +extern const device_type HP_TACO; + +#endif /* __HP_TACO_H__ */ diff --git a/src/mame/drivers/hp9845.cpp b/src/mame/drivers/hp9845.cpp index 2936cae3228..ec0e53da906 100644 --- a/src/mame/drivers/hp9845.cpp +++ b/src/mame/drivers/hp9845.cpp @@ -33,6 +33,7 @@ #include "cpu/z80/z80.h" #include "softlist.h" #include "cpu/hphybrid/hphybrid.h" +#include "machine/hp_taco.h" #define BIT_MASK(n) (1U << (n)) @@ -77,7 +78,8 @@ public: m_io_key0(*this , "KEY0"), m_io_key1(*this , "KEY1"), m_io_key2(*this , "KEY2"), - m_io_key3(*this , "KEY3") + m_io_key3(*this , "KEY3"), + m_t15(*this , "t15") { } UINT32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); @@ -90,7 +92,7 @@ public: void vblank_w(screen_device &screen, bool state); IRQ_CALLBACK_MEMBER(irq_callback); - void update_irl(void); + void update_irq(void); TIMER_DEVICE_CALLBACK_MEMBER(kb_scan); DECLARE_READ16_MEMBER(kb_scancode_r); @@ -99,6 +101,10 @@ public: DECLARE_WRITE8_MEMBER(pa_w); + DECLARE_WRITE_LINE_MEMBER(t15_irq_w); + DECLARE_WRITE_LINE_MEMBER(t15_flg_w); + DECLARE_WRITE_LINE_MEMBER(t15_sts_w); + private: required_device m_lpu; required_device m_ppu; @@ -107,6 +113,7 @@ private: required_ioport m_io_key1; required_ioport m_io_key2; required_ioport m_io_key3; + required_device m_t15; void set_video_mar(UINT16 mar); void video_fill_buff(bool buff_idx); @@ -136,12 +143,15 @@ private: // Interrupt handling UINT8 m_irl_pending; + UINT8 m_irh_pending; // State of keyboard ioport_value m_kb_state[ 4 ]; UINT8 m_kb_scancode; UINT16 m_kb_status; + // State of PPU I/O + UINT8 m_ppu_pa; }; static INPUT_PORTS_START(hp9845b) @@ -322,10 +332,13 @@ void hp9845b_state::machine_reset() m_video_frame = 0; m_irl_pending = 0; + m_irh_pending = 0; memset(&m_kb_state[ 0 ] , 0 , sizeof(m_kb_state)); m_kb_scancode = 0x7f; m_kb_status = 0; + + m_ppu_pa = 0; } void hp9845b_state::set_video_mar(UINT16 mar) @@ -479,13 +492,14 @@ IRQ_CALLBACK_MEMBER(hp9845b_state::irq_callback) if (irqline == HPHYBRID_IRL) { return m_irl_pending; } else { - return 0; + return m_irh_pending; } } -void hp9845b_state::update_irl(void) +void hp9845b_state::update_irq(void) { m_ppu->set_input_line(HPHYBRID_IRL , m_irl_pending != 0); + m_ppu->set_input_line(HPHYBRID_IRH , m_irh_pending != 0); } TIMER_DEVICE_CALLBACK_MEMBER(hp9845b_state::kb_scan) @@ -546,7 +560,7 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9845b_state::kb_scan) m_kb_scancode = i; BIT_SET(m_irl_pending , 0); BIT_SET(m_kb_status, 0); - update_irl(); + update_irq(); // Special case: pressing stop key sets LPU "status" flag if (i == 0x47) { @@ -572,24 +586,50 @@ WRITE16_MEMBER(hp9845b_state::kb_irq_clear_w) { BIT_CLR(m_irl_pending , 0); BIT_CLR(m_kb_status, 0); - update_irl(); + update_irq(); m_lpu->status_w(0); // TODO: beeper start } WRITE8_MEMBER(hp9845b_state::pa_w) { + m_ppu_pa = data; + // TODO: handle sts & flg - if (data == 0xf) { - // RHS tape drive (T15) - m_ppu->status_w(1); - m_ppu->flag_w(1); + if (data == 15) { + // RHS tape drive (T15) + m_ppu->status_w(m_t15->sts_r()); + m_ppu->flag_w(m_t15->flg_r()); } else { m_ppu->status_w(0); m_ppu->flag_w(0); } } +WRITE_LINE_MEMBER(hp9845b_state::t15_irq_w) +{ + if (state) { + BIT_SET(m_irh_pending , 7); + } else { + BIT_CLR(m_irh_pending , 7); + } + update_irq(); +} + +WRITE_LINE_MEMBER(hp9845b_state::t15_flg_w) +{ + if (m_ppu_pa == 15) { + m_ppu->flag_w(state); + } +} + +WRITE_LINE_MEMBER(hp9845b_state::t15_sts_w) +{ + if (m_ppu_pa == 15) { + m_ppu->status_w(state); + } +} + static MACHINE_CONFIG_START( hp9845a, hp9845_state ) //MCFG_CPU_ADD("lpu", HP_5061_3010, XTAL_11_4MHz) //MCFG_CPU_ADD("ppu", HP_5061_3011, XTAL_11_4MHz) @@ -638,6 +678,9 @@ static ADDRESS_MAP_START(ppu_io_map , AS_IO , 16 , hp9845b_state) // PA = 0, IC = 3 // Keyboard status input & keyboard interrupt clear AM_RANGE(HP_MAKE_IOADDR(0 , 3) , HP_MAKE_IOADDR(0 , 3)) AM_READWRITE(kb_status_r , kb_irq_clear_w) + // PA = 15, IC = 0..3 + // Right-hand side tape drive (T15) + AM_RANGE(HP_MAKE_IOADDR(15 , 0) , HP_MAKE_IOADDR(15 , 3)) AM_DEVREADWRITE("t15" , hp_taco_device , reg_r , reg_w) ADDRESS_MAP_END static MACHINE_CONFIG_START( hp9845b, hp9845b_state ) @@ -663,6 +706,12 @@ static MACHINE_CONFIG_START( hp9845b, hp9845b_state ) // Actual keyboard refresh rate should be KEY_SCAN_OSCILLATOR / 128 (2560 Hz) MCFG_TIMER_DRIVER_ADD_PERIODIC("kb_timer" , hp9845b_state , kb_scan , attotime::from_hz(100)) + // Tape controller + MCFG_DEVICE_ADD("t15" , HP_TACO , 4000000) + MCFG_TACO_IRQ_HANDLER(WRITELINE(hp9845b_state , t15_irq_w)) + MCFG_TACO_FLG_HANDLER(WRITELINE(hp9845b_state , t15_flg_w)) + MCFG_TACO_STS_HANDLER(WRITELINE(hp9845b_state , t15_sts_w)) + MCFG_SOFTWARE_LIST_ADD("optrom_list", "hp9845b_rom") MACHINE_CONFIG_END From 617295ec8644d6bfaf224450d170361f6d3ddbfc Mon Sep 17 00:00:00 2001 From: fulivi Date: Thu, 14 Jan 2016 13:36:59 +0100 Subject: [PATCH 2/7] hphybrid: interrupt vector fetching fixed (again) --- src/devices/cpu/hphybrid/hphybrid.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/devices/cpu/hphybrid/hphybrid.cpp b/src/devices/cpu/hphybrid/hphybrid.cpp index 052c7058604..71aec796760 100644 --- a/src/devices/cpu/hphybrid/hphybrid.cpp +++ b/src/devices/cpu/hphybrid/hphybrid.cpp @@ -673,12 +673,7 @@ UINT16 hp_hybrid_cpu_device::RM(UINT32 addr) return RIO(CURRENT_PA , addr_wo_bsc - HP_REG_R4_ADDR); case HP_REG_IV_ADDR: - // Correct? - if (!BIT(m_flags , HPHYBRID_IRH_SVC_BIT) && !BIT(m_flags , HPHYBRID_IRL_SVC_BIT)) { - return m_reg_IV; - } else { - return m_reg_IV | CURRENT_PA; - } + return m_reg_IV; case HP_REG_PA_ADDR: return CURRENT_PA; @@ -1021,7 +1016,7 @@ void hp_hybrid_cpu_device::check_for_interrupts(void) // Do a double-indirect JSM IV,I instruction WM(AEC_CASE_C , ++m_reg_R , m_reg_P); - m_reg_P = RM(AEC_CASE_I , RM(HP_REG_IV_ADDR)); + m_reg_P = RM(AEC_CASE_I , m_reg_IV + CURRENT_PA); m_reg_I = fetch(); } From 8d80c1f25952441f64a027aeed183f50302962ae Mon Sep 17 00:00:00 2001 From: fulivi Date: Tue, 9 Feb 2016 14:08:02 +0100 Subject: [PATCH 3/7] hp9845: major update to TACO driver, it can read&write tapes now. To be cleaned. --- src/devices/machine/hp_taco.cpp | 995 ++++++++++++++++++++++++++++---- src/devices/machine/hp_taco.h | 102 +++- 2 files changed, 971 insertions(+), 126 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 1597c5c5733..388cd1acdfe 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -44,19 +44,32 @@ // Timers enum { - TAPE_TMR_ID + TAPE_TMR_ID, + HOLE_TMR_ID }; // Constants #define CMD_REG_MASK 0xffc0 // Command register mask #define STATUS_REG_MASK 0x003f // Status register mask -#define STATUS_ERR_MASK 0x0002 // Mask of errors in status reg. #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) * TACH_TICKS_PER_INCH) // Tape length (in tachometer pulses): 140 ft of usable tape + 72" of punched tape at either end -#define TAPE_INIT_POS (80 * TACH_TICKS_PER_INCH) // Initial tape position: 80" from beginning (just past the punched part) -#define QUICK_CMD_USEC 10 // usec for "quick" command execution +#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) // Parts of command register #define CMD_CODE(reg) \ @@ -67,10 +80,14 @@ enum { (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_ALIGN_0, // 00: header alignment (?) + 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 @@ -82,11 +99,11 @@ enum { CMD_UNK_09, // 09: unknown CMD_MOVE, // 0a: move tape CMD_UNK_0b, // 0b: unknown - CMD_UNK_0c, // 0c: 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_ALIGN_PREAMBLE, // 10: align to end of preamble (?) + CMD_NOT_INDTA, // 10: scan for end of data CMD_UNK_11, // 11: unknown CMD_UNK_12, // 12: unknown CMD_UNK_13, // 13: unknown @@ -96,11 +113,11 @@ enum { CMD_UNK_17, // 17: unknown CMD_SCAN_RECORDS, // 18: scan records (count IRGs) CMD_RECORD_WRITE, // 19: write record words - CMD_UNK_MOVE, // 1a: some kind of tape movement - CMD_UNK_1b, // 1b: unknown - CMD_DELTA_MOVE_REC, // 1c: move tape a given distance (optionally stop at 1st record) (?) + 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 (optionally stop at 1st IRG) + CMD_DELTA_MOVE_IRG, // 1e: move tape a given distance, detect gaps in parallel CMD_END_READ // 1f: stop reading }; @@ -111,6 +128,9 @@ enum { #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: @@ -125,17 +145,17 @@ enum { // O O O O // static const hp_taco_device::tape_pos_t tape_holes[] = { - (hp_taco_device::tape_pos_t)(23.891 * TACH_TICKS_PER_INCH), // 24 - 0.218 / 2 - (hp_taco_device::tape_pos_t)(24.109 * TACH_TICKS_PER_INCH), // 24 + 0.218 / 2 - (hp_taco_device::tape_pos_t)(35.891 * TACH_TICKS_PER_INCH), // 36 - 0.218 / 2 - (hp_taco_device::tape_pos_t)(36.109 * TACH_TICKS_PER_INCH), // 36 + 0.218 / 2 - (hp_taco_device::tape_pos_t)(47.891 * TACH_TICKS_PER_INCH), // 48 - 0.218 / 2 - (hp_taco_device::tape_pos_t)(48.109 * TACH_TICKS_PER_INCH), // 48 + 0.218 / 2 - 72 * TACH_TICKS_PER_INCH, // 72 - 1752 * TACH_TICKS_PER_INCH, // 1752 - 1776 * TACH_TICKS_PER_INCH, // 1776 - 1788 * TACH_TICKS_PER_INCH, // 1788 - 1800 * TACH_TICKS_PER_INCH // 1800 + (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 @@ -148,6 +168,7 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, m_flg_handler(*this), m_sts_handler(*this), m_data_reg(0), + m_data_reg_full(false), m_cmd_reg(0), m_status_reg(0), m_tach_reg(0), @@ -163,6 +184,7 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, d m_flg_handler(*this), m_sts_handler(*this), m_data_reg(0), + m_data_reg_full(false), m_cmd_reg(0), m_status_reg(0), m_tach_reg(0), @@ -183,6 +205,7 @@ WRITE16_MEMBER(hp_taco_device::reg_w) case 0: // Data register m_data_reg = data; + m_data_reg_full = true; break; case 1: @@ -226,7 +249,7 @@ READ16_MEMBER(hp_taco_device::reg_r) break; case 3: - // Checksum register + // Checksum register: it clears when read res = m_checksum_reg; m_checksum_reg = 0; break; @@ -255,7 +278,9 @@ void hp_taco_device::device_start() 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)); @@ -265,20 +290,43 @@ void hp_taco_device::device_start() 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)); m_tape_timer = timer_alloc(TAPE_TMR_ID); + m_hole_timer = timer_alloc(HOLE_TMR_ID); + + FILE *in = fopen("tape_dump.bin" , "rb"); + if (in != NULL) { + load_tape(in); + fclose(in); + } +} + +// device_stop +void hp_taco_device::device_stop() +{ + LOG(("**device_stop**\n")); + FILE *out = fopen("tape_dump.bin" , "wb"); + if (out != NULL) { + save_tape(out); + fclose(out); + } } // device_reset void hp_taco_device::device_reset() { 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_start_time = attotime::never; + m_rd_it_valid = false; + m_gap_detect_start = NULL_TAPE_POS; m_irq = false; m_flg = true; @@ -290,23 +338,152 @@ 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(("Tape tmr @%g\n" , machine().time().as_double())); + + tape_pos_t length; + switch (CMD_CODE(m_cmd_reg)) { - case CMD_MOVE: - // Generate an interrupt each time a hole is crossed (tape doesn't stop) - update_tape_pos(); - m_tape_timer->adjust(time_to_target(next_hole(DIR_FWD(m_cmd_reg)) , SPEED_FAST(m_cmd_reg))); - BIT_SET(m_status_reg, STATUS_HOLE_BIT); + 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_DELTA_MOVE_REC: - case CMD_DELTA_MOVE_IRG: - // Interrupt & stop at end of movement + case CMD_RECORD_WRITE: + if (m_cmd_state == 0) { + if (m_rd_it->second == PREAMBLE_WORD) { + LOG(("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(("%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(("Got preamble\n")); + } else { + m_data_reg = m_rd_it->second; + m_checksum_reg += m_data_reg; + LOG(("RD %04x\n" , m_data_reg)); + } + adv_res_t res = adv_it(m_rd_it); + LOG(("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(("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; @@ -314,6 +491,46 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para irq_w(true); break; + case HOLE_TMR_ID: + LOG(("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; } @@ -335,62 +552,93 @@ void hp_taco_device::set_error(bool state) LOG(("error = %d\n" , state)); } -bool hp_taco_device::check_for_errors(void) +unsigned hp_taco_device::speed_to_tick_freq(void) const { - // Is it an error when "status" flag is already reporting an error? Dunno... - if ((m_status_reg & STATUS_ERR_MASK) != 0) { - set_error(true); + 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; - } else { + } + + 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; } } -unsigned hp_taco_device::speed_to_tick_freq(bool fast) +void hp_taco_device::move_tape_pos(tape_pos_t delta_pos) { - return fast ? TACH_FREQ_FAST : TACH_FREQ_SLOW; + 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(("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); - m_start_time = machine().time(); LOG(("delta_time = %g\n" , delta_time.as_double())); // How many tachometer ticks has the tape moved? - unsigned delta_tach = (unsigned)(delta_time.as_ticks(speed_to_tick_freq(SPEED_FAST(m_cmd_reg)))); + tape_pos_t delta_tach = (tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); LOG(("delta_tach = %u\n" , delta_tach)); - if (DIR_FWD(m_cmd_reg)) { - // Forward - m_tape_pos += delta_tach; + move_tape_pos(delta_tach); - // In real life tape would unspool.. - if (m_tape_pos > TAPE_LENGTH) { - m_tape_pos = TAPE_LENGTH; - LOG(("Tape unspooled at the end!\n")); - } - } else { - // Reverse - if (delta_tach >= m_tape_pos) { - m_tape_pos = 0; - LOG(("Tape unspooled at the start!\n")); - } else { - m_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; } - LOG(("Tape pos = %u\n" , m_tape_pos)); } // 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) { - if (tape_pos_a > tape_pos_b) { - // Ensure A always comes before B - tape_pos_t tmp; - tmp = tape_pos_a; - tape_pos_a = tape_pos_b; - tape_pos_b = tmp; - } + 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) { @@ -402,9 +650,9 @@ bool hp_taco_device::any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b) } // Position of next hole tape will reach in a given direction -hp_taco_device::tape_pos_t hp_taco_device::next_hole(bool fwd) const +hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const { - if (fwd) { + if (m_tape_fwd) { for (tape_pos_t hole : tape_holes) { if (hole > m_tape_pos) { LOG(("next hole fwd @%u = %u\n" , m_tape_pos , hole)); @@ -425,92 +673,631 @@ hp_taco_device::tape_pos_t hp_taco_device::next_hole(bool fwd) const } } -attotime hp_taco_device::time_to_distance(tape_pos_t distance, bool fast) +hp_taco_device::tape_pos_t hp_taco_device::met_first(tape_pos_t a , tape_pos_t b , bool fwd , bool& is_a) +{ + if (fwd) { + if (a < b) { + is_a = true; + return a; + } else { + is_a = false; + return b; + } + } else { + if (a >= b) { + is_a = true; + return a; + } else { + is_a = false; + return b; + } + } +} + +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(fast)); + return attotime::from_ticks(distance + 1 , speed_to_tick_freq()); } -attotime hp_taco_device::time_to_target(tape_pos_t target, bool fast) const +attotime hp_taco_device::time_to_target(tape_pos_t target) const { - return time_to_distance(abs(target - m_tape_pos), fast); + return time_to_distance(abs(target - m_tape_pos)); } -void hp_taco_device::start_tape(void) +bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0) { - m_start_time = machine().time(); - BIT_CLR(m_status_reg, STATUS_HOLE_BIT); + 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); + } 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) { - if (!m_start_time.is_never()) { - tape_pos_t tape_start_pos = m_tape_pos; - update_tape_pos(); - if (any_hole(tape_start_pos , m_tape_pos)) { - // Crossed one or more holes - BIT_SET(m_status_reg , STATUS_HOLE_BIT); + 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; + + // 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 should be enough for emulation. + // Anyway, 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 "1" encoded at the end + // - a 0 is encoded with a distance between flux reversals of 1/35200 s + // - a 1 is encoded with a distance that's 1.75 times that of a 0 + 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++; } - m_start_time = attotime::never; } - m_tape_timer->reset(); +} + +// 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(("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(("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(("next %04x (DR)\n" , m_next_word)); + } else { + // When data register is empty, write checksum word + m_next_word = m_checksum_reg; + LOG(("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::dump_sequence(FILE *out , tape_track_t::const_iterator it_start , unsigned n_words) +{ + if (n_words) { + UINT32 tmp32; + UINT16 tmp16; + + tmp32 = n_words; + fwrite(&tmp32 , sizeof(tmp32) , 1 , out); + tmp32 = it_start->first; + fwrite(&tmp32 , sizeof(tmp32) , 1 , out); + + for (unsigned i = 0; i < n_words; i++) { + tmp16 = it_start->second; + fwrite(&tmp16 , sizeof(tmp16) , 1 , out); + it_start++; + } + } +} + +void hp_taco_device::save_tape(FILE *out) const +{ + UINT32 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(out , it_start , n_words); + it_start = it; + n_words = 0; + } + next_pos = it->first + word_length(it->second); + n_words++; + } + dump_sequence(out , it_start , n_words); + // End of track + tmp32 = (UINT32)-1; + fwrite(&tmp32 , sizeof(tmp32) , 1 , out); + } +} + +bool hp_taco_device::load_track(FILE *in , tape_track_t& track) +{ + UINT32 tmp32; + + track.clear(); + + while (1) { + if (fread(&tmp32 , sizeof(tmp32) , 1 , in) != 1) { + return false; + } + + if (tmp32 == (UINT32)-1) { + return true; + } + + unsigned n_words = tmp32; + + if (fread(&tmp32 , sizeof(tmp32) , 1 , in) != 1) { + 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) , 1 , in) != 1) { + return false; + } + + // TODO: usare end() come hint + track.insert(std::make_pair(pos , tmp16)); + pos += word_length(tmp16); + } + } +} + +void hp_taco_device::load_tape(FILE *in) +{ + for (unsigned track_n = 0; track_n < 2; track_n++) { + if (!load_track(in , m_tracks[ track_n ])) { + LOG(("load_tape failed")); + for (track_n = 0; track_n < 2; track_n++) { + m_tracks[ track_n ].clear(); + } + break; + } + } + LOG(("load_tape done\n")); +} + +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; - // Should irq be raised anyway when already in error condition? Here we do nothing. + 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; - switch (CMD_CODE(new_cmd_reg)) { case CMD_CLEAR: set_error(false); BIT_CLR(m_status_reg, STATUS_HOLE_BIT); // This is a special command: it doesn't raise IRQ at completion and it - // doesn't replace the current command, if any. + // 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(("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: - stop_tape(); - cmd_duration = attotime::from_usec(QUICK_CMD_USEC); + 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: - if (!check_for_errors()) { - if (CMD_OPT(new_cmd_reg)) { - BIT_SET(m_status_reg, STATUS_TRACKB_BIT); - } else { - BIT_CLR(m_status_reg, STATUS_TRACKB_BIT); + // 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: - stop_tape(); - if (!check_for_errors()) { - start_tape(); - cmd_duration = time_to_target(next_hole(DIR_FWD(new_cmd_reg)) , SPEED_FAST(new_cmd_reg)); + if (start_tape_cmd(new_cmd_reg , 0 , 0)) { + time_to_hole = time_to_next_hole(); } break; - case CMD_DELTA_MOVE_REC: + 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(("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: - // TODO: record/irg detection - stop_tape(); - if (!check_for_errors()) { - start_tape(); - cmd_duration = time_to_distance(0x10000U - m_tach_reg , SPEED_FAST(new_cmd_reg)); + 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(("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")); - return; + break; } m_tape_timer->adjust(cmd_duration); - m_cmd_reg = new_cmd_reg; + m_hole_timer->adjust(time_to_hole); } diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index e279d045d10..a08b0c59ded 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -11,6 +11,8 @@ #ifndef __HP_TACO_H__ #define __HP_TACO_H__ +#include + #define MCFG_TACO_IRQ_HANDLER(_devcb) \ devcb = &hp_taco_device::set_irq_handler(*device , DEVCB_##_devcb); @@ -23,15 +25,15 @@ class hp_taco_device : public device_t { public: - // construction/destruction - hp_taco_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname); - hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + // construction/destruction + hp_taco_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname); + hp_taco_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); // static configuration helpers - template static devcb_base &set_irq_handler(device_t &device, _Object object) { return downcast(device).m_irq_handler.set_callback(object); } - template static devcb_base &set_flg_handler(device_t &device, _Object object) { return downcast(device).m_flg_handler.set_callback(object); } - template static devcb_base &set_sts_handler(device_t &device, _Object object) { return downcast(device).m_sts_handler.set_callback(object); } - + template static devcb_base &set_irq_handler(device_t &device, _Object object) { return downcast(device).m_irq_handler.set_callback(object); } + template static devcb_base &set_flg_handler(device_t &device, _Object object) { return downcast(device).m_flg_handler.set_callback(object); } + template static devcb_base &set_sts_handler(device_t &device, _Object object) { return downcast(device).m_sts_handler.set_callback(object); } + // Register read/write DECLARE_WRITE16_MEMBER(reg_w); DECLARE_READ16_MEMBER(reg_r); @@ -40,21 +42,30 @@ public: DECLARE_READ_LINE_MEMBER(flg_r); DECLARE_READ_LINE_MEMBER(sts_r); - typedef UINT32 tape_pos_t; + // Tape position, 1 unit = 1 inch / (968 * 1024) + typedef INT32 tape_pos_t; + + // Words stored on tape + typedef UINT16 tape_word_t; protected: - // device-level overrides - virtual void device_start() override; - virtual void device_reset() override; - virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; - + // device-level overrides + virtual void device_start() override; + virtual void device_stop() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + private: + // Storage of tracks: mapping from a tape position to word stored there + typedef std::map tape_track_t; + devcb_write_line m_irq_handler; devcb_write_line m_flg_handler; devcb_write_line m_sts_handler; // Registers UINT16 m_data_reg; + bool m_data_reg_full; UINT16 m_cmd_reg; UINT16 m_status_reg; UINT16 m_tach_reg; @@ -65,25 +76,72 @@ private: bool m_irq; bool m_flg; bool m_sts; + UINT8 m_cmd_state; - // Tape position + // Tape position & motion tape_pos_t m_tape_pos; - attotime m_start_time; + attotime m_start_time; // Tape moving if != never + bool m_tape_fwd; + bool m_tape_fast; // Timers emu_timer *m_tape_timer; + emu_timer *m_hole_timer; + + // Content of tape tracks + tape_track_t m_tracks[ 2 ]; + + // Reading & writing + bool m_tape_wr; + tape_pos_t m_rw_pos; + UINT16 m_next_word; + tape_track_t::iterator m_rd_it; + bool m_rd_it_valid; + + // Gap detection + tape_pos_t m_gap_detect_start; + + typedef enum { + ADV_NO_MORE_DATA, + ADV_CONT_DATA, + ADV_DISCONT_DATA + } adv_res_t; void irq_w(bool state); void set_error(bool state); - bool check_for_errors(void); - static unsigned speed_to_tick_freq(bool fast); + unsigned speed_to_tick_freq(void) const; + bool pos_offset(tape_pos_t& pos , tape_pos_t offset) const; + void move_tape_pos(tape_pos_t delta_pos); void update_tape_pos(void); - static bool any_hole(UINT32 tape_pos_a , UINT32 tape_pos_b); - UINT32 next_hole(bool fwd) const; - static attotime time_to_distance(UINT32 distance, bool fast); - attotime time_to_target(UINT32 target, bool fast) const; - void start_tape(void); + static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); + static bool any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b); + tape_pos_t next_hole(void) const; + static tape_pos_t met_first(tape_pos_t a , tape_pos_t b , bool fwd , bool& is_a); + attotime time_to_distance(tape_pos_t distance) const; + attotime time_to_target(tape_pos_t target) const; + bool start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0); + void start_tape(UINT16 cmd_reg); void stop_tape(void); + tape_track_t& current_track(void); + static tape_pos_t word_length(tape_word_t w); + static tape_pos_t word_end_pos(const tape_track_t::iterator& it); + static void adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos); + void write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length); + void write_gap(tape_pos_t a , tape_pos_t b); + bool just_gap(tape_pos_t a , tape_pos_t b); + tape_pos_t farthest_end(const tape_track_t::iterator& it) const; + bool next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive); + adv_res_t adv_it(tape_track_t::iterator& it); + attotime fetch_next_wr_word(void); + attotime time_to_rd_next_word(tape_pos_t& word_rd_pos); + bool next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap); + bool next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap); + static void dump_sequence(FILE *out , tape_track_t::const_iterator it_start , unsigned n_words); + void save_tape(FILE *out) const; + bool load_track(FILE *in , tape_track_t& track); + void load_tape(FILE *in); + attotime time_to_next_hole(void) const; + attotime time_to_tach_pulses(void) const; void start_cmd_exec(UINT16 new_cmd_reg); }; From a5b0e6cedea2f2346c0273c0dff471b3e4a34b71 Mon Sep 17 00:00:00 2001 From: fulivi Date: Mon, 15 Feb 2016 14:45:46 +0100 Subject: [PATCH 4/7] hp9845: first step in making TACO an image device, so far so good --- src/devices/machine/hp_taco.cpp | 224 +++++++++++++++++++++++--------- src/devices/machine/hp_taco.h | 28 +++- 2 files changed, 183 insertions(+), 69 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 388cd1acdfe..3f316e05b96 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -36,6 +36,8 @@ // 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)) @@ -70,6 +72,7 @@ enum { #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) \ @@ -164,6 +167,7 @@ const device_type HP_TACO = &device_creator; // 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), @@ -180,6 +184,7 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, 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), @@ -196,7 +201,7 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, d WRITE16_MEMBER(hp_taco_device::reg_w) { - LOG(("wr R%u = %04x\n", 4 + offset , data)); + LOG_0(("wr R%u = %04x\n", 4 + offset , data)); // Any I/O activity clears IRQ irq_w(false); @@ -255,7 +260,7 @@ READ16_MEMBER(hp_taco_device::reg_r) break; } - LOG(("rd R%u = %04x\n", 4 + offset , res)); + LOG_0(("rd R%u = %04x\n", 4 + offset , res)); return res; } @@ -270,9 +275,17 @@ 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(); @@ -292,31 +305,26 @@ void hp_taco_device::device_start() 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); - - FILE *in = fopen("tape_dump.bin" , "rb"); - if (in != NULL) { - load_tape(in); - fclose(in); - } } // device_stop void hp_taco_device::device_stop() { - LOG(("**device_stop**\n")); - FILE *out = fopen("tape_dump.bin" , "wb"); - if (out != NULL) { - save_tape(out); - fclose(out); - } } // device_reset void hp_taco_device::device_reset() { + LOG(("device_reset")); m_data_reg = 0; m_data_reg_full = false; m_cmd_reg = 0; @@ -324,7 +332,15 @@ void hp_taco_device::device_reset() 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 = false; + m_tape_wr = false; + m_rw_pos = 0; + m_next_word = 0; m_rd_it_valid = false; m_gap_detect_start = NULL_TAPE_POS; @@ -344,7 +360,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para switch (id) { case TAPE_TMR_ID: - LOG(("Tape tmr @%g\n" , machine().time().as_double())); + LOG_0(("Tape tmr @%g\n" , machine().time().as_double())); tape_pos_t length; @@ -363,7 +379,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para case CMD_RECORD_WRITE: if (m_cmd_state == 0) { if (m_rd_it->second == PREAMBLE_WORD) { - LOG(("Got preamble\n")); + LOG_0(("Got preamble\n")); m_cmd_state = 1; // m_rw_pos already at correct position m_tape_timer->adjust(fetch_next_wr_word()); @@ -410,7 +426,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para m_cmd_state = 1; tape_pos_t target = m_tape_pos; if (next_n_gap(target, 0x10000U - m_tach_reg, MIN_IRG_LENGTH)) { - LOG(("%u gaps @%d\n" , 0x10000U - m_tach_reg, target)); + LOG_0(("%u gaps @%d\n" , 0x10000U - m_tach_reg, target)); m_tape_timer->adjust(time_to_target(target)); } return; @@ -447,14 +463,14 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para if (m_rd_it->second == PREAMBLE_WORD) { m_cmd_state = 1; } - LOG(("Got preamble\n")); + LOG_0(("Got preamble\n")); } else { m_data_reg = m_rd_it->second; m_checksum_reg += m_data_reg; - LOG(("RD %04x\n" , m_data_reg)); + LOG_0(("RD %04x\n" , m_data_reg)); } adv_res_t res = adv_it(m_rd_it); - LOG(("adv_it %d\n" , res)); + LOG_0(("adv_it %d\n" , res)); if (res == ADV_NO_MORE_DATA) { m_rd_it_valid = false; } else { @@ -475,7 +491,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para m_tape_pos = m_rw_pos; // Note: checksum is not updated m_data_reg = m_rd_it->second; - LOG(("Final RD %04x\n" , m_data_reg)); + 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; @@ -492,7 +508,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para break; case HOLE_TMR_ID: - LOG(("Hole tmr @%g\n" , machine().time().as_double())); + LOG_0(("Hole tmr @%g\n" , machine().time().as_double())); BIT_SET(m_status_reg , STATUS_HOLE_BIT); @@ -541,7 +557,7 @@ void hp_taco_device::irq_w(bool state) if (state != m_irq) { m_irq = state; m_irq_handler(state); - LOG(("IRQ = %d\n" , state)); + LOG_0(("IRQ = %d\n" , state)); } } @@ -549,7 +565,7 @@ void hp_taco_device::set_error(bool state) { m_sts = !state; m_sts_handler(m_sts); - LOG(("error = %d\n" , state)); + LOG_0(("error = %d\n" , state)); } unsigned hp_taco_device::speed_to_tick_freq(void) const @@ -588,7 +604,7 @@ void hp_taco_device::move_tape_pos(tape_pos_t delta_pos) LOG(("Tape unspooled!\n")); } m_start_time = machine().time(); - LOG(("Tape pos = %u\n" , m_tape_pos)); + 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); @@ -603,10 +619,10 @@ void hp_taco_device::update_tape_pos(void) } attotime delta_time(machine().time() - m_start_time); - LOG(("delta_time = %g\n" , delta_time.as_double())); + 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(("delta_tach = %u\n" , delta_tach)); + LOG_0(("delta_tach = %u\n" , delta_tach)); move_tape_pos(delta_tach); @@ -655,7 +671,7 @@ 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(("next hole fwd @%u = %u\n" , m_tape_pos , hole)); + LOG_0(("next hole fwd @%u = %u\n" , m_tape_pos , hole)); return hole; } } @@ -664,7 +680,7 @@ hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const } else { for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) { if (tape_holes[ i ] < m_tape_pos) { - LOG(("next hole rev @%u = %u\n" , m_tape_pos , tape_holes[ i ])); + LOG_0(("next hole rev @%u = %u\n" , m_tape_pos , tape_holes[ i ])); return tape_holes[ i ]; } } @@ -734,6 +750,7 @@ bool hp_taco_device::start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 m // 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; @@ -807,7 +824,7 @@ void hp_taco_device::write_word(tape_pos_t start , tape_word_t word , tape_pos_t track.erase(it_low , it_high); track.insert(it_high , std::make_pair(start, word)); - LOG(("WR %04x @ T%u:%u\n" , word , BIT(m_status_reg , STATUS_TRACKB_BIT) , start)); + LOG_0(("WR %04x @ T%u:%u\n" , word , BIT(m_status_reg , STATUS_TRACKB_BIT) , start)); } // Write a gap on current track @@ -821,7 +838,7 @@ void hp_taco_device::write_gap(tape_pos_t a , tape_pos_t b) track.erase(it_low, it_high); - LOG(("GAP on T%u:[%u,%u)\n" , BIT(m_status_reg , STATUS_TRACKB_BIT) , a , b)); + 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) @@ -838,11 +855,11 @@ bool hp_taco_device::just_gap(tape_pos_t a , tape_pos_t b) 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; - } + 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) @@ -896,11 +913,11 @@ 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(("next %04x (DR)\n" , m_next_word)); + 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(("next %04x (CS)\n" , m_next_word)); + LOG_0(("next %04x (CS)\n" , m_next_word)); } // Update checksum with new word m_checksum_reg += m_next_word; @@ -984,36 +1001,48 @@ bool hp_taco_device::next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , un 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); + 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::dump_sequence(FILE *out , tape_track_t::const_iterator it_start , unsigned n_words) +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) , 1 , out); + fwrite(&tmp32 , sizeof(tmp32)); tmp32 = it_start->first; - fwrite(&tmp32 , sizeof(tmp32) , 1 , out); + fwrite(&tmp32 , sizeof(tmp32)); for (unsigned i = 0; i < n_words; i++) { tmp16 = it_start->second; - fwrite(&tmp16 , sizeof(tmp16) , 1 , out); + fwrite(&tmp16 , sizeof(tmp16)); it_start++; } } } -void hp_taco_device::save_tape(FILE *out) const +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; @@ -1021,28 +1050,28 @@ void hp_taco_device::save_tape(FILE *out) const 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(out , it_start , n_words); + dump_sequence(it_start , n_words); it_start = it; n_words = 0; } next_pos = it->first + word_length(it->second); n_words++; } - dump_sequence(out , it_start , n_words); + dump_sequence(it_start , n_words); // End of track tmp32 = (UINT32)-1; - fwrite(&tmp32 , sizeof(tmp32) , 1 , out); + fwrite(&tmp32 , sizeof(tmp32)); } } -bool hp_taco_device::load_track(FILE *in , tape_track_t& track) +bool hp_taco_device::load_track(tape_track_t& track) { UINT32 tmp32; track.clear(); while (1) { - if (fread(&tmp32 , sizeof(tmp32) , 1 , in) != 1) { + if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { return false; } @@ -1052,7 +1081,7 @@ bool hp_taco_device::load_track(FILE *in , tape_track_t& track) unsigned n_words = tmp32; - if (fread(&tmp32 , sizeof(tmp32) , 1 , in) != 1) { + if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { return false; } @@ -1061,7 +1090,7 @@ bool hp_taco_device::load_track(FILE *in , tape_track_t& track) for (unsigned i = 0; i < n_words; i++) { UINT16 tmp16; - if (fread(&tmp16 , sizeof(tmp16) , 1 , in) != 1) { + if (fread(&tmp16 , sizeof(tmp16)) != sizeof(tmp16)) { return false; } @@ -1072,18 +1101,42 @@ bool hp_taco_device::load_track(FILE *in , tape_track_t& track) } } -void hp_taco_device::load_tape(FILE *in) +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(in , m_tracks[ track_n ])) { + if (!load_track(m_tracks[ track_n ])) { LOG(("load_tape failed")); - for (track_n = 0; track_n < 2; track_n++) { - m_tracks[ track_n ].clear(); - } - break; + clear_tape(); + return false; } } + LOG(("load_tape done\n")); + return true; +} + +void hp_taco_device::set_tape_present(bool present) +{ + if (present) { + // FU_TEST + if (is_readonly()) { + //if (false) { + 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 @@ -1136,6 +1189,11 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) 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(false); + // FU_TEST + 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; @@ -1145,7 +1203,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) 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(("End of data @%d\n" , target)); + LOG_0(("End of data @%d\n" , target)); cmd_duration = time_to_target(target); } // Holes detected? @@ -1205,7 +1263,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) m_cmd_state = 0; tape_pos_t target = m_tape_pos; if (next_n_gap(target, 1, MIN_IRG_LENGTH)) { - LOG(("IRG @%d\n" , target)); + LOG_0(("IRG @%d\n" , target)); cmd_duration = time_to_target(target); } // Holes detected? @@ -1287,7 +1345,7 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) // 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(("END_READ %d\n" , m_rd_it_valid)); + 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(); } @@ -1301,3 +1359,41 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) m_tape_timer->adjust(cmd_duration); m_hole_timer->adjust(time_to_hole); } + +bool hp_taco_device::call_load() +{ + LOG(("call_load\n")); + 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\n")); + return IMAGE_INIT_PASS; +} + +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"; +} diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index a08b0c59ded..424031421ba 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -22,7 +22,8 @@ #define MCFG_TACO_STS_HANDLER(_devcb) \ devcb = &hp_taco_device::set_sts_handler(*device , DEVCB_##_devcb); -class hp_taco_device : public device_t +class hp_taco_device : public device_t , + public device_image_interface { public: // construction/destruction @@ -42,6 +43,19 @@ public: DECLARE_READ_LINE_MEMBER(flg_r); DECLARE_READ_LINE_MEMBER(sts_r); + // device_image_interface overrides + virtual bool call_load() override; + virtual bool call_create(int format_type, option_resolution *format_options) override; + virtual void call_unload() override; + virtual iodevice_t image_type() const override { return IO_MAGTAPE; } + virtual bool is_readable() const override { return true; } + virtual bool is_writeable() const override { return true; } + virtual bool is_creatable() const override { return true; } + virtual bool must_be_loaded() const override { return false; } + virtual bool is_reset_on_load() const override { return false; } + virtual const char *file_extensions() const override; + virtual const option_guide *create_option_guide() const override { return nullptr; } + // Tape position, 1 unit = 1 inch / (968 * 1024) typedef INT32 tape_pos_t; @@ -50,6 +64,7 @@ public: protected: // device-level overrides + virtual void device_config_complete() override; virtual void device_start() override; virtual void device_stop() override; virtual void device_reset() override; @@ -90,6 +105,7 @@ private: // Content of tape tracks tape_track_t m_tracks[ 2 ]; + bool m_image_dirty; // Reading & writing bool m_tape_wr; @@ -136,10 +152,12 @@ private: attotime time_to_rd_next_word(tape_pos_t& word_rd_pos); bool next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap); bool next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap); - static void dump_sequence(FILE *out , tape_track_t::const_iterator it_start , unsigned n_words); - void save_tape(FILE *out) const; - bool load_track(FILE *in , tape_track_t& track); - void load_tape(FILE *in); + void clear_tape(void); + void dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words); + void save_tape(void); + bool load_track(tape_track_t& track); + bool load_tape(void); + void set_tape_present(bool present); attotime time_to_next_hole(void) const; attotime time_to_tach_pulses(void) const; void start_cmd_exec(UINT16 new_cmd_reg); From 02ec85119dd27a500b0aea962623c0a5aea5d7db Mon Sep 17 00:00:00 2001 From: fulivi Date: Tue, 16 Feb 2016 13:45:04 +0100 Subject: [PATCH 5/7] hp9845: finished making TACO an image interface --- src/devices/machine/hp_taco.cpp | 105 ++++++++++++-------------------- src/devices/machine/hp_taco.h | 2 +- 2 files changed, 40 insertions(+), 67 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 3f316e05b96..befb49489a3 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -28,8 +28,6 @@ // 1 R Cartridge out (1) // 0 R Hole detected (1) -// TODO: R6 è modificato durante il conteggio impulsi? Viene azzerato alla lettura? - #include "emu.h" #include "hp_taco.h" @@ -171,15 +169,10 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, device_type type, m_irq_handler(*this), m_flg_handler(*this), m_sts_handler(*this), - 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_tape_pos(TAPE_INIT_POS) + 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) @@ -188,15 +181,10 @@ hp_taco_device::hp_taco_device(const machine_config &mconfig, const char *tag, d m_irq_handler(*this), m_flg_handler(*this), m_sts_handler(*this), - 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_tape_pos(TAPE_INIT_POS) + m_tape_pos(TAPE_INIT_POS), + m_image_dirty(false) { + clear_state(); } WRITE16_MEMBER(hp_taco_device::reg_w) @@ -325,24 +313,7 @@ void hp_taco_device::device_stop() void hp_taco_device::device_reset() { LOG(("device_reset")); - 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 = false; - m_tape_wr = false; - m_rw_pos = 0; - m_next_word = 0; - m_rd_it_valid = false; - m_gap_detect_start = NULL_TAPE_POS; + clear_state(); m_irq = false; m_flg = true; @@ -552,6 +523,31 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para } } +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) { @@ -689,27 +685,6 @@ hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const } } -hp_taco_device::tape_pos_t hp_taco_device::met_first(tape_pos_t a , tape_pos_t b , bool fwd , bool& is_a) -{ - if (fwd) { - if (a < b) { - is_a = true; - return a; - } else { - is_a = false; - return b; - } - } else { - if (a >= b) { - is_a = true; - return a; - } else { - is_a = false; - return b; - } - } -} - attotime hp_taco_device::time_to_distance(tape_pos_t distance) const { // +1 for rounding @@ -1094,7 +1069,6 @@ bool hp_taco_device::load_track(tape_track_t& track) return false; } - // TODO: usare end() come hint track.insert(std::make_pair(pos , tmp16)); pos += word_length(tmp16); } @@ -1125,9 +1099,7 @@ bool hp_taco_device::load_tape(void) void hp_taco_device::set_tape_present(bool present) { if (present) { - // FU_TEST if (is_readonly()) { - //if (false) { BIT_SET(m_status_reg, STATUS_WPR_BIT); } else { BIT_CLR(m_status_reg, STATUS_WPR_BIT); @@ -1191,8 +1163,6 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) 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(false); - // FU_TEST set_tape_present(is_loaded()); // This is a special command: it doesn't raise IRQ at completion and it // doesn't replace current command @@ -1362,8 +1332,11 @@ void hp_taco_device::start_cmd_exec(UINT16 new_cmd_reg) bool hp_taco_device::call_load() { - LOG(("call_load\n")); - if (!load_tape()) { + 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; @@ -1377,8 +1350,8 @@ bool hp_taco_device::call_load() bool hp_taco_device::call_create(int format_type, option_resolution *format_options) { - LOG(("call_create\n")); - return IMAGE_INIT_PASS; + LOG(("call_create %d\n" , has_been_created())); + return call_load(); } void hp_taco_device::call_unload() diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index 424031421ba..c8b794c79a2 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -123,6 +123,7 @@ private: ADV_DISCONT_DATA } adv_res_t; + void clear_state(void); void irq_w(bool state); void set_error(bool state); unsigned speed_to_tick_freq(void) const; @@ -132,7 +133,6 @@ private: static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); static bool any_hole(tape_pos_t tape_pos_a , tape_pos_t tape_pos_b); tape_pos_t next_hole(void) const; - static tape_pos_t met_first(tape_pos_t a , tape_pos_t b , bool fwd , bool& is_a); attotime time_to_distance(tape_pos_t distance) const; attotime time_to_target(tape_pos_t target) const; bool start_tape_cmd(UINT16 cmd_reg , UINT16 must_be_1 , UINT16 must_be_0); From 63b58daec9b1640fe820038c39cf0dbc08dd4086 Mon Sep 17 00:00:00 2001 From: fulivi Date: Wed, 17 Feb 2016 10:10:01 +0100 Subject: [PATCH 6/7] hp9845: TACO doc updated --- src/devices/machine/hp_taco.cpp | 113 ++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index befb49489a3..73b7c9a441b 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -8,17 +8,48 @@ *********************************************************************/ +// 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) +// [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 -// 9 RW ? Drive ON according to [1], doesn't match usage of firmware -// 8 RW ? Size of gaps according to [1] +// 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) @@ -28,6 +59,71 @@ // 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). +// #include "emu.h" #include "hp_taco.h" @@ -759,15 +855,6 @@ hp_taco_device::tape_pos_t hp_taco_device::word_length(tape_word_t w) zeros = 16 - ones; - // 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 should be enough for emulation. - // Anyway, 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 "1" encoded at the end - // - a 0 is encoded with a distance between flux reversals of 1/35200 s - // - a 1 is encoded with a distance that's 1.75 times that of a 0 return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; } From 74371049b132e1068ea519beee4a645c78f04f98 Mon Sep 17 00:00:00 2001 From: fulivi Date: Wed, 17 Feb 2016 11:20:49 +0100 Subject: [PATCH 7/7] hp9845: small improvements to 9845/TACO docs --- src/devices/machine/hp_taco.cpp | 8 ++++++++ src/mame/drivers/hp9845.cpp | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 73b7c9a441b..76203d31ee3 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -124,6 +124,14 @@ // * 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" diff --git a/src/mame/drivers/hp9845.cpp b/src/mame/drivers/hp9845.cpp index ec0e53da906..7f83f001fe3 100644 --- a/src/mame/drivers/hp9845.cpp +++ b/src/mame/drivers/hp9845.cpp @@ -16,12 +16,12 @@ // - LPU & PPU ROMs // - LPU & PPU RAMs // - Text mode screen -// - Keyboard (most of keys) +// - Keyboard +// - T15 tape drive // What's not yet in: // - Beeper -// - Rest of keyboard // - Graphic screen -// - Tape drive (this needs some heavy RE of the TACO chip) +// - Better naming of tape drive image (it's now "magt", should be "t15") // - Better documentation of this file // - Software list to load optional ROMs // What's wrong: