hp9825: added HLE of HP9885 floppy drive. Extended HPI format to

handle single-sided disks.
This commit is contained in:
fulivi 2019-01-12 18:57:39 +01:00
parent 933ab29627
commit c6dff30d7a
7 changed files with 1206 additions and 25 deletions

View File

@ -3511,6 +3511,8 @@ if (BUSES["HP9845_IO"]~=null) then
MAME_DIR .. "src/devices/bus/hp9845_io/98034.h",
MAME_DIR .. "src/devices/bus/hp9845_io/98035.cpp",
MAME_DIR .. "src/devices/bus/hp9845_io/98035.h",
MAME_DIR .. "src/devices/bus/hp9845_io/hp9885.cpp",
MAME_DIR .. "src/devices/bus/hp9845_io/hp9885.h",
}
end

View File

@ -13,6 +13,7 @@
#include "emu.h"
#include "98032.h"
#include "hp9885.h"
// Debugging
#define VERBOSE 0
@ -353,6 +354,7 @@ hp98032_gpio_slot_device::hp98032_gpio_slot_device(const machine_config &mconfig
{
option_reset();
option_add("loopback" , HP98032_GPIO_LOOPBACK);
option_add("hp9885" , HP9885);
set_default_option(nullptr);
set_fixed(false);
}

View File

@ -0,0 +1,974 @@
// license:BSD-3-Clause
// copyright-holders: F. Ulivi
/*********************************************************************
hp9885.cpp
HP9885M 8" floppy drive
=======================
This driver implements HLE of HP9885 floppy drive.
The HP9885M is a single-disk 8" floppy drive. It connects to
main system through a HP98032 GPIO module. The controller in a
HP9885M can drive 3 more external HP9885S drives. The "M" or "S"
in the name stand for master and slave, respectively.
The enclosure of HP9885M contains the floppy drive, the controller
electronics and the power supply whereas the HP9885S only has the
drive and the power supply. A master unit interfaces to slave units
through a standard daisy-chained Shugart bus.
The controller is based on a HP Nanoprocessor with a 2 kB FW ROM.
Unfortunately no dumps are available of the ROM, AFAIK, so the HLE
is needed.
The HP9885 supports a single disk format having these characteristics:
- Single side
- HP MMFM modulation
- 30 256-byte sectors per track
- 77 tracks (only 67 are actually used)
- 360 RPM
- A total capacity of 514560 bytes per disk.
Summary of the command words I identified
=========================================
*READ*
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+-----------------------------------+
| 0 X|Unit#| Sector count |
+-----+-----+-----------------------------------+
Bit 14 selects tighter margin for data reading (not emulated here)
*WRITE*
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+-----------------------------------+
| 1 0|Unit#| Sector count |
+-----+-----+-----------------------------------+
*SEEK*
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+--------------------+--------------+
| 1 1|Unit#| Track # [0..76] |Sector#[0..29]|
+-----+-----+--------------------+--------------+
*FORMAT TRACK* (not implemented yet)
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+--------------------+--------------+
| 1 1|Unit#| Track # [0..76] | 0x1e |
+-----+-----+--------------------+--------------+
*STEP IN*
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+--------------------+--------------+
| 1 1|Unit#| 0x7c | 0x1f |
+-----+-----+--------------------+--------------+
*ERASE TRACK* (not implemented yet)
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+--------------------+--------------+
| 1 1|Unit#| 0x7d | 0x1f |
+-----+-----+--------------------+--------------+
*READ STATUS*
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----+-----+--------------------+--------------+
| 1 1|Unit#| 0x7f | 0x1f |
+-----+-----+--------------------+--------------+
This is the structure of the status word:
+-----------------------------------------------+
|15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
+-----------------------+--+--+--+--+--+--+-----+
| Error code (0=OK) |--|XC|SC|NR|WP|DC|Unit#|
+-----------------------+--+--+--+--+--+--+-----+
DC: Disk changed
WP: Write protection
NR: Not ready
SC: Seek completed
XC: Transfer completed
Usage on HP9825
===============
-slot0 98032_gpio -slot0:98032_gpio:gpio hp9885 -flop <floppy image>
Usage on HP9845
===============
-slot0 98032_gpio -slot0:98032_gpio:gpio hp9885 -rom1 massd -flop <floppy image>
Note on floppy images
=====================
Images can be in two formats: MFI & HPI.
A pre-formatted image must be used as formatting is not supported.
It's not possible to format images for two reasons: the format
command is not implemented yet and no dump of the disk system tape
cartridge (HP part no. 09885-90035) is available.
The latter is needed during format operation to create the so-called
bootstraps on disk, which in turn are needed by 9825 systems.
A pre-formatted image (9825_empty.hpi) is available here:
http://www.hpmuseum.net/software/9825_discs.zip
TODO
====
+ Implement missing commands
+ PRESET
Acknowledgments
===============
Thanks to Dyke Shaffer for publishing a lot of HP internal docs
and source files regarding the HP9885.
Fun fact: data I/O between disk and 98032 module is not buffered
as there is no RAM in the controller. DMA must be used to keep
data flow at disk speed and avoid underruns. The nominal disk
data rate is one word every 32 µs.
*********************************************************************/
#include "emu.h"
#include "hp9885.h"
#include "formats/hpi_dsk.h"
// Debugging
#include "logmacro.h"
#define LOG_TIMER_MASK (LOG_GENERAL << 1)
#define LOG_TIMER(...) LOGMASKED(LOG_TIMER_MASK, __VA_ARGS__)
#define LOG_HS_MASK (LOG_TIMER_MASK << 1)
#define LOG_HS(...) LOGMASKED(LOG_HS_MASK, __VA_ARGS__)
#define LOG_HEAD_MASK (LOG_HS_MASK << 1)
#define LOG_HEAD(...) LOGMASKED(LOG_HEAD_MASK, __VA_ARGS__)
#define LOG_DISK_MASK (LOG_HEAD_MASK << 1)
#define LOG_DISK(...) LOGMASKED(LOG_DISK_MASK, __VA_ARGS__)
#undef VERBOSE
//#define VERBOSE (LOG_GENERAL | LOG_HS_MASK | LOG_HEAD_MASK)
#define VERBOSE 0
// 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(HP9885, hp9885_device, "hp9885" , "HP9885 floppy drive")
// Timers
enum {
FSM_TMR_ID,
HEAD_TMR_ID,
BIT_BYTE_TMR_ID
};
// Constants
constexpr unsigned MAX_TRACK = 76; // Maximum valid track
constexpr unsigned MAX_SECTOR = 29; // Maximum valid sector
constexpr unsigned UNKNOWN_TRACK= 0xff; // Current track unknown
constexpr unsigned STEP_MS = 8; // Step time (ms)
constexpr unsigned SETTLING_MS = 8; // Settling time (ms)
constexpr unsigned HEAD_TO_MS = 415; // Head unload timeout (ms)
constexpr unsigned HD_SETTLE_MS = 50; // Head settling time (ms)
constexpr uint16_t PASSWORD = 0xae87; // "Password" to enable commands
constexpr unsigned HALF_CELL_US = 1; // Half bit cell duration (µs)
constexpr unsigned STATUS_DELAY_US = 100; // Status delay (µs)
constexpr unsigned MISSED_ID_REVS = 2; // Disk rotations to stop ID search
// Bits in status word
constexpr unsigned STS_DISK_CHANGED = 2; // Disk changed
constexpr unsigned STS_WRITE_PROTECT= 3; // Write protection
constexpr unsigned STS_NOT_RDY = 4; // Drive not ready
constexpr unsigned STS_SEEK_COMPLETE= 5; // Seek completed
constexpr unsigned STS_XFER_COMPLETE= 6; // Data transfer completed
// Error codes
enum : unsigned {
ERR_NONE = 0,
ERR_NOT_POWERED = 1,
ERR_DOOR_OPEN = 2,
ERR_NO_DISK = 3,
ERR_WR_DISABLED = 4,
ERR_ID_ERROR = 5,
ERR_TRACK_ERROR = 6,
ERR_CRC_ERROR = 7,
ERR_HW_FAILURE = 8
};
hp9885_device::hp9885_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: hp98032_gpio_card_device(mconfig , HP9885 , tag , owner , clock)
, m_drive_connector{*this , "floppy"}
{
}
hp9885_device::~hp9885_device()
{
}
uint16_t hp9885_device::get_jumpers() const
{
return hp98032_gpio_slot_device::JUMPER_2 |
hp98032_gpio_slot_device::JUMPER_7 |
hp98032_gpio_slot_device::JUMPER_8 |
hp98032_gpio_slot_device::JUMPER_B |
hp98032_gpio_slot_device::JUMPER_E |
hp98032_gpio_slot_device::JUMPER_F;
}
uint16_t hp9885_device::input_r() const
{
uint16_t tmp = m_input;
if (m_obf) {
tmp |= m_output;
LOG_HS("DATA OUT %04x\n" , tmp);
}
return tmp;
}
uint8_t hp9885_device::ext_status_r() const
{
return 0;
}
void hp9885_device::output_w(uint16_t data)
{
m_input = data;
}
void hp9885_device::ext_control_w(uint8_t data)
{
LOG_HS("EXT CTRL %u\n" , data);
if (BIT(data , 0) &&
!BIT(m_status , STS_XFER_COMPLETE) &&
(m_op == OP_READ || m_op == OP_WRITE)) {
// CTL0 terminates current data transfer
LOG("xfer terminated\n");
BIT_SET(m_status , STS_XFER_COMPLETE);
// Prepare to output status
set_output();
}
}
WRITE_LINE_MEMBER(hp9885_device::pctl_w)
{
m_pctl = state;
if (m_pctl) {
if (!m_outputting) {
set_ibf(true);
}
} else {
LOG_HS("DATA IN %04x\n" , m_input);
new_word();
}
update_busy();
}
WRITE_LINE_MEMBER(hp9885_device::io_w)
{
LOG_HS("I/O = %d\n" , state);
}
WRITE_LINE_MEMBER(hp9885_device::preset_w)
{
LOG("PRESET = %d\n" , state);
}
static const floppy_format_type hp9885_floppy_formats[] = {
FLOPPY_MFI_FORMAT,
FLOPPY_HPI_FORMAT,
nullptr
};
void hp9885_device::device_add_mconfig(machine_config &config)
{
FLOPPY_CONNECTOR(config , "floppy" , "8ssdd" , FLOPPY_8_SSDD , true , hp9885_floppy_formats).set_fixed(true);
}
void hp9885_device::device_start()
{
save_item(NAME(m_input));
save_item(NAME(m_output));
save_item(NAME(m_status));
save_item(NAME(m_fsm_state));
save_item(NAME(m_head_state));
save_item(NAME(m_op));
save_item(NAME(m_pctl));
save_item(NAME(m_ibf));
save_item(NAME(m_obf));
save_item(NAME(m_outputting));
save_item(NAME(m_had_transition));
save_item(NAME(m_dskchg));
save_item(NAME(m_track));
save_item(NAME(m_seek_track));
save_item(NAME(m_seek_sector));
save_item(NAME(m_sector_cnt));
save_item(NAME(m_word_cnt));
save_item(NAME(m_rev_cnt));
save_item(NAME(m_am_detector));
save_item(NAME(m_crc));
m_fsm_timer = timer_alloc(FSM_TMR_ID);
m_head_timer = timer_alloc(HEAD_TMR_ID);
m_bit_byte_timer = timer_alloc(BIT_BYTE_TMR_ID);
m_drive = m_drive_connector->get_device();
m_drive->setup_ready_cb(floppy_image_device::ready_cb(&hp9885_device::floppy_ready_cb , this));
m_drive->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&hp9885_device::floppy_index_cb , this));
}
void hp9885_device::device_reset()
{
eir_w(0);
psts_w(1);
m_dskchg = true;
m_obf = false;
recalibrate();
m_seek_track = 0;
m_seek_sector = 0;
m_fsm_state = FSM_RECALIBRATING;
set_state(FSM_IDLE);
}
void hp9885_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
LOG_TIMER("Tmr %.06f ID %d FSM %d HD %d\n" , machine().time().as_double() , id , m_fsm_state , m_head_state);
switch (id) {
case FSM_TMR_ID:
do_FSM();
break;
case HEAD_TMR_ID:
if (m_head_state == HEAD_SETTLING) {
LOG_HEAD("%.06f Head loaded\n" , machine().time().as_double());
m_head_state = HEAD_LOADED;
// Trigger actions to be done on head loading
do_FSM();
m_head_timer->adjust(attotime::from_msec(HEAD_TO_MS - HD_SETTLE_MS));
} else {
LOG_HEAD("%.06f Head unloaded\n" , machine().time().as_double());
m_head_state = HEAD_UNLOADED;
}
break;
case BIT_BYTE_TMR_ID:
{
switch (m_fsm_state) {
case FSM_WAIT_ID_AM:
case FSM_WAIT_DATA_AM:
{
attotime edge;
attotime tm;
edge = m_drive->get_next_transition(m_pll.ctime);
bool half_bit = m_pll.feed_read_data(tm , edge , attotime::never);
m_am_detector <<= 1;
m_am_detector |= half_bit;
if (m_am_detector == 0x55552a54) {
// ID AM
// CDCDCDCDCDCDCDCD
// 0 0 0 0 1 1 1 0
// 0 1 1 1 0 0 0 0
LOG_DISK("Got ID AM\n");
preset_crc();
m_word_cnt = 2;
set_state(FSM_RD_ID);
} else if (m_am_detector == 0x55552a44) {
// DATA AM
// CDCDCDCDCDCDCDCD
// 0 0 0 0 1 0 1 0
// 0 1 1 1 0 0 0 0
LOG_DISK("Got Data AM\n");
if (m_fsm_state == FSM_WAIT_DATA_AM) {
m_rev_cnt = 0;
if (BIT(m_status , STS_XFER_COMPLETE)) {
output_status();
return;
} else {
preset_crc();
m_word_cnt = 129;
if (m_op == OP_READ) {
set_state(FSM_RD_DATA);
} else {
set_state(FSM_WR_DATA);
m_pll.start_writing(m_pll.ctime);
m_had_transition = false;
wr_word(m_input);
set_ibf(false);
}
}
}
}
}
break;
case FSM_RD_ID:
{
// This is needed when state is switched to one of the AM waiting states
m_am_detector = 0;
auto word = rd_word();
m_word_cnt--;
LOG_DISK("W %04x C %u\n" , word , m_word_cnt);
if (m_word_cnt && word != ((m_seek_sector << 8) | m_track)) {
set_state(FSM_WAIT_ID_AM);
} else if (m_word_cnt == 0) {
if (m_crc) {
LOG_DISK("Wrong CRC in ID\n");
set_state(FSM_WAIT_ID_AM);
} else {
LOG_DISK("Sector found\n");
set_state(FSM_WAIT_DATA_AM);
}
}
}
break;
case FSM_RD_DATA:
{
auto word = rd_word();
m_word_cnt--;
LOG_DISK("W %04x C %u\n" , word , m_word_cnt);
if (m_word_cnt >= 1) {
if (!BIT(m_status , STS_XFER_COMPLETE)) {
m_output = word;
m_obf = true;
update_busy();
}
} else if (m_word_cnt == 0) {
if (m_crc) {
LOG_DISK("Wrong CRC in data\n");
}
// Move to next sector
adv_sector();
if (BIT(m_status , STS_XFER_COMPLETE) || m_sector_cnt == 0) {
BIT_SET(m_status , STS_XFER_COMPLETE);
output_status();
} else {
set_state(FSM_POSITIONING);
do_FSM();
}
return;
}
}
break;
case FSM_WR_DATA:
{
m_word_cnt--;
if (m_word_cnt > 1) {
if (BIT(m_status , STS_XFER_COMPLETE)) {
wr_word(0);
} else {
wr_word(m_input);
if (m_word_cnt > 2) {
set_ibf(false);
}
}
} else if (m_word_cnt == 1) {
wr_word(m_crc);
} else {
m_pll.stop_writing(m_drive , m_pll.ctime);
// Move to next sector
adv_sector();
if (BIT(m_status , STS_XFER_COMPLETE) || m_sector_cnt == 0) {
BIT_SET(m_status , STS_XFER_COMPLETE);
output_status();
} else {
set_ibf(false);
set_state(FSM_POSITIONING);
do_FSM();
}
return;
}
}
break;
default:
LOG("Invalid FSM state %d\n" , m_fsm_state);
set_state(FSM_IDLE);
return;
}
timer.adjust(m_pll.ctime - machine().time());
}
break;
}
}
void hp9885_device::floppy_ready_cb(floppy_image_device *floppy , int state)
{
LOG("ready %d\n" , state);
if (state) {
// drive not ready
m_dskchg = true;
}
}
void hp9885_device::floppy_index_cb(floppy_image_device *floppy , int state)
{
if (state && m_rev_cnt && --m_rev_cnt == 0) {
// Sector not found
LOG("Sector not found\n");
stop_rdwr();
set_error(ERR_ID_ERROR);
output_status(true);
}
}
void hp9885_device::set_state(int new_state)
{
if (m_fsm_state != new_state) {
LOG("%.06f FSM %d->%d\n" , machine().time().as_double() , m_fsm_state , new_state);
m_fsm_state = new_state;
if (m_fsm_state == FSM_IDLE) {
m_op = OP_NONE;
m_outputting = false;
set_ibf(false);
m_fsm_timer->reset();
stop_rdwr();
m_rev_cnt = 0;
}
}
}
void hp9885_device::init_status(unsigned unit_no)
{
m_status = unit_no & 3;
if (unit_no == 0) {
if (m_drive->ready_r()) {
BIT_SET(m_status , STS_NOT_RDY);
}
if (m_dskchg) {
BIT_SET(m_status , STS_DISK_CHANGED);
}
if (m_drive->wpt_r()) {
BIT_SET(m_status , STS_WRITE_PROTECT);
}
} else {
// Units 1,2,3 are not present
BIT_SET(m_status , STS_NOT_RDY);
}
}
void hp9885_device::encode_error(bool writing)
{
if (m_status & 3) {
set_error(ERR_NOT_POWERED);
} else if (writing && BIT(m_status , STS_WRITE_PROTECT)) {
set_error(ERR_WR_DISABLED);
} else if (BIT(m_status , STS_NOT_RDY)) {
set_error(ERR_NO_DISK);
}
}
void hp9885_device::set_error(unsigned error_code)
{
m_status = (m_status & 0xff) | (error_code << 8);
if (error_code != ERR_NONE) {
LOG_HS("EIR 1\n");
eir_w(1);
psts_w(0);
} else {
LOG_HS("EIR 0\n");
eir_w(0);
psts_w(1);
}
}
void hp9885_device::new_word()
{
unsigned unit_no = (m_input >> 12) & 3;
switch (m_fsm_state) {
case FSM_IDLE:
if (m_input == PASSWORD) {
LOG("Got PW\n");
set_state(FSM_GOT_PW);
} else {
LOG("Wrong sequence\n");
// TODO:
// It probably does nothing IRL
}
set_ibf(false);
break;
case FSM_GOT_PW:
// Decode new command
switch (m_input & 0xc000) {
case 0x0000:
case 0x4000:
// Read
init_status(unit_no);
if (!BIT(m_status , STS_NOT_RDY)) {
m_sector_cnt = m_input & 0x0fff;
LOG("Read %u sectors @%u:%u\n" , m_sector_cnt , m_seek_track , m_seek_sector);
m_op = OP_READ;
set_state(FSM_POSITIONING);
if (load_head()) {
m_fsm_timer->adjust(attotime::zero);
}
set_output();
} else {
encode_error(false);
output_status();
}
break;
case 0x8000:
// Write
init_status(unit_no);
if (!BIT(m_status , STS_NOT_RDY) && !BIT(m_status , STS_WRITE_PROTECT)) {
m_sector_cnt = m_input & 0x0fff;
LOG("Write %u sectors @%u:%u\n" , m_sector_cnt , m_seek_track , m_seek_sector);
m_op = OP_WRITE;
set_state(FSM_POSITIONING);
if (load_head()) {
m_fsm_timer->adjust(attotime::zero);
}
set_ibf(false);
} else {
encode_error(true);
output_status(true);
}
break;
case 0xc000:
{
// Seek & other commands
uint8_t track_no = (m_input >> 5) & 0x7f;
uint8_t sect_no = m_input & 0x1f;
if (sect_no == 0x1e) {
// Format
LOG("Format\n");
// TODO:
} else if (sect_no == 0x1f) {
switch (track_no) {
case 0x7c:
// Step in
init_status(unit_no);
if (!BIT(m_status , STS_NOT_RDY)) {
LOG("Step in\n");
m_seek_track = m_track + 1;
m_op = OP_STEP_IN;
set_state(FSM_SEEKING);
if (load_head()) {
m_fsm_timer->adjust(attotime::zero);
}
} else {
encode_error(false);
output_status();
}
break;
case 0x7d:
// Write all track
LOG("Write all track\n");
// TODO:
break;
case 0x7f:
// Read status
init_status(unit_no);
if (!BIT(m_status , STS_NOT_RDY) && BIT(m_status , STS_DISK_CHANGED)) {
LOG("Get status DSKCHG\n");
m_op = OP_GET_STATUS;
set_state(FSM_POSITIONING);
if (load_head()) {
m_fsm_timer->adjust(attotime::zero);
}
} else {
LOG("Get status !DSKCHG\n");
encode_error(false);
output_status();
}
break;
default:
LOG("Unknown command %02x\n" , track_no);
}
} else {
// Plain seek
LOG("Seek to %u:%u\n" , track_no , sect_no);
m_seek_track = track_no;
m_seek_sector = sect_no;
set_state(FSM_IDLE);
}
}
break;
}
break;
case FSM_RD_STATUS1:
set_error(ERR_NONE);
set_state(FSM_RD_STATUS2);
m_outputting = false;
m_obf = false;
break;
case FSM_RD_STATUS2:
set_state(FSM_IDLE);
break;
case FSM_RD_DATA:
m_obf = false;
break;
default:
if (m_op != OP_WRITE) {
LOG("Got data in state %d!\n" , m_fsm_state);
}
}
}
void hp9885_device::do_FSM()
{
switch (m_fsm_state) {
case FSM_RECALIBRATING:
// Keep head loaded
load_head();
if (m_drive->trk00_r()) {
one_step(true);
} else {
set_state(FSM_POSITIONING);
m_fsm_timer->adjust(attotime::from_msec(SETTLING_MS));
m_track = 0;
}
break;
case FSM_SETTLING:
if (m_op == OP_READ || m_op == OP_WRITE) {
// Set seek complete
BIT_SET(m_status , STS_SEEK_COMPLETE);
if (m_sector_cnt--) {
m_rev_cnt = MISSED_ID_REVS;
set_state(FSM_WAIT_ID_AM);
start_rd();
} else {
output_status();
}
} else if (m_op == OP_STEP_IN) {
// Step IN
// Set seek complete
BIT_SET(m_status , STS_SEEK_COMPLETE);
output_status();
} else {
// Get status
output_status();
}
break;
case FSM_POSITIONING:
case FSM_SEEKING:
// Keep head loaded
load_head();
// Need recalibration?
if (m_track == UNKNOWN_TRACK) {
set_state(FSM_RECALIBRATING);
m_fsm_timer->adjust(attotime::zero);
} else if (m_seek_track != m_track) {
set_state(FSM_SEEKING);
one_step(m_seek_track < m_track);
} else {
if (m_fsm_state == FSM_SEEKING) {
m_fsm_timer->adjust(attotime::from_msec(SETTLING_MS));
} else {
m_fsm_timer->adjust(attotime::zero);
}
set_state(FSM_SETTLING);
}
break;
case FSM_STATUS_DELAY:
output_status();
break;
default:
LOG("Invalid state=%d\n" , m_fsm_state);
set_state(FSM_IDLE);
}
update_busy();
}
bool hp9885_device::load_head()
{
m_dskchg = false;
switch (m_head_state) {
case HEAD_UNLOADED:
case HEAD_SETTLING:
LOG_HEAD("%.06f Loading head..\n" , machine().time().as_double());
m_head_state = HEAD_SETTLING;
m_head_timer->adjust(attotime::from_msec(HD_SETTLE_MS));
return false;
case HEAD_LOADED:
LOG_HEAD("%.06f Keep head loaded\n" , machine().time().as_double());
m_head_timer->adjust(attotime::from_msec(HEAD_TO_MS));
return true;
default:
LOG("Invalid head state %d\n" , m_head_state);
m_head_state = HEAD_UNLOADED;
return false;
}
}
void hp9885_device::recalibrate()
{
m_track = UNKNOWN_TRACK;
}
void hp9885_device::one_step(bool outward)
{
if (outward) {
if (m_track > 0) {
m_track--;
}
} else {
if (m_track < MAX_TRACK) {
m_track++;
}
}
LOG_HEAD("%.06f Step to trk %u\n" , machine().time().as_double() , m_track);
m_drive->dir_w(outward);
m_drive->stp_w(0);
m_drive->stp_w(1);
m_fsm_timer->adjust(attotime::from_msec(STEP_MS));
}
void hp9885_device::adv_sector()
{
if (++m_seek_sector > MAX_SECTOR) {
m_seek_sector = 0;
if (m_seek_track < MAX_TRACK) {
m_seek_track++;
}
}
}
void hp9885_device::start_rd()
{
m_pll.set_clock(attotime::from_usec(HALF_CELL_US));
m_pll.read_reset(machine().time());
m_bit_byte_timer->adjust(attotime::zero);
m_am_detector = 0;
}
void hp9885_device::stop_rdwr()
{
m_bit_byte_timer->reset();
}
uint16_t hp9885_device::rd_word()
{
uint16_t word = 0;
for (unsigned i = 0; i < 16; ++i) {
attotime edge;
attotime tm;
edge = m_drive->get_next_transition(m_pll.ctime);
// Read & discard clock bit
m_pll.feed_read_data(tm , edge , attotime::never);
edge = m_drive->get_next_transition(m_pll.ctime);
bool data_bit = m_pll.feed_read_data(tm , edge , attotime::never);
word >>= 1;
if (data_bit) {
BIT_SET(word , 15);
}
update_crc(data_bit);
}
return word;
}
void hp9885_device::wr_word(uint16_t word)
{
for (unsigned i = 0; i < 16; ++i) {
bool data_bit = BIT(word , i);
bool clock_bit = !data_bit && !m_had_transition;
m_had_transition = data_bit || clock_bit;
attotime dummy;
m_pll.write_next_bit(clock_bit , dummy , nullptr , attotime::never);
m_pll.write_next_bit(data_bit , dummy , nullptr , attotime::never);
update_crc(data_bit);
}
m_pll.commit(m_drive , m_pll.ctime);
}
void hp9885_device::preset_crc()
{
m_crc = ~0;
}
void hp9885_device::update_crc(bool bit)
{
bool crc_x15 = BIT(m_crc , 0);
m_crc >>= 1;
if (bit ^ crc_x15) {
m_crc ^= 0x8408;
}
}
void hp9885_device::set_ibf(bool state)
{
m_ibf = state;
update_busy();
}
void hp9885_device::set_output()
{
m_outputting = true;
m_obf = false;
set_ibf(false);
}
void hp9885_device::output_status(bool delayed)
{
stop_rdwr();
if (delayed) {
set_output();
set_state(FSM_STATUS_DELAY);
m_fsm_timer->adjust(attotime::from_usec(STATUS_DELAY_US));
} else {
set_state(FSM_RD_STATUS1);
m_outputting = true;
m_obf = true;
// Set status in output buffer
m_output = m_status;
set_ibf(false);
}
}
void hp9885_device::update_busy()
{
bool busy = (!m_outputting && m_ibf) || (m_outputting && m_pctl && m_obf);
LOG_HS("PCTL %d BUSY %d OUT %d IBF %d OBF %d\n" , m_pctl , busy , m_outputting , m_ibf , m_obf);
pflg_w(!busy);
}

View File

@ -0,0 +1,136 @@
// license:BSD-3-Clause
// copyright-holders: F. Ulivi
/*********************************************************************
hp9885.h
HP9885M 8" floppy drive
*********************************************************************/
#ifndef MAME_BUS_HP9845_IO_HP9885_H
#define MAME_BUS_HP9845_IO_HP9885_H
#pragma once
#include "98032.h"
#include "imagedev/floppy.h"
#include "machine/fdc_pll.h"
class hp9885_device : public hp98032_gpio_card_device
{
public:
hp9885_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
virtual ~hp9885_device();
// hp98032_gpio_card_device overrides
virtual uint16_t get_jumpers() const override;
virtual uint16_t input_r() const override;
virtual uint8_t ext_status_r() const override;
virtual void output_w(uint16_t data) override;
virtual void ext_control_w(uint8_t data) override;
virtual DECLARE_WRITE_LINE_MEMBER(pctl_w) override;
virtual DECLARE_WRITE_LINE_MEMBER(io_w) override;
virtual DECLARE_WRITE_LINE_MEMBER(preset_w) override;
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;
private:
// FSM states
enum {
FSM_IDLE,
FSM_RECALIBRATING,
FSM_SETTLING,
FSM_GOT_PW,
FSM_POSITIONING,
FSM_SEEKING,
FSM_STATUS_DELAY,
FSM_RD_STATUS1,
FSM_RD_STATUS2,
FSM_WAIT_ID_AM,
FSM_RD_ID,
FSM_WAIT_DATA_AM,
FSM_RD_DATA,
FSM_WR_DATA
};
// Head states
enum {
HEAD_UNLOADED,
HEAD_SETTLING,
HEAD_LOADED
};
// Operations
enum {
OP_NONE,
OP_READ,
OP_WRITE,
OP_STEP_IN,
OP_GET_STATUS
};
required_device<floppy_connector> m_drive_connector;
floppy_image_device *m_drive;
uint16_t m_input;
uint16_t m_output;
uint16_t m_status;
int m_fsm_state;
int m_head_state;
int m_op;
bool m_pctl;
bool m_ibf;
bool m_obf;
bool m_outputting;
bool m_had_transition;
bool m_dskchg;
unsigned m_track;
unsigned m_seek_track;
unsigned m_seek_sector;
unsigned m_sector_cnt;
unsigned m_word_cnt;
unsigned m_rev_cnt;
uint32_t m_am_detector;
uint16_t m_crc; // x^15 is stored in LSB
// Timers
emu_timer *m_fsm_timer;
emu_timer *m_head_timer;
emu_timer *m_bit_byte_timer;
// PLL
fdc_pll_t m_pll;
void floppy_ready_cb(floppy_image_device *floppy , int state);
void floppy_index_cb(floppy_image_device *floppy , int state);
void set_state(int new_state);
void init_status(unsigned unit_no);
void encode_error(bool writing);
void set_error(unsigned error_code);
void new_word();
void do_FSM();
bool load_head();
void recalibrate();
void one_step(bool outward);
void adv_sector();
void start_rd();
void stop_rdwr();
uint16_t rd_word();
void wr_word(uint16_t word);
void preset_crc();
void update_crc(bool bit);
void set_ibf(bool state);
void set_output();
void output_status(bool delayed = false);
void update_busy();
};
DECLARE_DEVICE_TYPE(HP9885 , hp9885_device)
#endif /* MAME_BUS_HP9845_IO_HP9885_H */

View File

@ -4,9 +4,10 @@
hpi_dsk.cpp
HP9895A "HPI" disk images
HP9885/HP9895A "HPI" disk images
CHS = 77/2/30
CHS = 67/1/30 (SSDD)
CHS = 77/2/30 (DSDD)
Sector size 256 bytes
Cell size 2 µs
Gap1 = 16 x 0x00
@ -40,7 +41,7 @@
256 bytes per sector). There's also a "reduced" version holding
just 75 cylinders.
When loading, the disk image is translated to MMFM encoding so
that it can be loaded into HP9895 emulator.
that it can be loaded into HP9885/HP9895 emulator.
*********************************************************************/
@ -65,6 +66,7 @@ constexpr int ID_DATA_OFFSET = 30 * 16; // Nominal distance (in cells) between I
// Size of image file (holding 77 cylinders)
constexpr unsigned HPI_IMAGE_SIZE = HPI_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
constexpr unsigned HPI_RED_TRACKS = 75; // Reduced number of tracks
constexpr unsigned HPI_9885_TRACKS = 67; // Tracks visible to HP9885 drives
// Size of reduced image file (holding 75 cylinders)
constexpr unsigned HPI_RED_IMAGE_SIZE = HPI_RED_TRACKS * HPI_HEADS * HPI_SECTORS * HPI_SECTOR_SIZE;
@ -79,27 +81,79 @@ int hpi_format::identify(io_generic *io, uint32_t form_factor)
// we try to stay back and give only 50 points, since another image
// format may also have images of the same size (there is no header and no
// magic number for HPI format...
unsigned dummy_heads;
unsigned dummy_tracks;
if (((form_factor == floppy_image::FF_8) || (form_factor == floppy_image::FF_UNKNOWN)) &&
((size == HPI_RED_IMAGE_SIZE) || (size == HPI_IMAGE_SIZE))) {
geometry_from_size(size , dummy_heads , dummy_tracks)) {
return 50;
} else {
return 0;
}
}
bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
bool hpi_format::geometry_from_size(uint64_t image_size , unsigned& heads , unsigned& tracks)
{
image->set_variant(floppy_image::DSDD); // We actually need to derive the variant from the image size depending on the form factor
uint64_t size = io_generic_size(io);
unsigned cylinders;
if (size == HPI_RED_IMAGE_SIZE) {
cylinders = HPI_RED_TRACKS;
} else if (size == HPI_IMAGE_SIZE) {
cylinders = HPI_TRACKS;
} else {
if ((image_size % HPI_SECTOR_SIZE) != 0) {
// Not a whole number of sectors
return false;
}
unsigned sectors = unsigned(image_size / HPI_SECTOR_SIZE);
if ((sectors % HPI_SECTORS) != 0) {
// Not a whole number of tracks
return false;
}
unsigned tot_tracks = sectors / HPI_SECTORS;
// Possible combinations
//
// | Tot tracks | Heads | Tracks | Format |
// |------------+-------+--------+--------|
// | 67 | 1 | 67 | SSDD |
// | 77 | 1 | 77 | SSDD |
// | 150 | 2 | 75 | DSDD |
// | 154 | 2 | 77 | DSDD |
//
switch (tot_tracks) {
case HPI_9885_TRACKS:
heads = 1;
tracks = HPI_9885_TRACKS;
return true;
case HPI_TRACKS:
heads = 1;
tracks = HPI_TRACKS;
return true;
case HPI_RED_TRACKS * 2:
heads = 2;
tracks = HPI_RED_TRACKS;
return true;
case HPI_TRACKS * 2:
heads = 2;
tracks = HPI_TRACKS;
return true;
default:
return false;
}
}
bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
{
unsigned heads;
unsigned cylinders;
uint64_t size = io_generic_size(io);
if (!geometry_from_size(size, heads, cylinders)) {
return false;
}
int max_tracks;
int max_heads;
image->get_maximal_geometry(max_tracks , max_heads);
if (cylinders > max_tracks || heads > max_heads) {
return false;
}
image->set_variant(heads == 2 ? floppy_image::DSDD : floppy_image::SSDD);
// Suck in the whole image
std::vector<uint8_t> image_data(size);
@ -118,11 +172,11 @@ bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
unsigned list_offset = 0;
for (unsigned cyl = 0; cyl < cylinders; cyl++) {
for (unsigned head = 0; head < HPI_HEADS; head++) {
for (unsigned head = 0; head < heads; head++) {
std::vector<uint32_t> track_data;
for (unsigned sector = 0; sector < HPI_SECTORS; sector++) {
unsigned real_sector = sector_list[ (sector + list_offset) % HPI_SECTORS ];
unsigned offset_in_image = chs_to_lba(cyl, head, real_sector) * HPI_SECTOR_SIZE;
unsigned offset_in_image = chs_to_lba(cyl, head, real_sector, heads) * HPI_SECTOR_SIZE;
write_sector(track_data , cyl , real_sector + (head << 7) , &image_data[ offset_in_image ]);
}
fill_with_gap3(track_data);
@ -135,8 +189,12 @@ bool hpi_format::load(io_generic *io, uint32_t form_factor, floppy_image *image)
bool hpi_format::save(io_generic *io, floppy_image *image)
{
for (unsigned cyl = 0; cyl < HPI_TRACKS; cyl++) {
for (unsigned head = 0; head < HPI_HEADS; head++) {
int tracks;
int heads;
image->get_actual_geometry(tracks, heads);
for (int cyl = 0; cyl < tracks; cyl++) {
for (int head = 0; head < heads; head++) {
uint8_t bitstream[ 21000 ];
int bitstream_size;
generate_bitstream_from_track(cyl , head , CELL_SIZE , bitstream , bitstream_size , image , 0);
@ -145,7 +203,7 @@ bool hpi_format::save(io_generic *io, floppy_image *image)
uint8_t sector_data[ HPI_SECTOR_SIZE ];
while (get_next_sector(bitstream , bitstream_size , pos , track_no , head_no , sector_no , sector_data)) {
if (track_no == cyl && head_no == head && sector_no < HPI_SECTORS) {
unsigned offset_in_image = chs_to_lba(cyl, head, sector_no) * HPI_SECTOR_SIZE;
unsigned offset_in_image = chs_to_lba(cyl, head, sector_no, heads) * HPI_SECTOR_SIZE;
io_generic_write(io, sector_data, offset_in_image, HPI_SECTOR_SIZE);
}
}
@ -300,9 +358,9 @@ void hpi_format::fill_with_gap3(std::vector<uint32_t> &buffer)
}
}
unsigned hpi_format::chs_to_lba(unsigned cylinder , unsigned head , unsigned sector)
unsigned hpi_format::chs_to_lba(unsigned cylinder , unsigned head , unsigned sector , unsigned heads)
{
return sector + (head + cylinder * HPI_HEADS) * HPI_SECTORS;
return sector + (head + cylinder * heads) * HPI_SECTORS;
}
std::vector<uint8_t> hpi_format::get_next_id_n_block(const uint8_t *bitstream , int bitstream_size , int& pos , int& start_pos)

View File

@ -36,6 +36,7 @@ public:
private:
typedef std::array<uint8_t , HPI_SECTORS> sector_list_t;
static bool geometry_from_size(uint64_t image_size , unsigned& heads , unsigned& tracks);
static void interleaved_sectors(unsigned il_factor , sector_list_t& sector_list);
void write_mmfm_bit(std::vector<uint32_t> &buffer , bool data_bit , bool clock_bit);
void write_mmfm_byte(std::vector<uint32_t> &buffer , uint8_t data , uint8_t clock = 0);
@ -43,7 +44,7 @@ private:
void write_crc(std::vector<uint32_t> &buffer , uint16_t crc);
void write_sector(std::vector<uint32_t> &buffer , uint8_t track_no , uint8_t sect_head_no , const uint8_t *sect_data);
void fill_with_gap3(std::vector<uint32_t> &buffer);
static unsigned chs_to_lba(unsigned cylinder , unsigned head , unsigned sector);
static unsigned chs_to_lba(unsigned cylinder , unsigned head , unsigned sector , unsigned heads);
std::vector<uint8_t> get_next_id_n_block(const uint8_t *bitstream , int bitstream_size , int& pos , int& start_pos);
bool get_next_sector(const uint8_t *bitstream , int bitstream_size , int& pos , unsigned& track , unsigned& head , unsigned& sector , uint8_t *sector_data);

View File

@ -17,10 +17,16 @@
// - Printer
// - Beeper
// - Internal expansion ROMs
// - I/O expansion slots: 98032, 98034 & 98035 modules can be connected
// What's not yet in:
// - External expansion ROMs
// - Configurable RAM size
// - I/O expansion slots: 98034 & 98035 modules from hp9845 emulation can be used here, too
//
// Thanks to Dyke Shaffer for publishing (on https://groups.io/g/VintHPcom)
// the source code of 98217 mass memory ROM. The 98217.bin image was reconstructed
// by re-assembling the source code (this is the reason why it's marked as
// a BAD_DUMP). And thanks to Ansgar Kueckes for adapting his assembler to
// handle HP9825 source files.
//
// 9825A & 9825T can also be emulated. At the moment I haven't all the necessary
// ROM dumps, though.
@ -225,8 +231,9 @@ void hp9825_state::cpu_io_map(address_map &map)
void hp9825_state::cpu_mem_map(address_map &map)
{
map.unmap_value_low();
map(0x0000 , 0x2fff).rom();
map(0x3400 , 0x3bff).rom();
// map(0x0000 , 0x2fff).rom();
// map(0x3400 , 0x3bff).rom();
map(0x0000 , 0x3bff).rom();
map(0x4000 , 0x4fff).rom();
map(0x5000 , 0x7fff).ram();
}
@ -824,6 +831,7 @@ ROM_START(hp9825b)
ROM_LOAD("sysrom1.bin" , 0x0000 , 0x2000 , CRC(fe429268) SHA1(f2fe7c5abca92bd13f81b4385fc4fce0cafb0da0))
ROM_LOAD("sysrom2.bin" , 0x2000 , 0x2000 , CRC(96093b5d) SHA1(c6ec4cafd019887df0fa849b3c7070bb74faee54))
ROM_LOAD("sysrom3.bin" , 0x4000 , 0x2000 , CRC(f9470f67) SHA1(b80cb4a366d93bd7acc3508ce987bb11c5986b2a))
ROM_LOAD("98217.bin" , 0x6000 , 0x0800 , BAD_DUMP CRC(ea1fcf63) SHA1(e535c82897210a1c67c1ca16f44f936d4c470463))
ROM_LOAD("genio_t.bin" , 0x6800 , 0x0800 , CRC(ade1d1ed) SHA1(9af74a65b29ef1885f74164238ecf8d16ac995d6))
ROM_LOAD("plot72.bin" , 0x7000 , 0x0800 , CRC(0a9cb8db) SHA1(d0d126fca108f2715e1e408cb31b09ba69385ac4))
ROM_LOAD("advpgm_t.bin", 0x8000 , 0x0800 , CRC(965b5e5a) SHA1(ff44dd15f8fa4ca03dfd970ed8b200e8a071ec13))