mirror of
https://github.com/holub/mame
synced 2025-06-06 04:43:45 +03:00
Merge pull request #2568 from fulivi/hp80_dev01
Emulation of HP85 tape drive
This commit is contained in:
commit
961169d5e8
@ -905,6 +905,18 @@ if (FORMATS["HECT_TAP"]~=null or _OPTIONS["with-tools"]) then
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
--
|
||||
--@src/lib/formats/hti_tape.h,FORMATS["HTI_TAP"] = true
|
||||
--------------------------------------------------
|
||||
|
||||
if (FORMATS["HTI_TAP"]~=null or _OPTIONS["with-tools"]) then
|
||||
files {
|
||||
MAME_DIR.. "src/lib/formats/hti_tape.cpp",
|
||||
MAME_DIR.. "src/lib/formats/hti_tape.h",
|
||||
}
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
--
|
||||
--@src/lib/formats/hpi_dsk.h,FORMATS["HPI_DSK"] = true
|
||||
|
@ -1010,6 +1010,18 @@ if (MACHINES["HP_TACO"]~=null) then
|
||||
}
|
||||
end
|
||||
|
||||
---------------------------------------------------
|
||||
--
|
||||
--@src/devices/machine/1ma6.h,MACHINES["1MA6"] = true
|
||||
---------------------------------------------------
|
||||
|
||||
if (MACHINES["1MA6"]~=null) then
|
||||
files {
|
||||
MAME_DIR .. "src/devices/machine/1ma6.cpp",
|
||||
MAME_DIR .. "src/devices/machine/1ma6.h",
|
||||
}
|
||||
end
|
||||
|
||||
---------------------------------------------------
|
||||
--
|
||||
--@src/devices/machine/i2cmem.h,MACHINES["I2CMEM"] = true
|
||||
|
@ -415,6 +415,7 @@ MACHINES["F3853"] = true
|
||||
MACHINES["HD63450"] = true
|
||||
MACHINES["HD64610"] = true
|
||||
MACHINES["HP_TACO"] = true
|
||||
MACHINES["1MA6"] = true
|
||||
MACHINES["I2CMEM"] = true
|
||||
MACHINES["I7220"] = true
|
||||
MACHINES["I80130"] = true
|
||||
@ -814,6 +815,7 @@ FORMATS["GTP_CAS"] = true
|
||||
FORMATS["HECTOR_MINIDISC"] = true
|
||||
FORMATS["HECT_DSK"] = true
|
||||
FORMATS["HECT_TAP"] = true
|
||||
FORMATS["HTI_TAP"] = true
|
||||
FORMATS["HPI_DSK"] = true
|
||||
FORMATS["HP_IPC_DSK"] = true
|
||||
FORMATS["IQ151_DSK"] = true
|
||||
|
702
src/devices/machine/1ma6.cpp
Normal file
702
src/devices/machine/1ma6.cpp
Normal file
@ -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;
|
||||
}
|
117
src/devices/machine/1ma6.h
Normal file
117
src/devices/machine/1ma6.h
Normal file
@ -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 */
|
@ -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;
|
||||
m_gap_detect_start = hti_format_t::NULL_TAPE_POS;
|
||||
}
|
||||
|
||||
hp_taco_device::tape_track_t& hp_taco_device::current_track(void)
|
||||
unsigned hp_taco_device::current_track(void)
|
||||
{
|
||||
return m_tracks[ BIT(m_status_reg , STATUS_TRACKB_BIT) ];
|
||||
}
|
||||
|
||||
// Return physical length of a 16-bit word on tape
|
||||
hp_taco_device::tape_pos_t hp_taco_device::word_length(tape_word_t w)
|
||||
{
|
||||
unsigned zeros , ones;
|
||||
|
||||
// pop count of w
|
||||
ones = (w & 0x5555) + ((w >> 1) & 0x5555);
|
||||
ones = (ones & 0x3333) + ((ones >> 2) & 0x3333);
|
||||
ones = (ones & 0x0f0f) + ((ones >> 4) & 0x0f0f);
|
||||
ones = (ones & 0x00ff) + ((ones >> 8) & 0x00ff);
|
||||
|
||||
zeros = 16 - ones;
|
||||
|
||||
return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN;
|
||||
}
|
||||
|
||||
hp_taco_device::tape_pos_t hp_taco_device::word_end_pos(const tape_track_t::iterator& it)
|
||||
{
|
||||
return it->first + word_length(it->second);
|
||||
}
|
||||
|
||||
void hp_taco_device::adjust_it(tape_track_t& track , tape_track_t::iterator& it , tape_pos_t pos)
|
||||
{
|
||||
if (it != track.begin()) {
|
||||
--it;
|
||||
if (word_end_pos(it) <= pos) {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write a word on current tape track
|
||||
void hp_taco_device::write_word(tape_pos_t start , tape_word_t word , tape_pos_t& length)
|
||||
{
|
||||
tape_track_t& track = current_track();
|
||||
tape_track_t::iterator it_low = track.lower_bound(start);
|
||||
adjust_it(track , it_low , start);
|
||||
length = word_length(word);
|
||||
tape_pos_t end_pos = start + length;
|
||||
tape_track_t::iterator it_high = track.lower_bound(end_pos);
|
||||
|
||||
track.erase(it_low , it_high);
|
||||
|
||||
track.insert(it_high , std::make_pair(start, word));
|
||||
LOG_0(("WR %04x @ T%u:%u\n" , word , BIT(m_status_reg , STATUS_TRACKB_BIT) , start));
|
||||
}
|
||||
|
||||
// Write a gap on current track
|
||||
void hp_taco_device::write_gap(tape_pos_t a , tape_pos_t b)
|
||||
{
|
||||
ensure_a_lt_b(a , b);
|
||||
tape_track_t& track = current_track();
|
||||
tape_track_t::iterator it_low = track.lower_bound(a);
|
||||
adjust_it(track , it_low , a);
|
||||
tape_track_t::iterator it_high = track.lower_bound(b);
|
||||
|
||||
track.erase(it_low, it_high);
|
||||
|
||||
LOG_0(("GAP on T%u:[%u,%u)\n" , BIT(m_status_reg , STATUS_TRACKB_BIT) , a , b));
|
||||
}
|
||||
|
||||
bool hp_taco_device::just_gap(tape_pos_t a , tape_pos_t b)
|
||||
{
|
||||
ensure_a_lt_b(a , b);
|
||||
tape_track_t& track = current_track();
|
||||
tape_track_t::iterator it_low = track.lower_bound(a);
|
||||
tape_track_t::iterator it_high = track.lower_bound(b);
|
||||
|
||||
adjust_it(track, it_low, a);
|
||||
|
||||
return it_low == it_high;
|
||||
}
|
||||
|
||||
hp_taco_device::tape_pos_t hp_taco_device::farthest_end(const tape_track_t::iterator& it) const
|
||||
{
|
||||
if (m_tape_fwd) {
|
||||
return word_end_pos(it);
|
||||
} else {
|
||||
return it->first;
|
||||
}
|
||||
}
|
||||
|
||||
bool hp_taco_device::next_data(tape_track_t::iterator& it , tape_pos_t pos , bool inclusive)
|
||||
{
|
||||
tape_track_t& track = current_track();
|
||||
it = track.lower_bound(pos);
|
||||
if (m_tape_fwd) {
|
||||
if (inclusive) {
|
||||
adjust_it(track, it, pos);
|
||||
}
|
||||
return it != track.end();
|
||||
} else {
|
||||
// Never more than 2 iterations
|
||||
do {
|
||||
if (it == track.begin()) {
|
||||
it = track.end();
|
||||
return false;
|
||||
}
|
||||
--it;
|
||||
} while (!inclusive && word_end_pos(it) > pos);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
hp_taco_device::adv_res_t hp_taco_device::adv_it(tape_track_t::iterator& it)
|
||||
{
|
||||
tape_track_t& track = current_track();
|
||||
if (m_tape_fwd) {
|
||||
tape_pos_t prev_pos = word_end_pos(it);
|
||||
++it;
|
||||
if (it == track.end()) {
|
||||
return ADV_NO_MORE_DATA;
|
||||
} else {
|
||||
adv_res_t res = prev_pos == it->first ? ADV_CONT_DATA : ADV_DISCONT_DATA;
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
if (it == track.begin()) {
|
||||
it = track.end();
|
||||
return ADV_NO_MORE_DATA;
|
||||
} else {
|
||||
tape_pos_t prev_pos = it->first;
|
||||
--it;
|
||||
return prev_pos == word_end_pos(it) ? ADV_CONT_DATA : ADV_DISCONT_DATA;
|
||||
}
|
||||
}
|
||||
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();
|
||||
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;
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -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;
|
||||
|
400
src/lib/formats/hti_tape.cpp
Normal file
400
src/lib/formats/hti_tape.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
93
src/lib/formats/hti_tape.h
Normal file
93
src/lib/formats/hti_tape.h
Normal file
@ -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_ */
|
@ -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)
|
||||
|
@ -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,106 +291,128 @@ 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;
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
// Find next gap
|
||||
if (!inp_image.next_gap(track , pos , true , MIN_IRG_SIZE)) {
|
||||
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) {
|
||||
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()) {
|
||||
@ -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)
|
||||
void tape_image_t::save_words(hti_format_t& img , unsigned track , tape_pos_t& pos , const tape_word_t *block , unsigned block_len)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
// Store sectors into image
|
||||
hti_format_t out_image;
|
||||
|
||||
uint8_t tmp[ 4 ];
|
||||
unsigned rec_no = 0;
|
||||
for (unsigned track = 0; track < TRACKS_NO; track++) {
|
||||
tape_pos_t pos = START_POS;
|
||||
|
||||
place_integer_be(tmp , 0 , 4 , MAGIC);
|
||||
if (stream->write(tmp , 4) != 4) {
|
||||
return IMGTOOLERR_WRITEERROR;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user