HP9845: TACO driver re-written (#5601)

* hp9825: fixed a bug in 9825t

* hp9845: TACO driver re-written from scratch, DC100 tape separated into
a new device, various adaptations

* hp9845: "new TACO" renamed to just "TACO"
This commit is contained in:
fulivi 2019-09-09 22:05:41 +02:00 committed by R. Belmont
parent eaae4640aa
commit d4e2fbd306
15 changed files with 2553 additions and 2474 deletions

View File

@ -1190,6 +1190,18 @@ if (MACHINES["HD64610"]~=null) then
}
end
---------------------------------------------------
--
--@src/devices/machine/hp_dc100_tape.h,MACHINES["HP_DC100_TAPE"] = true
---------------------------------------------------
if (MACHINES["HP_DC100_TAPE"]~=null) then
files {
MAME_DIR .. "src/devices/machine/hp_dc100_tape.cpp",
MAME_DIR .. "src/devices/machine/hp_dc100_tape.h",
}
end
---------------------------------------------------
--
--@src/devices/machine/hp_taco.h,MACHINES["HP_TACO"] = true

View File

@ -471,6 +471,7 @@ MACHINES["ER2055"] = true
MACHINES["F3853"] = true
MACHINES["HD63450"] = true
MACHINES["HD64610"] = true
MACHINES["HP_DC100_TAPE"] = true
MACHINES["HP_TACO"] = true
MACHINES["1MA6"] = true
MACHINES["1MB5"] = true

View File

@ -404,9 +404,9 @@ bool hp_hybrid_cpu_device::execute_one_bpc(uint16_t opcode , uint16_t& next_pc)
case 0x4000:
// JSM
m_icount -= 5;
next_pc = remove_mae(get_ea(opcode));
m_reg_R = (m_reg_R + 1) & m_addr_mask_low16;
WM(AEC_CASE_C , m_reg_R , m_reg_P);
next_pc = remove_mae(get_ea(opcode));
return true;
case 0x4800:

View File

@ -29,8 +29,14 @@
#include "1ma6.h"
// Debugging
#define VERBOSE 0
#include "logmacro.h"
#define LOG_DBG_MASK (LOG_GENERAL << 1)
#define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__)
#define LOG_RW_MASK (LOG_DBG_MASK << 1)
#define LOG_RW(...) LOGMASKED(LOG_RW_MASK, __VA_ARGS__)
#undef VERBOSE
//#define VERBOSE (LOG_GENERAL | LOG_DBG_MASK | LOG_RW_MASK)
#define VERBOSE 0
// Device type definition
DEFINE_DEVICE_TYPE(HP_1MA6, hp_1ma6_device, "hp_1ma6", "HP 1MA6")
@ -63,19 +69,14 @@ namespace {
}
// **** Constants ****
constexpr double FAST_SPEED = 60.0; // Fast speed: 60 ips
constexpr double SLOW_SPEED = 10.0; // Slow speed: 10 ips
constexpr double MOVING_THRESHOLD = 2.0; // Tape is moving when speed > 2.0 ips
constexpr double ACCELERATION = 1200.0; // Acceleration when speed set point is changed: 1200 ips^2
// 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;
constexpr hti_format_t::tape_pos_t TACH_TICK_LEN = hti_format_t::ONE_INCH_POS / 32;
// 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;
constexpr hti_format_t::tape_pos_t MIN_GAP_SIZE = TACH_TICK_LEN;
// Bits in control register
enum control_bits : unsigned
@ -110,10 +111,8 @@ enum {
};
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)
: device_t(mconfig , HP_1MA6 , tag , owner , clock)
, m_tape(*this , "drive")
{
clear_state();
}
@ -141,7 +140,7 @@ READ8_MEMBER(hp_1ma6_device::reg_r)
switch (offset) {
case 0:
// Status register
update_tape_pos();
m_tape->update_speed_pos();
if (m_cmd_state == CMD_IDLE) {
BIT_SET(m_status_reg , STS_READY_BIT);
} else if (m_cmd_state == CMD_STOPPING ||
@ -151,24 +150,15 @@ READ8_MEMBER(hp_1ma6_device::reg_r)
}
if ((m_control_reg & (BIT_MASK(CTL_WRITE_SYNC_BIT) |
BIT_MASK(CTL_WRITE_GAP_BIT))) != 0 &&
(!m_cartridge_in || !is_readonly())) {
(m_tape->cart_out_r() || !m_tape->wpr_r())) {
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) {
if (m_cmd_state == CMD_IDLE ||
m_tape->gap_reached(MIN_GAP_SIZE)) {
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;
@ -177,7 +167,7 @@ READ8_MEMBER(hp_1ma6_device::reg_r)
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) {
if (!m_tape->cart_out_r()) {
BIT_SET(m_status_reg , STS_CASSETTE_IN_BIT);
}
break;
@ -191,143 +181,137 @@ READ8_MEMBER(hp_1ma6_device::reg_r)
return res;
}
void hp_1ma6_device::device_start()
WRITE_LINE_MEMBER(hp_1ma6_device::cart_out_w)
{
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);
LOG_DBG("cart_out_w %d\n" , state);
if (state) {
// STS_CASSETTE_IN_BIT is set by reading status register
BIT_CLR(m_status_reg , STS_CASSETTE_IN_BIT);
}
}
void hp_1ma6_device::device_reset()
WRITE_LINE_MEMBER(hp_1ma6_device::hole_w)
{
clear_state();
m_tape_timer->reset();
m_hole_timer->reset();
if (state) {
LOG_DBG("hole_w\n");
BIT_SET(m_status_reg , STS_HOLE_BIT);
}
}
void hp_1ma6_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
WRITE_LINE_MEMBER(hp_1ma6_device::tacho_tick_w)
{
update_tape_pos();
if (state) {
LOG_DBG("tacho_tick_w\n");
BIT_SET(m_status_reg , STS_TACH_BIT);
}
}
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:
WRITE_LINE_MEMBER(hp_1ma6_device::motion_w)
{
if (state) {
LOG_DBG("motion_w @%.6f st=%d\n" , machine().time().as_double() , m_cmd_state);
switch (m_cmd_state) {
case CMD_STOPPING:
if (!m_tape->is_moving()) {
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 , is_moving_fwd());
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 CMD_STARTING:
{
hp_dc100_tape_device::tape_op_t op = hp_dc100_tape_device::OP_IDLE;
switch (m_control_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:
if (m_tape->is_above_threshold()) {
// Start RD
m_cmd_state = CMD_RD_WAIT_SYNC;
op = hp_dc100_tape_device::OP_READ;
}
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):
// Start simple movement
m_cmd_state = CMD_FAST_FWD_REV;
break;
case BIT_MASK(CTL_WRITE_DATA_BIT):
if (m_tape->is_above_threshold()) {
// Start re-writing
m_cmd_state = CMD_WR_WAIT_SYNC;
// Need to achieve sync first (hence RD op)
op = hp_dc100_tape_device::OP_READ;
}
break;
case BIT_MASK(CTL_WRITE_SYNC_BIT):
if (m_tape->is_above_threshold()) {
// Start WR
load_wr_word();
m_cmd_state = CMD_WR_PREAMBLE;
op = hp_dc100_tape_device::OP_WRITE;
}
break;
case BIT_MASK(CTL_WRITE_SYNC_BIT) | BIT_MASK(CTL_WRITE_GAP_BIT):
if (m_tape->is_above_threshold()) {
// Start erasing (gap writing)
m_cmd_state = CMD_WR_GAP;
op = hp_dc100_tape_device::OP_ERASE;
}
break;
default:
break;
}
m_tape->set_op(op);
}
break;
default:
break;
}
}
}
WRITE_LINE_MEMBER(hp_1ma6_device::rd_bit_w)
{
LOG_RW("RD bit %d (st=%d,sr=%02x,i=%u)\n" , state , m_cmd_state , m_data_sr , m_bit_idx);
switch (m_cmd_state) {
case CMD_RD_WAIT_SYNC:
if (state) {
// Got sync (bit is 1)
LOG_RW("RD synced\n");
m_cmd_state = CMD_RD;
m_bit_idx = 7;
m_data_sr = 0;
}
break;
case HOLE_TMR_ID:
BIT_SET(m_status_reg , STS_HOLE_BIT);
m_hole_timer->adjust(time_to_next_hole());
case CMD_RD:
if (state) {
BIT_SET(m_data_sr , m_bit_idx);
}
if (m_bit_idx) {
m_bit_idx--;
} else {
LOG_RW("RD byte %02x\n" , m_data_sr);
m_data_reg = m_data_sr;
m_bit_idx = 7;
m_data_sr = 0;
BIT_SET(m_status_reg , STS_READY_BIT);
}
break;
case CMD_WR_WAIT_SYNC:
m_cmd_state = CMD_WR_PREAMBLE;
load_wr_word();
m_tape->set_op(hp_dc100_tape_device::OP_WRITE);
break;
default:
@ -335,74 +319,50 @@ void hp_1ma6_device::device_timer(emu_timer &timer, device_timer_id id, int para
}
}
image_init_result hp_1ma6_device::call_load()
READ_LINE_MEMBER(hp_1ma6_device::wr_bit_r)
{
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) ? '>' : ' ';
bool bit = m_cmd_state == CMD_WR_PREAMBLE ? false : BIT(m_data_sr , m_bit_idx);
if (m_bit_idx) {
m_bit_idx--;
} else {
m1 = '<';
m2 = BIT(m_control_reg , CTL_FAST_BIT) ? '<' : ' ';
if (m_cmd_state == CMD_WR) {
load_wr_word();
}
m_cmd_state = CMD_WR;
m_bit_idx = 7;
}
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;
LOG_RW("WR bit %d (sr=%02x,i=%u)\n" , bit , m_data_sr , m_bit_idx);
return bit;
}
const char *hp_1ma6_device::file_extensions() const
void hp_1ma6_device::device_add_mconfig(machine_config &config)
{
return "hti";
HP_DC100_TAPE(config , m_tape , 0);
m_tape->set_acceleration(ACCELERATION);
m_tape->set_set_points(SLOW_SPEED , FAST_SPEED);
m_tape->set_tick_size(TACH_TICK_LEN);
m_tape->set_bits_per_word(16);
m_tape->set_go_threshold(MOVING_THRESHOLD);
m_tape->cart_out().set(FUNC(hp_1ma6_device::cart_out_w));
m_tape->hole().set(FUNC(hp_1ma6_device::hole_w));
m_tape->tacho_tick().set(FUNC(hp_1ma6_device::tacho_tick_w));
m_tape->motion_event().set(FUNC(hp_1ma6_device::motion_w));
m_tape->rd_bit().set(FUNC(hp_1ma6_device::rd_bit_w));
m_tape->wr_bit().set(FUNC(hp_1ma6_device::wr_bit_r));
}
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_bit_idx));
save_item(NAME(m_data_sr));
}
void hp_1ma6_device::device_reset()
{
clear_state();
}
void hp_1ma6_device::clear_state()
@ -410,300 +370,40 @@ 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_bit_idx = 0;
m_data_sr = 0;
m_cmd_state = CMD_IDLE;
set_cartridge_in(false);
set_cartridge_in(is_loaded());
}
unsigned hp_1ma6_device::current_track() const
void hp_1ma6_device::load_wr_word()
{
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);
m_bit_idx = 7;
m_data_sr = m_data_reg;
LOG_RW("WR byte %02x\n" , m_data_sr);
BIT_SET(m_status_reg , STS_READY_BIT);
}
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;
if (!BIT(new_ctl_reg , CTL_POWER_UP_BIT) ||
!BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) {
m_cmd_state = CMD_STOPPING;
} else {
m_cmd_state = CMD_STARTING;
}
m_tape_timer->adjust(cmd_duration);
}
m_tape->set_op(hp_dc100_tape_device::OP_IDLE);
m_tape->set_track_no(BIT(m_control_reg , CTL_TRACK_NO_BIT));
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;
hp_dc100_tape_device::tape_speed_t new_speed = hp_dc100_tape_device::SP_STOP;
if (BIT(new_ctl_reg , CTL_POWER_UP_BIT) &&
BIT(new_ctl_reg , CTL_MOTOR_ON_BIT)) {
new_speed = BIT(new_ctl_reg , CTL_FAST_BIT) ? hp_dc100_tape_device::SP_FAST :
hp_dc100_tape_device::SP_SLOW;
}
m_image_dirty = false;
set_cartridge_in(true);
return image_init_result::PASS;
m_tape->set_speed_setpoint(new_speed , BIT(new_ctl_reg , CTL_DIR_FWD_BIT));
motion_w(1);
}

View File

@ -14,9 +14,9 @@
#pragma once
#include "formats/hti_tape.h"
#include "machine/hp_dc100_tape.h"
class hp_1ma6_device : public device_t ,
public device_image_interface
class hp_1ma6_device : public device_t
{
public:
// construction/destruction
@ -26,89 +26,49 @@ public:
DECLARE_WRITE8_MEMBER(reg_w);
DECLARE_READ8_MEMBER(reg_r);
DECLARE_WRITE_LINE_MEMBER(cart_out_w);
DECLARE_WRITE_LINE_MEMBER(hole_w);
DECLARE_WRITE_LINE_MEMBER(tacho_tick_w);
DECLARE_WRITE_LINE_MEMBER(motion_w);
DECLARE_WRITE_LINE_MEMBER(rd_bit_w);
DECLARE_READ_LINE_MEMBER(wr_bit_r);
protected:
// device-level overrides
virtual void device_add_mconfig(machine_config &config) override;
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:
required_device<hp_dc100_tape_device> m_tape;
// 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;
unsigned m_bit_idx;
uint8_t m_data_sr;
// Command FSM state
typedef enum {
CMD_IDLE,
CMD_STOPPING,
CMD_STARTING,
CMD_RD_WAIT_SYNC,
CMD_RD_MSB,
CMD_RD_LSB,
CMD_WR_SYNC,
CMD_WR_MSB,
CMD_WR_LSB,
CMD_RD,
CMD_WR_WAIT_SYNC,
CMD_WR_PREAMBLE,
CMD_WR,
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 load_wr_word();
void start_cmd_exec(uint8_t new_ctl_reg);
image_init_result internal_load(bool is_create);
};
// device type definition

View File

@ -0,0 +1,914 @@
// license:BSD-3-Clause
// copyright-holders:F. Ulivi
/*********************************************************************
hp_dc100_tape.cpp
HP DC100 tape cartridge & drive
This driver emulates the DC100 tape drive as used by HP9825, 9845
and 85 systems.
Main features:
- Handles HTI tape images
- Supports 3 speed setpoints (stop, slow & fast) and forward/reverse
tape direction
- Applies a constant acceleration whenever a change of speed setpoint
and/or direction occurs
- Reports through a callback "motion" events such as the reaching/leaving
of setpoint, the crossing of speed thresholds, etc.
- Simulates tachometer ticks
- Handles 3 different tape operations: reading, writing and erasing.
- Simulates the serial reading and writing of bits from/to tape
and their correct timing
- Reports various tape conditions as the presence of cartridge,
of tape holes, of the write-protection switch etc.
- Checks for gaps (erased zones) of arbitrary length
*********************************************************************/
#include "emu.h"
#include "hp_dc100_tape.h"
// Debugging
#include "logmacro.h"
#define LOG_TMR_MASK (LOG_GENERAL << 1)
#define LOG_TMR(...) LOGMASKED(LOG_TMR_MASK, __VA_ARGS__)
#define LOG_DBG_MASK (LOG_TMR_MASK << 1)
#define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__)
#define LOG_RW_MASK (LOG_DBG_MASK << 1)
#define LOG_RW(...) LOGMASKED(LOG_RW_MASK, __VA_ARGS__)
#undef VERBOSE
//#define VERBOSE (LOG_GENERAL | LOG_TMR_MASK | LOG_DBG_MASK | LOG_RW_MASK)
#define VERBOSE (LOG_GENERAL)
// Bit manipulation
namespace {
template<typename T> constexpr T BIT_MASK(unsigned n)
{
return (T)1U << n;
}
template<typename T> void BIT_CLR(T& w , unsigned n)
{
w &= ~BIT_MASK<T>(n);
}
template<typename T> void BIT_SET(T& w , unsigned n)
{
w |= BIT_MASK<T>(n);
}
}
// Device type definition
DEFINE_DEVICE_TYPE(HP_DC100_TAPE, hp_dc100_tape_device, "hp_dc100_tape", "HP DC100 tape drive")
// Timers
enum {
BIT_TMR_ID,
TACHO_TMR_ID,
HOLE_TMR_ID,
MOTION_TMR_ID
};
// Constants
constexpr double MOTION_MARGIN = 1e-5; // Margin to ensure motion events have passed when timer expires (10 µs)
constexpr hti_format_t::tape_pos_t TAPE_INIT_POS = 80 * hti_format_t::ONE_INCH_POS; // Initial tape position: 80" from beginning (just past the punched part)
hp_dc100_tape_device::hp_dc100_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig , HP_DC100_TAPE , tag , owner , clock)
, device_image_interface(mconfig , *this)
, m_cart_out_handler(*this)
, m_hole_handler(*this)
, m_tacho_tick_handler(*this)
, m_motion_handler(*this)
, m_rd_bit_handler(*this)
, m_wr_bit_handler(*this)
, m_image()
, m_image_dirty(false)
{
}
image_init_result hp_dc100_tape_device::call_load()
{
return internal_load(false);
}
image_init_result hp_dc100_tape_device::call_create(int format_type, util::option_resolution *format_options)
{
return internal_load(true);
}
void hp_dc100_tape_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_tape_present(false);
}
std::string hp_dc100_tape_device::call_display()
{
std::string buffer;
// Mostly lifted from cassette_image_device::call_display ;)
// Do not show anything if image not loaded or tape not moving
if (cart_out_r() || !has_started()) {
return buffer;
}
char track = m_track ? 'B' : 'A';
char r_w = m_current_op == OP_WRITE || m_current_op == OP_ERASE ? 'W' : 'R';
char m1;
char m2;
if (is_moving_fwd()) {
m1 = '>';
m2 = m_tape_speed == SP_FAST ? '>' : ' ';
} else {
m1 = '<';
m2 = m_tape_speed == SP_FAST ? '<' : ' ';
}
int pos_in = get_approx_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_dc100_tape_device::file_extensions() const
{
return "hti";
}
void hp_dc100_tape_device::set_acceleration(double accel)
{
m_acceleration = accel;
}
void hp_dc100_tape_device::set_set_points(double slow_sp , double fast_sp)
{
m_slow_set_point = slow_sp;
m_fast_set_point = fast_sp;
}
void hp_dc100_tape_device::set_tick_size(hti_format_t::tape_pos_t size)
{
m_tick_size = size;
}
void hp_dc100_tape_device::set_bits_per_word(unsigned bits)
{
m_image.set_bits_per_word(bits);
}
void hp_dc100_tape_device::set_go_threshold(double threshold)
{
m_go_threshold = threshold;
}
void hp_dc100_tape_device::set_track_no(unsigned track)
{
if (m_track != track) {
LOG_DBG("Setting track %u (op=%d)\n" , track , static_cast<int>(m_current_op));
auto saved_op = m_current_op;
if (m_current_op != OP_IDLE) {
// Close current op on old track
set_op(OP_IDLE);
}
m_track = track;
if (saved_op != OP_IDLE) {
// Resume op on new track
set_op(saved_op);
}
}
}
bool hp_dc100_tape_device::set_speed_setpoint(tape_speed_t speed , bool fwd)
{
if (!m_present) {
return false;
}
double new_setpoint = compute_set_point(speed, fwd);
if (m_set_point != new_setpoint) {
update_speed_pos();
LOG_DBG("Speed SP changed %f->%f %.6f p=%d\n" , m_set_point , new_setpoint , machine().time().as_double() , m_tape_pos);
m_tape_speed = speed;
m_set_point = new_setpoint;
// Speed set point changed, accelerate/decelerate
m_accelerating = true;
if (m_start_time.is_never()) {
// Tape starting now
start_tape();
// When tape starts m_current_op is always OP_IDLE
m_gap_detect_start = m_tape_pos;
}
adjust_tacho_timer();
adjust_hole_timer();
set_motion_timer();
return true;
} else {
return false;
}
}
void hp_dc100_tape_device::set_op(tape_op_t op , bool force)
{
if (!m_present || m_start_time.is_never()) {
return;
}
if (!m_in_set_op && (op != m_current_op || force)) {
LOG_DBG("Op %d->%d (f=%d)\n" , m_current_op , op , force);
m_in_set_op = true;
update_speed_pos();
auto prev_op = m_current_op;
// Close current operating state
stop_op();
m_in_set_op = false;
m_current_op = op;
switch (op) {
case OP_IDLE:
m_gap_detect_start = m_tape_pos;
break;
case OP_READ:
if (prev_op == OP_WRITE) {
LOG("Starting RD after WR?\n");
}
if (fabs(m_speed) < m_slow_set_point) {
LOG("Starting RD at %f speed?\n" , m_speed);
}
m_rd_it_valid = m_image.next_data(get_track_no() , m_tape_pos , is_moving_fwd() , false , m_rd_it);
load_rd_word();
m_gap_detect_start = m_tape_pos;
break;
case OP_WRITE:
if (m_accelerating || fabs(m_speed) != m_slow_set_point) {
LOG("Starting WR at %f speed (acc=%d)?\n" , m_speed , m_accelerating);
}
if (prev_op == OP_READ) {
// Switching from RD to WR
// Clear out the part of m_rw_word that is going to be written
LOG_RW("Switch RD->WR @%d, idx=%d, w=%04x\n" , m_rw_pos , m_bit_idx , m_rw_word);
if (is_moving_fwd()) {
if (--m_bit_idx >= 0) {
m_rw_word &= 0xffffU << (m_bit_idx + 1);
} else {
m_bit_idx = 15;
m_rw_pos = m_next_bit_pos;
m_rw_word = 0;
}
} else {
if (++m_bit_idx < 16) {
m_rw_word &= 0xffffU >> (16 - m_bit_idx);
} else {
m_bit_idx = 0;
m_rw_pos = m_next_bit_pos;
m_rw_word = 0;
}
}
time_to_distance(m_next_bit_pos - m_tape_pos, m_next_bit_pos, m_bit_timer);
} else {
m_rw_word = 0;
if (is_moving_fwd()) {
m_bit_idx = 15;
} else {
m_bit_idx = 0;
}
m_next_bit_pos = m_rw_pos = m_tape_pos;
m_bit_timer->adjust(attotime::zero);
}
break;
case OP_ERASE:
LOG_DBG("Start GAP @%d\n" , m_tape_pos);
m_rw_pos = m_tape_pos;
break;
default:
LOG("Invalid op!\n");
break;
}
}
}
void hp_dc100_tape_device::update_speed_pos()
{
if (m_start_time.is_never()) {
// Tape stopped
return;
}
attotime delta_time{machine().time() - m_start_time};
if (delta_time.is_zero()) {
return;
}
m_start_time = machine().time();
double delta_time_double = delta_time.as_double();
// Space in const A phase
double space_const_a;
// Time in const V phase
double time_const_v;
// Report a motion event
bool motion_event = false;
// Direction inverted
bool inverted = false;
if (m_accelerating) {
auto prev_speed = m_speed;
double time_to_const_v = fabs(m_set_point - m_speed) / m_acceleration;
double acceleration = m_set_point > m_speed ? m_acceleration : -m_acceleration;
bool retrigger_motion = false;
if (delta_time_double < time_to_const_v) {
space_const_a = const_a_space(acceleration , delta_time_double);
m_speed += delta_time_double * acceleration;
time_const_v = 0.0;
} else {
space_const_a = const_a_space(acceleration , time_to_const_v);
time_const_v = delta_time_double - time_to_const_v;
LOG_DBG("Acceleration ends\n");
m_accelerating = false;
m_speed = m_set_point;
motion_event = true;
retrigger_motion = true;
if (m_speed == 0.0) {
// Tape stops
stop_tape();
}
}
if ((fabs(prev_speed) - m_slow_set_point) * (fabs(m_speed) - m_slow_set_point) <= 0.0 ||
(fabs(prev_speed) - m_go_threshold) * (fabs(m_speed) - m_go_threshold) < 0.0) {
// Slow speed threshold crossed
// In-motion threshold crossed
LOG_DBG("Thr crossed %f->%f\n" , prev_speed , m_speed);
motion_event = true;
retrigger_motion = true;
}
if (prev_speed * m_speed < 0.0) {
// Direction inverted (speed sign flipped)
LOG_DBG("Dir inverted s=%f\n" , m_speed);
inverted = true;
retrigger_motion = true;
}
if (retrigger_motion) {
set_motion_timer();
}
} else {
space_const_a = 0.0;
time_const_v = delta_time_double;
}
hti_format_t::tape_pos_t delta_pos = (hti_format_t::tape_pos_t)((space_const_a + m_speed * time_const_v) * hti_format_t::ONE_INCH_POS);
LOG_DBG("dp=%d\n" , delta_pos);
if (!hti_format_t::pos_offset(m_tape_pos , true , delta_pos)) {
LOG("Tape unspooled!\n");
}
if (inverted) {
adjust_tacho_timer();
adjust_hole_timer();
if (m_gap_detect_start != hti_format_t::NULL_TAPE_POS) {
m_gap_detect_start = m_tape_pos;
}
}
if (motion_event) {
// Must be done here or pos & speed won't be correct
m_motion_handler(1);
}
}
hti_format_t::tape_pos_t hp_dc100_tape_device::get_approx_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_pos = (hti_format_t::tape_pos_t)(delta_time.as_double() * m_speed * hti_format_t::ONE_INCH_POS);
auto tape_pos = m_tape_pos;
hti_format_t::pos_offset(tape_pos , true , delta_pos);
return tape_pos;
}
bool hp_dc100_tape_device::gap_reached(hti_format_t::tape_pos_t min_gap_size)
{
update_speed_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 , is_moving_fwd() , -min_gap_size);
if (m_image.just_gap(get_track_no() , tmp , m_tape_pos)) {
return true;
}
}
return false;
}
void hp_dc100_tape_device::time_to_next_gap(hti_format_t::tape_pos_t min_gap_size , bool new_gap , emu_timer *target_timer)
{
update_speed_pos();
bool fwd = is_moving_fwd();
hti_format_t::tape_pos_t tmp = m_tape_pos;
bool found = true;
if (new_gap) {
hti_format_t::track_iterator_t it;
found = m_image.next_data(get_track_no() , tmp , fwd , true , it);
if (found) {
tmp = hti_format_t::farthest_end(it , !fwd);
}
}
if (found && m_image.next_gap(get_track_no() , tmp , fwd , min_gap_size)) {
hti_format_t::tape_pos_t dummy;
LOG_DBG("TTNG T%u S%d N%d %d->%d\n" , get_track_no() , min_gap_size , new_gap , m_tape_pos , tmp);
time_to_distance(tmp - m_tape_pos, dummy, target_timer);
} else {
LOG_DBG("TTNG T%u S%d N%d %d->X\n" , get_track_no() , min_gap_size , new_gap , m_tape_pos);
target_timer->reset();
}
}
void hp_dc100_tape_device::device_start()
{
m_cart_out_handler.resolve_safe();
m_hole_handler.resolve_safe();
m_tacho_tick_handler.resolve_safe();
m_motion_handler.resolve_safe();
m_rd_bit_handler.resolve_safe();
m_wr_bit_handler.resolve_safe(0);
save_item(NAME(m_acceleration));
save_item(NAME(m_slow_set_point));
save_item(NAME(m_fast_set_point));
save_item(NAME(m_tick_size));
save_item(NAME(m_go_threshold));
save_item(NAME(m_tape_pos));
save_item(NAME(m_set_point));
save_item(NAME(m_speed));
save_item(NAME(m_start_time));
save_item(NAME(m_accelerating));
save_item(NAME(m_track));
save_item(NAME(m_in_set_op));
save_item(NAME(m_present));
save_item(NAME(m_rd_it_valid));
save_item(NAME(m_rw_word));
save_item(NAME(m_bit_idx));
save_item(NAME(m_rw_pos));
save_item(NAME(m_gap_detect_start));
save_item(NAME(m_next_bit_pos));
save_item(NAME(m_next_tacho_pos));
save_item(NAME(m_next_hole_pos));
save_item(NAME(m_image_dirty));
m_bit_timer = timer_alloc(BIT_TMR_ID);
m_tacho_timer = timer_alloc(TACHO_TMR_ID);
m_hole_timer = timer_alloc(HOLE_TMR_ID);
m_motion_timer = timer_alloc(MOTION_TMR_ID);
}
void hp_dc100_tape_device::device_reset()
{
clear_state();
}
void hp_dc100_tape_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
LOG_TMR("%.6f TMR %d p=%d s=%.3f(%.3f) a=%d\n" , machine().time().as_double() , id , m_tape_pos , m_speed , m_set_point , m_accelerating);
update_speed_pos();
switch (id) {
case BIT_TMR_ID:
m_tape_pos = m_next_bit_pos;
if (m_current_op == OP_READ) {
bool bit = BIT(m_rd_it->second , m_bit_idx);
m_rd_bit_handler(bit);
if (is_moving_fwd()) {
if (--m_bit_idx >= 0) {
time_to_distance(m_image.bit_length(BIT(m_rd_it->second , m_bit_idx)), m_next_bit_pos, m_bit_timer);
} else {
m_rd_it_valid = m_image.adv_it(get_track_no() , true , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA;
load_rd_word();
}
} else {
if (++m_bit_idx < 16) {
time_to_distance(-m_image.bit_length(BIT(m_rd_it->second , m_bit_idx)), m_next_bit_pos, m_bit_timer);
} else {
m_rd_it_valid = m_image.adv_it(get_track_no() , false , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA;
load_rd_word();
}
}
} else if (m_current_op == OP_WRITE) {
bool bit = m_wr_bit_handler();
hti_format_t::tape_pos_t bit_len = m_image.bit_length(bit);
if (bit) {
BIT_SET(m_rw_word , m_bit_idx);
}
if (is_moving_fwd()) {
if (--m_bit_idx < 0) {
store_wr_word();
}
} else {
if (++m_bit_idx >= 16) {
store_wr_word();
}
bit_len = -bit_len;
}
time_to_distance(bit_len, m_next_bit_pos, m_bit_timer);
}
break;
case TACHO_TMR_ID:
m_tape_pos = m_next_tacho_pos;
m_tacho_tick_handler(1);
adjust_tacho_timer();
break;
case HOLE_TMR_ID:
m_tape_pos = m_next_hole_pos;
m_hole_handler(1);
adjust_hole_timer();
break;
case MOTION_TMR_ID:
// In itself it does nothing (all work is in update_speed_pos)
break;
default:
break;
}
}
void hp_dc100_tape_device::clear_state()
{
m_tape_pos = TAPE_INIT_POS;
m_tape_speed = SP_STOP;
m_set_point = 0.0;
m_speed = 0.0;
m_start_time = attotime::never;
m_accelerating = false;
m_track = 0;
m_current_op = OP_IDLE;
m_in_set_op = false;
m_present = true;
m_rd_it_valid = false;
m_rw_word = 0;
m_bit_idx = 0;
m_rw_pos = hti_format_t::NULL_TAPE_POS;
m_gap_detect_start = hti_format_t::NULL_TAPE_POS;
m_bit_timer->reset();
m_tacho_timer->reset();
m_hole_timer->reset();
m_motion_timer->reset();
set_tape_present(false);
set_tape_present(is_loaded());
}
image_init_result hp_dc100_tape_device::internal_load(bool is_create)
{
LOG("load %d\n" , 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)) {
LOG("load failed\n");
seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format");
set_tape_present(false);
return image_init_result::FAIL;
}
LOG("load OK\n");
m_image_dirty = false;
set_tape_present(true);
return image_init_result::PASS;
}
void hp_dc100_tape_device::set_tape_present(bool present)
{
if (m_present != present) {
m_present = present;
m_cart_out_handler(!m_present);
}
}
double hp_dc100_tape_device::compute_set_point(tape_speed_t speed , bool fwd) const
{
double sp;
if (speed == SP_SLOW) {
sp = m_slow_set_point;
} else if (speed == SP_FAST) {
sp = m_fast_set_point;
} else {
sp = 0.0;
}
if (!fwd) {
sp = -sp;
}
return sp;
}
void hp_dc100_tape_device::start_tape()
{
LOG_DBG("Tape started %.6f p=%d\n" , machine().time().as_double() , m_tape_pos);
m_start_time = machine().time();
m_accelerating = true;
m_speed = 0;
}
void hp_dc100_tape_device::stop_tape()
{
LOG_DBG("Tape stops %.6f p=%d\n" , machine().time().as_double() , m_tape_pos);
m_start_time = attotime::never;
m_accelerating = false;
m_speed = 0;
m_tacho_timer->reset();
m_hole_timer->reset();
m_motion_timer->reset();
stop_op();
}
double hp_dc100_tape_device::const_a_space(double a , double t) const
{
// Space traveled in time 't' at constant acceleration 'a' starting with 'm_speed' speed
return t * (m_speed + a / 2 * t);
}
attotime hp_dc100_tape_device::time_to_threshold(double threshold , bool zero_allowed) const
{
attotime time{ attotime::never };
auto delta_sp = m_set_point - m_speed;
auto delta_t = threshold - m_speed;
LOG_DBG("Dsp=%.6f D+th=%.6f\n" , delta_sp , delta_t);
if ((delta_sp * delta_t > 0.0 && fabs(delta_t) <= fabs(delta_sp)) ||
(zero_allowed && delta_t == 0.0)) {
time = attotime::from_double(fabs(delta_t) / m_acceleration);
LOG_DBG("Time to +th: %.6f\n" , time.as_double());
}
delta_t = -threshold - m_speed;
LOG_DBG("Dsp=%.6f D-th=%.6f\n" , delta_sp , delta_t);
if ((delta_sp * delta_t > 0.0 && fabs(delta_t) <= fabs(delta_sp)) ||
(zero_allowed && delta_t == 0.0)) {
double tm = fabs(delta_t) / m_acceleration;
if (tm < time.as_double()) {
time = attotime::from_double(tm);
LOG_DBG("Time to -th: %.6f\n" , time.as_double());
}
}
return time;
}
void hp_dc100_tape_device::set_motion_timer()
{
if (!m_accelerating) {
m_motion_timer->reset();
} else {
// There are 4 possible future "motion" events:
// 1. Slow speed threshold is crossed
// 2. The "in motion" threshold is crossed
// 3. Tape direction reverses
// 4. Set point is reached
// Motion timer is set to expire at the event that occurs first
attotime time{ time_to_threshold(m_slow_set_point , true) };
attotime tmp{ time_to_threshold(m_go_threshold , false) };
if (tmp < time) {
time = tmp;
}
// Time to the moment when tape inverts its motion
// (i.e. when m_speed crosses 0)
tmp = time_to_threshold(0.0 , false);
if (tmp < time) {
time = tmp;
}
// Time to reach set point
tmp = attotime::from_double(fabs(m_speed - m_set_point) / m_acceleration);
if (tmp < time) {
time = tmp;
}
if (time.is_never()) {
// Should never get here
m_motion_timer->reset();
} else {
// Add margin
time += attotime::from_double(MOTION_MARGIN);
m_motion_timer->adjust(time);
}
}
}
void hp_dc100_tape_device::time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const
{
if (m_start_time.is_never()) {
// If tape is stopped we'll never get there..
target_timer->reset();
return;
}
target_pos = m_tape_pos;
if (!hti_format_t::pos_offset(target_pos , true , distance)) {
// Beyond end of tape
target_timer->reset();
return;
}
double space = double(distance) / hti_format_t::ONE_INCH_POS;
double time_const_a;
if (m_accelerating) {
// Time to reach constant V phase
double time_to_const_v = fabs(m_set_point - m_speed) / m_acceleration;
// Signed acceleration
double acceleration = m_set_point > m_speed ? m_acceleration : -m_acceleration;
// Compute time to cover distance with constant acceleration
// It's the smallest non-negative root of this quadratic equation:
// 1/2*acceleration*t^2+m_speed*t=space
double delta = m_speed * m_speed + 2 * acceleration * space;
bool has_root = delta >= 0.0;
double time_in_const_a = 0.0;
if (has_root) {
double time_in_const_a_pos = (sqrt(delta) - m_speed) / acceleration;
double time_in_const_a_neg = -(sqrt(delta) + m_speed) / acceleration;
LOG_DBG("TTD %.6f %.6f\n" , time_in_const_a_pos , time_in_const_a_neg);
if (time_in_const_a_pos >= 0.0) {
if (time_in_const_a_neg >= 0.0) {
// pos + neg +
time_in_const_a = std::min(time_in_const_a_pos , time_in_const_a_neg);
} else {
// pos + neg -
time_in_const_a = time_in_const_a_pos;
}
} else {
if (time_in_const_a_neg >= 0.0) {
// pos - neg +
time_in_const_a = time_in_const_a_neg;
} else {
// pos - neg -
has_root = false;
}
}
}
LOG_DBG("TTD %d %d %.6f %.6f %.6f\n" , distance , has_root , m_speed , time_to_const_v , time_in_const_a);
if (has_root && time_in_const_a <= time_to_const_v) {
// Entirely in the constant A phase
time_const_a = time_in_const_a;
space = 0.0;
} else {
// Partly in const A & partly in const V
double space_in_const_a = const_a_space(acceleration , time_to_const_v);
space -= space_in_const_a;
time_const_a = time_to_const_v;
}
} else {
// Entirely in const V
time_const_a = 0.0;
}
// Time in constant V
double time_const_v;
if (space != 0.0) {
if (m_set_point == 0.0) {
target_timer->reset();
return;
} else {
time_const_v = space / m_set_point;
if (time_const_v < 0.0) {
target_timer->reset();
return;
}
}
} else {
time_const_v = 0.0;
}
LOG_DBG("TTD %.6f %.6f\n" , time_const_a , time_const_v);
target_timer->adjust(attotime::from_double(time_const_a + time_const_v));
}
void hp_dc100_tape_device::adjust_tacho_timer()
{
auto tick_fract = m_tape_pos % m_tick_size;
hti_format_t::tape_pos_t dist_to_next;
if (is_moving_fwd()) {
// Distance to next tick in the fwd direction
dist_to_next = m_tick_size - tick_fract;
} else if (tick_fract) {
// Distance to next tick in the rev direction when we're not exactly
// on a tick (tick_fract != 0)
dist_to_next = -tick_fract;
} else {
// Distance to next tick in the rev direction when we're exactly
// on a tick (tick_fract == 0)
dist_to_next = -m_tick_size;
}
LOG_DBG("Next tick @%d (pos=%d)\n" , dist_to_next , m_tape_pos);
time_to_distance(dist_to_next, m_next_tacho_pos, m_tacho_timer);
}
void hp_dc100_tape_device::adjust_hole_timer()
{
auto hole_pos = m_image.next_hole(m_tape_pos , is_moving_fwd());
if (hole_pos == hti_format_t::NULL_TAPE_POS) {
m_hole_timer->reset();
} else {
time_to_distance(hole_pos - m_tape_pos, m_next_hole_pos, m_hole_timer);
}
}
void hp_dc100_tape_device::stop_op()
{
if (m_current_op == OP_WRITE) {
store_wr_word();
} else if (m_current_op == OP_ERASE) {
LOG_DBG("Wr gap from %d to %d\n" , m_rw_pos , m_tape_pos);
m_image.write_gap(get_track_no() , m_rw_pos , m_tape_pos);
m_image_dirty = true;
}
m_bit_timer->reset();
m_current_op = OP_IDLE;
m_gap_detect_start = hti_format_t::NULL_TAPE_POS;
}
void hp_dc100_tape_device::load_rd_word()
{
if (m_rd_it_valid) {
bool fwd = is_moving_fwd();
if (fwd) {
m_bit_idx = 15;
} else {
m_bit_idx = 0;
}
// This is actually the nearest end (dir is inverted)
m_rw_pos = m_next_bit_pos = hti_format_t::farthest_end(m_rd_it , !fwd);
// Compute end of bit cell
hti_format_t::tape_pos_t bit_len = m_image.bit_length(BIT(m_rd_it->second , m_bit_idx));
if (!fwd) {
bit_len = -bit_len;
}
time_to_distance(m_next_bit_pos + bit_len - m_tape_pos , m_next_bit_pos , m_bit_timer);
LOG_RW("RD %04x @%d\n" , m_rd_it->second , m_next_bit_pos);
} else {
LOG_RW("End of RD data @%d\n" , m_tape_pos);
stop_op();
m_gap_detect_start = m_tape_pos;
}
}
void hp_dc100_tape_device::store_wr_word()
{
bool fwd = is_moving_fwd();
if ((fwd && m_bit_idx == 15) ||
(!fwd && m_bit_idx == 0)) {
return;
}
hti_format_t::tape_pos_t word_length = m_image.word_length(m_rw_word);
if (!fwd) {
m_rw_pos -= word_length;
}
LOG_RW("WR %04x @%d\n" , m_rw_word , m_rw_pos);
m_image.write_word(get_track_no() , m_rw_pos , m_rw_word , word_length , fwd);
m_image_dirty = true;
m_rw_word = 0;
if (fwd) {
m_bit_idx = 15;
m_rw_pos += word_length;
} else {
m_bit_idx = 0;
}
}

View File

@ -0,0 +1,178 @@
// license:BSD-3-Clause
// copyright-holders:F. Ulivi
/*********************************************************************
hp_dc100_tape.h
HP DC100 tape cartridge & drive
*********************************************************************/
#ifndef MAME_MACHINE_HP_DC100_TAPE_H
#define MAME_MACHINE_HP_DC100_TAPE_H
#pragma once
#include "formats/hti_tape.h"
class hp_dc100_tape_device : public device_t,
public device_image_interface
{
public:
// Construction
hp_dc100_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// 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;
// **** Units ****
// Tape position or distance 1/(968 * 1024) in (see hti_format_t::tape_pos_t)
// Speed in/s
// Acceleration in/s^2
// Settings
void set_acceleration(double accel);
void set_set_points(double slow_sp , double fast_sp);
void set_tick_size(hti_format_t::tape_pos_t size);
void set_bits_per_word(unsigned bits);
void set_go_threshold(double threshold);
// Commands
void set_track_no(unsigned track);
enum tape_speed_t {
SP_STOP, // Stop
SP_SLOW, // Slow speed
SP_FAST // Fast speed
};
bool set_speed_setpoint(tape_speed_t speed , bool fwd);
enum tape_op_t {
OP_IDLE, // No R/W
OP_READ, // Read from tape
OP_WRITE, // Write to tape
OP_ERASE // Erase tape
};
void set_op(tape_op_t op , bool force = false);
void update_speed_pos();
// State access
hti_format_t::tape_pos_t get_pos() const { return m_tape_pos; }
hti_format_t::tape_pos_t get_approx_pos() const;
double get_speed() const { return m_speed; }
double get_speed_setpoint() const { return m_set_point; }
unsigned get_track_no() const { return m_track; }
tape_op_t get_op() const { return m_current_op; }
bool has_started() const { return !m_start_time.is_never(); }
bool is_moving() const { return fabs(m_speed) >= m_go_threshold; }
bool is_accelerating() const { return m_accelerating; }
bool is_moving_fwd() const { return m_speed == 0.0 ? m_set_point >= 0.0 : m_speed > 0.0; }
bool cart_out_r() const { return !m_present; }
bool wpr_r() const { return !m_present || is_readonly(); }
bool is_above_threshold() const { return fabs(m_speed) >= m_slow_set_point; }
bool gap_reached(hti_format_t::tape_pos_t min_gap_size);
void time_to_next_gap(hti_format_t::tape_pos_t min_gap_size , bool new_gap , emu_timer *target_timer);
// Callbacks
// Cartridge is out (1)
auto cart_out() { return m_cart_out_handler.bind(); }
// On a hole (1)
auto hole() { return m_hole_handler.bind(); }
// Tachometer tick (1)
auto tacho_tick() { return m_tacho_tick_handler.bind(); }
// Motion event (1)
// One of the following:
// 1. "In motion" threshold crossed
// 2. Slow speed threshold crossed
// 3. Accelerated phase ended (== set point reached)
auto motion_event() { return m_motion_handler.bind(); }
// Read bit
auto rd_bit() { return m_rd_bit_handler.bind(); }
// Bit to write
auto wr_bit() { return m_wr_bit_handler.bind(); }
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
private:
devcb_write_line m_cart_out_handler;
devcb_write_line m_hole_handler;
devcb_write_line m_tacho_tick_handler;
devcb_write_line m_motion_handler;
devcb_write_line m_rd_bit_handler;
devcb_read_line m_wr_bit_handler;
// Settings
double m_acceleration;
double m_slow_set_point;
double m_fast_set_point;
hti_format_t::tape_pos_t m_tick_size;
double m_go_threshold;
// State
hti_format_t::tape_pos_t m_tape_pos;
tape_speed_t m_tape_speed;
double m_set_point;
double m_speed;
attotime m_start_time; // Tape moving if != never
bool m_accelerating;
unsigned m_track;
tape_op_t m_current_op;
bool m_in_set_op;
bool m_present;
hti_format_t::track_iterator_t m_rd_it;
bool m_rd_it_valid;
hti_format_t::tape_word_t m_rw_word;
int m_bit_idx;
hti_format_t::tape_pos_t m_rw_pos; // Start position of current R/W word or start position of gap when erasing
hti_format_t::tape_pos_t m_gap_detect_start;
// Timers
emu_timer *m_bit_timer;
emu_timer *m_tacho_timer;
emu_timer *m_hole_timer;
emu_timer *m_motion_timer;
hti_format_t::tape_pos_t m_next_bit_pos;
hti_format_t::tape_pos_t m_next_tacho_pos;
hti_format_t::tape_pos_t m_next_hole_pos;
// Image of tape
hti_format_t m_image;
bool m_image_dirty;
void clear_state();
image_init_result internal_load(bool is_create);
void set_tape_present(bool present);
double compute_set_point(tape_speed_t speed , bool fwd) const;
void start_tape();
void stop_tape();
double const_a_space(double a , double t) const;
attotime time_to_threshold(double threshold , bool zero_allowed) const;
void set_motion_timer();
void time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const;
void adjust_tacho_timer();
void adjust_hole_timer();
void stop_op();
void load_rd_word();
void store_wr_word();
};
// device type definition
DECLARE_DEVICE_TYPE(HP_DC100_TAPE, hp_dc100_tape_device)
#endif // MAME_MACHINE_HP_DC100_TAPE_H

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,9 @@
#pragma once
#include "formats/hti_tape.h"
#include "machine/hp_dc100_tape.h"
class hp_taco_device : public device_t ,
public device_image_interface
class hp_taco_device : public device_t
{
public:
// construction/destruction
@ -35,113 +35,86 @@ public:
DECLARE_READ_LINE_MEMBER(flg_r);
DECLARE_READ_LINE_MEMBER(sts_r);
// 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;
DECLARE_WRITE_LINE_MEMBER(cart_out_w);
DECLARE_WRITE_LINE_MEMBER(hole_w);
DECLARE_WRITE_LINE_MEMBER(tacho_tick_w);
DECLARE_WRITE_LINE_MEMBER(motion_w);
DECLARE_WRITE_LINE_MEMBER(rd_bit_w);
DECLARE_READ_LINE_MEMBER(wr_bit_r);
protected:
hp_taco_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
// device-level overrides
virtual void device_add_mconfig(machine_config &config) override;
virtual void device_start() override;
virtual void device_stop() override;
virtual void device_reset() override;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
private:
required_device<hp_dc100_tape_device> m_tape;
devcb_write_line m_irq_handler;
devcb_write_line m_flg_handler;
devcb_write_line m_sts_handler;
// Registers
uint16_t m_data_reg;
bool m_data_reg_full;
uint16_t m_cmd_reg;
uint16_t m_status_reg;
uint16_t m_tach_reg;
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;
uint16_t m_timing_reg;
uint16_t m_threshold_reg;
// State
bool m_irq;
bool m_flg;
bool m_sts;
bool m_error;
bool m_gap_in_read;
// Command FSM state
typedef enum {
CMD_IDLE,
CMD_INVERTING,
CMD_PH0,
CMD_PH1,
CMD_PH2,
CMD_END,
CMD_STOPPING
} cmd_state_t;
cmd_state_t m_cmd_state;
// Tape position & motion
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;
// Timers
emu_timer *m_tape_timer;
emu_timer *m_hole_timer;
emu_timer *m_timeout_timer;
// Content of tape
hti_format_t m_image;
bool m_image_dirty;
emu_timer *m_gap_timer;
emu_timer *m_evd_timer;
emu_timer *m_error_timer;
// Reading & writing
bool m_tape_wr;
hti_format_t::tape_pos_t m_rw_pos;
uint16_t m_next_word;
hti_format_t::track_iterator_t m_rd_it;
bool m_rd_it_valid;
uint16_t m_working_reg;
unsigned m_bit_idx;
// Gap detection
hti_format_t::tape_pos_t m_gap_detect_start;
void clear_state(void);
void clear_state();
void irq_w(bool state);
void set_error(bool state);
bool is_braking(void) const;
unsigned speed_to_tick_freq(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);
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);
unsigned current_track(void);
attotime fetch_next_wr_word(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 sts_w(bool state);
void set_error(bool error , bool gap_in_read);
hti_format_t::tape_pos_t min_gap_size() const;
void set_gap_timer();
void set_evd_timer();
void set_tape_present(bool present);
attotime time_to_next_hole(void) const;
attotime time_to_tach_pulses(void) const;
void terminate_cmd_now(void);
void set_data_timeout(bool long_timeout);
void cmd_fsm(void);
void send_go();
void send_stop();
void end_cmd();
void irq_and_end();
bool is_at_slow_speed() const;
void start_rd();
void start_wr();
void update_checksum(uint16_t data);
void cmd_fsm();
static uint8_t get_cmd(uint16_t cmd_reg);
static bool is_cmd_rd_wr(uint16_t cmd_reg);
static bool is_cmd_rd(uint16_t cmd_reg);
static bool is_cmd_wr(uint16_t cmd_reg);
static bool is_double_hole_cmd(uint16_t cmd_reg);
void start_cmd_exec(uint16_t new_cmd_reg);
image_init_result internal_load(bool is_create);
};
// device type definition

View File

@ -11,7 +11,8 @@
#include "imageutl.h"
static constexpr uint32_t FILE_MAGIC = 0x5441434f; // Magic value at start of image file: "TACO"
static constexpr uint32_t OLD_FILE_MAGIC = 0x5441434f; // Magic value at start of old-format image file: "TACO"
static constexpr uint32_t FILE_MAGIC = 0x48544930; // Magic value at start of image file: "HTI0"
// *** Position of tape holes ***
// At beginning of tape:
@ -40,6 +41,7 @@ static const hti_format_t::tape_pos_t tape_holes[] = {
};
hti_format_t::hti_format_t()
: m_bits_per_word(16)
{
clear_tape();
}
@ -49,14 +51,15 @@ 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) {
auto magic = pick_integer_be(tmp , 0 , 4);
if (magic != FILE_MAGIC && magic != OLD_FILE_MAGIC) {
return false;
}
uint64_t offset = 4;
for (tape_track_t& track : m_tracks) {
if (!load_track(io , offset , track)) {
if (!load_track(io , offset , track , magic == OLD_FILE_MAGIC)) {
clear_tape();
return false;
}
@ -114,7 +117,7 @@ hti_format_t::tape_pos_t hti_format_t::word_length(tape_word_t w)
zeros = 16 - ones;
return zeros * ZERO_BIT_LEN + (ones + 1) * ONE_BIT_LEN;
return zeros * ZERO_BIT_LEN + ones * ONE_BIT_LEN;
}
hti_format_t::tape_pos_t hti_format_t::farthest_end(const track_iterator_t& it , bool forward)
@ -187,7 +190,7 @@ void hti_format_t::write_word(unsigned track_no , tape_pos_t start , tape_word_t
// as the record expands & contracts when re-written with different content.
// Without this fix, a gap could form in the slack big enough to cause
// false gap detections.
if (forward && it_high != track.end() && (it_high->first - end_pos) >= (ZERO_BIT_LEN * 16 + ONE_BIT_LEN)) {
if (forward && it_high != track.end() && (it_high->first - end_pos) >= (ZERO_BIT_LEN * 16)) {
track.insert(it_high, std::make_pair(end_pos, 0));
it_high--;
}
@ -264,6 +267,50 @@ hti_format_t::adv_res_t hti_format_t::adv_it(unsigned track_no , bool forward ,
}
}
bool hti_format_t::sync_with_record(unsigned track_no , track_iterator_t& it , unsigned& bit_idx)
{
while ((it->second & (1U << bit_idx)) == 0) {
if (bit_idx) {
bit_idx--;
} else {
bit_idx = 15;
auto res = adv_it(track_no, true, it);
if (res != ADV_CONT_DATA) {
return false;
}
}
}
if (bit_idx) {
bit_idx--;
} else {
bit_idx = 15;
}
return true;
}
hti_format_t::adv_res_t hti_format_t::next_word(unsigned track_no , track_iterator_t& it , unsigned& bit_idx , tape_word_t& word)
{
if (bit_idx == 15) {
auto res = adv_it(track_no, true, it);
if (res == ADV_NO_MORE_DATA) {
return res;
}
word = it->second;
return res;
} else {
word = it->second << (15 - bit_idx);
auto res = adv_it(track_no, true, it);
if (res == ADV_DISCONT_DATA) {
bit_idx = 15;
it--;
} else if (res == ADV_CONT_DATA) {
word |= (it->second >> (bit_idx + 1));
}
return res;
}
}
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;
@ -323,10 +370,12 @@ bool hti_format_t::next_gap(unsigned track_no , tape_pos_t& pos , bool forward ,
return n_gaps == 0;
}
bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& track)
bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t& track , bool old_format)
{
uint8_t tmp[ 4 ];
uint32_t tmp32;
tape_pos_t delta_pos = 0;
tape_pos_t last_word_end = 0;
track.clear();
@ -350,7 +399,10 @@ bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t&
tmp32 = pick_integer_le(tmp, 0, 4);
tape_pos_t pos = (tape_pos_t)tmp32;
tape_pos_t pos = (tape_pos_t)tmp32 + delta_pos;
tape_word_t word_accum = 0;
unsigned bits_in_accum = 0;
for (unsigned i = 0; i < n_words; i++) {
uint16_t tmp16;
@ -359,8 +411,56 @@ bool hti_format_t::load_track(io_generic *io , uint64_t& offset , tape_track_t&
offset += 2;
tmp16 = pick_integer_le(tmp, 0, 2);
track.insert(std::make_pair(pos , tmp16));
pos += word_length(tmp16);
if (!old_format) {
track.insert(std::make_pair(pos , tmp16));
pos += word_length(tmp16);
} else if (m_bits_per_word == 16) {
// Convert HP9845 & HP85 old format
// Basically, in old format each word had 17 bits (an implicit 1
// was added at the end). In new format we just keep the 16 bits
// and don't add the 17th bit.
if (i == 0 && tmp16 == 0 && (pos - last_word_end) > 16384) {
// This mysterious heuristic is meant to turn the first
// word of a record into a proper preamble word (from 0 to 1)
// provided this is actually at the beginning of a new record
// (enough distance from end of last record)
tmp16 = 1;
}
track.insert(std::make_pair(pos, tmp16));
pos += word_length(tmp16);
last_word_end = pos;
delta_pos -= ONE_BIT_LEN;
} else {
// Convert HP9825 old format
// In moving from old to new format we make the 17th bit at the
// end of each word explicit
word_accum |= (tmp16 >> bits_in_accum);
// Avoid storing overlapping words
if (pos >= last_word_end) {
track.insert(std::make_pair(pos, word_accum));
}
pos += word_length(word_accum);
last_word_end = pos;
if (bits_in_accum == 0) {
word_accum = 0;
} else {
word_accum = tmp16 << (16 - bits_in_accum);
}
word_accum |= (1U << (15 - bits_in_accum));
if (++bits_in_accum >= 16) {
track.insert(std::make_pair(pos, word_accum));
pos += word_length(word_accum);
last_word_end = pos;
word_accum = 0;
bits_in_accum = 0;
}
}
}
if (bits_in_accum) {
track.insert(std::make_pair(pos, word_accum));
tape_pos_t shift = (tape_pos_t)(16 - bits_in_accum) * ZERO_BIT_LEN;
delta_pos += shift;
last_word_end = pos + word_length(word_accum);
}
}
}

View File

@ -4,8 +4,8 @@
"HTI" format
Format of images of DC-100 tape cassettes as used in HP 9845
and HP 85 systems.
Format of images of DC-100 tape cassettes as used in HP 9825,
HP 9845 and HP 85 systems.
*********************************************************************/
#ifndef MAME_FORMATS_HTI_TAPE_H
@ -49,10 +49,16 @@ public:
// Iterator to access words on tape
typedef tape_track_t::iterator track_iterator_t;
// Set no. of bits per word (needed when loading old format)
void set_bits_per_word(unsigned bits) { m_bits_per_word = bits; }
bool load_tape(io_generic *io);
void save_tape(io_generic *io);
void clear_tape();
// Return physical length of a bit on tape
static constexpr tape_pos_t bit_length(bool bit) { return bit ? ONE_BIT_LEN : ZERO_BIT_LEN; }
// Return physical length of a 16-bit word on tape
static tape_pos_t word_length(tape_word_t w);
@ -84,14 +90,22 @@ public:
// Advance an iterator to next word of data
adv_res_t adv_it(unsigned track_no , bool forward , track_iterator_t& it);
// Sync with the preamble of a record
bool sync_with_record(unsigned track_no , track_iterator_t& it , unsigned& bit_idx);
// Get a data word from record, after syncing
adv_res_t next_word(unsigned track_no , track_iterator_t& it , unsigned& bit_idx , tape_word_t& word);
// 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 ];
// Bits per word in old format
unsigned m_bits_per_word;
static bool load_track(io_generic *io , uint64_t& offset , tape_track_t& track);
bool load_track(io_generic *io , uint64_t& offset , tape_track_t& track , bool old_format);
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);

View File

@ -16,11 +16,10 @@
#include "logmacro.h"
#define LOG_REG_MASK (LOG_GENERAL << 1)
#define LOG_REG(...) LOGMASKED(LOG_REG_MASK, __VA_ARGS__)
#define LOG_TMR_MASK (LOG_REG_MASK << 1)
#define LOG_TMR(...) LOGMASKED(LOG_TMR_MASK, __VA_ARGS__)
#define LOG_DBG_MASK (LOG_TMR_MASK << 1)
#define LOG_DBG_MASK (LOG_REG_MASK << 1)
#define LOG_DBG(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__)
#undef VERBOSE
//#define VERBOSE (LOG_GENERAL | LOG_REG_MASK | LOG_DBG_MASK)
#define VERBOSE (LOG_GENERAL)
// Bit manipulation
@ -44,11 +43,9 @@ namespace {
// Constants
constexpr double FAST_SPEED = 90.0; // Fast speed: 90 ips
constexpr double SLOW_SPEED = 22.0; // Slow speed: 22 ips
constexpr double MIN_RD_SPEED = 22.0; // Minimum speed to read data off the tape
constexpr double MOVING_THRESHOLD = 2.0; // Tape is moving (from MVG bit POV) when speed > 2.0 ips
constexpr double ACCELERATION = 1200.0; // Acceleration when speed set point is changed: 1200 ips^2
constexpr unsigned TACH_TICKS_PER_INCH = 483; // Tachometer pulses per inch
constexpr double INVERSION_MARGIN = 1e-5; // Margin to ensure speed is away from 0 when motion is inverted (10 µs)
constexpr hti_format_t::tape_pos_t TACH_TICK_LENGTH = hti_format_t::ONE_INCH_POS / TACH_TICKS_PER_INCH; // Length of each tach tick
// Bits in command register
@ -74,33 +71,36 @@ enum : unsigned {
STAT_REG_EOT_BIT = 0 // End of tape (1)
};
// Timers
enum {
BIT_TMR_ID,
TACHO_TMR_ID,
HOLE_TMR_ID,
INV_TMR_ID
};
// Device type definition
DEFINE_DEVICE_TYPE(HP9825_TAPE, hp9825_tape_device, "hp9825_tape", "HP9825 tape sub-system")
hp9825_tape_device::hp9825_tape_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig , HP9825_TAPE , tag , owner , clock)
, device_image_interface(mconfig , *this)
, m_flg_handler(*this)
, m_sts_handler(*this)
, m_dmar_handler(*this)
, m_led_handler(*this)
, m_tape(*this , "drive")
, m_short_gap_timer(*this , "short_tmr")
, m_long_gap_timer(*this , "long_tmr")
, m_image()
, m_image_dirty(false)
{
}
void hp9825_tape_device::device_add_mconfig(machine_config &config)
{
HP_DC100_TAPE(config , m_tape , 0);
m_tape->set_acceleration(ACCELERATION);
m_tape->set_set_points(SLOW_SPEED , FAST_SPEED);
m_tape->set_tick_size(TACH_TICK_LENGTH);
m_tape->set_bits_per_word(17);
m_tape->set_go_threshold(MOVING_THRESHOLD);
m_tape->cart_out().set(FUNC(hp9825_tape_device::cart_out_w));
m_tape->hole().set(FUNC(hp9825_tape_device::hole_w));
m_tape->tacho_tick().set(FUNC(hp9825_tape_device::tacho_tick_w));
m_tape->motion_event().set(FUNC(hp9825_tape_device::motion_w));
m_tape->rd_bit().set(FUNC(hp9825_tape_device::rd_bit_w));
m_tape->wr_bit().set(FUNC(hp9825_tape_device::wr_bit_r));
TTL74123(config , m_short_gap_timer , 0);
m_short_gap_timer->set_connection_type(TTL74123_NOT_GROUNDED_NO_DIODE);
m_short_gap_timer->set_resistor_value(RES_K(37.9));
@ -124,11 +124,6 @@ void hp9825_tape_device::device_start()
m_dmar_handler.resolve_safe();
m_led_handler.resolve_safe();
m_bit_timer = timer_alloc(BIT_TMR_ID);
m_tacho_timer = timer_alloc(TACHO_TMR_ID);
m_hole_timer = timer_alloc(HOLE_TMR_ID);
m_inv_timer = timer_alloc(INV_TMR_ID);
save_item(NAME(m_cmd_reg));
save_item(NAME(m_stat_reg));
save_item(NAME(m_flg));
@ -140,24 +135,10 @@ void hp9825_tape_device::device_start()
save_item(NAME(m_dma_req));
save_item(NAME(m_in_gap));
save_item(NAME(m_no_go));
save_item(NAME(m_present));
save_item(NAME(m_valid_bits));
save_item(NAME(m_trans_cnt));
save_item(NAME(m_short_gap_out));
save_item(NAME(m_long_gap_out));
save_item(NAME(m_image_dirty));
save_item(NAME(m_tape_pos));
save_item(NAME(m_next_bit_pos));
save_item(NAME(m_next_tacho_pos));
save_item(NAME(m_next_hole_pos));
save_item(NAME(m_speed));
save_item(NAME(m_start_time));
save_item(NAME(m_accelerating));
save_item(NAME(m_rw_stat));
save_item(NAME(m_rd_it_valid));
save_item(NAME(m_rw_word));
save_item(NAME(m_bit_idx));
save_item(NAME(m_gap_start));
}
void hp9825_tape_device::device_reset()
@ -181,13 +162,6 @@ void hp9825_tape_device::clear_state()
m_no_go = false;
m_valid_bits = false;
m_trans_cnt = 0;
m_tape_pos = 80 * hti_format_t::ONE_INCH_POS;
m_speed = 0.0;
m_start_time = attotime::never;
m_accelerating = false;
m_rw_stat = RW_IDLE;
m_rd_it_valid = false;
m_gap_start = hti_format_t::NULL_TAPE_POS;
m_short_gap_timer->b_w(0);
m_long_gap_timer->a_w(1);
@ -197,152 +171,6 @@ void hp9825_tape_device::clear_state()
m_sts_handler(true);
m_dmar_handler(false);
m_led_handler(false);
m_bit_timer->reset();
m_tacho_timer->reset();
m_hole_timer->reset();
m_inv_timer->reset();
set_tape_present(false);
set_tape_present(is_loaded());
}
void hp9825_tape_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
LOG_TMR("%.6f TMR %d s=%.3f p=%d a=%d\n" , machine().time().as_double() , id , m_speed , m_tape_pos , m_accelerating);
update_speed_pos();
switch (id) {
case BIT_TMR_ID:
m_tape_pos = m_next_bit_pos;
if (m_rw_stat == RW_READING) {
// Reading
// Tape pos here is aligned with the beginning of bit cell. It'd be more correct
// to align with end of cell, though. This solution is a lot simpler and
// it's basically harmless.
// 17th bit is sync (always 1)
bool bit = m_bit_idx == 16 ? true : BIT(m_rd_it->second , 15 - m_bit_idx);
rd_bit(bit);
if (is_moving_fwd()) {
m_bit_idx++;
if (m_bit_idx >= 17) {
m_rd_it_valid = m_image.adv_it(current_track() , true , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA;
load_rd_word();
} else {
time_to_distance(bit_size(bit), m_next_bit_pos, m_bit_timer);
}
} else {
if (m_bit_idx > 0) {
m_bit_idx--;
time_to_distance(-bit_size(bit), m_next_bit_pos, m_bit_timer);
} else {
m_rd_it_valid = m_image.adv_it(current_track() , false , m_rd_it) != hti_format_t::ADV_NO_MORE_DATA;
load_rd_word();
}
}
} else if (m_rw_stat == RW_WRITING) {
// Writing
// Tape pos is aligned with beginning of bit cell
bool bit = m_data_in;
if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) {
set_flg(true);
}
wr_bit(bit);
time_to_distance(bit_size(bit), m_next_bit_pos, m_bit_timer);
}
break;
case TACHO_TMR_ID:
m_tape_pos = m_next_tacho_pos;
if (!BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) {
set_flg(true);
}
adjust_tacho_timer();
break;
case HOLE_TMR_ID:
m_tape_pos = m_next_hole_pos;
BIT_SET(m_stat_reg , STAT_REG_EOT_BIT);
update_sts();
adjust_hole_timer();
break;
case INV_TMR_ID:
// In itself it does nothing (all work is in update_speed_pos)
break;
default:
break;
}
LOG_TMR("%.6f TMR %d s=%.3f p=%d a=%d\n" , machine().time().as_double() , id , m_speed , m_tape_pos , m_accelerating);
}
image_init_result hp9825_tape_device::internal_load(bool is_create)
{
LOG("load %d\n" , 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)) {
LOG("load failed\n");
seterror(IMAGE_ERROR_INVALIDIMAGE , "Wrong format");
set_tape_present(false);
return image_init_result::FAIL;
}
LOG("load OK\n");
m_image_dirty = false;
set_tape_present(true);
return image_init_result::PASS;
}
image_init_result hp9825_tape_device::call_load()
{
return internal_load(false);
}
image_init_result hp9825_tape_device::call_create(int format_type, util::option_resolution *format_options)
{
return internal_load(true);
}
void hp9825_tape_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_tape_present(false);
}
std::string hp9825_tape_device::call_display()
{
// TODO:
return std::string();
}
const char *hp9825_tape_device::file_extensions() const
{
return "hti";
}
READ16_MEMBER(hp9825_tape_device::tape_r)
@ -398,16 +226,16 @@ WRITE16_MEMBER(hp9825_tape_device::tape_w)
case 1:
// R5: write command
{
double old_set_point = get_speed_set_point();
auto old_cmd_reg = m_cmd_reg;
m_cmd_reg = data;
check_for_speed_change(old_set_point);
check_for_speed_change();
// Direction bit is mirrored (inverted) in status register
if (BIT(m_cmd_reg , CMD_REG_DIR_BIT)) {
BIT_CLR(m_stat_reg , STAT_REG_DIR_BIT);
} else {
BIT_SET(m_stat_reg , STAT_REG_DIR_BIT);
}
m_tape->set_track_no(!BIT(m_cmd_reg , CMD_REG_TRACK_SEL_BIT));
if (BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) {
// DMA disabled
m_search_complete = false;
@ -418,7 +246,7 @@ WRITE16_MEMBER(hp9825_tape_device::tape_w)
if ((old_cmd_reg ^ m_cmd_reg) &
(BIT_MASK<uint8_t>(CMD_REG_WR_GATE_BIT) | BIT_MASK<uint8_t>(CMD_REG_THRESHOLD_BIT))) {
// Something changed in Wr gate or threshold bit, start rd/wr
update_speed_pos();
m_tape->update_speed_pos();
start_rd_wr();
}
}
@ -436,7 +264,7 @@ WRITE16_MEMBER(hp9825_tape_device::tape_w)
case 3:
// R7: reset status
if (m_present) {
if (!m_tape->cart_out_r()) {
BIT_CLR(m_stat_reg , STAT_REG_COUT_BIT);
update_sts();
}
@ -485,6 +313,92 @@ void hp9825_tape_device::set_flg(bool state)
}
}
WRITE_LINE_MEMBER(hp9825_tape_device::cart_out_w)
{
LOG_DBG("cart_out_w %d\n" , state);
if (state) {
// STAT_REG_COUT_BIT is cleared by a write to R7
BIT_SET(m_stat_reg, STAT_REG_COUT_BIT);
}
if (m_tape->wpr_r()) {
BIT_SET(m_stat_reg, STAT_REG_WPR_BIT);
} else {
BIT_CLR(m_stat_reg, STAT_REG_WPR_BIT);
}
update_sts();
}
WRITE_LINE_MEMBER(hp9825_tape_device::hole_w)
{
if (state) {
LOG_DBG("hole_w\n");
BIT_SET(m_stat_reg , STAT_REG_EOT_BIT);
update_sts();
}
}
WRITE_LINE_MEMBER(hp9825_tape_device::tacho_tick_w)
{
if (state) {
LOG_DBG("tacho_tick_w\n");
if (!BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) {
set_flg(true);
}
}
}
WRITE_LINE_MEMBER(hp9825_tape_device::motion_w)
{
if (state) {
LOG_DBG("motion_w\n");
// Update MVG bit
if (m_tape->is_moving()) {
if (!BIT(m_stat_reg , STAT_REG_MVG_BIT)) {
BIT_SET(m_stat_reg , STAT_REG_MVG_BIT);
m_led_handler(true);
}
} else {
if (BIT(m_stat_reg , STAT_REG_MVG_BIT)) {
BIT_CLR(m_stat_reg , STAT_REG_MVG_BIT);
m_led_handler(false);
}
}
start_rd_wr();
}
}
WRITE_LINE_MEMBER(hp9825_tape_device::rd_bit_w)
{
m_short_gap_timer->b_w(1);
m_short_gap_timer->b_w(0);
m_long_gap_timer->b_w(1);
m_long_gap_timer->b_w(0);
m_data_out = m_valid_bits && state;
if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) {
set_flg(true);
}
m_trans_cnt++;
LOG_DBG("TC %u IG %d VB %d\n" , m_trans_cnt , m_in_gap , m_valid_bits);
if ((m_trans_cnt & 0x0c) == 0x0c) {
m_valid_bits = true;
}
if (BIT(m_trans_cnt , 2) && m_in_gap) {
m_in_gap = false;
m_long_gap_timer->a_w(m_in_gap);
update_sts();
}
}
READ_LINE_MEMBER(hp9825_tape_device::wr_bit_r)
{
if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) {
set_flg(true);
}
return m_data_in;
}
void hp9825_tape_device::update_sts()
{
// Inputs to STS computation:
@ -494,7 +408,6 @@ void hp9825_tape_device::update_sts()
// STAT_REG_COUT_BIT
// m_search_complete
// m_in_gap
auto prev_set_point = get_speed_set_point();
auto prev_exception = m_exception;
m_exception =
@ -503,7 +416,7 @@ void hp9825_tape_device::update_sts()
m_search_complete;
if (prev_exception != m_exception) {
check_for_speed_change(prev_set_point);
check_for_speed_change();
}
m_no_go = m_exception && !BIT(m_cmd_reg , CMD_REG_MOTOR_BIT);
@ -526,23 +439,6 @@ void hp9825_tape_device::update_dmar()
m_dmar_handler(m_dma_req && !BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT));
}
void hp9825_tape_device::set_tape_present(bool present)
{
m_present = present;
if (present) {
if (is_readonly()) {
BIT_SET(m_stat_reg, STAT_REG_WPR_BIT);
} else {
BIT_CLR(m_stat_reg, STAT_REG_WPR_BIT);
}
// STAT_REG_COUT_BIT is cleared by a write to R7
} else {
BIT_SET(m_stat_reg, STAT_REG_COUT_BIT);
BIT_SET(m_stat_reg, STAT_REG_WPR_BIT);
update_sts();
}
}
bool hp9825_tape_device::is_moving_fwd() const
{
return BIT(m_cmd_reg , CMD_REG_DIR_BIT);
@ -553,412 +449,37 @@ bool hp9825_tape_device::is_speed_fast() const
return !BIT(m_cmd_reg , CMD_REG_SPEED_BIT);
}
bool hp9825_tape_device::is_actual_dir_fwd() const
void hp9825_tape_device::check_for_speed_change()
{
// Actual direction can be different from commanded direction in accelerated phases (e.g. when tape
// direction is turned around)
return m_speed == 0.0 ? is_moving_fwd() : m_speed > 0.0;
}
hp_dc100_tape_device::tape_speed_t new_speed;
double hp9825_tape_device::get_speed_set_point() const
{
if (m_exception || BIT(m_cmd_reg , CMD_REG_MOTOR_BIT)) {
// Stop
return 0.0;
new_speed = hp_dc100_tape_device::SP_STOP;
} else {
double c;
if (is_speed_fast()) {
// HS
c = FAST_SPEED;
} else {
// LS
c = SLOW_SPEED;
}
if (!is_moving_fwd()) {
// Reverse
c = -c;
}
return c;
new_speed = is_speed_fast() ? hp_dc100_tape_device::SP_FAST :
hp_dc100_tape_device::SP_SLOW;
}
}
void hp9825_tape_device::start_tape()
{
LOG_DBG("Tape started %.6f p=%d\n" , machine().time().as_double() , m_tape_pos);
m_start_time = machine().time();
m_accelerating = true;
m_speed = 0;
}
bool changed = m_tape->set_speed_setpoint(new_speed , is_moving_fwd());
void hp9825_tape_device::stop_tape()
{
LOG_DBG("Tape stops %.6f p=%d\n" , machine().time().as_double() , m_tape_pos);
m_start_time = attotime::never;
m_accelerating = false;
m_speed = 0;
m_tacho_timer->reset();
m_hole_timer->reset();
m_inv_timer->reset();
stop_rd_wr();
}
void hp9825_tape_device::check_for_speed_change(double prev_set_point)
{
double set_point = get_speed_set_point();
if (prev_set_point != set_point) {
update_speed_pos();
LOG_DBG("Speed SP changed %f->%f %.6f p=%d\n" , prev_set_point , set_point , machine().time().as_double() , m_tape_pos);
// Speed set point changed, accelerate/decelerate
m_accelerating = true;
if (m_start_time.is_never()) {
// Tape starting now
start_tape();
}
if (changed) {
start_rd_wr(true);
adjust_tacho_timer();
adjust_hole_timer();
set_inv_timer();
}
}
void hp9825_tape_device::update_speed_pos()
{
if (m_start_time.is_never()) {
// Tape stopped
return;
}
attotime delta_time{machine().time() - m_start_time};
if (delta_time.is_zero()) {
return;
}
m_start_time = machine().time();
double delta_time_double = delta_time.as_double();
// Space in const A phase
double space_const_a;
// Time in const V phase
double time_const_v;
// Do R/W start/stop
bool rw_start_stop = false;
if (m_accelerating) {
double set_point = get_speed_set_point();
double time_to_const_v = fabs(set_point - m_speed) / ACCELERATION;
double acceleration = set_point > m_speed ? ACCELERATION : -ACCELERATION;
if (delta_time_double < time_to_const_v) {
space_const_a = const_a_space(acceleration , delta_time_double);
auto prev_speed = m_speed;
m_speed += delta_time_double * acceleration;
rw_start_stop = (fabs(prev_speed) >= MIN_RD_SPEED) != (fabs(m_speed) >= MIN_RD_SPEED);
if (prev_speed * m_speed < 0.0) {
// Direction inverted (speed sign flipped)
LOG_DBG("Dir inverted s=%f\n" , m_speed);
adjust_tacho_timer();
adjust_hole_timer();
}
set_inv_timer();
time_const_v = 0.0;
} else {
space_const_a = const_a_space(acceleration , time_to_const_v);
time_const_v = delta_time_double - time_to_const_v;
LOG_DBG("Acceleration ends\n");
m_accelerating = false;
m_speed = set_point;
m_inv_timer->reset();
if (m_speed == 0.0) {
// Tape stops
stop_tape();
} else {
rw_start_stop = true;
}
}
} else {
space_const_a = 0.0;
time_const_v = delta_time_double;
}
// Update MVG bit
if (fabs(m_speed) >= MOVING_THRESHOLD) {
if (!BIT(m_stat_reg , STAT_REG_MVG_BIT)) {
BIT_SET(m_stat_reg , STAT_REG_MVG_BIT);
m_led_handler(true);
}
} else {
if (BIT(m_stat_reg , STAT_REG_MVG_BIT)) {
BIT_CLR(m_stat_reg , STAT_REG_MVG_BIT);
m_led_handler(false);
}
}
hti_format_t::tape_pos_t delta_pos = (hti_format_t::tape_pos_t)((space_const_a + m_speed * time_const_v) * hti_format_t::ONE_INCH_POS);
LOG_DBG("dp=%d\n" , delta_pos);
if (!hti_format_t::pos_offset(m_tape_pos , true , delta_pos)) {
LOG("Tape unspooled!\n");
}
if (rw_start_stop) {
start_rd_wr();
}
}
void hp9825_tape_device::set_inv_timer()
{
// Set inversion timer to go off at the moment the tape inverts its motion
// (i.e. when m_speed crosses 0)
if (m_speed == 0.0 || (get_speed_set_point() * m_speed) > 0.0) {
LOG_DBG("Inv tmr reset\n");
// No inversion in sight
m_inv_timer->reset();
} else {
// INVERSION_MARGIN is added to ensure that m_speed has already cleared the
// 0-crossing when inv timer goes off
LOG_DBG("Inv tmr set %.6f %f\n" , machine().time().as_double() , m_speed);
m_inv_timer->adjust(attotime::from_double(fabs(m_speed) / ACCELERATION + INVERSION_MARGIN));
}
}
void hp9825_tape_device::time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const
{
if (m_start_time.is_never()) {
// If tape is stopped we'll never get there..
target_timer->reset();
return;
}
target_pos = m_tape_pos;
if (!hti_format_t::pos_offset(target_pos , true , distance)) {
// Beyond end of tape
target_timer->reset();
return;
}
double space = double(distance) / hti_format_t::ONE_INCH_POS;
double set_point = get_speed_set_point();
double time_const_a;
if (m_accelerating) {
// Time to reach constant V phase
double time_to_const_v = fabs(set_point - m_speed) / ACCELERATION;
// Signed acceleration
double acceleration = set_point > m_speed ? ACCELERATION : -ACCELERATION;
// Compute time to cover distance with constant acceleration
// It's the smallest non-negative root of this quadratic equation:
// 1/2*acceleration*t^2+m_speed*t=space
double delta = m_speed * m_speed + 2 * acceleration * space;
bool has_root = delta >= 0.0;
double time_in_const_a = 0.0;
if (has_root) {
double time_in_const_a_pos = (sqrt(delta) - m_speed) / acceleration;
double time_in_const_a_neg = -(sqrt(delta) + m_speed) / acceleration;
LOG_DBG("TTD %.6f %.6f\n" , time_in_const_a_pos , time_in_const_a_neg);
if (time_in_const_a_pos >= 0.0) {
if (time_in_const_a_neg >= 0.0) {
// pos + neg +
time_in_const_a = std::min(time_in_const_a_pos , time_in_const_a_neg);
} else {
// pos + neg -
time_in_const_a = time_in_const_a_pos;
}
} else {
if (time_in_const_a_neg >= 0.0) {
// pos - neg +
time_in_const_a = time_in_const_a_neg;
} else {
// pos - neg -
has_root = false;
}
}
}
LOG_DBG("TTD %d %d %.6f %.6f %.6f\n" , distance , has_root , m_speed , time_to_const_v , time_in_const_a);
if (has_root && time_in_const_a <= time_to_const_v) {
// Entirely in the constant A phase
time_const_a = time_in_const_a;
space = 0.0;
} else {
// Partly in const A & partly in const V
double space_in_const_a = const_a_space(acceleration , time_to_const_v);
space -= space_in_const_a;
time_const_a = time_to_const_v;
}
} else {
// Entirely in const V
time_const_a = 0.0;
}
// Time in constant V
double time_const_v;
if (space != 0.0) {
if (set_point == 0.0) {
target_timer->reset();
return;
} else {
time_const_v = space / set_point;
if (time_const_v < 0.0) {
target_timer->reset();
return;
}
}
} else {
time_const_v = 0.0;
}
LOG_DBG("TTD %.6f %.6f\n" , time_const_a , time_const_v);
target_timer->adjust(attotime::from_double(time_const_a + time_const_v));
}
double hp9825_tape_device::const_a_space(double a , double t) const
{
// Space traveled in time 't' at constant acceleration 'a' starting with 'm_speed' speed
return t * (m_speed + a / 2 * t);
}
hti_format_t::tape_pos_t hp9825_tape_device::get_next_hole() const
{
return hti_format_t::next_hole(m_tape_pos , is_actual_dir_fwd()) - m_tape_pos;
}
void hp9825_tape_device::adjust_tacho_timer()
{
hti_format_t::tape_pos_t tick = TACH_TICK_LENGTH;
if (!is_actual_dir_fwd()) {
tick = -tick;
}
time_to_distance(tick , m_next_tacho_pos , m_tacho_timer);
}
void hp9825_tape_device::adjust_hole_timer()
{
time_to_distance(get_next_hole() , m_next_hole_pos , m_hole_timer);
}
unsigned hp9825_tape_device::current_track() const
{
return !BIT(m_cmd_reg , CMD_REG_TRACK_SEL_BIT);
}
constexpr hti_format_t::tape_pos_t hp9825_tape_device::bit_size(bool bit)
{
return bit ? hti_format_t::ONE_BIT_LEN : hti_format_t::ZERO_BIT_LEN;
}
void hp9825_tape_device::start_rd_wr(bool recalc)
{
if (fabs(m_speed) >= MIN_RD_SPEED && BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT)) {
if (m_tape->is_above_threshold() && BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT)) {
// Reading
if (m_rw_stat != RW_READING || recalc) {
stop_rd_wr();
LOG_DBG("Start RD @%d s=%f t=%u\n" , m_tape_pos , m_speed , current_track());
m_rd_it_valid = m_image.next_data(current_track() , m_tape_pos , is_actual_dir_fwd() , false , m_rd_it);
m_rw_stat = RW_READING;
load_rd_word();
}
} else if (!m_accelerating && !BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT) && BIT(m_cmd_reg , CMD_REG_THRESHOLD_BIT)) {
m_tape->set_op(hp_dc100_tape_device::OP_READ , recalc);
} else if (!m_tape->is_accelerating() && !BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT) && BIT(m_cmd_reg , CMD_REG_THRESHOLD_BIT)) {
// Data writing
if (m_rw_stat != RW_WRITING) {
stop_rd_wr();
// Start WR, only LS FWD is allowed
if (m_speed == SLOW_SPEED) {
LOG_DBG("Start WR @%d\n" , m_tape_pos);
// Looking for sync
m_bit_idx = 17;
m_rw_word = 0;
time_to_distance(bit_size(true) , m_next_bit_pos , m_bit_timer);
m_rw_stat = RW_WRITING;
} else {
LOG("Starting WR s=%f ???\n" , m_speed);
}
}
m_tape->set_op(hp_dc100_tape_device::OP_WRITE);
} else if (!BIT(m_cmd_reg , CMD_REG_WR_GATE_BIT) && !BIT(m_cmd_reg , CMD_REG_THRESHOLD_BIT)) {
// Gap writing
if (m_rw_stat != RW_WRITING_GAP) {
stop_rd_wr();
LOG_DBG("Start GAP @%d\n" , m_tape_pos);
m_gap_start = m_tape_pos;
m_rw_stat = RW_WRITING_GAP;
}
m_tape->set_op(hp_dc100_tape_device::OP_ERASE);
} else {
stop_rd_wr();
m_tape->set_op(hp_dc100_tape_device::OP_IDLE);
}
}
void hp9825_tape_device::load_rd_word()
{
if (m_rd_it_valid) {
if (is_moving_fwd()) {
m_bit_idx = 0;
} else {
m_bit_idx = 16;
}
// This is actually the nearest end (dir is inverted)
m_next_bit_pos = hti_format_t::farthest_end(m_rd_it , !is_actual_dir_fwd());
LOG_DBG("Valid np=%d\n" , m_next_bit_pos);
time_to_distance(m_next_bit_pos - m_tape_pos , m_next_bit_pos , m_bit_timer);
} else {
LOG_DBG("Invalid\n");
stop_rd_wr();
}
}
void hp9825_tape_device::rd_bit(bool bit)
{
m_short_gap_timer->b_w(1);
m_short_gap_timer->b_w(0);
m_long_gap_timer->b_w(1);
m_long_gap_timer->b_w(0);
m_data_out = m_valid_bits && bit;
if (BIT(m_cmd_reg , CMD_REG_FLG_SEL_BIT)) {
set_flg(true);
}
m_trans_cnt++;
LOG_DBG("TC %u IG %d VB %d\n" , m_trans_cnt , m_in_gap , m_valid_bits);
if ((m_trans_cnt & 0x0c) == 0x0c) {
m_valid_bits = true;
}
if (BIT(m_trans_cnt , 2) && m_in_gap) {
m_in_gap = false;
m_long_gap_timer->a_w(m_in_gap);
update_sts();
}
}
void hp9825_tape_device::wr_bit(bool bit)
{
LOG_DBG("WR %d idx=%u\n" , bit , m_bit_idx);
m_rw_word <<= 1;
m_rw_word |= bit;
// A bit of heuristic here to achieve synchronization (i.e. find word boundary in bit stream)
if (m_bit_idx >= 17 && m_bit_idx < (17 + 16)) {
m_bit_idx++;
} else if (m_bit_idx == (17 + 16) && (m_rw_word & 3) == 3) {
m_bit_idx = 16;
}
if (m_bit_idx < 16) {
m_bit_idx++;
} else if (m_bit_idx == 16) {
// Write word
if (!BIT(m_rw_word , 0)) {
LOG_DBG("Sync lost! w=%05x\n" , m_rw_word & 0x1ffff);
} else {
hti_format_t::tape_word_t w = (hti_format_t::tape_word_t)(m_rw_word >> 1);
hti_format_t::tape_pos_t wr_pos = m_tape_pos - hti_format_t::word_length(w) + hti_format_t::ONE_BIT_LEN;
LOG_DBG("Wr word %04x @%d\n" , w , wr_pos);
m_image.write_word(current_track() , wr_pos , w , wr_pos);
m_image_dirty = true;
m_bit_idx = 0;
}
}
}
void hp9825_tape_device::stop_rd_wr()
{
if (m_rw_stat == RW_WRITING_GAP) {
LOG_DBG("Wr gap from %d to %d\n" , m_gap_start , m_tape_pos);
m_image.write_gap(current_track() , m_gap_start , m_tape_pos);
m_image_dirty = true;
}
m_bit_timer->reset();
m_rw_stat = RW_IDLE;
}

View File

@ -14,9 +14,10 @@
#pragma once
#include "formats/hti_tape.h"
#include "machine/hp_dc100_tape.h"
#include "machine/74123.h"
class hp9825_tape_device : public device_t , public device_image_interface
class hp9825_tape_device : public device_t
{
public:
// construction/destruction
@ -26,20 +27,6 @@ public:
virtual void device_add_mconfig(machine_config &config) override;
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;
DECLARE_READ16_MEMBER(tape_r);
DECLARE_WRITE16_MEMBER(tape_w);
@ -52,12 +39,20 @@ public:
DECLARE_WRITE_LINE_MEMBER(short_gap_w);
DECLARE_WRITE_LINE_MEMBER(long_gap_w);
DECLARE_WRITE_LINE_MEMBER(cart_out_w);
DECLARE_WRITE_LINE_MEMBER(hole_w);
DECLARE_WRITE_LINE_MEMBER(tacho_tick_w);
DECLARE_WRITE_LINE_MEMBER(motion_w);
DECLARE_WRITE_LINE_MEMBER(rd_bit_w);
DECLARE_READ_LINE_MEMBER(wr_bit_r);
private:
devcb_write_line m_flg_handler;
devcb_write_line m_sts_handler;
devcb_write_line m_dmar_handler;
devcb_write_line m_led_handler;
required_device<hp_dc100_tape_device> m_tape;
required_device<ttl74123_device> m_short_gap_timer; // U43a
required_device<ttl74123_device> m_long_gap_timer; // U43b
@ -75,73 +70,19 @@ private:
bool m_dma_req; // U9-9
bool m_in_gap; // U39-4
bool m_no_go; // U6-3
bool m_present;
bool m_valid_bits; // U39-5
uint8_t m_trans_cnt; // U42
bool m_short_gap_out; // U43-13
bool m_long_gap_out; // U43-5
// Timers
emu_timer *m_bit_timer;
emu_timer *m_tacho_timer;
emu_timer *m_hole_timer;
emu_timer *m_inv_timer;
// Image of tape
hti_format_t m_image;
bool m_image_dirty;
// Tape motion
hti_format_t::tape_pos_t m_tape_pos;
hti_format_t::tape_pos_t m_next_bit_pos;
hti_format_t::tape_pos_t m_next_tacho_pos;
hti_format_t::tape_pos_t m_next_hole_pos;
double m_speed;
attotime m_start_time; // Tape moving if != never
bool m_accelerating;
// R/W
enum {
RW_IDLE,
RW_READING,
RW_WRITING,
RW_WRITING_GAP
};
int m_rw_stat;
hti_format_t::track_iterator_t m_rd_it;
bool m_rd_it_valid;
uint32_t m_rw_word; // Need 17 bits because of sync bit
unsigned m_bit_idx; // 0 is MSB, 15 is LSB, 16 is sync bit, >16 means "looking for sync"
hti_format_t::tape_pos_t m_gap_start;
void clear_state();
image_init_result internal_load(bool is_create);
void set_flg(bool state);
void update_sts();
void update_dmar();
void set_tape_present(bool present);
bool is_moving_fwd() const;
bool is_speed_fast() const;
bool is_actual_dir_fwd() const;
double get_speed_set_point() const;
void start_tape();
void stop_tape();
void check_for_speed_change(double prev_set_point);
void update_speed_pos();
void set_inv_timer();
void time_to_distance(hti_format_t::tape_pos_t distance , hti_format_t::tape_pos_t& target_pos , emu_timer *target_timer) const;
double const_a_space(double a , double t) const;
hti_format_t::tape_pos_t get_next_hole() const;
void adjust_tacho_timer();
void adjust_hole_timer();
unsigned current_track() const;
static constexpr hti_format_t::tape_pos_t bit_size(bool bit);
void check_for_speed_change();
void start_rd_wr(bool recalc = false);
void load_rd_word();
void rd_bit(bool bit);
void wr_bit(bool bit);
void stop_rd_wr();
};
// device type definition

View File

@ -43,8 +43,8 @@ static constexpr tape_pos_t FMT_IFG_SIZE = 2.5 * hti_format_t::ONE_INCH_POS;
// Formatted size of IRGs: 1"
static constexpr tape_pos_t FMT_IRG_SIZE = hti_format_t::ONE_INCH_POS;
// Formatted size of records: 2.85"
static constexpr tape_pos_t FMT_REC_SIZE = 2.85 * hti_format_t::ONE_INCH_POS;
// Formatted size of records: 2.67"
static constexpr tape_pos_t FMT_REC_SIZE = 2.67 * hti_format_t::ONE_INCH_POS;
// Starting position on tracks: 74" from beginning of tape
static constexpr tape_pos_t TRACK_START = 74 * hti_format_t::ONE_INCH_POS;
@ -145,6 +145,7 @@ typedef struct {
tape_image_85::tape_image_85(void)
: dirty(false)
{
image.set_bits_per_word(16);
}
void tape_image_85::format_img(void)
@ -263,22 +264,24 @@ bool tape_image_85::load_sif_file(file_no_t file_no , sif_file_t& out)
if (!image.next_data(track , pos , true , false , it)) {
break;
}
// 0 Sync word
// 1 File word
// 2 Record word
// 3 Length word
// 4 Checksum
tape_word_t hdr[ 5 ];
auto res = hti_format_t::ADV_CONT_DATA;
for (unsigned i = 0; i < 5; i++) {
unsigned bit_idx = 15;
if (!image.sync_with_record(track , it , bit_idx)) {
// Couldn't align
return false;
}
// 0 File word
// 1 Record word
// 2 Length word
// 3 Checksum
tape_word_t hdr[ 4 ];
for (unsigned i = 0; i < 4; i++) {
auto res = image.next_word(track , it , bit_idx , hdr[ i ]);
if (res != hti_format_t::ADV_CONT_DATA) {
return false;
}
hdr[ i ] = it->second;
res = image.adv_it(track , true , it);
}
if (hdr[ 0 ] != SYNC_WORD ||
checksum(&hdr[ 1 ] , 3) != hdr[ 4 ]) {
if (checksum(&hdr[ 0 ] , 3) != hdr[ 3 ]) {
return false;
}
@ -287,7 +290,7 @@ bool tape_image_85::load_sif_file(file_no_t file_no , sif_file_t& out)
bool hdr_has_body;
unsigned hdr_body_len;
if (!dec_rec_header(&hdr[ 1 ] , hdr_file_no , hdr_rec_no , hdr_has_body , hdr_body_len)) {
if (!dec_rec_header(&hdr[ 0 ] , hdr_file_no , hdr_rec_no , hdr_has_body , hdr_body_len)) {
return false;
}
@ -304,11 +307,10 @@ bool tape_image_85::load_sif_file(file_no_t file_no , sif_file_t& out)
tape_word_t body[ MAX_RECORD_SIZE / 2 + 1 ];
unsigned word_no = (hdr_body_len + 1) / 2 + 1;
for (unsigned i = 0; i < word_no; i++) {
auto res = image.next_word(track , it , bit_idx , body[ i ]);
if (res != hti_format_t::ADV_CONT_DATA) {
return false;
}
body[ i ] = it->second;
res = image.adv_it(track , true , it);
}
if (checksum(&body[ 0 ] , word_no - 1) != body[ word_no - 1 ]) {
return false;

View File

@ -148,10 +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 * hti_format_t::ONE_INCH_POS)) // Size of sectors including padding: 2.85"
#define PREAMBLE_WORD 0 // Value of preamble word
#define FORMAT_SECT_SIZE ((tape_pos_t)(2.67 * hti_format_t::ONE_INCH_POS)) // Size of sectors including padding: 2.67"
#define PREAMBLE_WORD 0x0001 // Value of preamble word
#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"
#define MIN_IRG_SIZE ((tape_pos_t)(16 * 1024)) // Minimum size of IRG gaps: 0.017"
// File types
#define BKUP_FILETYPE 0
@ -324,6 +324,7 @@ static const struct io_procs my_stream_procs = {
imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream)
{
hti_format_t inp_image;
inp_image.set_bits_per_word(16);
io_generic io;
io.file = (void *)stream;
@ -357,16 +358,19 @@ imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream)
}
if (state == 1) {
// Extract record data
if (it->second != PREAMBLE_WORD) {
// The top 8 bits are ignored by TACO when aligning with preamble
unsigned bit_idx = 7;
if (!inp_image.sync_with_record(track , it , bit_idx)) {
// Couldn't align
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);
auto res = inp_image.next_word(track , it , bit_idx , buffer[ i ]);
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)) {