diff options
| author | 2017-08-16 15:36:06 -0400 | |
|---|---|---|
| committer | 2017-08-16 15:36:06 -0400 | |
| commit | 961169d5e8da89c214936e0b0751da61349509bc (patch) | |
| tree | b5e9c003acbc995d1993910aac19d62bb3327b5b /src | |
| parent | 3159d52e2fd9640abddaedaa7e527f6999b8f849 (diff) | |
| parent | 4c5ed8b64b6ce10de022a151b9d647c3b25fcab1 (diff) | |
Merge pull request #2568 from fulivi/hp80_dev01
Emulation of HP85 tape drive
Diffstat (limited to 'src')
| -rw-r--r-- | src/devices/machine/1ma6.cpp | 702 | ||||
| -rw-r--r-- | src/devices/machine/1ma6.h | 117 | ||||
| -rw-r--r-- | src/devices/machine/hp_taco.cpp | 575 | ||||
| -rw-r--r-- | src/devices/machine/hp_taco.h | 64 | ||||
| -rw-r--r-- | src/lib/formats/hti_tape.cpp | 400 | ||||
| -rw-r--r-- | src/lib/formats/hti_tape.h | 93 | ||||
| -rw-r--r-- | src/mame/drivers/hp80.cpp | 4 | ||||
| -rw-r--r-- | src/tools/imgtool/modules/hp9845_tape.cpp | 423 |
8 files changed, 1605 insertions, 773 deletions
diff --git a/src/devices/machine/1ma6.cpp b/src/devices/machine/1ma6.cpp new file mode 100644 index 00000000000..3f2fa2f942a --- /dev/null +++ b/src/devices/machine/1ma6.cpp @@ -0,0 +1,702 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + 1ma6.cpp + + HP-85 tape controller (1MA6-0001) + + *Status of tape tests of service ROM* + + Test Code Description Status + ================================== + I Write protect OK + P Status test OK + Q Speed test OK + R Hole detection Fails with "HOLE D" error (*1) + S Write test OK + T Read test OK + U Record test OK (*2) + + *1 Hole test fails because it depends on the diameter of the + holes being correct (this driver doesn't emulate it). + *2 Record test is buggy (byte @74ab in service ROM should be 0x54). + Test succeeds if this bug is corrected first. + +*********************************************************************/ + +#include "1ma6.h" + +// Debugging +#define VERBOSE 0 +#include "logmacro.h" + +// Device type definition +DEFINE_DEVICE_TYPE(HP_1MA6, hp_1ma6_device, "hp_1ma6", "HP 1MA6") + +// Bit manipulation +namespace { + static constexpr unsigned BIT_MASK(unsigned n) + { + return 1U << n; + } + + template<typename T> void BIT_CLR(T& w , unsigned n) + { + w &= ~(T)BIT_MASK(n); + } + + template<typename T> void BIT_SET(T& w , unsigned n) + { + w |= (T)BIT_MASK(n); + } + + template<typename T> void COPY_BIT(bool bit , T& w , unsigned n) + { + if (bit) { + BIT_SET(w , n); + } else { + BIT_CLR(w , n); + } + } +} + +// **** Constants **** +// One tachometer tick every 1/32 of inch +static constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / 32; +static constexpr hti_format_t::tape_pos_t TAPE_INIT_POS = hti_format_t::ONE_INCH_POS * 80; +// Slow tape speed (10 ips) +static constexpr unsigned TACH_FREQ_SLOW = hti_format_t::ONE_INCH_POS * 10; +// Fast tape speed (60 ips) +static constexpr unsigned TACH_FREQ_FAST = hti_format_t::ONE_INCH_POS * 60; +// Minimum gap size (totally made up) +static constexpr hti_format_t::tape_pos_t MIN_GAP_SIZE = TACH_TICK_LEN; +// Braking distance at fast speed: 1.5 in (this value assumes the deceleration is 1200 in/s^2, the same as in TACO) +static constexpr hti_format_t::tape_pos_t FAST_BRAKE_DIST = hti_format_t::ONE_INCH_POS * 1.5; +// Braking distance at slow speed: 1/24 in +static constexpr hti_format_t::tape_pos_t SLOW_BRAKE_DIST = hti_format_t::ONE_INCH_POS / 24; + +// Bits in control register +static constexpr unsigned CTL_TRACK_NO_BIT = 0; // Track selection +static constexpr unsigned CTL_POWER_UP_BIT = 1; // Tape controller power up +static constexpr unsigned CTL_MOTOR_ON_BIT = 2; // Motor control +static constexpr unsigned CTL_DIR_FWD_BIT = 3; // Tape direction = forward +static constexpr unsigned CTL_FAST_BIT = 4; // Speed = fast +static constexpr unsigned CTL_WRITE_DATA_BIT = 5; // Write data +static constexpr unsigned CTL_WRITE_SYNC_BIT = 6; // Write SYNC +static constexpr unsigned CTL_WRITE_GAP_BIT = 7; // Write gap + +// Bits in status register +static constexpr unsigned STS_CASSETTE_IN_BIT = 0; // Cassette in +static constexpr unsigned STS_STALL_BIT = 1; // Tape stalled +static constexpr unsigned STS_ILIM_BIT = 2; // Overcurrent +static constexpr unsigned STS_WRITE_EN_BIT = 3; // Write enabled +static constexpr unsigned STS_HOLE_BIT = 4; // Hole detected +static constexpr unsigned STS_GAP_BIT = 5; // Gap detected +static constexpr unsigned STS_TACH_BIT = 6; // Tachometer tick +static constexpr unsigned STS_READY_BIT = 7; // Ready + +// Timers +enum { + TAPE_TMR_ID, + HOLE_TMR_ID +}; + +hp_1ma6_device::hp_1ma6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig , HP_1MA6 , tag , owner , clock), + device_image_interface(mconfig , *this), + m_image(), + m_image_dirty(false) +{ + clear_state(); +} + +WRITE8_MEMBER(hp_1ma6_device::reg_w) +{ + LOG("WR %u=%02x\n" , offset , data); + switch(offset) { + case 0: + // Control register + start_cmd_exec(data); + break; + + case 1: + // Data register + m_data_reg = data; + break; + } +} + +READ8_MEMBER(hp_1ma6_device::reg_r) +{ + uint8_t res = 0; + + switch (offset) { + case 0: + // Status register + update_tape_pos(); + if (m_cmd_state == CMD_IDLE || !m_cartridge_in) { + BIT_SET(m_status_reg , STS_READY_BIT); + } else if (m_cmd_state == CMD_STOPPING || + m_cmd_state == CMD_FAST_FWD_REV || + m_cmd_state == CMD_WR_GAP) { + BIT_CLR(m_status_reg , STS_READY_BIT); + } + if ((m_control_reg & (BIT_MASK(CTL_WRITE_SYNC_BIT) | + BIT_MASK(CTL_WRITE_GAP_BIT))) != 0 && + (!m_cartridge_in || !is_readonly())) { + BIT_SET(m_status_reg , STS_WRITE_EN_BIT); + } else { + BIT_CLR(m_status_reg , STS_WRITE_EN_BIT); + } + // Gap detection + if (m_cmd_state == CMD_IDLE) { + BIT_SET(m_status_reg , STS_GAP_BIT); + } else if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS) { + if (abs(m_gap_detect_start - m_tape_pos) >= MIN_GAP_SIZE) { + auto tmp = m_tape_pos; + hti_format_t::pos_offset(tmp , is_moving_fwd() , -MIN_GAP_SIZE); + if (m_image.just_gap(current_track() , tmp , m_tape_pos)) { + BIT_SET(m_status_reg , STS_GAP_BIT); + } + } else { + BIT_SET(m_status_reg , STS_GAP_BIT); + } + } + + res = m_status_reg; + // Clear latching bits + BIT_CLR(m_status_reg , STS_HOLE_BIT); + BIT_CLR(m_status_reg , STS_GAP_BIT); + BIT_CLR(m_status_reg , STS_TACH_BIT); + BIT_CLR(m_status_reg , STS_READY_BIT); + if (m_cartridge_in) { + BIT_SET(m_status_reg , STS_CASSETTE_IN_BIT); + } + break; + + case 1: + // Data register + res = m_data_reg; + break; + } + LOG("RD %u=%02x\n" , offset , res); + return res; +} + +void hp_1ma6_device::device_start() +{ + save_item(NAME(m_data_reg)); + save_item(NAME(m_status_reg)); + save_item(NAME(m_control_reg)); + save_item(NAME(m_tape_pos)); + save_item(NAME(m_prev_pos)); + save_item(NAME(m_start_time)); + save_item(NAME(m_image_dirty)); + 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); +} + +void hp_1ma6_device::device_reset() +{ + clear_state(); + + m_tape_timer->reset(); + m_hole_timer->reset(); +} + +void hp_1ma6_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + update_tape_pos(); + + switch (id) { + case TAPE_TMR_ID: + { + LOG("TMR %.6f %d %d\n" , machine().time().as_double() , m_tape_pos , m_cmd_state); + attotime cmd_duration = attotime::never; + switch (m_cmd_state) { + case CMD_STOPPING: + m_cmd_state = CMD_IDLE; + stop_tape(); + break; + + case CMD_RD_WAIT_SYNC: + m_tape_pos = m_rw_pos; + LOG("RD WS @ %d = %04x\n" , m_tape_pos , m_rd_it->second); + if (m_rd_it->second != 0xffff) { + // Sync achieved + m_cmd_state = CMD_RD_MSB; + } + cmd_duration = advance_rd_state(); + break; + + case CMD_RD_MSB: + m_tape_pos = m_rw_pos; + m_next_word = m_rd_it->second; + LOG("RD T%u @ %d = %04x\n" , current_track() , m_rw_pos , m_next_word); + m_data_reg = (uint8_t)(m_next_word >> 8); + BIT_SET(m_status_reg , STS_READY_BIT); + // Time to reach LSB: half the length of a 0 word + // Actually it varies basing on MSB content + cmd_duration = time_to_distance(hti_format_t::word_length(0) / 2); + m_cmd_state = CMD_RD_LSB; + break; + + case CMD_RD_LSB: + m_data_reg = (uint8_t)m_next_word; + BIT_SET(m_status_reg , STS_READY_BIT); + m_cmd_state = CMD_RD_MSB; + cmd_duration = advance_rd_state(); + break; + + case CMD_WR_SYNC: + m_tape_pos = m_rw_pos; + if (m_rd_it->second != 0xffff) { + // Got preamble + m_next_word = (hti_format_t::tape_word_t)m_data_reg << 8; + BIT_SET(m_status_reg , STS_READY_BIT); + m_cmd_state = CMD_WR_LSB; + cmd_duration = time_to_distance(hti_format_t::word_length(0) / 2); + } else { + cmd_duration = advance_rd_state(); + } + break; + + case CMD_WR_MSB: + // **** FWD **** + // | m_rw_pos --> m_next_word + // | m_tape_pos --> (start of next word) + // V + // + // **** REV **** + // ^ m_tape_pos --> m_next_word + // | m_rw_pos --> (start of previous word) + // | + { + auto length = hti_format_t::word_length(m_next_word); + if (!is_moving_fwd()) { + hti_format_t::pos_offset(m_rw_pos , false , length); + } + LOG("WR T%u @ %d = %04x\n" , current_track() , m_rw_pos , m_next_word); + if (m_cartridge_in) { + m_image.write_word(current_track() , m_rw_pos , m_next_word , length); + m_image_dirty = true; + } + if (is_moving_fwd()) { + hti_format_t::pos_offset(m_rw_pos , true , length); + } + m_tape_pos = m_rw_pos; + + m_next_word = (hti_format_t::tape_word_t)m_data_reg << 8; + BIT_SET(m_status_reg , STS_READY_BIT); + m_cmd_state = CMD_WR_LSB; + cmd_duration = time_to_distance(hti_format_t::word_length(0) / 2); + } + break; + + case CMD_WR_LSB: + { + m_next_word = (m_next_word & 0xff00) | m_data_reg; + BIT_SET(m_status_reg , STS_READY_BIT); + m_cmd_state = CMD_WR_MSB; + auto length = hti_format_t::word_length(m_next_word); + auto tmp = m_rw_pos; + hti_format_t::pos_offset(tmp , is_moving_fwd() , length); + cmd_duration = time_to_target(tmp); + } + break; + + default: + break; + } + m_tape_timer->adjust(cmd_duration); + } + break; + + case HOLE_TMR_ID: + BIT_SET(m_status_reg , STS_HOLE_BIT); + m_hole_timer->adjust(time_to_next_hole()); + break; + + default: + break; + } +} + +image_init_result hp_1ma6_device::call_load() +{ + LOG("call_load\n"); + return internal_load(false); +} + +image_init_result hp_1ma6_device::call_create(int format_type, util::option_resolution *format_options) +{ + LOG("call_create\n"); + return internal_load(true); +} + +void hp_1ma6_device::call_unload() +{ + LOG("call_unload dirty=%d\n" , m_image_dirty); + + device_reset(); + + if (m_image_dirty) { + io_generic io; + io.file = (device_image_interface *)this; + io.procs = &image_ioprocs; + io.filler = 0; + m_image.save_tape(&io); + m_image_dirty = false; + } + + m_image.clear_tape(); + set_cartridge_in(false); +} + +std::string hp_1ma6_device::call_display() +{ + std::string buffer; + // Mostly lifted from cassette_image_device::call_display ;) + // See also hp_taco_device::call_display + + // Do not show anything if image not loaded or tape not moving + if (!exists() || m_start_time.is_never()) { + return buffer; + } + + char track = BIT(m_control_reg , CTL_TRACK_NO_BIT) ? 'B' : 'A'; + char r_w = m_cmd_state == CMD_WR_SYNC || + m_cmd_state == CMD_WR_MSB || + m_cmd_state == CMD_WR_LSB || + m_cmd_state == CMD_WR_GAP ? 'W' : 'R'; + char m1; + char m2; + + if (is_moving_fwd()) { + m1 = '>'; + m2 = BIT(m_control_reg , CTL_FAST_BIT) ? '>' : ' '; + } else { + m1 = '<'; + m2 = BIT(m_control_reg , CTL_FAST_BIT) ? '<' : ' '; + } + + int pos_in = current_tape_pos() / hti_format_t::ONE_INCH_POS; + + buffer = string_format("%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); + + return buffer; +} + +const char *hp_1ma6_device::file_extensions() const +{ + return "hti"; +} + +void hp_1ma6_device::clear_state() +{ + m_data_reg = 0; + m_status_reg = 0; + m_control_reg = 0; + m_tape_pos = TAPE_INIT_POS; + m_prev_pos = TAPE_INIT_POS; + m_start_time = attotime::never; + m_rw_pos = hti_format_t::NULL_TAPE_POS; + m_next_word = 0; + m_rd_it_valid = false; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; + m_cmd_state = CMD_IDLE; + set_cartridge_in(false); + set_cartridge_in(is_loaded()); +} + +unsigned hp_1ma6_device::current_track() const +{ + return BIT(m_control_reg , CTL_TRACK_NO_BIT); +} + +unsigned hp_1ma6_device::speed_to_tick_freq(void) const +{ + unsigned freq = BIT(m_control_reg , CTL_FAST_BIT) ? TACH_FREQ_FAST : TACH_FREQ_SLOW; + + if (m_cmd_state == CMD_STOPPING) { + // Braking + freq /= 2; + } + + return freq; +} + +attotime hp_1ma6_device::time_to_distance(hti_format_t::tape_pos_t distance) const +{ + // +1 for rounding + return attotime::from_ticks(distance + 1 , speed_to_tick_freq()); +} + +attotime hp_1ma6_device::time_to_target(hti_format_t::tape_pos_t target) const +{ + return time_to_distance(abs(target - m_tape_pos)); +} + +attotime hp_1ma6_device::time_to_next_hole(void) const +{ + auto pos = hti_format_t::next_hole(m_tape_pos , is_moving_fwd()); + + if (pos == hti_format_t::NULL_TAPE_POS) { + return attotime::never; + } else { + return time_to_target(pos); + } +} + +attotime hp_1ma6_device::time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos) const +{ + if (m_rd_it_valid) { + word_rd_pos = hti_format_t::farthest_end(m_rd_it , is_moving_fwd()); + return time_to_target(word_rd_pos); + } else { + return attotime::never; + } +} + +hti_format_t::tape_pos_t hp_1ma6_device::current_tape_pos() const +{ + if (m_start_time.is_never()) { + // Tape not moving + return m_tape_pos; + } + + attotime delta_time(machine().time() - m_start_time); + hti_format_t::tape_pos_t delta_tach = (hti_format_t::tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); + + auto tape_pos = m_tape_pos; + if (!hti_format_t::pos_offset(tape_pos , is_moving_fwd() , delta_tach)) { + LOG("Tape unspooled!\n"); + } + + return tape_pos; +} + +void hp_1ma6_device::update_tape_pos() +{ + if (m_start_time.is_never()) { + // Tape not moving + return; + } + + m_tape_pos = current_tape_pos(); + m_start_time = machine().time(); + + // Tachometer ticks + if ((m_prev_pos / TACH_TICK_LEN) != (m_tape_pos / TACH_TICK_LEN)) { + BIT_SET(m_status_reg , STS_TACH_BIT); + } + m_prev_pos = m_tape_pos; +} + +bool hp_1ma6_device::is_reading() const +{ + return m_cmd_state >= CMD_RD_WAIT_SYNC && + m_cmd_state <= CMD_RD_LSB; +} + +bool hp_1ma6_device::is_writing() const +{ + return m_cmd_state >= CMD_WR_SYNC && + m_cmd_state <= CMD_WR_GAP; +} + +bool hp_1ma6_device::is_moving_fwd() const +{ + return BIT(m_control_reg , CTL_DIR_FWD_BIT); +} + +void hp_1ma6_device::set_cartridge_in(bool in) +{ + m_cartridge_in = in; + if (!m_cartridge_in) { + BIT_CLR(m_status_reg , STS_CASSETTE_IN_BIT); + } +} + +void hp_1ma6_device::start_tape() +{ + m_start_time = machine().time(); + m_prev_pos = m_tape_pos; + if (!is_writing()) { + m_gap_detect_start = m_tape_pos; + } else { + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; + } + m_hole_timer->adjust(time_to_next_hole()); +} + +void hp_1ma6_device::stop_tape() +{ + m_start_time = attotime::never; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; + m_hole_timer->reset(); + m_tape_timer->reset(); +} + +attotime hp_1ma6_device::advance_rd_state() +{ + auto res = m_image.adv_it(current_track() , is_moving_fwd() , m_rd_it); + if (res == hti_format_t::ADV_NO_MORE_DATA) { + m_rd_it_valid = false; + } else if (res == hti_format_t::ADV_DISCONT_DATA && is_reading()) { + // Discontinuous data: move back to sync search (probably..) + m_cmd_state = CMD_RD_WAIT_SYNC; + } + return time_to_rd_next_word(m_rw_pos); +} + +void hp_1ma6_device::start_cmd_exec(uint8_t new_ctl_reg) +{ + if (new_ctl_reg == m_control_reg) { + return; + } + + cmd_state_t new_state; + + if (!BIT(new_ctl_reg , CTL_POWER_UP_BIT)) { + new_state = CMD_IDLE; + } else if (!BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) { + new_state = CMD_STOPPING; + } else switch (new_ctl_reg & (BIT_MASK(CTL_FAST_BIT) | + BIT_MASK(CTL_WRITE_DATA_BIT) | + BIT_MASK(CTL_WRITE_SYNC_BIT) | + BIT_MASK(CTL_WRITE_GAP_BIT))) { + case 0: + new_state = CMD_RD_WAIT_SYNC; + break; + + case BIT_MASK(CTL_FAST_BIT): + case BIT_MASK(CTL_WRITE_GAP_BIT): + case BIT_MASK(CTL_WRITE_GAP_BIT) | BIT_MASK(CTL_WRITE_DATA_BIT): + new_state = CMD_FAST_FWD_REV; + break; + + case BIT_MASK(CTL_WRITE_DATA_BIT): + new_state = CMD_WR_SYNC; + break; + + case BIT_MASK(CTL_WRITE_SYNC_BIT): + new_state = CMD_WR_MSB; + break; + + case BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT): + new_state = CMD_WR_GAP; + break; + + default: + LOG("Unknown command %02x\n" , new_ctl_reg); + return; + } + + update_tape_pos(); + LOG("State %d -> %d pos = %d\n" , m_cmd_state , new_state , m_tape_pos); + + if (m_cmd_state == CMD_WR_GAP) { + // Finish gap writing + LOG("T%u: Gap from %d to %d\n" , current_track() , m_rw_pos , m_tape_pos); + if (m_cartridge_in) { + m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); + m_image_dirty = true; + } + } + + uint8_t saved_dir_speed = m_control_reg & (BIT_MASK(CTL_DIR_FWD_BIT) | BIT_MASK(CTL_FAST_BIT)); + + m_control_reg = new_ctl_reg; + + attotime cmd_duration = attotime::never; + + switch (new_state) { + case CMD_IDLE: + m_cmd_state = CMD_IDLE; + stop_tape(); + break; + + case CMD_STOPPING: + if (m_cmd_state != CMD_IDLE && + m_cmd_state != CMD_STOPPING) { + m_cmd_state = CMD_STOPPING; + // Keep speed & direction from before the STOP command + m_control_reg = (m_control_reg & ~(BIT_MASK(CTL_DIR_FWD_BIT) | BIT_MASK(CTL_FAST_BIT))) | saved_dir_speed; + cmd_duration = time_to_distance(BIT(m_control_reg , CTL_FAST_BIT) ? FAST_BRAKE_DIST : SLOW_BRAKE_DIST); + } + break; + + case CMD_RD_WAIT_SYNC: + m_cmd_state = CMD_RD_WAIT_SYNC; + start_tape(); + m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , is_moving_fwd() , false , m_rd_it); + cmd_duration = time_to_rd_next_word(m_rw_pos); + // TODO: ?? + BIT_CLR(m_status_reg , STS_READY_BIT); + break; + + case CMD_WR_SYNC: + m_cmd_state = CMD_WR_SYNC; + start_tape(); + m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , is_moving_fwd() , false , m_rd_it); + cmd_duration = time_to_rd_next_word(m_rw_pos); + BIT_SET(m_status_reg , STS_READY_BIT); + break; + + case CMD_WR_MSB: + m_cmd_state = CMD_WR_MSB; + start_tape(); + m_rw_pos = m_tape_pos; + // SYNC word: MSB = 00, LSB = data register + m_next_word = (hti_format_t::tape_word_t)m_data_reg; + BIT_SET(m_status_reg , STS_READY_BIT); + cmd_duration = time_to_distance(hti_format_t::word_length(m_next_word)); + break; + + case CMD_WR_GAP: + m_cmd_state = CMD_WR_GAP; + start_tape(); + m_rw_pos = m_tape_pos; + break; + + case CMD_FAST_FWD_REV: + m_cmd_state = CMD_FAST_FWD_REV; + start_tape(); + break; + + default: + break; + } + + m_tape_timer->adjust(cmd_duration); +} + +image_init_result hp_1ma6_device::internal_load(bool is_create) +{ + device_reset(); + + io_generic io; + io.file = (device_image_interface *)this; + io.procs = &image_ioprocs; + io.filler = 0; + if (is_create) { + m_image.clear_tape(); + m_image.save_tape(&io); + } else if (!m_image.load_tape(&io)) { + seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); + set_cartridge_in(false); + return image_init_result::FAIL; + } + + m_image_dirty = false; + set_cartridge_in(true); + + return image_init_result::PASS; +} diff --git a/src/devices/machine/1ma6.h b/src/devices/machine/1ma6.h new file mode 100644 index 00000000000..2a227c496c6 --- /dev/null +++ b/src/devices/machine/1ma6.h @@ -0,0 +1,117 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + 1ma6.h + + HP-85 tape controller (1MA6-0001) + +*********************************************************************/ + +#ifndef MAME_MACHINE_1MA6_H +#define MAME_MACHINE_1MA6_H + +#pragma once + +#include "formats/hti_tape.h" + +class hp_1ma6_device : public device_t , + public device_image_interface +{ +public: + // construction/destruction + hp_1ma6_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + // Register read/write + DECLARE_WRITE8_MEMBER(reg_w); + DECLARE_READ8_MEMBER(reg_r); + +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_image_interface overrides + virtual image_init_result call_load() override; + virtual image_init_result call_create(int format_type, util::option_resolution *format_options) override; + virtual void call_unload() override; + virtual std::string call_display() 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; + +private: + // Registers + uint8_t m_data_reg; + uint8_t m_status_reg; + uint8_t m_control_reg; + + // Tape position & motion + hti_format_t::tape_pos_t m_tape_pos; + hti_format_t::tape_pos_t m_prev_pos; + attotime m_start_time; // Tape moving if != never + + // Timers + emu_timer *m_tape_timer; + emu_timer *m_hole_timer; + + // Content of tape + hti_format_t m_image; + bool m_image_dirty; + + // Reading & writing + hti_format_t::tape_pos_t m_rw_pos; + hti_format_t::tape_word_t m_next_word; + hti_format_t::track_iterator_t m_rd_it; + bool m_rd_it_valid; + + // Gap detection + hti_format_t::tape_pos_t m_gap_detect_start; + + // Command FSM state + typedef enum { + CMD_IDLE, + CMD_STOPPING, + CMD_RD_WAIT_SYNC, + CMD_RD_MSB, + CMD_RD_LSB, + CMD_WR_SYNC, + CMD_WR_MSB, + CMD_WR_LSB, + CMD_WR_GAP, + CMD_FAST_FWD_REV + } cmd_state_t; + cmd_state_t m_cmd_state; + + // Cartridge-in status + bool m_cartridge_in; + + void clear_state(); + unsigned current_track() const; + unsigned speed_to_tick_freq(void) const; + attotime time_to_distance(hti_format_t::tape_pos_t distance) const; + attotime time_to_target(hti_format_t::tape_pos_t target) const; + attotime time_to_next_hole(void) const; + attotime time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos) const; + hti_format_t::tape_pos_t current_tape_pos() const; + void update_tape_pos(); + bool is_reading() const; + bool is_writing() const; + bool is_moving_fwd() const; + void set_cartridge_in(bool in); + void start_tape(); + void stop_tape(); + attotime advance_rd_state(); + void start_cmd_exec(uint8_t new_ctl_reg); + image_init_result internal_load(bool is_create); +}; + +// device type definition +DECLARE_DEVICE_TYPE(HP_1MA6, hp_1ma6_device) + +#endif /* MAME_MACHINE_1MA6_H */ diff --git a/src/devices/machine/hp_taco.cpp b/src/devices/machine/hp_taco.cpp index 359df5bd023..1a375a7accd 100644 --- a/src/devices/machine/hp_taco.cpp +++ b/src/devices/machine/hp_taco.cpp @@ -164,15 +164,11 @@ enum { #define STATUS_REG_MASK 0x003f // Status register mask #define TACH_TICKS_PER_INCH 968 // Tachometer pulses per inch of tape movement #define TAPE_POS_FRACT 1024 // 10 bits of fractional part in tape_pos_t -#define ONE_INCH_POS (TACH_TICKS_PER_INCH * TAPE_POS_FRACT) // Value in tape_pos_t representing 1 inch of tape #define TACH_FREQ_SLOW 21276 // Tachometer pulse frequency for slow speed (21.98 ips) #define TACH_FREQ_FAST 87196 // Tachometer pulse frequency for fast speed (90.08 ips) #define TACH_FREQ_BRAKE_SLOW 11606 // Tachometer pulse frequency when stopping from slow speed (11.99 ips) #define TACH_FREQ_BRAKE_FAST 44566 // Tachometer pulse frequency when stopping from fast speed (46.04 ips) -#define TAPE_LENGTH ((140 * 12 + 72 * 2) * ONE_INCH_POS) // Tape length: 140 ft of usable tape + 72" of punched tape at either end -#define TAPE_INIT_POS (80 * ONE_INCH_POS) // Initial tape position: 80" from beginning (just past the punched part) -#define ZERO_BIT_LEN 619 // Length of 0 bits at slow tape speed: 1/(35200 Hz) -#define ONE_BIT_LEN 1083 // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN +#define TAPE_INIT_POS (80 * hti_format_t::ONE_INCH_POS) // Initial tape position: 80" from beginning (just past the punched part) #define QUICK_CMD_USEC 25 // usec for "quick" command execution (totally made up) #define FAST_BRAKE_DIST 3350450 // Braking distance at fast speed (~3.38 in) // There are 2 braking distances here: The first one (commented out) is the theoretical value, the second one @@ -182,15 +178,13 @@ enum { //#define SLOW_BRAKE_DIST 197883 // Braking distance at slow speed (~0.2 in) #define SLOW_BRAKE_DIST 71000 // Braking distance at slow speed (~0.07 in) #define PREAMBLE_WORD 0 // Value of preamble word -#define END_GAP_LENGTH (6 * ONE_INCH_POS) // Length of final gap: 6" +#define END_GAP_LENGTH (6 * hti_format_t::ONE_INCH_POS) // Length of final gap: 6" // Minimum gap lengths are probably counted from tacho pulses in real TACO: short gaps could be equal to 64 pulses and long ones // to 1472 (23 * 64) -#define SHORT_GAP_LENGTH ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Minimum length of short gaps: 0.066" ([1], pg 8-10) -#define LONG_GAP_LENGTH ((tape_pos_t)(1.5 * ONE_INCH_POS)) // Minimum length of long gaps: 1.5" ([1], pg 8-10) -#define NULL_TAPE_POS ((tape_pos_t)-1) // Special value for invalid/unknown tape position -#define PREAMBLE_TIMEOUT ((tape_pos_t)(2.6 * ONE_INCH_POS)) // Min. length of gap making preamble search time out (totally made up) -#define DATA_TIMEOUT ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Min. length of gap that will cause data reading to time out (totally made up) -#define FILE_MAGIC 0x4f434154 // Magic value at start of image file: "TACO" +#define SHORT_GAP_LENGTH ((hti_format_t::tape_pos_t)(0.066 * hti_format_t::ONE_INCH_POS)) // Minimum length of short gaps: 0.066" ([1], pg 8-10) +#define LONG_GAP_LENGTH ((hti_format_t::tape_pos_t)(1.5 * hti_format_t::ONE_INCH_POS)) // Minimum length of long gaps: 1.5" ([1], pg 8-10) +#define PREAMBLE_TIMEOUT ((hti_format_t::tape_pos_t)(2.6 * hti_format_t::ONE_INCH_POS)) // Min. length of gap making preamble search time out (totally made up) +#define DATA_TIMEOUT ((hti_format_t::tape_pos_t)(0.066 * hti_format_t::ONE_INCH_POS)) // Min. length of gap that will cause data reading to time out (totally made up) // Parts of command register #define CMD_CODE(reg) \ @@ -255,32 +249,6 @@ enum { #define STATUS_WPR_MASK BIT_MASK(STATUS_WPR_BIT) // Write protection #define STATUS_ERR_MASK (STATUS_CART_OUT_MASK) // Mask of errors in status reg. -// *** Position of tape holes *** -// At beginning of tape: -// *START* -// |<-----24"----->|<---12"--->|<---12"--->|<-----24"----->| -// O O O O O O O -// |<->| |<->| |<->| -// 0.218" 0.218" 0.218" -// At end of tape: -// *END* -// |<-----24"----->|<---12"--->|<---12"--->|<-----24"----->| -// O O O O -// -const hp_taco_device::tape_pos_t hp_taco_device::tape_holes[] = { - (hp_taco_device::tape_pos_t)(23.891 * ONE_INCH_POS), // 24 - 0.218 / 2 - (hp_taco_device::tape_pos_t)(24.109 * ONE_INCH_POS), // 24 + 0.218 / 2 - (hp_taco_device::tape_pos_t)(35.891 * ONE_INCH_POS), // 36 - 0.218 / 2 - (hp_taco_device::tape_pos_t)(36.109 * ONE_INCH_POS), // 36 + 0.218 / 2 - (hp_taco_device::tape_pos_t)(47.891 * ONE_INCH_POS), // 48 - 0.218 / 2 - (hp_taco_device::tape_pos_t)(48.109 * ONE_INCH_POS), // 48 + 0.218 / 2 - 72 * ONE_INCH_POS, // 72 - 1752 * ONE_INCH_POS, // 1752 - 1776 * ONE_INCH_POS, // 1776 - 1788 * ONE_INCH_POS, // 1788 - 1800 * ONE_INCH_POS // 1800 -}; - // Device type definition DEFINE_DEVICE_TYPE(HP_TACO, hp_taco_device, "hp_taco", "HP TACO") @@ -291,6 +259,7 @@ 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_image(), m_image_dirty(false) { clear_state(); @@ -460,7 +429,7 @@ void hp_taco_device::device_timer(emu_timer &timer, device_timer_id id, int para switch (CMD_CODE(m_cmd_reg)) { case CMD_FINAL_GAP: case CMD_WRITE_IRG: - write_gap(m_rw_pos , m_tape_pos); + m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); m_rw_pos = m_tape_pos; break; @@ -547,7 +516,7 @@ void hp_taco_device::clear_state(void) m_rw_pos = 0; m_next_word = 0; m_rd_it_valid = false; - m_gap_detect_start = NULL_TAPE_POS; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; set_tape_present(false); set_tape_present(is_loaded()); @@ -581,31 +550,7 @@ unsigned hp_taco_device::speed_to_tick_freq(void) const (m_tape_fast ? TACH_FREQ_FAST * TAPE_POS_FRACT : TACH_FREQ_SLOW * TAPE_POS_FRACT); } -bool hp_taco_device::pos_offset(tape_pos_t& pos , tape_pos_t offset) const -{ - if (offset == 0) { - return true; - } - - if (!m_tape_fwd) { - offset = -offset; - } - - pos += offset; - - // In real life tape would unspool.. - if (pos > TAPE_LENGTH) { - pos = TAPE_LENGTH; - return false; - } else if (pos < 0) { - pos = 0; - return false; - } else { - return true; - } -} - -hp_taco_device::tape_pos_t hp_taco_device::current_tape_pos(void) const +hti_format_t::tape_pos_t hp_taco_device::current_tape_pos(void) const { if (m_start_time.is_never()) { // Tape not moving @@ -615,11 +560,11 @@ hp_taco_device::tape_pos_t hp_taco_device::current_tape_pos(void) const attotime delta_time(machine().time() - m_start_time); LOG_0(("delta_time = %g\n" , delta_time.as_double())); // How many tachometer ticks has the tape moved? - tape_pos_t delta_tach = (tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); + hti_format_t::tape_pos_t delta_tach = (hti_format_t::tape_pos_t)(delta_time.as_ticks(speed_to_tick_freq())); LOG_0(("delta_tach = %u\n" , delta_tach)); - tape_pos_t tape_pos = m_tape_pos; - if (!pos_offset(tape_pos , delta_tach)) { + auto tape_pos = m_tape_pos; + if (!hti_format_t::pos_offset(tape_pos , m_tape_fwd , delta_tach)) { LOG(("Tape unspooled!\n")); } @@ -639,10 +584,10 @@ void hp_taco_device::update_tape_pos(void) // Gap detection bool gap_detected = false; - if (m_gap_detect_start != NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= min_gap_size()) { - tape_pos_t tmp = m_tape_pos; - pos_offset(tmp , -min_gap_size()); - gap_detected = just_gap(tmp , m_tape_pos); + if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS && abs(m_gap_detect_start - m_tape_pos) >= min_gap_size()) { + auto tmp = m_tape_pos; + hti_format_t::pos_offset(tmp , m_tape_fwd , -min_gap_size()); + gap_detected = m_image.just_gap(current_track() , tmp , m_tape_pos); } if (gap_detected) { BIT_SET(m_status_reg, STATUS_GAP_BIT); @@ -660,9 +605,9 @@ void hp_taco_device::update_tach_reg(void) return; } - tape_pos_t pos = current_tape_pos(); - tape_pos_t pos_int = pos / TAPE_POS_FRACT; - tape_pos_t ref_int = m_tach_reg_ref / TAPE_POS_FRACT; + auto pos = current_tape_pos(); + hti_format_t::tape_pos_t pos_int = pos / TAPE_POS_FRACT; + hti_format_t::tape_pos_t ref_int = m_tach_reg_ref / TAPE_POS_FRACT; uint16_t reg_value = (uint16_t)(abs(pos_int - ref_int) + m_tach_reg); LOG_0(("Tach = %04x @ pos = %d, ref_pos = %d\n" , reg_value , pos , m_tach_reg_ref)); @@ -682,48 +627,13 @@ void hp_taco_device::freeze_tach_reg(bool freeze) } -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; - } -} - -// Position of next hole tape will reach in a given direction -hp_taco_device::tape_pos_t hp_taco_device::next_hole(void) const -{ - if (m_tape_fwd) { - for (tape_pos_t hole : tape_holes) { - if (hole > m_tape_pos) { - LOG_0(("next hole fwd @%u = %u\n" , m_tape_pos , hole)); - return hole; - } - } - // No more holes: will hit end of tape - return NULL_TAPE_POS; - } else { - for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) { - if (tape_holes[ i ] < m_tape_pos) { - LOG_0(("next hole rev @%u = %u\n" , m_tape_pos , tape_holes[ i ])); - return tape_holes[ i ]; - } - } - // No more holes: will hit start of tape - return NULL_TAPE_POS; - } -} - -attotime hp_taco_device::time_to_distance(tape_pos_t distance) const +attotime hp_taco_device::time_to_distance(hti_format_t::tape_pos_t distance) const { // +1 for rounding return attotime::from_ticks(distance + 1 , speed_to_tick_freq()); } -attotime hp_taco_device::time_to_target(tape_pos_t target) const +attotime hp_taco_device::time_to_target(hti_format_t::tape_pos_t target) const { return time_to_distance(abs(target - m_tape_pos)); } @@ -758,7 +668,7 @@ bool hp_taco_device::start_tape_cmd(uint16_t cmd_reg , uint16_t must_be_1 , uint if (m_tape_wr) { // Write command: disable gap detector - m_gap_detect_start = NULL_TAPE_POS; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; BIT_CLR(m_status_reg, STATUS_GAP_BIT); m_image_dirty = true; } else if (not_moving || prev_tape_braking || prev_tape_wr != m_tape_wr || prev_tape_fwd != m_tape_fwd || prev_tape_fast != m_tape_fast) { @@ -791,140 +701,12 @@ bool hp_taco_device::start_tape_cmd(uint16_t cmd_reg , uint16_t must_be_1 , uint void hp_taco_device::stop_tape(void) { m_start_time = attotime::never; - m_gap_detect_start = NULL_TAPE_POS; -} - -hp_taco_device::tape_track_t& hp_taco_device::current_track(void) -{ - return m_tracks[ BIT(m_status_reg , STATUS_TRACKB_BIT) ]; -} - -// Return physical length of a 16-bit word on tape -hp_taco_device::tape_pos_t hp_taco_device::word_length(tape_word_t w) -{ - unsigned zeros , ones; - - // pop count of w - ones = (w & 0x5555) + ((w >> 1) & 0x5555); - ones = (ones & 0x3333) + ((ones >> 2) & 0x3333); - ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f); - ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff); - - zeros = 16 - ones; - - return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; + m_gap_detect_start = hti_format_t::NULL_TAPE_POS; } -hp_taco_device::tape_pos_t hp_taco_device::word_end_pos(const tape_track_t::iterator& it) +unsigned hp_taco_device::current_track(void) { - return it->first + word_length(it->second); -} - -void hp_taco_device::adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos) -{ - if (it != track.begin()) { - --it; - if (word_end_pos(it) <= pos) { - ++it; - } - } -} - -// Write a word on current tape track -void hp_taco_device::write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length) -{ - tape_track_t& track = current_track(); - tape_track_t::iterator it_low = track.lower_bound(start); - adjust_it(track , it_low , start); - length = word_length(word); - tape_pos_t end_pos = start + length; - tape_track_t::iterator it_high = track.lower_bound(end_pos); - - track.erase(it_low , it_high); - - track.insert(it_high , std::make_pair(start, word)); - LOG_0(("WR %04x @ T%u:%u\n" , word , BIT(m_status_reg , STATUS_TRACKB_BIT) , start)); -} - -// Write a gap on current track -void hp_taco_device::write_gap(tape_pos_t a , tape_pos_t b) -{ - ensure_a_lt_b(a , b); - tape_track_t& track = current_track(); - tape_track_t::iterator it_low = track.lower_bound(a); - adjust_it(track , it_low , a); - tape_track_t::iterator it_high = track.lower_bound(b); - - track.erase(it_low, it_high); - - LOG_0(("GAP on T%u:[%u,%u)\n" , BIT(m_status_reg , STATUS_TRACKB_BIT) , a , b)); -} - -bool hp_taco_device::just_gap(tape_pos_t a , tape_pos_t b) -{ - ensure_a_lt_b(a , b); - tape_track_t& track = current_track(); - tape_track_t::iterator it_low = track.lower_bound(a); - tape_track_t::iterator it_high = track.lower_bound(b); - - adjust_it(track, it_low, a); - - return it_low == it_high; -} - -hp_taco_device::tape_pos_t hp_taco_device::farthest_end(const tape_track_t::iterator& it) const -{ - if (m_tape_fwd) { - return word_end_pos(it); - } else { - return it->first; - } -} - -bool hp_taco_device::next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive) -{ - tape_track_t& track = current_track(); - it = track.lower_bound(pos); - if (m_tape_fwd) { - if (inclusive) { - adjust_it(track, it, pos); - } - return it != track.end(); - } else { - // Never more than 2 iterations - do { - if (it == track.begin()) { - it = track.end(); - return false; - } - --it; - } while (!inclusive && word_end_pos(it) > pos); - return true; - } -} - -hp_taco_device::adv_res_t hp_taco_device::adv_it(tape_track_t::iterator& it) -{ - tape_track_t& track = current_track(); - if (m_tape_fwd) { - tape_pos_t prev_pos = word_end_pos(it); - ++it; - if (it == track.end()) { - return ADV_NO_MORE_DATA; - } else { - adv_res_t res = prev_pos == it->first ? ADV_CONT_DATA : ADV_DISCONT_DATA; - return res; - } - } else { - if (it == track.begin()) { - it = track.end(); - return ADV_NO_MORE_DATA; - } else { - tape_pos_t prev_pos = it->first; - --it; - return prev_pos == word_end_pos(it) ? ADV_CONT_DATA : ADV_DISCONT_DATA; - } - } + return BIT(m_status_reg , STATUS_TRACKB_BIT); } attotime hp_taco_device::fetch_next_wr_word(void) @@ -945,209 +727,24 @@ attotime hp_taco_device::fetch_next_wr_word(void) // Update checksum with new word m_checksum_reg += m_next_word; - return time_to_distance(word_length(m_next_word)); + return time_to_distance(hti_format_t::word_length(m_next_word)); } -attotime hp_taco_device::time_to_rd_next_word(tape_pos_t& word_rd_pos) +attotime hp_taco_device::time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos) { if (m_rd_it_valid) { - word_rd_pos = farthest_end(m_rd_it); + word_rd_pos = hti_format_t::farthest_end(m_rd_it , m_tape_fwd); return time_to_target(word_rd_pos); } else { return attotime::never; } } -hp_taco_device::tape_pos_t hp_taco_device::min_gap_size(void) const +hti_format_t::tape_pos_t hp_taco_device::min_gap_size(void) const { return LONG_GAP(m_cmd_reg) ? LONG_GAP_LENGTH : SHORT_GAP_LENGTH; } -/** - * Scan for next "n_gaps" gaps - * - * @param[in,out] pos Start position on input, start of gap on output - * @param it Pointer to data word where scan is to start - * @param n_gaps Number of gaps to scan - * @param min_gap Minimum gap size - * - * @return true if n_gaps gaps are found - */ -bool hp_taco_device::next_n_gap(tape_pos_t& pos , tape_track_t::iterator it , unsigned n_gaps , tape_pos_t min_gap) -{ - tape_track_t& track = current_track(); - bool done = false; - tape_track_t::iterator prev_it; - - if (m_tape_fwd) { - tape_pos_t next_pos; - - while (1) { - if (it == track.end()) { - next_pos = TAPE_LENGTH; - done = true; - } else { - next_pos = it->first; - } - if (((next_pos - pos) >= min_gap && --n_gaps == 0) || done) { - break; - } - adv_res_t adv_res; - do { - prev_it = it; - adv_res = adv_it(it); - } while (adv_res == ADV_CONT_DATA); - pos = word_end_pos(prev_it); - } - } else { - tape_pos_t next_pos; - - while (1) { - if (it == track.end()) { - next_pos = 0; - done = true; - } else { - next_pos = word_end_pos(it); - } - if (((pos - next_pos) >= min_gap && --n_gaps == 0) || done) { - break; - } - adv_res_t adv_res; - do { - prev_it = it; - adv_res = adv_it(it); - } while (adv_res == ADV_CONT_DATA); - pos = prev_it->first; - } - } - - // Set "pos" where minimum gap size is met - pos_offset(pos , min_gap); - - return n_gaps == 0; -} - -bool hp_taco_device::next_n_gap(tape_pos_t& pos , unsigned n_gaps , tape_pos_t min_gap) -{ - tape_track_t::iterator it; - // First align with next data - next_data(it, pos, true); - // Then scan for n_gaps - return next_n_gap(pos, it, n_gaps, min_gap); -} - -void hp_taco_device::clear_tape(void) -{ - for (tape_track_t& track : m_tracks) { - track.clear(); - } -} - -void hp_taco_device::dump_sequence(tape_track_t::const_iterator it_start , unsigned n_words) -{ - if (n_words) { - uint32_t tmp32; - uint16_t tmp16; - - tmp32 = n_words; - fwrite(&tmp32 , sizeof(tmp32)); - tmp32 = it_start->first; - fwrite(&tmp32 , sizeof(tmp32)); - - for (unsigned i = 0; i < n_words; i++) { - tmp16 = it_start->second; - fwrite(&tmp16 , sizeof(tmp16)); - ++it_start; - } - } -} - -void hp_taco_device::save_tape(void) -{ - uint32_t tmp32; - - fseek(0, SEEK_SET); - - tmp32 = FILE_MAGIC; - fwrite(&tmp32 , sizeof(tmp32)); - - for (const tape_track_t& track : m_tracks) { - tape_pos_t next_pos = (tape_pos_t)-1; - unsigned n_words = 0; - tape_track_t::const_iterator it_start; - for (tape_track_t::const_iterator it = track.cbegin(); it != track.cend(); ++it) { - if (it->first != next_pos) { - dump_sequence(it_start , n_words); - it_start = it; - n_words = 0; - } - next_pos = it->first + word_length(it->second); - n_words++; - } - dump_sequence(it_start , n_words); - // End of track - tmp32 = (uint32_t)-1; - fwrite(&tmp32 , sizeof(tmp32)); - } -} - -bool hp_taco_device::load_track(tape_track_t& track) -{ - uint32_t tmp32; - - track.clear(); - - while (1) { - if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { - return false; - } - - if (tmp32 == (uint32_t)-1) { - return true; - } - - unsigned n_words = tmp32; - - if (fread(&tmp32 , sizeof(tmp32)) != sizeof(tmp32)) { - return false; - } - - tape_pos_t pos = (tape_pos_t)tmp32; - - for (unsigned i = 0; i < n_words; i++) { - uint16_t tmp16; - - if (fread(&tmp16 , sizeof(tmp16)) != sizeof(tmp16)) { - return false; - } - - track.insert(std::make_pair(pos , tmp16)); - pos += word_length(tmp16); - } - } -} - -bool hp_taco_device::load_tape(void) -{ - uint32_t magic; - - if (fread(&magic , sizeof(magic)) != sizeof(magic) || - magic != FILE_MAGIC) { - return false; - } - - for (tape_track_t& track : m_tracks) { - if (!load_track(track)) { - LOG(("load_tape failed")); - clear_tape(); - return false; - } - } - - LOG(("load_tape done\n")); - return true; -} - void hp_taco_device::set_tape_present(bool present) { if (present) { @@ -1165,9 +762,9 @@ void hp_taco_device::set_tape_present(bool present) attotime hp_taco_device::time_to_next_hole(void) const { - tape_pos_t pos = next_hole(); + auto pos = hti_format_t::next_hole(m_tape_pos , m_tape_fwd); - if (pos == NULL_TAPE_POS) { + if (pos == hti_format_t::NULL_TAPE_POS) { return attotime::never; } else { return time_to_target(pos); @@ -1176,7 +773,7 @@ attotime hp_taco_device::time_to_next_hole(void) const attotime hp_taco_device::time_to_tach_pulses(void) const { - return time_to_distance((tape_pos_t)(0x10000U - m_tach_reg) * TAPE_POS_FRACT); + return time_to_distance((hti_format_t::tape_pos_t)(0x10000U - m_tach_reg) * TAPE_POS_FRACT); } void hp_taco_device::terminate_cmd_now(void) @@ -1236,7 +833,7 @@ void hp_taco_device::cmd_fsm(void) m_cmd_state = CMD_PH1; } else { // PH1 - write_gap(m_rw_pos , m_tape_pos); + m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); cmd_duration = attotime::zero; m_cmd_state = CMD_END; } @@ -1246,7 +843,7 @@ void hp_taco_device::cmd_fsm(void) if (m_cmd_state == CMD_PH0) { // PH0 // Search for preamble first - m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); + m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , false , m_rd_it); cmd_duration = time_to_rd_next_word(m_rw_pos); // Set T/O for preamble search set_data_timeout(true); @@ -1262,8 +859,8 @@ void hp_taco_device::cmd_fsm(void) m_timeout_timer->reset(); irq_w(true); } else { - adv_res_t res = adv_it(m_rd_it); - if (res != ADV_NO_MORE_DATA) { + auto res = m_image.adv_it(current_track() , m_tape_fwd , m_rd_it); + if (res != hti_format_t::ADV_NO_MORE_DATA) { cmd_duration = time_to_rd_next_word(m_rw_pos); } // Set T/O for arrival of data words @@ -1278,13 +875,13 @@ void hp_taco_device::cmd_fsm(void) // PH0 m_next_word = PREAMBLE_WORD; m_rw_pos = m_tape_pos; - cmd_duration = time_to_distance(word_length(m_next_word)); + cmd_duration = time_to_distance(hti_format_t::word_length(m_next_word)); m_cmd_state = CMD_PH1; } else { // PH1 & PH2 of CMD_RECORD_WRITE - tape_pos_t length; - write_word(m_rw_pos , m_next_word , length); - pos_offset(m_rw_pos , length); + hti_format_t::tape_pos_t length; + m_image.write_word(current_track() , m_rw_pos , m_next_word , length); + hti_format_t::pos_offset(m_rw_pos , m_tape_fwd , length); // Just to be sure.. m_tape_pos = m_rw_pos; cmd_duration = fetch_next_wr_word(); @@ -1317,8 +914,8 @@ void hp_taco_device::cmd_fsm(void) case CMD_INGAP_MOVE: if (m_cmd_state == CMD_PH0) { // PH0 - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, min_gap_size())) { + auto target = m_tape_pos; + if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { LOG_0(("IRG @%d\n" , target)); cmd_duration = time_to_target(target); } @@ -1335,16 +932,16 @@ void hp_taco_device::cmd_fsm(void) case CMD_NOT_INDTA: if (m_cmd_state == CMD_PH0) { // PH0 - if (next_data(m_rd_it , m_tape_pos , true)) { - cmd_duration = time_to_target(farthest_end(m_rd_it)); + if (m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , true , m_rd_it)) { + cmd_duration = time_to_target(hti_format_t::farthest_end(m_rd_it , m_tape_fwd)); } // Set T/O for data set_data_timeout(true); m_cmd_state = CMD_PH1; } else { // PH1 - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, min_gap_size())) { + auto target = m_tape_pos; + if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { LOG_0(("End of data @%d\n" , target)); cmd_duration = time_to_target(target); } @@ -1363,7 +960,7 @@ void hp_taco_device::cmd_fsm(void) m_cmd_state = CMD_PH1; } else { // PH1 - write_gap(m_rw_pos , m_tape_pos); + m_image.write_gap(current_track() , m_rw_pos , m_tape_pos); cmd_duration = attotime::zero; m_cmd_state = CMD_END; } @@ -1381,16 +978,16 @@ void hp_taco_device::cmd_fsm(void) break; } } - if (next_data(m_rd_it , m_tape_pos , true)) { - cmd_duration = time_to_target(farthest_end(m_rd_it)); + if (m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , true , m_rd_it)) { + cmd_duration = time_to_target(hti_format_t::farthest_end(m_rd_it , m_tape_fwd)); } // Set T/O for data set_data_timeout(true); m_cmd_state = CMD_PH1; } else if (m_cmd_state == CMD_PH1) { // PH1 - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, min_gap_size())) { + auto target = m_tape_pos; + if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { LOG_0(("Gap @%d (%u to go)\n" , target , 0x10000U - m_tach_reg)); cmd_duration = time_to_target(target); } @@ -1407,8 +1004,8 @@ void hp_taco_device::cmd_fsm(void) m_cmd_state = CMD_PH1; } else { // PH1 - if (next_data(m_rd_it , m_tape_pos , true)) { - cmd_duration = time_to_target(farthest_end(m_rd_it)); + if (m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , true , m_rd_it)) { + cmd_duration = time_to_target(hti_format_t::farthest_end(m_rd_it , m_tape_fwd)); } // Apparently this cmd doesn't set no-data T/O m_cmd_state = CMD_END; @@ -1430,8 +1027,8 @@ void hp_taco_device::cmd_fsm(void) m_cmd_state = CMD_PH1; } else { // PH1 - tape_pos_t target = m_tape_pos; - if (next_n_gap(target, 1, min_gap_size())) { + auto target = m_tape_pos; + if (m_image.next_gap(current_track() , target, m_tape_fwd, min_gap_size())) { LOG_0(("GAP @%d\n" , target)); cmd_duration = time_to_target(target); } @@ -1445,7 +1042,7 @@ void hp_taco_device::cmd_fsm(void) // Should also check if tape position has gone too far to read word @ m_rd_it if (!m_rd_it_valid) { // Search for preamble first - m_rd_it_valid = next_data(m_rd_it , m_tape_pos , false); + m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , m_tape_fwd , false , m_rd_it); // Set T/O for preamble search set_data_timeout(true); @@ -1483,9 +1080,9 @@ void hp_taco_device::cmd_fsm(void) } // Set T/O for arrival of data words set_data_timeout(false); - adv_res_t res = adv_it(m_rd_it); + auto res = m_image.adv_it(current_track() , m_tape_fwd , m_rd_it); LOG_0(("adv_it %d\n" , res)); - if (res == ADV_NO_MORE_DATA) { + if (res == hti_format_t::ADV_NO_MORE_DATA) { m_rd_it_valid = false; } } @@ -1511,8 +1108,8 @@ void hp_taco_device::cmd_fsm(void) // Note: checksum is not updated m_data_reg = m_rd_it->second; LOG_0(("Final RD %04x\n" , m_data_reg)); - adv_res_t res = adv_it(m_rd_it); - if (res == ADV_NO_MORE_DATA) { + auto res = m_image.adv_it(current_track() , m_tape_fwd , m_rd_it); + if (res == hti_format_t::ADV_NO_MORE_DATA) { m_rd_it_valid = false; } cmd_duration = attotime::zero; @@ -1672,20 +1269,24 @@ void hp_taco_device::start_cmd_exec(uint16_t new_cmd_reg) image_init_result hp_taco_device::internal_load(bool is_create) { - device_reset(); - - if (is_create) { - clear_tape(); - save_tape(); - } else if (!load_tape()) { - seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); - set_tape_present(false); - return image_init_result::FAIL; - } + device_reset(); + + io_generic io; + io.file = (device_image_interface *)this; + io.procs = &image_ioprocs; + io.filler = 0; + if (is_create) { + m_image.clear_tape(); + m_image.save_tape(&io); + } else if (!m_image.load_tape(&io)) { + seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format"); + set_tape_present(false); + return image_init_result::FAIL; + } - m_image_dirty = false; + m_image_dirty = false; - set_tape_present(true); + set_tape_present(true); return image_init_result::PASS; } @@ -1703,17 +1304,21 @@ image_init_result hp_taco_device::call_create(int format_type, util::option_reso void hp_taco_device::call_unload() { - LOG(("call_unload dirty=%d\n" , m_image_dirty)); + LOG(("call_unload dirty=%d\n" , m_image_dirty)); - device_reset(); + device_reset(); - if (m_image_dirty) { - save_tape(); - m_image_dirty = false; - } + if (m_image_dirty) { + io_generic io; + io.file = (device_image_interface *)this; + io.procs = &image_ioprocs; + io.filler = 0; + m_image.save_tape(&io); + m_image_dirty = false; + } - clear_tape(); - set_tape_present(false); + m_image.clear_tape(); + set_tape_present(false); } std::string hp_taco_device::call_display() @@ -1739,7 +1344,7 @@ std::string hp_taco_device::call_display() m2 = m_tape_fast ? '<' : ' '; } - int pos_in = current_tape_pos() / ONE_INCH_POS; + int pos_in = current_tape_pos() / hti_format_t::ONE_INCH_POS; buffer = string_format("%c %c %c%c [%04d/1824]" , track , r_w , m1 , m2 , pos_in); diff --git a/src/devices/machine/hp_taco.h b/src/devices/machine/hp_taco.h index c0536eabb9c..d5fe4fb9b23 100644 --- a/src/devices/machine/hp_taco.h +++ b/src/devices/machine/hp_taco.h @@ -13,7 +13,7 @@ #pragma once -#include <map> +#include "formats/hti_tape.h" #define MCFG_TACO_IRQ_HANDLER(_devcb) \ devcb = &hp_taco_device::set_irq_handler(*device , DEVCB_##_devcb); @@ -58,14 +58,6 @@ public: virtual const char *file_extensions() const override; protected: - // Tape position, 1 unit = 1 inch / (968 * 1024) - typedef int32_t tape_pos_t; - - // Words stored on tape - typedef uint16_t tape_word_t; - - static const tape_pos_t tape_holes[]; - hp_taco_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); // device-level overrides @@ -75,9 +67,6 @@ protected: 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_pos_t, tape_word_t> tape_track_t; - devcb_write_line m_irq_handler; devcb_write_line m_flg_handler; devcb_write_line m_sts_handler; @@ -88,7 +77,7 @@ private: uint16_t m_cmd_reg; uint16_t m_status_reg; uint16_t m_tach_reg; - tape_pos_t m_tach_reg_ref; + hti_format_t::tape_pos_t m_tach_reg_ref; bool m_tach_reg_frozen; uint16_t m_checksum_reg; bool m_clear_checksum_reg; @@ -112,7 +101,7 @@ private: cmd_state_t m_cmd_state; // Tape position & motion - tape_pos_t m_tape_pos; + hti_format_t::tape_pos_t m_tape_pos; attotime m_start_time; // Tape moving if != never bool m_tape_fwd; bool m_tape_fast; @@ -122,63 +111,38 @@ private: emu_timer *m_hole_timer; emu_timer *m_timeout_timer; - // Content of tape tracks - tape_track_t m_tracks[ 2 ]; + // Content of tape + hti_format_t m_image; bool m_image_dirty; // Reading & writing bool m_tape_wr; - tape_pos_t m_rw_pos; + hti_format_t::tape_pos_t m_rw_pos; uint16_t m_next_word; - tape_track_t::iterator m_rd_it; + hti_format_t::track_iterator_t 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; + hti_format_t::tape_pos_t m_gap_detect_start; void clear_state(void); void irq_w(bool state); void set_error(bool state); bool is_braking(void) const; unsigned speed_to_tick_freq(void) const; - bool pos_offset(tape_pos_t& pos , tape_pos_t offset) const; - tape_pos_t current_tape_pos(void) const; + hti_format_t::tape_pos_t current_tape_pos(void) const; void update_tape_pos(void); void update_tach_reg(void); void freeze_tach_reg(bool freeze); - static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); - tape_pos_t next_hole(void) const; - attotime time_to_distance(tape_pos_t distance) const; - attotime time_to_target(tape_pos_t target) const; + attotime time_to_distance(hti_format_t::tape_pos_t distance) const; + attotime time_to_target(hti_format_t::tape_pos_t target) const; attotime time_to_stopping_pos(void) const; bool start_tape_cmd(uint16_t cmd_reg , uint16_t must_be_1 , uint16_t must_be_0); 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); + unsigned current_track(void); attotime fetch_next_wr_word(void); - attotime time_to_rd_next_word(tape_pos_t& word_rd_pos); - tape_pos_t min_gap_size(void) const; - 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); - 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); + attotime time_to_rd_next_word(hti_format_t::tape_pos_t& word_rd_pos); + hti_format_t::tape_pos_t min_gap_size(void) const; void set_tape_present(bool present); attotime time_to_next_hole(void) const; attotime time_to_tach_pulses(void) const; diff --git a/src/lib/formats/hti_tape.cpp b/src/lib/formats/hti_tape.cpp new file mode 100644 index 00000000000..4b26f9778f8 --- /dev/null +++ b/src/lib/formats/hti_tape.cpp @@ -0,0 +1,400 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + "HTI" format + +*********************************************************************/ + +#include "hti_tape.h" +#include "imageutl.h" + +static constexpr uint32_t FILE_MAGIC = 0x5441434f; // Magic value at start of image file: "TACO" +static constexpr hti_format_t::tape_pos_t ZERO_BIT_LEN = 619; // Length of 0 bits at slow tape speed: 1/(35200 Hz) +static constexpr hti_format_t::tape_pos_t ONE_BIT_LEN = 1083; // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN + +// *** 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 hti_format_t::tape_pos_t tape_holes[] = { + (hti_format_t::tape_pos_t)(23.891 * hti_format_t::ONE_INCH_POS), // 24 - 0.218 / 2 + (hti_format_t::tape_pos_t)(24.109 * hti_format_t::ONE_INCH_POS), // 24 + 0.218 / 2 + (hti_format_t::tape_pos_t)(35.891 * hti_format_t::ONE_INCH_POS), // 36 - 0.218 / 2 + (hti_format_t::tape_pos_t)(36.109 * hti_format_t::ONE_INCH_POS), // 36 + 0.218 / 2 + (hti_format_t::tape_pos_t)(47.891 * hti_format_t::ONE_INCH_POS), // 48 - 0.218 / 2 + (hti_format_t::tape_pos_t)(48.109 * hti_format_t::ONE_INCH_POS), // 48 + 0.218 / 2 + 72 * hti_format_t::ONE_INCH_POS, // 72 + 1752 * hti_format_t::ONE_INCH_POS, // 1752 + 1776 * hti_format_t::ONE_INCH_POS, // 1776 + 1788 * hti_format_t::ONE_INCH_POS, // 1788 + 1800 * hti_format_t::ONE_INCH_POS // 1800 +}; + +hti_format_t::hti_format_t() +{ + clear_tape(); +} + +bool hti_format_t::load_tape(io_generic *io) +{ + uint8_t tmp[ 4 ]; + + io_generic_read(io, tmp, 0, 4); + if (pick_integer_be(tmp , 0 , 4) != FILE_MAGIC) { + return false; + } + + uint64_t offset = 4; + + for (tape_track_t& track : m_tracks) { + if (!load_track(io , offset , track)) { + clear_tape(); + return false; + } + } + + return true; +} + +void hti_format_t::save_tape(io_generic *io) +{ + uint8_t tmp[ 4 ]; + + place_integer_be(tmp, 0, 4, FILE_MAGIC); + io_generic_write(io, tmp, 0, 4); + + uint64_t offset = 4; + + for (const tape_track_t& track : m_tracks) { + 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(io , offset , it_start , n_words); + it_start = it; + n_words = 0; + } + next_pos = it->first + word_length(it->second); + n_words++; + } + dump_sequence(io , offset , it_start , n_words); + // End of track + place_integer_le(tmp, 0, 4, (uint32_t)-1); + io_generic_write(io, tmp, offset, 4); + offset += 4; + } +} + +void hti_format_t::clear_tape() +{ + for (tape_track_t& track : m_tracks) { + track.clear(); + } +} + +hti_format_t::tape_pos_t hti_format_t::word_length(tape_word_t w) +{ + unsigned zeros , ones; + + // pop count of w + ones = (w & 0x5555) + ((w >> 1) & 0x5555); + ones = (ones & 0x3333) + ((ones >> 2) & 0x3333); + ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f); + ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff); + + zeros = 16 - ones; + + return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; +} + +hti_format_t::tape_pos_t hti_format_t::farthest_end(const track_iterator_t& it , bool forward) +{ + if (forward) { + return word_end_pos(it); + } else { + return it->first; + } +} + +bool hti_format_t::pos_offset(tape_pos_t& pos , bool forward , tape_pos_t offset) +{ + if (offset == 0) { + return true; + } + + if (!forward) { + 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; + } +} + +hti_format_t::tape_pos_t hti_format_t::next_hole(tape_pos_t pos , bool forward) +{ + if (forward) { + for (tape_pos_t hole : tape_holes) { + if (hole > pos) { + return hole; + } + } + // No more holes: will hit end of tape + return NULL_TAPE_POS; + } else { + for (int i = (sizeof(tape_holes) / sizeof(tape_holes[ 0 ])) - 1; i >= 0; i--) { + if (tape_holes[ i ] < pos) { + return tape_holes[ i ]; + } + } + // No more holes: will hit start of tape + return NULL_TAPE_POS; + } +} + +void hti_format_t::write_word(unsigned track_no , tape_pos_t start , tape_word_t word , tape_pos_t& length) +{ + tape_track_t& track = m_tracks[ track_no ]; + track_iterator_t it_low = track.lower_bound(start); + adjust_it(track , it_low , start); + length = word_length(word); + tape_pos_t end_pos = start + length; + track_iterator_t it_high = track.lower_bound(end_pos); + + track.erase(it_low , it_high); + + track.insert(it_high , std::make_pair(start, word)); +} + +void hti_format_t::write_gap(unsigned track_no , tape_pos_t a , tape_pos_t b) +{ + ensure_a_lt_b(a , b); + tape_track_t& track = m_tracks[ track_no ]; + track_iterator_t it_low = track.lower_bound(a); + adjust_it(track , it_low , a); + track_iterator_t it_high = track.lower_bound(b); + + track.erase(it_low, it_high); +} + +bool hti_format_t::just_gap(unsigned track_no , tape_pos_t a , tape_pos_t b) +{ + ensure_a_lt_b(a , b); + tape_track_t& track = m_tracks[ track_no ]; + track_iterator_t it_low = track.lower_bound(a); + track_iterator_t it_high = track.lower_bound(b); + + adjust_it(track, it_low, a); + + return it_low == it_high; +} + +bool hti_format_t::next_data(unsigned track_no , tape_pos_t pos , bool forward , bool inclusive , track_iterator_t& it) +{ + tape_track_t& track = m_tracks[ track_no ]; + it = track.lower_bound(pos); + if (forward) { + 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; + } +} + +hti_format_t::adv_res_t hti_format_t::adv_it(unsigned track_no , bool forward , track_iterator_t& it) +{ + tape_track_t& track = m_tracks[ track_no ]; + if (forward) { + 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; + } + } +} + +bool hti_format_t::next_gap(unsigned track_no , tape_pos_t& pos , bool forward , tape_pos_t min_gap) +{ + tape_track_t::iterator it; + // First align with next data + next_data(track_no , pos , forward , true , it); + // Then scan for 1st gap + tape_track_t& track = m_tracks[ track_no ]; + bool done = false; + track_iterator_t prev_it; + unsigned n_gaps = 1; + + if (forward) { + 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(track_no , forward , 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(track_no , forward , it); + } while (adv_res == ADV_CONT_DATA); + pos = prev_it->first; + } + } + + // Set "pos" where minimum gap size is met + pos_offset(pos , forward , min_gap); + + return n_gaps == 0; +} + +bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& track) +{ + uint8_t tmp[ 4 ]; + uint32_t tmp32; + + track.clear(); + + while (1) { + // Read no. of words to follow + io_generic_read(io, tmp, offset, 4); + offset += 4; + + tmp32 = pick_integer_le(tmp, 0, 4); + + // Track ends + if (tmp32 == (uint32_t)-1) { + return true; + } + + unsigned n_words = tmp32; + + // Read tape position of block + io_generic_read(io, tmp, offset, 4); + offset += 4; + + tmp32 = pick_integer_le(tmp, 0, 4); + + tape_pos_t pos = (tape_pos_t)tmp32; + + for (unsigned i = 0; i < n_words; i++) { + uint16_t tmp16; + + io_generic_read(io, tmp, offset, 2); + offset += 2; + tmp16 = pick_integer_le(tmp, 0, 2); + + track.insert(std::make_pair(pos , tmp16)); + pos += word_length(tmp16); + } + } +} + +void hti_format_t::dump_sequence(io_generic *io , uint64_t& offset , tape_track_t::const_iterator it_start , unsigned n_words) +{ + if (n_words) { + uint8_t tmp[ 8 ]; + place_integer_le(tmp, 0, 4, n_words); + place_integer_le(tmp, 4, 4, it_start->first); + io_generic_write(io, tmp, offset, 8); + offset += 8; + + for (unsigned i = 0; i < n_words; i++) { + place_integer_le(tmp, 0, 2, it_start->second); + io_generic_write(io, tmp, offset, 2); + offset += 2; + ++it_start; + } + } +} + +hti_format_t::tape_pos_t hti_format_t::word_end_pos(const track_iterator_t& it) +{ + return it->first + word_length(it->second); +} + +void hti_format_t::adjust_it(tape_track_t& track , track_iterator_t& it , tape_pos_t pos) +{ + if (it != track.begin()) { + --it; + if (word_end_pos(it) <= pos) { + ++it; + } + } +} + +void hti_format_t::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; + } +} diff --git a/src/lib/formats/hti_tape.h b/src/lib/formats/hti_tape.h new file mode 100644 index 00000000000..cb8b1a39262 --- /dev/null +++ b/src/lib/formats/hti_tape.h @@ -0,0 +1,93 @@ +// license:BSD-3-Clause +// copyright-holders:F. Ulivi +/********************************************************************* + + "HTI" format + + Format of images of DC-100 tape cassettes as used in HP 9845 + and HP 85 systems. + +*********************************************************************/ + +#ifndef _HTI_TAPE_H_ +#define _HTI_TAPE_H_ + +#pragma once + +#include <map> +#include "ioprocs.h" + +class hti_format_t { +public: + hti_format_t(); + + // Tape position, 1 unit = 1 inch / (968 * 1024) + typedef int32_t tape_pos_t; + + // 1 inch in tape_pos_t + static constexpr tape_pos_t ONE_INCH_POS = 968 * 1024; + + // Special value for invalid/unknown tape position + static constexpr tape_pos_t NULL_TAPE_POS = -1; + + // Tape length: 140 ft of usable tape + 72" of punched tape at either end + static constexpr tape_pos_t TAPE_LENGTH = (140 * 12 + 72 * 2) * ONE_INCH_POS; + + // Words stored on tape + typedef uint16_t tape_word_t; + + // Storage of tracks: mapping from a tape position to word stored there + typedef std::map<tape_pos_t, tape_word_t> tape_track_t; + + // Iterator to access words on tape + typedef tape_track_t::iterator track_iterator_t; + + bool load_tape(io_generic *io); + void save_tape(io_generic *io); + void clear_tape(); + + // Return physical length of a 16-bit word on tape + static tape_pos_t word_length(tape_word_t w); + + static tape_pos_t farthest_end(const track_iterator_t& it , bool forward); + + static bool pos_offset(tape_pos_t& pos , bool forward , tape_pos_t offset); + + // Position of next hole tape will reach in a given direction + static tape_pos_t next_hole(tape_pos_t pos , bool forward); + + // Write a data word on tape + void write_word(unsigned track_no , tape_pos_t start , tape_word_t word , tape_pos_t& length); + // Write a gap on tape + void write_gap(unsigned track_no , tape_pos_t a , tape_pos_t b); + + // Check that a section of tape has no data (it's just gap) + bool just_gap(unsigned track_no , tape_pos_t a , tape_pos_t b); + + // Return position of next data word in a given direction + bool next_data(unsigned track_no , tape_pos_t pos , bool forward , bool inclusive , track_iterator_t& it); + + typedef enum { + ADV_NO_MORE_DATA, + ADV_CONT_DATA, + ADV_DISCONT_DATA + } adv_res_t; + + // Advance an iterator to next word of data + adv_res_t adv_it(unsigned track_no , bool forward , track_iterator_t& it); + + // Scan for beginning of next gap in a given direction + bool next_gap(unsigned track_no , tape_pos_t& pos , bool forward , tape_pos_t min_gap); +private: + // Content of tape tracks + tape_track_t m_tracks[ 2 ]; + + static bool load_track(io_generic *io , uint64_t& offset , tape_track_t& track); + static void dump_sequence(io_generic *io , uint64_t& offset , tape_track_t::const_iterator it_start , unsigned n_words); + + static tape_pos_t word_end_pos(const track_iterator_t& it); + static void adjust_it(tape_track_t& track , track_iterator_t& it , tape_pos_t pos); + static void ensure_a_lt_b(tape_pos_t& a , tape_pos_t& b); +}; + +#endif /* _HTI_TAPE_H_ */ diff --git a/src/mame/drivers/hp80.cpp b/src/mame/drivers/hp80.cpp index 1a2495c9f84..5438720d12f 100644 --- a/src/mame/drivers/hp80.cpp +++ b/src/mame/drivers/hp80.cpp @@ -14,6 +14,7 @@ #include "sound/beep.h" #include "sound/dac.h" #include "sound/volt_reg.h" +#include "machine/1ma6.h" // Debugging #define VERBOSE 1 @@ -1017,6 +1018,7 @@ static ADDRESS_MAP_START(cpu_mem_map , AS_PROGRAM , 8 , hp85_state) AM_RANGE(0xff02 , 0xff02) AM_READWRITE(keysts_r , keysts_w) AM_RANGE(0xff03 , 0xff03) AM_READWRITE(keycod_r , keycod_w) AM_RANGE(0xff04 , 0xff07) AM_READWRITE(crtc_r , crtc_w) + AM_RANGE(0xff08 , 0xff09) AM_DEVREADWRITE("tape" , hp_1ma6_device , reg_r , reg_w) AM_RANGE(0xff0a , 0xff0a) AM_READWRITE(clksts_r , clksts_w) AM_RANGE(0xff0b , 0xff0b) AM_READWRITE(clkdat_r , clkdat_w) AM_RANGE(0xff18 , 0xff18) AM_WRITE(rselec_w) @@ -1051,6 +1053,8 @@ static MACHINE_CONFIG_START(hp85) MCFG_SOUND_ADD("beeper" , BEEP , MASTER_CLOCK / 8192) MCFG_MIXER_ROUTE(ALL_OUTPUTS , "mono" , 0.5 , 0) + // Tape drive + MCFG_DEVICE_ADD("tape" , HP_1MA6 , 0) MACHINE_CONFIG_END ROM_START(hp85) diff --git a/src/tools/imgtool/modules/hp9845_tape.cpp b/src/tools/imgtool/modules/hp9845_tape.cpp index f0fed452608..bdf3fdf28ca 100644 --- a/src/tools/imgtool/modules/hp9845_tape.cpp +++ b/src/tools/imgtool/modules/hp9845_tape.cpp @@ -112,6 +112,7 @@ #include "imgtool.h" #include "formats/imageutl.h" +#include "formats/hti_tape.h" // Constants #define SECTOR_LEN 256 // Bytes in a sector @@ -131,14 +132,10 @@ #define CHARS_PER_FNAME_EXT (CHARS_PER_FNAME + 1 + CHARS_PER_EXT) // Characters in filename + extension #define PAD_WORD 0xffff // Word value for padding #define FIRST_FILE_SECTOR (FIRST_DIR_SECTOR + SECTORS_PER_DIR * DIR_COPIES) // First file sector -#define MAGIC 0x5441434f // Magic value at start of image file: "TACO" -#define ONE_INCH_POS (968 * 1024) // 1 inch of tape in tape_pos_t units -#define START_POS ((tape_pos_t)(72.25 * ONE_INCH_POS)) // Start position on each track +#define START_POS ((tape_pos_t)(72.25 * hti_format_t::ONE_INCH_POS)) // Start position on each track #define DZ_WORDS 350 // Words in deadzone -#define IRG_SIZE ONE_INCH_POS // Size of inter-record-gap: 1" -#define IFG_SIZE ((tape_pos_t)(2.5 * ONE_INCH_POS)) // Size of inter-file-gap: 2.5" -#define ZERO_BIT_LEN 619 // Length of "0" bits when encoded on tape -#define ONE_BIT_LEN 1083 // Length of "1" bits when encoded on tape +#define IRG_SIZE hti_format_t::ONE_INCH_POS // Size of inter-record-gap: 1" +#define IFG_SIZE ((tape_pos_t)(2.5 * hti_format_t::ONE_INCH_POS)) // Size of inter-file-gap: 2.5" #define HDR_W0_ZERO_MASK 0x4000 // Mask of zero bits in word 0 of header #define RES_FREE_FIELD 0x2000 // Mask of "reserved free field" bit #define FILE_ID_BIT 0x8000 // Mask of "file identifier" bit @@ -151,11 +148,10 @@ #define BYTES_AVAILABLE_MASK 0xff00 // Mask of "bytes available" field #define BYTES_USED 0x00ff // "bytes used" field = 256 #define BYTES_USED_MASK 0x00ff // Mask of "bytes used" field -#define FORMAT_SECT_SIZE ((tape_pos_t)(2.85 * ONE_INCH_POS)) // Size of sectors including padding: 2.85" -#define PAD_WORD_LENGTH (17 * ONE_BIT_LEN) // Size of PAD_WORD on tape +#define FORMAT_SECT_SIZE ((tape_pos_t)(2.85 * hti_format_t::ONE_INCH_POS)) // Size of sectors including padding: 2.85" #define PREAMBLE_WORD 0 // Value of preamble word -#define WORDS_PER_SECTOR_W_MARGIN 256 // Maximum number of words in a sector with a lot of margin (there are actually never more than about 160 words) -#define MIN_IRG_SIZE ((tape_pos_t)(0.066 * ONE_INCH_POS)) // Minimum size of IRG gaps: 0.066" +#define WORDS_PER_HEADER_N_SECTOR (WORDS_PER_SECTOR + 5) +#define MIN_IRG_SIZE ((tape_pos_t)(0.066 * hti_format_t::ONE_INCH_POS)) // Minimum size of IRG gaps: 0.066" // File types #define BKUP_FILETYPE 0 @@ -187,10 +183,10 @@ #define EOLN (CRLF == 1 ? "\r" : (CRLF == 2 ? "\n" : (CRLF == 3 ? "\r\n" : NULL))) // Words stored on tape -typedef uint16_t tape_word_t; +using tape_word_t = hti_format_t::tape_word_t; // Tape position, 1 unit = 1 inch / (968 * 1024) -typedef int32_t tape_pos_t; +using tape_pos_t = hti_format_t::tape_pos_t; /******************************************************************************** * Directory entries @@ -257,10 +253,7 @@ private: static bool filename_char_check(uint8_t c); static bool filename_check(const uint8_t *filename); bool decode_dir(void); - static tape_pos_t word_length(tape_word_t w); - static tape_pos_t block_end_pos(tape_pos_t pos , const tape_word_t *block , unsigned block_len); - static bool save_word(imgtool::stream *stream , tape_pos_t& pos , tape_word_t w); - static bool save_words(imgtool::stream *stream , tape_pos_t& pos , const tape_word_t *block , unsigned block_len); + void save_words(hti_format_t& img , unsigned track , tape_pos_t& pos , const tape_word_t *block , unsigned block_len); static tape_word_t checksum(const tape_word_t *block , unsigned block_len); }; @@ -298,108 +291,130 @@ void tape_image_t::format_img(void) dirty = true; } +static int my_seekproc(void *file, int64_t offset, int whence) +{ + reinterpret_cast<imgtool::stream *>(file)->seek(offset, whence); + return 0; +} + +static size_t my_readproc(void *file, void *buffer, size_t length) +{ + return reinterpret_cast<imgtool::stream *>(file)->read(buffer, length); +} + +static size_t my_writeproc(void *file, const void *buffer, size_t length) +{ + reinterpret_cast<imgtool::stream *>(file)->write(buffer, length); + return length; +} + +static uint64_t my_filesizeproc(void *file) +{ + return reinterpret_cast<imgtool::stream *>(file)->size(); +} + +static const struct io_procs my_stream_procs = { + nullptr, + my_seekproc, + my_readproc, + my_writeproc, + my_filesizeproc +}; + imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream) { - stream->seek(0 , SEEK_SET); + hti_format_t inp_image; - uint8_t tmp[ 4 ]; + io_generic io; + io.file = (void *)stream; + io.procs = &my_stream_procs; + io.filler = 0; - if (stream->read(tmp , 4) != 4) { + if (!inp_image.load_tape(&io)) { return IMGTOOLERR_READERROR; } - if (pick_integer_be(tmp , 0 , 4) != MAGIC) { - return IMGTOOLERR_CORRUPTIMAGE; - } - unsigned exp_sector = 0; - // Loader state: - // 0 Wait for DZ - // 1 Wait for sector data - // 2 Wait for gap - unsigned state; - tape_pos_t end_pos = 0; - for (unsigned track = 0; track < TRACKS_NO; track++) { - state = 0; - - while (1) { - if (stream->read(tmp , 4) != 4) { - return IMGTOOLERR_READERROR; - } - uint32_t words_no = pick_integer_le(tmp , 0 , 4); - if (words_no == (uint32_t)-1) { - // Track ended - break; - } - if (stream->read(tmp , 4) != 4) { - return IMGTOOLERR_READERROR; - } - tape_pos_t pos = pick_integer_le(tmp , 0 , 4); - tape_word_t buffer[ WORDS_PER_SECTOR_W_MARGIN ]; - for (unsigned i = 0; i < words_no; i++) { - if (stream->read(tmp , 2) != 2) { - return IMGTOOLERR_READERROR; - } - if (i < WORDS_PER_SECTOR_W_MARGIN) { - buffer[ i ] = pick_integer_le(tmp , 0 , 2); - } - } + unsigned last_sector_on_track = SECTORS_PER_TRACK; + for (unsigned track = 0; track < TRACKS_NO; track++ , last_sector_on_track += SECTORS_PER_TRACK) { + tape_pos_t pos = 0; + // Loader state: + // 0 Wait for DZ + // 1 Wait for sector data + // 2 Wait for gap + unsigned state = 0; + + while (exp_sector != last_sector_on_track) { switch (state) { case 0: - // Skip DZ - state = 1; + case 1: + { + hti_format_t::track_iterator_t it; + + if (!inp_image.next_data(track , pos , true , false , it)) { + // No more data on tape + return IMGTOOLERR_CORRUPTIMAGE; + } + if (state == 1) { + // Extract record data + if (it->second != PREAMBLE_WORD) { + return IMGTOOLERR_CORRUPTIMAGE; + } + tape_word_t buffer[ WORDS_PER_HEADER_N_SECTOR ]; + for (unsigned i = 0; i < WORDS_PER_HEADER_N_SECTOR; i++) { + auto res = inp_image.adv_it(track , true , it); + if (res != hti_format_t::ADV_CONT_DATA) { + return IMGTOOLERR_CORRUPTIMAGE; + } + buffer[ i ] = it->second; + } + if (buffer[ 3 ] != checksum(&buffer[ 0 ], 3) || + buffer[ 4 + WORDS_PER_SECTOR ] != checksum(&buffer[ 4 ], WORDS_PER_SECTOR)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + // Check record content + if (exp_sector != (buffer[ 1 ] & 0xfff)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if (((buffer[ 0 ] & FILE_ID_BIT) != 0) != (exp_sector == 0)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if ((buffer[ 0 ] & (HDR_W0_ZERO_MASK | RES_FREE_FIELD | SIF_FILE_NO_MASK)) != (RES_FREE_FIELD | SIF_FILE_NO)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if ((buffer[ 1 ] & SIF_FREE_FIELD_MASK) != SIF_FREE_FIELD) { + return IMGTOOLERR_CORRUPTIMAGE; + } + bool in_use = (buffer[ 0 ] & SECTOR_IN_USE) != 0; + if ((buffer[ 2 ] & BYTES_AVAILABLE_MASK) != BYTES_AVAILABLE || + (in_use && (buffer[ 2 ] & BYTES_USED_MASK) != BYTES_USED) || + (!in_use && (buffer[ 2 ] & BYTES_USED_MASK) != 0)) { + return IMGTOOLERR_CORRUPTIMAGE; + } + if (in_use) { + set_sector(exp_sector, &buffer[ 4 ]); + } else { + unset_sector(exp_sector); + } + exp_sector++; + } + pos = it->first; + state = 2; + } break; case 2: - if ((pos - end_pos) < MIN_IRG_SIZE) { - // Gap too short, discard data block - break; - } - // Intentional fall-through - - case 1: - if (words_no < 6 + WORDS_PER_SECTOR || buffer[ 0 ] != PREAMBLE_WORD || - buffer[ 4 ] != checksum(&buffer[ 1 ], 3) || - buffer[ 5 + WORDS_PER_SECTOR ] != checksum(&buffer[ 5 ], WORDS_PER_SECTOR)) { - return IMGTOOLERR_CORRUPTIMAGE; - } - // Check sector content - if (exp_sector != (buffer[ 2 ] & 0xfff)) { - return IMGTOOLERR_CORRUPTIMAGE; - } - if (((buffer[ 1 ] & FILE_ID_BIT) != 0 && exp_sector != 0) || - ((buffer[ 1 ] & FILE_ID_BIT) == 0 && exp_sector == 0)) { - return IMGTOOLERR_CORRUPTIMAGE; - } - if ((buffer[ 1 ] & (HDR_W0_ZERO_MASK | RES_FREE_FIELD | SIF_FILE_NO_MASK)) != (RES_FREE_FIELD | SIF_FILE_NO)) { - return IMGTOOLERR_CORRUPTIMAGE; - } - if ((buffer[ 2 ] & SIF_FREE_FIELD_MASK) != SIF_FREE_FIELD) { + // Find next gap + if (!inp_image.next_gap(track , pos , true , MIN_IRG_SIZE)) { return IMGTOOLERR_CORRUPTIMAGE; } - bool in_use = (buffer[ 1 ] & SECTOR_IN_USE) != 0; - if ((buffer[ 3 ] & BYTES_AVAILABLE_MASK) != BYTES_AVAILABLE || - (in_use && (buffer[ 3 ] & BYTES_USED_MASK) != BYTES_USED) || - (!in_use && (buffer[ 3 ] & BYTES_USED_MASK) != 0)) { - return IMGTOOLERR_CORRUPTIMAGE; - } - if (in_use) { - set_sector(exp_sector, &buffer[ 5 ]); - } else { - unset_sector(exp_sector); - } - exp_sector++; - state = 2; + state = 1; break; + } - end_pos = block_end_pos(pos, &buffer[ 0 ], words_no); } } - if (exp_sector != TOT_SECTORS) { - return IMGTOOLERR_CORRUPTIMAGE; - } - if (!decode_dir()) { return IMGTOOLERR_CORRUPTDIR; } @@ -409,69 +424,17 @@ imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream) return IMGTOOLERR_SUCCESS; } -tape_pos_t tape_image_t::word_length(tape_word_t w) -{ - unsigned zeros , ones; - - // pop count of w - ones = (w & 0x5555) + ((w >> 1) & 0x5555); - ones = (ones & 0x3333) + ((ones >> 2) & 0x3333); - ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f); - ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff); - - zeros = 16 - ones; - - return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN; -} - -tape_pos_t tape_image_t::block_end_pos(tape_pos_t pos , const tape_word_t *block , unsigned block_len) +void tape_image_t::save_words(hti_format_t& img , unsigned track , tape_pos_t& pos , const tape_word_t *block , unsigned block_len) { - while (block_len--) { - pos += word_length(*block++); - } - return pos; -} - -bool tape_image_t::save_word(imgtool::stream *stream , tape_pos_t& pos , tape_word_t w) -{ - uint8_t tmp[ 2 ]; - - place_integer_le(tmp , 0 , 2 , w); - if (stream->write(tmp , 2) != 2) { - return false; - } - - pos += word_length(w); - - return true; -} - -bool tape_image_t::save_words(imgtool::stream *stream , tape_pos_t& pos , const tape_word_t *block , unsigned block_len) -{ - uint8_t tmp[ 4 ]; - - // Number of words (including preamble) - place_integer_le(tmp , 0 , 4 , block_len + 1); - if (stream->write(tmp , 4) != 4) { - return false; - } - // Start position - place_integer_le(tmp , 0 , 4 , pos); - if (stream->write(tmp , 4) != 4) { - return false; - } // Preamble - if (!save_word(stream , pos , PREAMBLE_WORD)) { - return false; - } + tape_pos_t length; + img.write_word(track , pos , PREAMBLE_WORD , length); + pos += length; // Words for (unsigned i = 0; i < block_len; i++) { - if (!save_word(stream, pos, block[ i ])) { - return false; - } + img.write_word(track , pos , *block++ , length); + pos += length; } - - return true; } tape_word_t tape_image_t::checksum(const tape_word_t *block , unsigned block_len) @@ -488,95 +451,80 @@ imgtoolerr_t tape_image_t::save_to_file(imgtool::stream *stream) // Encode copies of directory into sectors encode_dir(); - // Store sectors - stream->seek(0 , SEEK_SET); - - uint8_t tmp[ 4 ]; + // Store sectors into image + hti_format_t out_image; - place_integer_be(tmp , 0 , 4 , MAGIC); - if (stream->write(tmp , 4) != 4) { - return IMGTOOLERR_WRITEERROR; - } - - tape_pos_t pos = START_POS; + unsigned rec_no = 0; + for (unsigned track = 0; track < TRACKS_NO; track++) { + tape_pos_t pos = START_POS; - for (unsigned i = 0; i < TOT_SECTORS; i++) { - if (i == TOT_SECTORS / 2) { - // Track 0 -> 1 - place_integer_le(tmp , 0 , 4 , (uint32_t)-1); - if (stream->write(tmp , 4) != 4) { - return IMGTOOLERR_WRITEERROR; - } + // Start of either track + // Deadzone + 1" of gap + tape_word_t deadzone[ DZ_WORDS ]; + for (auto& dz : deadzone) { + dz = PAD_WORD; } - if (i == 0 || i == TOT_SECTORS / 2) { - // Start of either track - pos = START_POS; - // Deadzone + 1" of gap - tape_word_t deadzone[ DZ_WORDS ]; - for (unsigned j = 0; j < DZ_WORDS; j++) { - deadzone[ j ] = PAD_WORD; + save_words(out_image, track, pos, deadzone, DZ_WORDS); + pos += IRG_SIZE; + + for (unsigned i = 0; i < SECTORS_PER_TRACK; i++ , rec_no++) { + bool in_use = alloc_map[ rec_no ]; + // Sector header + tape_word_t sector[ WORDS_PER_HEADER_N_SECTOR ]; + + // Header word 0: file identifier bit, reserved free-field bit, empty record indicator & file # + sector[ 0 ] = RES_FREE_FIELD | SIF_FILE_NO; + if (rec_no == 0) { + sector[ 0 ] |= FILE_ID_BIT; } - if (!save_words(stream, pos, deadzone, DZ_WORDS)) { - return IMGTOOLERR_WRITEERROR; + if (in_use) { + sector[ 0 ] |= SECTOR_IN_USE; + } + // Header word 1: free-field & sector # + sector[ 1 ] = SIF_FREE_FIELD | rec_no; + // Header word 2: bytes available & bytes used + sector[ 2 ] = BYTES_AVAILABLE; + if (in_use) { + sector[ 2 ] |= BYTES_USED; + } + // Checksum of header + sector[ 3 ] = checksum(§or[ 0 ] , 3); + // Sector payload + if (in_use) { + memcpy(§or[ 4 ] , &img[ rec_no ][ 0 ] , SECTOR_LEN); + } else { + for (unsigned j = 4; j < (4 + WORDS_PER_SECTOR); j++) { + sector[ j ] = PAD_WORD; + } } + // Checksum of payload + sector[ 4 + WORDS_PER_SECTOR ] = checksum(§or[ 4 ] , WORDS_PER_SECTOR); - pos += IRG_SIZE; - } - bool in_use = alloc_map[ i ]; - // Sector header - tape_word_t sector[ WORDS_PER_SECTOR_W_MARGIN ]; - - // Header word 0: file identifier bit, reserved free-field bit, empty record indicator & file # - sector[ 0 ] = RES_FREE_FIELD | SIF_FILE_NO; - if (i == 0) { - sector[ 0 ] |= FILE_ID_BIT; - } - if (in_use) { - sector[ 0 ] |= SECTOR_IN_USE; - } - // Header word 1: free-field & sector # - sector[ 1 ] = SIF_FREE_FIELD | i; - // Header word 2: bytes available & bytes used - sector[ 2 ] = BYTES_AVAILABLE; - if (in_use) { - sector[ 2 ] |= BYTES_USED; - } - // Checksum of header - sector[ 3 ] = checksum(§or[ 0 ] , 3); - // Sector payload - if (in_use) { - memcpy(§or[ 4 ] , &img[ i ][ 0 ] , SECTOR_LEN); - } else { - for (unsigned j = 4; j < (4 + WORDS_PER_SECTOR); j++) { - sector[ j ] = PAD_WORD; + tape_pos_t start_pos = pos; + save_words(out_image, track, pos, sector, WORDS_PER_HEADER_N_SECTOR); + + // Pad sector up to FORMAT_SECT_SIZE + while ((pos - start_pos) < FORMAT_SECT_SIZE) { + tape_pos_t length; + out_image.write_word(track , pos , PAD_WORD , length); + pos += length; + } + + // Gap between sectors + if (rec_no == 0) { + pos += IFG_SIZE; + } else { + pos += IRG_SIZE; } - } - // Checksum of payload - sector[ 4 + WORDS_PER_SECTOR ] = checksum(§or[ 4 ] , WORDS_PER_SECTOR); - // Pad sector up to FORMAT_SECT_SIZE - tape_pos_t sect_size = 0; - for (unsigned j = 0; j < 5 + WORDS_PER_SECTOR; j++) { - sect_size += word_length(sector[ j ]); - } - unsigned padding_words = (FORMAT_SECT_SIZE - sect_size) / PAD_WORD_LENGTH; - for (unsigned j = 5 + WORDS_PER_SECTOR; j < 5 + WORDS_PER_SECTOR + padding_words; j++) { - sector[ j ] = PAD_WORD; - } - if (!save_words(stream, pos, sector, 5 + WORDS_PER_SECTOR + padding_words)) { - return IMGTOOLERR_WRITEERROR; - } - // Gap between sectors - if (i == 0) { - pos += IFG_SIZE; - } else { - pos += IRG_SIZE; } } - place_integer_le(tmp , 0 , 4 , (uint32_t)-1); - if (stream->write(tmp , 4) != 4) { - return IMGTOOLERR_WRITEERROR; - } + io_generic io; + io.file = (void *)stream; + io.procs = &my_stream_procs; + io.filler = 0; + + out_image.save_tape(&io); return IMGTOOLERR_SUCCESS; } @@ -1046,7 +994,7 @@ static imgtoolerr_t hp9845_tape_open(imgtool::image &image, imgtool::stream::ptr { tape_state_t& state = get_tape_state(image); - state.stream = stream.get(); + state.stream = stream.release(); tape_image_t& tape_image = get_tape_image(state); @@ -1054,7 +1002,6 @@ static imgtoolerr_t hp9845_tape_open(imgtool::image &image, imgtool::stream::ptr if (err) return err; - state.stream = stream.release(); return IMGTOOLERR_SUCCESS; } |
