hp9825: added DC100 tape drive

This commit is contained in:
fulivi 2018-10-16 17:39:31 +02:00
parent c5df218215
commit 830c3203f5
6 changed files with 1192 additions and 8 deletions

View File

@ -2235,6 +2235,8 @@ files {
MAME_DIR .. "src/mame/machine/hp48.cpp",
MAME_DIR .. "src/mame/machine/hp48_port.cpp",
MAME_DIR .. "src/mame/machine/hp48_port.h",
MAME_DIR .. "src/mame/machine/hp9825_tape.cpp",
MAME_DIR .. "src/mame/machine/hp9825_tape.h",
MAME_DIR .. "src/mame/machine/hp9845_printer.cpp",
MAME_DIR .. "src/mame/machine/hp9845_printer.h",
MAME_DIR .. "src/mame/video/hp48.cpp",

View File

@ -10,8 +10,6 @@
#include "imageutl.h"
static constexpr uint32_t FILE_MAGIC = 0x5441434f; // Magic value at start of image file: "TACO"
static constexpr hti_format_t::tape_pos_t ZERO_BIT_LEN = 619; // Length of 0 bits at slow tape speed: 1/(35200 Hz)
static constexpr hti_format_t::tape_pos_t ONE_BIT_LEN = 1083; // Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN
// *** Position of tape holes ***
// At beginning of tape:

View File

@ -33,6 +33,12 @@ public:
// Tape length: 140 ft of usable tape + 72" of punched tape at either end
static constexpr tape_pos_t TAPE_LENGTH = (140 * 12 + 72 * 2) * ONE_INCH_POS;
// Length of 0 bits at slow tape speed: 1/(35200 Hz)
static constexpr tape_pos_t ZERO_BIT_LEN = 619;
// Length of 1 bits at slow tape speed: 1.75 times ZERO_BIT_LEN
static constexpr tape_pos_t ONE_BIT_LEN = 1083;
// Words stored on tape
typedef uint16_t tape_word_t;

View File

@ -13,11 +13,11 @@
// - 12 kw of system ROM
// - Keyboard (SHIFT LOCK & RESET not implemented)
// - Display & run light
// - DC100 tape drive
// What's not yet in:
// - Internal & external expansion ROMs
// - Configurable RAM size
// - Printer
// - DC100 tape drive
// - I/O expansion slots: 98034 & 98035 modules from hp9845 emulation can be used here, too
// - Beeper
//
@ -27,6 +27,7 @@
#include "emu.h"
#include "cpu/hphybrid/hphybrid.h"
#include "machine/timer.h"
#include "machine/hp9825_tape.h"
#include "hp9825.lh"
// CPU clock (generated by a trimmered RC oscillator)
@ -35,6 +36,10 @@ constexpr unsigned MAIN_CLOCK = 6000000;
// KDP chip clock
constexpr unsigned KDP_CLOCK = MAIN_CLOCK / 4;
// Peripheral Addresses (PA)
constexpr uint8_t KDP_PA = 0;
constexpr uint8_t TAPE_PA = 1;
// Bit manipulation
namespace {
template<typename T> constexpr T BIT_MASK(unsigned n)
@ -60,6 +65,7 @@ public:
: driver_device(mconfig, type, tag)
, m_cpu(*this , "cpu")
, m_cursor_timer(*this , "cursor_timer")
, m_tape(*this , "tape")
, m_io_key(*this , "KEY%u" , 0)
, m_shift_key(*this , "KEY_SHIFT")
, m_display(*this , "char_%u_%u" , 0U , 0U)
@ -72,6 +78,7 @@ public:
private:
required_device<hp_09825_67907_cpu_device> m_cpu;
required_device<timer_device> m_cursor_timer;
required_device<hp9825_tape_device> m_tape;
required_ioport_array<4> m_io_key;
required_ioport m_shift_key;
output_finder<32 , 7> m_display;
@ -89,6 +96,10 @@ private:
unsigned m_autorepeat_cnt;
uint8_t m_irl_pending;
uint8_t m_irh_pending;
// FLG/STS handling
uint8_t m_pa;
uint16_t m_flg_status;
uint16_t m_sts_status;
virtual void machine_start() override;
virtual void machine_reset() override;
@ -109,6 +120,11 @@ private:
IRQ_CALLBACK_MEMBER(irq_callback);
void update_irq();
void set_irq(uint8_t sc , int state);
DECLARE_WRITE8_MEMBER(pa_w);
void update_flg_sts();
void set_sts(uint8_t sc , int state);
void set_flg(uint8_t sc , int state);
};
void hp9825_state::machine_start()
@ -139,13 +155,17 @@ void hp9825_state::machine_reset()
m_irl_pending = 0;
m_irh_pending = 0;
update_irq();
m_pa = 0;
m_flg_status = 0;
m_sts_status = 0;
}
void hp9825_state::cpu_io_map(address_map &map)
{
map.unmap_value_low();
map(HP_MAKE_IOADDR(0 , 0) , HP_MAKE_IOADDR(0 , 0)).rw(FUNC(hp9825_state::kb_scancode_r) , FUNC(hp9825_state::disp_w));
map(HP_MAKE_IOADDR(0 , 1) , HP_MAKE_IOADDR(0 , 1)).rw(FUNC(hp9825_state::kdp_status_r) , FUNC(hp9825_state::kdp_control_w));
map(HP_MAKE_IOADDR(KDP_PA , 0) , HP_MAKE_IOADDR(KDP_PA , 0)).rw(FUNC(hp9825_state::kb_scancode_r) , FUNC(hp9825_state::disp_w));
map(HP_MAKE_IOADDR(KDP_PA , 1) , HP_MAKE_IOADDR(KDP_PA , 1)).rw(FUNC(hp9825_state::kdp_status_r) , FUNC(hp9825_state::kdp_control_w));
map(HP_MAKE_IOADDR(TAPE_PA , 0) , HP_MAKE_IOADDR(TAPE_PA , 3)).rw(m_tape , FUNC(hp9825_tape_device::tape_r) , FUNC(hp9825_tape_device::tape_w));
// TODO:
}
@ -163,7 +183,7 @@ READ16_MEMBER(hp9825_state::kb_scancode_r)
if (m_shift_key->read()) {
BIT_SET(res , 7);
}
set_irq(0 , false);
set_irq(KDP_PA , false);
return res;
}
@ -417,7 +437,7 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9825_state::kb_scan)
if (max_seq_len) {
m_scancode = max_seq_idx;
m_key_pressed = true;
set_irq(0 , true);
set_irq(KDP_PA , true);
}
}
@ -431,7 +451,7 @@ TIMER_DEVICE_CALLBACK_MEMBER(hp9825_state::kb_scan)
}
if (m_autorepeating && BIT(~prev_cnt & m_autorepeat_cnt , 3)) {
// Repeat key every time bit 3 of autorepeat counter goes 0->1
set_irq(0 , true);
set_irq(KDP_PA , true);
}
} else {
m_autorepeating = false;
@ -488,6 +508,44 @@ void hp9825_state::set_irq(uint8_t sc , int state)
update_irq();
}
WRITE8_MEMBER(hp9825_state::pa_w)
{
m_pa = data;
update_flg_sts();
}
void hp9825_state::update_flg_sts()
{
bool sts = BIT(m_sts_status , m_pa);
bool flg = BIT(m_flg_status , m_pa);
m_cpu->status_w(sts);
m_cpu->flag_w(flg);
}
void hp9825_state::set_sts(uint8_t sc , int state)
{
if (state) {
BIT_SET(m_sts_status, sc);
} else {
BIT_CLR(m_sts_status, sc);
}
if (sc == m_pa) {
update_flg_sts();
}
}
void hp9825_state::set_flg(uint8_t sc , int state)
{
if (state) {
BIT_SET(m_flg_status, sc);
} else {
BIT_CLR(m_flg_status, sc);
}
if (sc == m_pa) {
update_flg_sts();
}
}
MACHINE_CONFIG_START(hp9825_state::hp9825b)
HP_09825_67907(config , m_cpu , MAIN_CLOCK);
// Just guessing... settings borrowed from hp9845
@ -496,12 +554,19 @@ MACHINE_CONFIG_START(hp9825_state::hp9825b)
m_cpu->set_addrmap(AS_PROGRAM , &hp9825_state::cpu_mem_map);
m_cpu->set_addrmap(AS_IO , &hp9825_state::cpu_io_map);
m_cpu->set_irq_acknowledge_callback(FUNC(hp9825_state::irq_callback));
m_cpu->pa_changed_cb().set(FUNC(hp9825_state::pa_w));
TIMER(config , m_cursor_timer , 0).configure_generic(timer_device::expired_delegate(FUNC(hp9825_state::cursor_blink) , this));
// Keyboard scan timer. A scan of the whole keyboard should take 2^14 KDP clocks.
TIMER(config , "kb_timer" , 0).configure_periodic(timer_device::expired_delegate(FUNC(hp9825_state::kb_scan) , this) , attotime::from_ticks(16384 , KDP_CLOCK));
// Tape drive
HP9825_TAPE(config , m_tape , 0);
m_tape->flg().set([this , sc = TAPE_PA](int state) { set_flg(sc , state); });
m_tape->sts().set([this , sc = TAPE_PA](int state) { set_sts(sc , state); });
m_tape->dmar().set(m_cpu , FUNC(hp_09825_67907_cpu_device::dmar_w));
config.set_default_layout(layout_hp9825);
MACHINE_CONFIG_END

View File

@ -0,0 +1,963 @@
// license:BSD-3-Clause
// copyright-holders:F. Ulivi
/*********************************************************************
hp9825_tape.cpp
HP9825 tape sub-system
*********************************************************************/
#include "emu.h"
#include "hp9825_tape.h"
#include "machine/rescap.h"
// Debugging
#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(...) LOGMASKED(LOG_DBG_MASK, __VA_ARGS__)
#undef VERBOSE
#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);
}
}
// 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
enum : unsigned {
CMD_REG_MOTOR_BIT = 7, // Motor on (0)
CMD_REG_WR_GATE_BIT = 6, // Write gate (0)
CMD_REG_SPEED_BIT = 5, // Tape speed (1 = slow)
CMD_REG_DIR_BIT = 4, // Tape direction (1 = fwd)
CMD_REG_FLG_SEL_BIT = 3, // FLG selection (0 = tacho pulses, 1 = bit clock)
CMD_REG_THRESHOLD_BIT = 2, // Threshold selection
CMD_REG_DMA_EN_BIT = 1, // DMA enable (0)
CMD_REG_TRACK_SEL_BIT = 0 // Track selection (1 = A)
};
// Bits in status register
enum : unsigned {
STAT_REG_WPR_BIT = 7, // Write protected (1)
STAT_REG_DIR_BIT = 6, // Tape direction (1 = rev)
STAT_REG_MVG_BIT = 5, // Tape moving (1)
STAT_REG_GAP_BIT = 4, // Gap (1) or data (0)
STAT_REG_COUT_BIT = 2, // Cartridge out (1)
STAT_REG_SVF_BIT = 1, // Servo failure (1)
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_short_gap_timer(*this , "short_tmr")
, m_long_gap_timer(*this , "long_tmr")
, m_image()
, m_image_dirty(false)
{
}
MACHINE_CONFIG_START(hp9825_tape_device::device_add_mconfig)
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));
m_short_gap_timer->set_capacitor_value(CAP_N(10));
m_short_gap_timer->set_a_pin_value(0);
m_short_gap_timer->set_clear_pin_value(1);
m_short_gap_timer->out_cb().set(FUNC(hp9825_tape_device::short_gap_w));
TTL74123(config , m_long_gap_timer , 0);
m_long_gap_timer->set_connection_type(TTL74123_NOT_GROUNDED_NO_DIODE);
m_long_gap_timer->set_resistor_value(RES_K(28.7));
m_long_gap_timer->set_capacitor_value(CAP_U(0.22));
m_long_gap_timer->set_clear_pin_value(1);
m_long_gap_timer->out_cb().set(FUNC(hp9825_tape_device::long_gap_w));
MACHINE_CONFIG_END
void hp9825_tape_device::device_start()
{
m_flg_handler.resolve_safe();
m_sts_handler.resolve_safe();
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));
save_item(NAME(m_sts));
save_item(NAME(m_data_out));
save_item(NAME(m_data_in));
save_item(NAME(m_exception));
save_item(NAME(m_search_complete));
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()
{
clear_state();
}
void hp9825_tape_device::clear_state()
{
m_cmd_reg = ~0;
m_stat_reg = 0;
// Not actually reset in real hw
m_flg = false;
m_sts = true;
m_data_out = false;
m_data_in = false;
m_exception = false;
m_search_complete = false;
m_dma_req = false;
m_in_gap = true;
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);
m_long_gap_timer->b_w(0);
m_flg_handler(false);
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)
{
uint16_t res = 0;
switch (offset) {
case 0:
// R4: read data out
if (m_data_out) {
BIT_SET(res , 0);
}
if (!m_no_go) {
set_flg(false);
}
break;
case 1:
// R5: read status
res = m_stat_reg;
break;
case 2:
// R6: clear EOT
BIT_CLR(m_stat_reg , STAT_REG_EOT_BIT);
update_sts();
break;
default:
logerror("Reading @ offset %u\n" , offset);
break;
}
LOG_REG("R R%u=%02x\n" , offset + 4 , res);
return res;
}
WRITE16_MEMBER(hp9825_tape_device::tape_w)
{
LOG_REG("W R%u=%02x\n" , offset + 4 , data);
switch (offset) {
case 0:
// R4: write data in
m_data_in = BIT(data , 0);
m_dma_req = false;
update_dmar();
if (!m_no_go) {
set_flg(false);
}
break;
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);
// 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);
}
if (BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) {
// DMA disabled
m_search_complete = false;
}
update_sts();
update_dmar();
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();
start_rd_wr();
}
}
break;
case 2:
// R6: clear DMA
if (!BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) {
m_search_complete = true;
update_sts();
}
m_dma_req = false;
update_dmar();
break;
case 3:
// R7: reset status
if (m_present) {
BIT_CLR(m_stat_reg , STAT_REG_COUT_BIT);
update_sts();
}
break;
default:
logerror("Writing @ offset %u\n" , offset);
break;
}
}
WRITE_LINE_MEMBER(hp9825_tape_device::short_gap_w)
{
LOG_DBG("Short gap %d\n" , state);
m_short_gap_out = state;
if (!m_short_gap_out) {
m_valid_bits = false;
m_data_out = false;
m_trans_cnt = 0;
m_in_gap = true;
m_long_gap_timer->a_w(m_in_gap);
update_sts();
}
}
WRITE_LINE_MEMBER(hp9825_tape_device::long_gap_w)
{
LOG_DBG("Long gap %d\n" , state);
if (m_long_gap_out && !state && !BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT)) {
m_dma_req = true;
update_dmar();
}
m_long_gap_out = state;
if (m_long_gap_out) {
BIT_CLR(m_stat_reg , STAT_REG_GAP_BIT);
} else {
BIT_SET(m_stat_reg , STAT_REG_GAP_BIT);
}
}
void hp9825_tape_device::set_flg(bool state)
{
if (state != m_flg) {
m_flg = state;
m_flg_handler(m_flg);
}
}
void hp9825_tape_device::update_sts()
{
// Inputs to STS computation:
// CMD_REG_MOTOR_BIT
// CMD_REG_DMA_EN_BIT
// STAT_REG_EOT_BIT
// 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 =
BIT(m_stat_reg , STAT_REG_EOT_BIT) ||
BIT(m_stat_reg , STAT_REG_COUT_BIT) ||
m_search_complete;
if (prev_exception != m_exception) {
check_for_speed_change(prev_set_point);
}
m_no_go = m_exception && !BIT(m_cmd_reg , CMD_REG_MOTOR_BIT);
// U6-6
bool sts_2 = m_in_gap && BIT(m_cmd_reg , CMD_REG_DMA_EN_BIT);
bool new_sts = m_no_go || sts_2;
if (new_sts != m_sts) {
m_sts = new_sts;
m_sts_handler(m_sts);
}
if (m_no_go) {
set_flg(true);
}
}
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);
}
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
{
// 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;
}
double hp9825_tape_device::get_speed_set_point() const
{
if (m_exception || BIT(m_cmd_reg , CMD_REG_MOTOR_BIT)) {
// Stop
return 0.0;
} 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;
}
}
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;
}
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();
}
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)) {
// 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)) {
// 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);
}
}
} 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;
}
} else {
stop_rd_wr();
}
}
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

@ -0,0 +1,150 @@
// license:BSD-3-Clause
// copyright-holders:F. Ulivi
/*********************************************************************
hp9825_tape.h
HP9825 tape sub-system
*********************************************************************/
#ifndef MAME_MACHINE_HP9825_TAPE_H
#define MAME_MACHINE_HP9825_TAPE_H
#pragma once
#include "formats/hti_tape.h"
#include "machine/74123.h"
class hp9825_tape_device : public device_t , public device_image_interface
{
public:
// construction/destruction
hp9825_tape_device(const machine_config &mconfig, 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_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);
auto flg() { return m_flg_handler.bind(); }
auto sts() { return m_sts_handler.bind(); }
auto dmar() { return m_dmar_handler.bind(); }
auto led() { return m_led_handler.bind(); }
DECLARE_WRITE_LINE_MEMBER(short_gap_w);
DECLARE_WRITE_LINE_MEMBER(long_gap_w);
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<ttl74123_device> m_short_gap_timer; // U43a
required_device<ttl74123_device> m_long_gap_timer; // U43b
// Registers
uint8_t m_cmd_reg;
uint8_t m_stat_reg;
// State
bool m_flg;
bool m_sts;
bool m_data_out; // U38-9
bool m_data_in; // U13-6
bool m_exception; // U4-6
bool m_search_complete; // U9-6
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 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
DECLARE_DEVICE_TYPE(HP9825_TAPE, hp9825_tape_device)
#endif // MAME_MACHINE_HP9825_TAPE_H