diff --git a/src/devices/bus/ieee488/hp9895.cpp b/src/devices/bus/ieee488/hp9895.cpp index be874511939..30df3426ac1 100644 --- a/src/devices/bus/ieee488/hp9895.cpp +++ b/src/devices/bus/ieee488/hp9895.cpp @@ -2,12 +2,55 @@ // copyright-holders: F. Ulivi /********************************************************************* - hp9895.cpp + hp9895.cpp - HP9895 floppy disk drive + HP9895 floppy disk drive - Reference manual: - HP 09895-90030, feb 81, 9895A Flexible Disc Memory Service Manual + Phew, this one was tough! + + This is a dual 8" floppy disk drive that interfaces through + HPIB/IEEE-488 bus. It implements the so-called "Amigo" command + set. + + Its main components are: + * A Z80A CPU @ 4 MHz with 8 kB of firmware ROM and 1 kB of + static RAM + * A HP PHI chip that interfaces CPU to HPIB bus + * A disk controller implemented with a lot of discrete TTLs + * 2 MPI 8" disk drives + + Data I/O with the disk is carried out through 2 shift registers, + one for data bits (@ 0x60 address) and one for clock bits (@ 0x61 + address). CPU is stalled by setting WAIT/ to 0 whenever it accesses + the data register and the hw is not ready for the byte. Once + the next byte boundary is reached (the SDOK signal activates) the + CPU is released and either the data byte is read from shift register + or written into it. At the same time clock shift register is + copied into clock register when reading or viceversa when writing. + + The 9895 drive can operate in 2 modes: HP/High density or IBM/low + density. This table summarizes the differences between the modes. + See also page 2-12 of service manual. + + | Characteristic | HP mode | IBM mode | + |----------------+----------+-----------| + | Bit cell size | 2 µs | 4 µs | + | Modulation | MMFM | FM | + | Bit order | LS first | MS first | + | Sync bytes | 4x FF | 6x 00 | + | Formatted size | 1155 kB | 250.25 kB | + + Reference manual: + HP 09895-90030, feb 81, 9895A Flexible Disc Memory Service Manual + + Reference manual for the floppy drives: + Magnetic Peripherals, inc., feb 83, 9406-4 Flexible Disk Drive + Hardware Maintenance Manual + + TODO/Issues: + * floppy_image_device sometimes reports the wrong state for ready & + wpt signals + * IBM mode hasn't been tested yet *********************************************************************/ @@ -16,31 +59,338 @@ // Debugging #define VERBOSE 1 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) +#define VERBOSE_0 0 +#define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) + +// Macros to clear/set single bits +#define BIT_MASK(n) (1U << (n)) +#define BIT_CLR(w , n) ((w) &= ~BIT_MASK(n)) +#define BIT_SET(w , n) ((w) |= BIT_MASK(n)) + +// Bits in RESET register +#define REG_RESET_TIMEOUT_START_BIT 0 // Start TIMEOUT oneshot (1) +#define REG_RESET_OVERUN_CLEAR_BIT 1 // Clear OVERUN (sic) (1) +#define REG_RESET_PROGRES_BIT 3 // PROGRES (1) + +// Bits in CNTL register +#define REG_CNTL_READON_BIT 1 // Enable reading (1) +#define REG_CNTL_WRITON_BIT 2 // Enable writing (1) +#define REG_CNTL_WRITDRV_BIT 3 // Enable writing to floppy (1) +#define REG_CNTL_CRCOUT_BIT 4 // Enable output of CRC word (1) +#define REG_CNTL_CRCON_BIT 5 // Enable updating of CRC word (1) or preset CRC to 0xffff (0) + +// Bits in DRV register +#define REG_DRV_STEP_BIT 0 // Step pulse to drive (1) +#define REG_DRV_MOVEIN_BIT 1 // Move heads inward (1) +#define REG_DRV_MGNENA_BIT 2 // Enable checking of bit cell margins (1) +#define REG_DRV_IN_USE_BIT 3 // "In use" signal to drive (1) +#define REG_DRV_LOWCURR_BIT 4 // Reduce write current in inner tracks (1) +#define REG_DRV_HEADSEL_BIT 7 // Head selection (1 = Head 1) + +// Bits in XV register +#define REG_XV_DRIVE3_BIT 0 // Select drive #3 (1) +#define REG_XV_DRIVE2_BIT 1 // Select drive #2 (1) +#define REG_XV_DRIVE1_BIT 2 // Select drive #1 (1) +#define REG_XV_DRIVE0_BIT 3 // Select drive #0 (1) +#define REG_XV_HIDEN_BIT 4 // Select HP/High density mode (1) or IBM/Low density mode (0) +#define REG_XV_PRECMP_BIT 5 // Enable pre-compensation + +// Bits in DRIVSTAT register +#define REG_DRIVSTAT_INDEX_BIT 0 // Index pulse from drive (1) +#define REG_DRIVSTAT_DISCHNG_BIT 1 // Disk changed (1) +#define REG_DRIVSTAT_TRACK0_BIT 2 // Heads on track #0 (1) +#define REG_DRIVSTAT_WRPROT_BIT 3 // Disk is write-protected (1) +#define REG_DRIVSTAT_READY_BIT 4 // Disk is ready (1) +#define REG_DRIVSTAT_CRCERR_BIT 5 // Error in CRC (1) +#define REG_DRIVSTAT_OVERUN_BIT 6 // I/O overrun between disk and CPU (1) +#define REG_DRIVSTAT_TWOSIDE_BIT 7 // 2-sided disk (1) + +// Bits in SWITCHES(2) registers +#define REG_SWITCHES_HPIB_ADDR_SHIFT 0 // LSB of HPIB address +#define REG_SWITCHES_HPIB_ADDR_MASK 7 // Mask of HPIB address +#define REG_SWITCHES_W_TEST_BIT 3 // "W" test push-button (1) +#define REG_SWITCHES_S_TEST_BIT 4 // "S" test push-button (1) +#define REG_SWITCHES_LOOP_BIT 5 // Test loop option (1) +#define REG_SWITCHES_TIMEOUT_BIT 6 // TIMEOUT (1) +#define REG_SWITCHES_AMDT_BIT 7 // Address mark detected (1) + +// Timers +enum { + TIMEOUT_TMR_ID, + BYTE_TMR_ID, + HALF_BIT_TMR_ID +}; + +// Timings +#define TIMEOUT_MSEC 450 // Timeout duration (ms) +#define HPMODE_BIT_FREQ 500000 // HP-mode bit frequency (Hz) +#define IBMMODE_BIT_FREQ 250000 // IBM-mode bit frequency (Hz) + +#define MIN_SYNC_BITS 29 // Number of bits to synchronize // device type definition const device_type HP9895 = &device_creator; +// Masks of drive selectors in XV register +static const uint8_t xv_drive_masks[] = { + BIT_MASK(REG_XV_DRIVE0_BIT), + BIT_MASK(REG_XV_DRIVE1_BIT) +}; + hp9895_device::hp9895_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, HP9895, "HP9895", tag, owner, clock, "HP9895", __FILE__), device_ieee488_interface(mconfig, *this), m_cpu(*this , "cpu"), - m_phi(*this , "phi") + m_phi(*this , "phi"), + m_drives{{*this , "floppy0"} , {*this , "floppy1"}}, + m_switches{*this , "switches"} { } -#if 0 +static INPUT_PORTS_START(hp9895_port) + PORT_START("switches") + PORT_CONFNAME(REG_SWITCHES_HPIB_ADDR_MASK << REG_SWITCHES_HPIB_ADDR_SHIFT , 0x00 , "HPIB address") + PORT_CONFSETTING(0 << REG_SWITCHES_HPIB_ADDR_SHIFT , "0") + PORT_CONFSETTING(1 << REG_SWITCHES_HPIB_ADDR_SHIFT , "1") + PORT_CONFSETTING(2 << REG_SWITCHES_HPIB_ADDR_SHIFT , "2") + PORT_CONFSETTING(3 << REG_SWITCHES_HPIB_ADDR_SHIFT , "3") + PORT_CONFSETTING(4 << REG_SWITCHES_HPIB_ADDR_SHIFT , "4") + PORT_CONFSETTING(5 << REG_SWITCHES_HPIB_ADDR_SHIFT , "5") + PORT_CONFSETTING(6 << REG_SWITCHES_HPIB_ADDR_SHIFT , "6") + PORT_CONFSETTING(7 << REG_SWITCHES_HPIB_ADDR_SHIFT , "7") + PORT_CONFNAME(BIT_MASK(REG_SWITCHES_W_TEST_BIT) , 0x00 , "W Test") + PORT_CONFSETTING(0x00 , DEF_STR(Off)) + PORT_CONFSETTING(BIT_MASK(REG_SWITCHES_W_TEST_BIT) , DEF_STR(On)) + PORT_CONFNAME(BIT_MASK(REG_SWITCHES_S_TEST_BIT) , 0x00 , "S Test") + PORT_CONFSETTING(0x00 , DEF_STR(Off)) + PORT_CONFSETTING(BIT_MASK(REG_SWITCHES_S_TEST_BIT) , DEF_STR(On)) + PORT_CONFNAME(BIT_MASK(REG_SWITCHES_LOOP_BIT) , 0x00 , "Loop") + PORT_CONFSETTING(0x00 , DEF_STR(Off)) + PORT_CONFSETTING(BIT_MASK(REG_SWITCHES_LOOP_BIT) , DEF_STR(On)) +INPUT_PORTS_END + ioport_constructor hp9895_device::device_input_ports() const { - // TODO: inputs=HPIB address, "S" & "W" switches, "loop" pin + return INPUT_PORTS_NAME(hp9895_port); } -#endif + void hp9895_device::device_start() { + save_item(NAME(m_cpu_irq)); + save_item(NAME(m_current_drive_idx)); + save_item(NAME(m_dskchg)); + save_item(NAME(m_crc)); + save_item(NAME(m_crcerr_syn)); + save_item(NAME(m_overrun)); + save_item(NAME(m_accdata)); + save_item(NAME(m_timeout)); + save_item(NAME(m_cntl_reg)); + save_item(NAME(m_clock_sr)); + save_item(NAME(m_clock_reg)); + save_item(NAME(m_data_sr)); + save_item(NAME(m_wr_context)); + save_item(NAME(m_had_transition)); + save_item(NAME(m_lckup)); + save_item(NAME(m_amdt)); + save_item(NAME(m_sync_cnt)); + save_item(NAME(m_hiden)); + save_item(NAME(m_mgnena)); + + m_timeout_timer = timer_alloc(TIMEOUT_TMR_ID); + m_byte_timer = timer_alloc(BYTE_TMR_ID); + m_half_bit_timer = timer_alloc(HALF_BIT_TMR_ID); + + for (auto& d : m_drives) { + d->get_device()->setup_ready_cb(floppy_image_device::ready_cb(&hp9895_device::floppy_ready_cb , this)); + } } void hp9895_device::device_reset() { m_cpu_irq = false; + m_current_drive = nullptr; + m_current_drive_idx = ~0; + for (auto& d : m_dskchg) { + d = true; + } + preset_crc(); + m_crcerr_syn = false; + m_overrun = false; + m_accdata = false; + m_timeout = true; + m_cntl_reg = 0; + m_clock_sr = 0; + m_clock_reg = 0; + m_data_sr = 0; + m_wr_context = 0; + m_had_transition = false; + m_lckup = true; // Because READON = 0 + m_amdt = false; + m_sync_cnt = 0; + m_hiden = false; + m_mgnena = false; + m_timeout_timer->reset(); + m_byte_timer->reset(); + m_half_bit_timer->reset(); +#if 0 + // DEBUG DEBUG DEBUG DEBUG + for (auto& r : m_ready) r = 2; +#endif +} + +void hp9895_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) { + case TIMEOUT_TMR_ID: + LOG(("Timeout!\n")); + m_timeout = true; + if (m_mgnena) { + // CPU is resumed by timeout if MGNENA=1 + m_cpu->trigger(1); + } + break; + + case BYTE_TMR_ID: + { + if (m_accdata) { + // Resume CPU when it's waiting for SDOK + m_cpu->trigger(1); + } else { + // No access to data register by CPU + LOG(("Data overrun!\n")); + m_overrun = true; + } + m_accdata = false; + + m_crcerr_syn = m_crc != 0; + + if (!BIT(m_cntl_reg , REG_CNTL_CRCON_BIT)) { + // CRC not enabled, keep it in preset state (all ones) + preset_crc(); + } + + attotime sdok_time{machine().time()}; + LOG_0(("SDOK @ %.06f\n" , sdok_time.as_double())); + bool do_crc_upd = true; + if (BIT(m_cntl_reg , REG_CNTL_WRITON_BIT)) { + // Writing + m_pll.commit(get_write_device() , sdok_time); + m_pll.ctime = sdok_time; + + // Check for AMDT when in loopback mode + if (!m_lckup && !m_amdt && BIT(m_cntl_reg , REG_CNTL_READON_BIT)) { + if (m_hiden) { + m_amdt = m_data_sr != 0xff; + } else { + m_amdt = m_data_sr != 0; + } + } + + LOG_0(("WR D=%02x/C=%02x\n" , m_data_sr , m_clock_sr)); + do_crc_upd = false; + for (unsigned i = 0; i < 8; i++) { + bool clock_bit; + bool data_bit; + + clock_bit = shift_sr(m_clock_sr, false); + data_bit = shift_sr(m_data_sr, true); + + if (BIT(m_cntl_reg , REG_CNTL_CRCOUT_BIT)) { + // Substitute data bits from DSR with those from CRC when CRCOUT=1 + data_bit = BIT(m_crc , 15); + m_crc <<= 1; + } else if (BIT(m_cntl_reg , REG_CNTL_CRCON_BIT)) { + // Update CRC + update_crc(data_bit); + } + write_bit(data_bit, clock_bit); + } + // When shifting is done DSR is filled with 1s and CSR with 0s + } + if (BIT(m_cntl_reg , REG_CNTL_READON_BIT)) { + // Reading + m_pll.ctime = sdok_time; + + for (unsigned i = 0; i < 8; i++) { + read_bit(do_crc_upd); + } + LOG_0(("RD D=%02x/C=%02x\n" , m_data_sr , m_clock_sr)); + } + LOG_0(("next SDOK @ %.06f\n" , m_pll.ctime.as_double())); + timer.adjust(m_pll.ctime - sdok_time); + } + break; + + case HALF_BIT_TMR_ID: + { + m_pll.ctime = machine().time(); + if (m_lckup) { + // Trying to lock on synchronization bytes + attotime edge; + attotime tm; + get_next_transition(m_pll.ctime, edge); + bool half_bit0 = m_pll.feed_read_data(tm , edge , attotime::never); + get_next_transition(m_pll.ctime, edge); + bool half_bit1 = m_pll.feed_read_data(tm , edge , attotime::never); + if (half_bit0 == half_bit1) { + // If half bits are equal, no synch + LOG_0(("Reset sync_cnt\n")); + m_sync_cnt = 0; + } else if (++m_sync_cnt >= MIN_SYNC_BITS) { + // Synchronized, now wait for AM + LOG_0(("Synchronized @ %.6f\n" , machine().time().as_double())); + m_lckup = false; + if (BIT(m_cntl_reg , REG_CNTL_WRITON_BIT)) { + // When loopback is active, leave AM detection to byte timer as + // byte boundary is already synchronized + timer.reset(); + return; + } else { + // Align with bit cell + // Synchronization bits in HP mode: 32x 1s -> C/D bits = 01010101... + // Synchronization bits in IBM mode: 32x 0s -> C/D bits = 10101010... + if (m_hiden != half_bit1) { + // Discard 1/2 bit cell if synchronization achieved in the clock part + get_next_transition(m_pll.ctime, edge); + m_pll.feed_read_data(tm , edge , attotime::never); + } + // Load CSR & DSR as they are after synchronization bits + if (m_hiden) { + m_clock_sr = 0; + m_data_sr = ~0; + } else { + m_clock_sr = ~0; + m_data_sr = 0; + } + } + } + } else { + // Looking for AM + /// CRC is not updated because it can't be possibly enabled at this point + read_bit(false); + if ((m_hiden && !BIT(m_data_sr , 7)) || + (!m_hiden && BIT(m_data_sr , 0))) { + // Got AM as soon as bits being shifted into DSR change value wrt synchronization bits + m_amdt = true; + // Finish the current byte + for (unsigned i = 0; i < 7; i++) { + read_bit(false); + } + attotime adjust{m_pll.ctime - machine().time()}; + LOG_0(("Got AM @ %.6f, ctime=%.6f, adj=%.6f, D=%02x/C=%02x\n" , machine().time().as_double() , m_pll.ctime.as_double() , adjust.as_double() , m_data_sr , m_clock_sr)); + // Disable half-bit timer & enable byte timer + timer.reset(); + m_byte_timer->adjust(adjust); + return; + } + } + timer.adjust(m_pll.ctime - machine().time()); + } + break; + + default: + break; + } } void hp9895_device::ieee488_eoi(int state) @@ -160,6 +510,349 @@ WRITE16_MEMBER(hp9895_device::z80_m1_w) } } +WRITE8_MEMBER(hp9895_device::data_w) +{ + LOG_0(("W DATA=%02x\n" , data)); + // CPU stalls until next SDOK + m_cpu->suspend_until_trigger(1 , true); + m_data_sr = data; + m_clock_sr = m_clock_reg; + m_accdata = true; +} + +WRITE8_MEMBER(hp9895_device::clock_w) +{ + LOG_0(("W CLOCK=%02x\n" , data)); + m_clock_reg = data; +} + +WRITE8_MEMBER(hp9895_device::reset_w) +{ + LOG_0(("W RESET=%02x\n" , data)); + if (BIT(data , REG_RESET_TIMEOUT_START_BIT)) { + m_timeout = false; + m_timeout_timer->adjust(attotime::from_msec(TIMEOUT_MSEC)); + } + if (BIT(data , REG_RESET_OVERUN_CLEAR_BIT)) { + m_overrun = false; + } + // TODO: PROGRES +} + +WRITE8_MEMBER(hp9895_device::leds_w) +{ + LOG(("W LEDS=%02x %c%c%c%c%c\n" , data , BIT(data , 4) ? '.' : '*' , BIT(data , 3) ? '.' : '*' , BIT(data , 2) ? '.' : '*' , BIT(data , 1) ? '.' : '*' , BIT(data , 0) ? '.' : '*')); + // TODO: +} + +WRITE8_MEMBER(hp9895_device::cntl_w) +{ + if (data != m_cntl_reg) { + LOG_0(("W CNTL=%02x -> %02x\n" , m_cntl_reg , data)); + uint8_t old_cntl_reg = m_cntl_reg; + m_cntl_reg = data; + + bool old_writon = BIT(old_cntl_reg , REG_CNTL_WRITON_BIT); + bool new_writon = BIT(m_cntl_reg , REG_CNTL_WRITON_BIT); + bool old_readon = BIT(old_cntl_reg , REG_CNTL_READON_BIT); + bool new_readon = BIT(m_cntl_reg , REG_CNTL_READON_BIT); + + bool byte_timer_running = old_writon || m_amdt; + bool byte_timer_needed = new_writon || (new_readon && m_amdt); + + if (!byte_timer_running && byte_timer_needed) { + LOG_0(("Enable byte tmr\n")); + attotime byte_period = get_half_bit_cell_period() * 16; + m_byte_timer->adjust(byte_period); + } else if (byte_timer_running && !byte_timer_needed) { + LOG_0(("Disable byte tmr\n")); + m_byte_timer->reset(); + } + + if (!old_writon && !old_readon && (new_writon || new_readon)) { + m_pll.set_clock(get_half_bit_cell_period()); + } + + if (!old_writon && new_writon) { + // Writing enabled + LOG_0(("Start writing..\n")); + m_pll.start_writing(machine().time()); + m_wr_context = 0; + m_had_transition = false; + } else if (old_writon && !new_writon) { + // Writing disabled + LOG_0(("Stop writing..\n")); + m_pll.stop_writing(get_write_device() , machine().time()); + } + if (!old_readon && new_readon) { + // Reading enabled + LOG_0(("Start reading..\n")); + m_pll.read_reset(machine().time()); + m_sync_cnt = 0; + m_half_bit_timer->adjust(get_half_bit_cell_period()); + } else if (old_readon && !new_readon) { + // Reading disabled + LOG_0(("Stop reading..\n")); + m_half_bit_timer->reset(); + m_lckup = true; + m_amdt = false; + } + if (!new_readon && !new_writon) { + m_crcerr_syn = false; + BIT_CLR(m_cntl_reg, REG_CNTL_CRCON_BIT); + BIT_CLR(m_cntl_reg, REG_CNTL_CRCOUT_BIT); + preset_crc(); + } + } +} + +WRITE8_MEMBER(hp9895_device::drv_w) +{ + LOG_0(("W DRV=%02x\n" , data)); + m_mgnena = BIT(data , REG_DRV_MGNENA_BIT); + if (m_current_drive != nullptr) { + m_current_drive->stp_w(!BIT(data , REG_DRV_STEP_BIT)); + m_current_drive->dir_w(!BIT(data , REG_DRV_MOVEIN_BIT)); + // TODO: in use signal + m_current_drive->ss_w(BIT(data , REG_DRV_HEADSEL_BIT)); + } +} + +WRITE8_MEMBER(hp9895_device::xv_w) +{ + LOG_0(("W XV=%02x\n" , data)); + // Disk Changed flag is cleared when drive is ready and it is deselected + if (m_current_drive_idx < 2 && (data & xv_drive_masks[ m_current_drive_idx ]) == 0 && !m_current_drive->ready_r()) { + if (m_dskchg[ m_current_drive_idx ]) { + LOG(("Dskchg %u cleared\n" , m_current_drive_idx)); + } + m_dskchg[ m_current_drive_idx ] = false; + } + + m_current_drive = nullptr; + m_current_drive_idx = ~0; + for (unsigned i = 0; i < 2; i++) { + if (data & xv_drive_masks[ i ]) { + m_current_drive = m_drives[ i ]->get_device(); + m_current_drive_idx = i; + break; + } + } + + m_hiden = BIT(data , REG_XV_HIDEN_BIT); +} + +READ8_MEMBER(hp9895_device::data_r) +{ + m_clock_reg = m_clock_sr; + m_accdata = true; + LOG_0(("R DATA=%02x\n" , m_data_sr)); + // CPU stalls until next SDOK + m_cpu->suspend_until_trigger(1 , true); + return m_data_sr; +} + +READ8_MEMBER(hp9895_device::clock_r) +{ + return m_clock_reg; +} + +READ8_MEMBER(hp9895_device::drivstat_r) +{ + uint8_t res = 0; + + if (m_current_drive != nullptr) { + if (m_current_drive->idx_r()) { + BIT_SET(res , REG_DRIVSTAT_INDEX_BIT); + } + if (m_dskchg[ m_current_drive_idx ]) { + BIT_SET(res , REG_DRIVSTAT_DISCHNG_BIT); + } + if (!m_current_drive->trk00_r()) { + BIT_SET(res , REG_DRIVSTAT_TRACK0_BIT); + } + if (m_current_drive->wpt_r()) { + BIT_SET(res , REG_DRIVSTAT_WRPROT_BIT); + } + if (!m_current_drive->ready_r()) { + BIT_SET(res , REG_DRIVSTAT_READY_BIT); + } + if (!m_current_drive->twosid_r()) { + BIT_SET(res , REG_DRIVSTAT_TWOSIDE_BIT); + } + } + if (m_crcerr_syn) { + BIT_SET(res , REG_DRIVSTAT_CRCERR_BIT); + } + if (m_overrun) { + BIT_SET(res , REG_DRIVSTAT_OVERUN_BIT); + } + LOG_0(("R DRIVSTAT=%02x\n" , res)); + return res; +} + +READ8_MEMBER(hp9895_device::switches_r) +{ + uint8_t res = get_switches2(); + res |= m_switches->read(); + return res; +} + +READ8_MEMBER(hp9895_device::switches2_r) +{ + return get_switches2(); +} + +void hp9895_device::floppy_ready_cb(floppy_image_device *floppy , int state) +{ +#if 0 + // DEBUG DEBUG DEBUG DEBUG + for (unsigned i = 0; i < 2; i++) { + if (floppy == m_drives[ i ]->get_device()) { + if (m_ready[ i ] != state) { + LOG(("Ready %u=%d\n" , i , state)); + m_ready[ i ] = state; + } + break; + } + } +#endif + + if (state) { + // Set Disk Changed flag when a drive is not ready + for (unsigned i = 0; i < 2; i++) { + if (floppy == m_drives[ i ]->get_device()) { + LOG(("Dskchg %u set\n" , i)); + m_dskchg[ i ] = true; + break; + } + } + } +} + +uint8_t hp9895_device::get_switches2(void) const +{ + uint8_t res = 0; + + if (m_timeout) { + BIT_SET(res, REG_SWITCHES_TIMEOUT_BIT); + } + if (m_amdt) { + BIT_SET(res, REG_SWITCHES_AMDT_BIT); + } + + return res; +} + +attotime hp9895_device::get_half_bit_cell_period(void) const +{ + return attotime::from_hz((m_hiden ? HPMODE_BIT_FREQ : IBMMODE_BIT_FREQ) * 2); +} + +floppy_image_device *hp9895_device::get_write_device(void) const +{ + if (!BIT(m_cntl_reg , REG_CNTL_WRITDRV_BIT)) { + return nullptr; + } else { + return m_current_drive; + } +} + +void hp9895_device::preset_crc(void) +{ + m_crc = ~0; +} + +void hp9895_device::update_crc(bool bit) +{ + bool msb = BIT(m_crc , 15); + + m_crc <<= 1; + if (bit ^ msb) { + m_crc ^= 0x1021; + } +} + +bool hp9895_device::shift_sr(uint8_t& sr , bool input_bit) +{ + bool res; + + if (m_hiden) { + res = BIT(sr , 0); + sr >>= 1; + if (input_bit) { + BIT_SET(sr , 7); + } + } else { + res = BIT(sr , 7); + sr <<= 1; + if (input_bit) { + BIT_SET(sr , 0); + } + } + return res; +} + +void hp9895_device::get_next_transition(const attotime& from_when , attotime& edge) +{ + edge = attotime::never; + + if (BIT(m_cntl_reg , REG_CNTL_WRITON_BIT)) { + // Loop back write transitions into reading data path + for (int idx = 0; idx < m_pll.write_position; idx++) { + if (m_pll.write_buffer[ idx ] >= from_when) { + edge = m_pll.write_buffer[ idx ]; + break; + } + } + } else if (m_current_drive != nullptr) { + edge = m_current_drive->get_next_transition(from_when); + } +} + +void hp9895_device::read_bit(bool crc_upd) +{ + attotime edge; + attotime tm; + + get_next_transition(m_pll.ctime, edge); + bool clock_bit = m_pll.feed_read_data(tm , edge , attotime::never); + get_next_transition(m_pll.ctime, edge); + bool data_bit = m_pll.feed_read_data(tm , edge , attotime::never); + + shift_sr(m_clock_sr, clock_bit); + data_bit = shift_sr(m_data_sr, data_bit); + + if (crc_upd && + BIT(m_cntl_reg , REG_CNTL_CRCON_BIT) && + !BIT(m_cntl_reg , REG_CNTL_CRCOUT_BIT)) { + update_crc(data_bit); + } +} + +void hp9895_device::write_bit(bool data_bit , bool clock_bit) +{ + if (m_hiden) { + // **** HP mode **** + // m_wr_context delays data bits by 2 bit cells + // Bit Content + // ============ + // 2 Data @ t-2 + // 1 Data @ t-1 + // 0 Data @ t + m_wr_context = (m_wr_context << 1) | data_bit; + data_bit = BIT(m_wr_context , 2); + clock_bit = !data_bit && (clock_bit || !m_had_transition); + m_had_transition = data_bit || clock_bit; + } + // else... IBM mode, nothing to do + + attotime dummy; + + m_pll.write_next_bit(clock_bit , dummy , nullptr , attotime::never); + m_pll.write_next_bit(data_bit , dummy , nullptr , attotime::never); +} + ROM_START(hp9895) ROM_REGION(0x2000 , "cpu" , 0) ROM_LOAD("1818-1391a.bin" , 0 , 0x2000 , CRC(b50dbfb5) SHA1(96edf9af78be75fbad2a0245b8af43958ba32752)) @@ -175,9 +868,20 @@ static ADDRESS_MAP_START(z80_io_map , AS_IO , 8 , hp9895_device) ADDRESS_MAP_UNMAP_HIGH ADDRESS_MAP_GLOBAL_MASK(0xff) AM_RANGE(0x10 , 0x17) AM_DEVWRITE("phi" , phi_device , reg8_w) AM_READ(phi_reg_r) - // TODO: 60-67 range + AM_RANGE(0x60 , 0x60) AM_READWRITE(data_r , data_w) + AM_RANGE(0x61 , 0x61) AM_READWRITE(clock_r , clock_w) + AM_RANGE(0x62 , 0x62) AM_READWRITE(drivstat_r , reset_w) + AM_RANGE(0x63 , 0x63) AM_READWRITE(switches_r , leds_w) + AM_RANGE(0x64 , 0x64) AM_WRITE(cntl_w) + AM_RANGE(0x65 , 0x65) AM_WRITE(drv_w) + AM_RANGE(0x66 , 0x66) AM_WRITE(xv_w) + AM_RANGE(0x67 , 0x67) AM_READ(switches2_r) ADDRESS_MAP_END +static SLOT_INTERFACE_START(hp9895_floppies) + SLOT_INTERFACE("8dsdd" , FLOPPY_8_DSDD) +SLOT_INTERFACE_END + static MACHINE_CONFIG_FRAGMENT(hp9895) MCFG_CPU_ADD("cpu" , Z80 , 4000000) MCFG_CPU_PROGRAM_MAP(z80_program_map) @@ -195,6 +899,11 @@ static MACHINE_CONFIG_FRAGMENT(hp9895) MCFG_PHI_REN_WRITE_CB(WRITELINE(hp9895_device , phi_ren_w)) MCFG_PHI_DIO_READWRITE_CB(READ8(hp9895_device , phi_dio_r) , WRITE8(hp9895_device , phi_dio_w)) MCFG_PHI_INT_WRITE_CB(WRITELINE(hp9895_device , phi_int_w)) + + MCFG_FLOPPY_DRIVE_ADD("floppy0" , hp9895_floppies , "8dsdd" , floppy_image_device::default_floppy_formats) + MCFG_SLOT_FIXED(true) + MCFG_FLOPPY_DRIVE_ADD("floppy1" , hp9895_floppies , "8dsdd" , floppy_image_device::default_floppy_formats) + MCFG_SLOT_FIXED(true) MACHINE_CONFIG_END const tiny_rom_entry *hp9895_device::device_rom_region() const diff --git a/src/devices/bus/ieee488/hp9895.h b/src/devices/bus/ieee488/hp9895.h index c1403c73c1e..29d776e6d13 100644 --- a/src/devices/bus/ieee488/hp9895.h +++ b/src/devices/bus/ieee488/hp9895.h @@ -17,6 +17,8 @@ #include "ieee488.h" #include "cpu/z80/z80.h" #include "machine/phi.h" +#include "imagedev/floppy.h" +#include "machine/fdc_pll.h" class hp9895_device : public device_t, public device_ieee488_interface @@ -26,9 +28,10 @@ public: hp9895_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); // device-level overrides - //virtual ioport_constructor device_input_ports() const override; + virtual ioport_constructor device_input_ports() const 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; virtual const tiny_rom_entry *device_rom_region() const override; virtual machine_config_constructor device_mconfig_additions() const override; @@ -63,11 +66,71 @@ public: DECLARE_READ8_MEMBER(phi_reg_r); DECLARE_WRITE16_MEMBER(z80_m1_w); + // Floppy interface + DECLARE_WRITE8_MEMBER(data_w); + DECLARE_WRITE8_MEMBER(clock_w); + DECLARE_WRITE8_MEMBER(reset_w); + DECLARE_WRITE8_MEMBER(leds_w); + DECLARE_WRITE8_MEMBER(cntl_w); + DECLARE_WRITE8_MEMBER(drv_w); + DECLARE_WRITE8_MEMBER(xv_w); + DECLARE_READ8_MEMBER(data_r); + DECLARE_READ8_MEMBER(clock_r); + DECLARE_READ8_MEMBER(drivstat_r); + DECLARE_READ8_MEMBER(switches_r); + DECLARE_READ8_MEMBER(switches2_r); + + // Floppy drive interface + void floppy_ready_cb(floppy_image_device *floppy , int state); + private: required_device m_cpu; required_device m_phi; + required_device m_drives[ 2 ]; + required_ioport m_switches; bool m_cpu_irq; + floppy_image_device *m_current_drive; + unsigned m_current_drive_idx; + bool m_dskchg[ 2 ]; + uint16_t m_crc; // U77 + bool m_crcerr_syn; + bool m_overrun; + bool m_accdata; + bool m_timeout; + uint8_t m_cntl_reg; // U31 + uint8_t m_clock_sr; // U22 & U4 + uint8_t m_clock_reg; // U23 & U5 + uint8_t m_data_sr; // U24 & U6 + uint8_t m_wr_context; + bool m_had_transition; + bool m_lckup; + bool m_amdt; + uint8_t m_sync_cnt; // U28 & U73 + bool m_hiden; + bool m_mgnena; +#if 0 + // DEBUG DEBUG DEBUG DEBUG + int m_ready[ 2 ]; +#endif + + // Timers + emu_timer *m_timeout_timer; + emu_timer *m_byte_timer; + emu_timer *m_half_bit_timer; + + // PLL + fdc_pll_t m_pll; + + uint8_t get_switches2(void) const; + attotime get_half_bit_cell_period(void) const; + floppy_image_device *get_write_device(void) const; + void preset_crc(void); + void update_crc(bool bit); + bool shift_sr(uint8_t& sr , bool input_bit); + void get_next_transition(const attotime& from_when , attotime& edge); + void read_bit(bool crc_upd); + void write_bit(bool data_bit , bool clock_bit); }; // device type definition diff --git a/src/devices/machine/fdc_pll.cpp b/src/devices/machine/fdc_pll.cpp index cf616468a12..bec0102f95c 100644 --- a/src/devices/machine/fdc_pll.cpp +++ b/src/devices/machine/fdc_pll.cpp @@ -22,12 +22,17 @@ void fdc_pll_t::set_clock(const attotime &_period) } void fdc_pll_t::reset(const attotime &when) +{ + read_reset(when); + write_position = 0; + write_start_time = attotime::never; +} + +void fdc_pll_t::read_reset(const attotime &when) { ctime = when; phase_adjust = attotime::zero; freq_hist = 0; - write_position = 0; - write_start_time = attotime::never; } void fdc_pll_t::start_writing(const attotime &tm) @@ -55,8 +60,13 @@ void fdc_pll_t::commit(floppy_image_device *floppy, const attotime &tm) int fdc_pll_t::get_next_bit(attotime &tm, floppy_image_device *floppy, const attotime &limit) { - attotime edge = floppy ? floppy->get_next_transition(ctime) : attotime::never; + attotime edge = floppy ? floppy->get_next_transition(ctime) : attotime::never; + return feed_read_data(tm , edge , limit); +} + +int fdc_pll_t::feed_read_data(attotime &tm, const attotime& edge, const attotime &limit) +{ attotime next = ctime + period + phase_adjust; #if 0 diff --git a/src/devices/machine/fdc_pll.h b/src/devices/machine/fdc_pll.h index 31eaae07d1b..16de4c0458f 100644 --- a/src/devices/machine/fdc_pll.h +++ b/src/devices/machine/fdc_pll.h @@ -21,7 +21,9 @@ public: void set_clock(const attotime &period); void reset(const attotime &when); + void read_reset(const attotime &when); int get_next_bit(attotime &tm, floppy_image_device *floppy, const attotime &limit); + int feed_read_data(attotime &tm, const attotime& edge, const attotime &limit); bool write_next_bit(bool bit, attotime &tm, floppy_image_device *floppy, const attotime &limit); void start_writing(const attotime &tm); void commit(floppy_image_device *floppy, const attotime &tm); diff --git a/src/devices/machine/phi.cpp b/src/devices/machine/phi.cpp index 88f6f20a42a..0fd0c4a90b9 100644 --- a/src/devices/machine/phi.cpp +++ b/src/devices/machine/phi.cpp @@ -33,6 +33,8 @@ // Debugging #define VERBOSE 1 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) +#define VERBOSE_0 0 +#define LOG_0(x) do { if (VERBOSE_0) logerror x; } while (0) // Macros to clear/set single bits #define BIT_MASK(n) (1U << (n)) @@ -250,7 +252,7 @@ void phi_device::set_ext_signal(phi_488_signal_t signal , int state) state = !state; if (m_ext_signals[ signal ] != state) { m_ext_signals[ signal ] = state; - LOG(("EXT EOI %d DAV %d NRFD %d NDAC %d IFC %d SRQ %d ATN %d REN %d\n" , + LOG_0(("EXT EOI %d DAV %d NRFD %d NDAC %d IFC %d SRQ %d ATN %d REN %d\n" , m_ext_signals[ PHI_488_EOI ] , m_ext_signals[ PHI_488_DAV ] , m_ext_signals[ PHI_488_NRFD ] , @@ -409,7 +411,7 @@ void phi_device::device_reset() void phi_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { - LOG(("tmr %d enabled %d\n" , id , timer.enabled())); + LOG_0(("tmr %d enabled %d\n" , id , timer.enabled())); update_fsm(); } @@ -521,7 +523,7 @@ uint8_t phi_device::get_dio(void) void phi_device::set_dio(uint8_t data) { if (data != m_dio) { - LOG(("DIO=%02x\n" , data)); + LOG_0(("DIO=%02x\n" , data)); m_dio = data; if (!m_loopback) { m_dio_write_func(~data); @@ -542,7 +544,7 @@ void phi_device::set_signal(phi_488_signal_t signal , bool state) { if (state != m_signals[ signal ]) { m_signals[ signal ] = state; - LOG(("INT EOI %d DAV %d NRFD %d NDAC %d IFC %d SRQ %d ATN %d REN %d\n" , + LOG_0(("INT EOI %d DAV %d NRFD %d NDAC %d IFC %d SRQ %d ATN %d REN %d\n" , m_signals[ PHI_488_EOI ] , m_signals[ PHI_488_DAV ] , m_signals[ PHI_488_NRFD ] , @@ -565,9 +567,16 @@ void phi_device::pon_msg(void) m_t_spms = false; m_l_state = PHI_L_LIDS; m_sr_state = PHI_SR_NPRS; - m_pp_state = PHI_PP_PPIS; m_pp_pacs = false; - m_ppr_msg = my_address(); + uint8_t addr = my_address(); + if (addr <= 7) { + // If address <= 7, PP is automatically enabled and configured for PPR = ~address + m_ppr_msg = addr ^ 7; + m_pp_state = PHI_PP_PPSS; + } else { + m_ppr_msg = 0; + m_pp_state = PHI_PP_PPIS; + } m_s_sense = true; m_c_state = PHI_C_CIDS; m_be_counter = 0; @@ -610,10 +619,10 @@ void phi_device::update_fsm(void) // TODO: RL FSM // Loop until all changes settle while (changed) { - LOG(("SH %d AH %d T %d SPMS %d L %d SR %d PP %d PACS %d PPR %u S %d C %d\n" , + LOG_0(("SH %d AH %d T %d SPMS %d L %d SR %d PP %d PACS %d PPR %u S %d C %d\n" , m_sh_state , m_ah_state , m_t_state , m_t_spms , m_l_state , m_sr_state , m_pp_state , m_pp_pacs , m_ppr_msg , m_s_sense , m_c_state)); - LOG(("O E/F=%d/%d I E/F=%d/%d\n" , m_fifo_out.empty() , m_fifo_out.full() , m_fifo_in.empty() , m_fifo_in.full())); + LOG_0(("O E/F=%d/%d I E/F=%d/%d\n" , m_fifo_out.empty() , m_fifo_out.full() , m_fifo_in.empty() , m_fifo_in.full())); changed = false; // SH FSM @@ -641,7 +650,7 @@ void phi_device::update_fsm(void) if ((m_nba_origin = nba_msg(new_byte , new_eoi)) != NBA_NONE) { m_sh_state = PHI_SH_SDYS; m_sh_dly_timer->adjust(attotime::from_nsec(DELAY_T1)); - LOG(("SH DLY enabled %d\n" , m_sh_dly_timer->enabled())); + LOG_0(("SH DLY enabled %d\n" , m_sh_dly_timer->enabled())); } break; @@ -653,6 +662,7 @@ void phi_device::update_fsm(void) case PHI_SH_STRS: if (!get_signal(PHI_488_NDAC)) { + LOG(("TX %02x/%d\n" , m_dio , m_signals[ PHI_488_EOI ])); m_sh_state = PHI_SH_SGNS; clear_nba((nba_origin_t)m_nba_origin); } @@ -669,15 +679,13 @@ void phi_device::update_fsm(void) // SH outputs // EOI is controlled by SH & C FSMs - bool eoi_signal; + bool eoi_signal = false; + uint8_t dio_byte = 0; set_signal(PHI_488_DAV , m_sh_state == PHI_SH_STRS); if (m_sh_state == PHI_SH_SDYS || m_sh_state == PHI_SH_STRS) { nba_msg(new_byte , new_eoi); - set_dio(new_byte); + dio_byte = new_byte; eoi_signal = new_eoi; - } else { - set_dio(0); - eoi_signal = false; } // AH FSM @@ -883,8 +891,9 @@ void phi_device::update_fsm(void) changed = true; } // PP outputs - if (m_pp_state == PHI_PP_PPAS && m_s_sense == !!BIT(m_reg_control , REG_CTRL_PP_RESPONSE_BIT) && m_ppr_msg <= 7) { - set_dio(1 << m_ppr_msg); + if (m_pp_state == PHI_PP_PPAS && m_s_sense == !!BIT(m_reg_control , REG_CTRL_PP_RESPONSE_BIT)) { + LOG(("PP %u\n" , m_ppr_msg)); + dio_byte |= (1U << m_ppr_msg); } // C FSM @@ -988,6 +997,7 @@ void phi_device::update_fsm(void) m_c_state == PHI_C_CAWS || m_c_state == PHI_C_CTRS); eoi_signal = eoi_signal || m_c_state == PHI_C_CPWS || m_c_state == PHI_C_CPPS; set_signal(PHI_488_EOI , eoi_signal); + set_dio(dio_byte); } // Update status register @@ -1307,11 +1317,11 @@ bool phi_device::byte_received(uint8_t byte , bool eoi) if (m_l_state == PHI_L_LACS) { if (m_fifo_in.full() || BIT(m_reg_int_cond , REG_INT_DEV_CLEAR_BIT)) { // No room for received byte, stall handshake - LOG(("..stalled\n")); + LOG_0(("..stalled\n")); return false; } else { m_fifo_in.enqueue(word); - LOG(("..OK\n")); + LOG_0(("..OK\n")); if (m_t_state != PHI_T_TACS && m_t_state != PHI_T_ID3 && m_t_state != PHI_T_ID5 && m_t_state != PHI_T_SPAS) { // If PHI didn't send this byte to itself, set data freeze @@ -1320,7 +1330,7 @@ bool phi_device::byte_received(uint8_t byte , bool eoi) } } if (end_of_transfer) { - LOG(("End of byte transfer enable\n")); + LOG_0(("End of byte transfer enable\n")); m_fifo_out.dequeue(); m_be_counter = 0; } else {