// license:BSD-3-Clause // copyright-holders:F. Ulivi /********************************************************************* phi.h HP PHI (Processor-to-Hpib-Interface) (1AA6-6x04) PHI supports these features of HP-IB: * SH1 * AH1 * T1/TE1 * L1/LE1 * SR1 * RL2 * PP1 * DC1 * DT1 * C1,C2,C3,C4,C5 * HP non-standard IDENTIFY sequence Fun fact: PHI has no clock input, its FSMs are driven only by changes in input signals and by a few internal monostables Main reference for this ASIC: HP 12009-90001, sep 82, HP12009A HP-IB Interface Reference Manual *********************************************************************/ #include "emu.h" #include "phi.h" // Debugging #include "logmacro.h" #define LOG_NOISY_MASK (LOG_GENERAL << 1) #define LOG_NOISY(...) LOGMASKED(LOG_NOISY_MASK, __VA_ARGS__) #define LOG_REG_MASK (LOG_NOISY_MASK << 1) #define LOG_REG(...) LOGMASKED(LOG_REG_MASK, __VA_ARGS__) #define LOG_INT_MASK (LOG_REG_MASK << 1) #define LOG_INT(...) LOGMASKED(LOG_INT_MASK, __VA_ARGS__) #undef VERBOSE #define VERBOSE LOG_GENERAL // Bit manipulation namespace { template constexpr T BIT_MASK(unsigned n) { return (T)1U << n; } template void BIT_CLR(T& w , unsigned n) { w &= ~BIT_MASK(n); } template void BIT_SET(T& w , unsigned n) { w |= BIT_MASK(n); } } // Timers enum { SH_DELAY_TMR_ID, C_DELAY_TMR_ID }; // Register addresses enum { REG_R_INT_COND = 0, // R 0: Interrupting conditions REG_W_INT_COND = 0, // W 0: Interrupting conditions REG_R_INT_MASK = 1, // R 1: Interrupt mask REG_W_INT_MASK = 1, // W 1: Interrupt mask REG_R_INBOUND_FIFO = 2, // R 2: Inbound FIFO REG_W_OUTBOUND_FIFO = 2,// W 2: Outbound FIFO REG_R_STATUS = 3, // R 3: Status REG_W_STATUS = 3, // W 3: Status REG_R_CONTROL = 4, // R 4: Control REG_W_CONTROL = 4, // W 4: Control REG_R_ADDRESS = 5, // R 5: HPIB address REG_W_ADDRESS = 5, // W 5: HPIB address REG_R_1ST_ID = 6, // R 6: 1st ID byte REG_W_1ST_ID = 6, // W 6: 1st ID byte REG_R_2ND_ID = 7, // R 7: 2nd ID byte REG_W_2ND_ID = 7 // W 7: 2nd ID byte }; // All valid bits in registers constexpr uint16_t REG_ALL_MASK = 0xc0ff; // D0 & D1 bits constexpr uint16_t REG_D0D1_MASK = 0xc000; // Mask of D0/D1 bits constexpr unsigned REG_D0D1_SHIFT = 14; // Position of D0/D1 bits // D8-D15 bits constexpr uint16_t REG_D08D15_MASK = 0xff; // Mask of D8:D15 bits // Bits in INT_COND & INT_MASK constexpr unsigned REG_INT_DEV_CLEAR_BIT = 0; // Device clear constexpr unsigned REG_INT_FIFO_IDLE_BIT = 1; // FIFO idle constexpr unsigned REG_INT_FIFO_AV_BIT = 2; // FIFO bytes available constexpr unsigned REG_INT_FIFO_ROOM_BIT = 3; // FIFO room available constexpr unsigned REG_INT_SRQ_BIT = 4; // Service request constexpr unsigned REG_INT_PP_RESPONSE_BIT = 5; // PP response constexpr unsigned REG_INT_PROC_ABORT_BIT = 6; // Processor handshake abort constexpr unsigned REG_INT_STATUS_CH_BIT = 7; // Status change constexpr unsigned REG_INT_PARITY_ERR_BIT = 14; // Parity error constexpr unsigned REG_INT_PENDING_BIT = 15; // Interrupt pending constexpr uint16_t REG_INT_CLEARABLE_MASK = 0x40c1; // Mask of clearable bits constexpr uint16_t REG_INT_STATE_MASK = 0x803e; // Mask of "state" bits // Bits in inbound FIFO //constexpr uint16_t REG_IFIFO_NORMAL_MASK = 0x0000; // Mask of D0/D1 bits for "normal" bytes constexpr uint16_t REG_IFIFO_CNT_EXP_MASK = 0x8000; // Mask for a byte that caused byte count to expire constexpr uint16_t REG_IFIFO_LAST_MASK = 0xc000; // Mask for last byte in a record constexpr uint16_t REG_IFIFO_2_ADDR_MASK = 0x4000; // Mask for secondary addresses constexpr unsigned REG_IFIFO_TALK_BIT = 5; // Bit of "talk" flag // Bits in outbound FIFO constexpr unsigned REG_OFIFO_SPECIAL_BIT = 14; // Bit to discriminate between normal bytes and the rest constexpr unsigned REG_OFIFO_END_BIT = 15; // Bit of EOI constexpr uint16_t REG_OFIFO_IFCMD_MASK = 0x4000; // Mask of interface commands constexpr uint16_t REG_OFIFO_UNCNT_MASK = 0xc000; // Mask of uncounted transfer enable constexpr unsigned REG_OFIFO_LF_INH_BIT = 15; // Bit of LF detection inhibit // Bits in status register constexpr unsigned REG_STATUS_DATA_FREEZE_BIT = 0; // Outbound data freeze constexpr unsigned REG_STATUS_LISTEN_BIT = 1; // Addressed to listen constexpr unsigned REG_STATUS_TALK_BIT = 2; // Addressed to talk or identify constexpr unsigned REG_STATUS_SYS_CTRL_BIT = 3; // System controller constexpr unsigned REG_STATUS_CONTROLLER_BIT = 4; // Current controller constexpr unsigned REG_STATUS_REMOTE_BIT = 5; // Remote state constexpr unsigned REG_STATUS_D0D1_BIT = 6; // D0/D1 bit access constexpr uint16_t REG_STATUS_STATE_MASK = 0x3e; // Mask of "state" bits // Bits in control register constexpr unsigned REG_CTRL_INIT_OFIFO_BIT = 0; // Initialize outbound FIFO constexpr unsigned REG_CTRL_DMA_FIFO_BIT = 1; // DMA FIFO selection constexpr unsigned REG_CTRL_SERVICE_REQ_BIT = 2; // Request service constexpr unsigned REG_CTRL_PP_RESPONSE_BIT = 3; // Respond to PP constexpr unsigned REG_CTRL_IFC_BIT = 4; // IFC value constexpr unsigned REG_CTRL_REN_BIT = 5; // REN value constexpr unsigned REG_CTRL_PAR_FREEZE_BIT = 6; // Parity freeze constexpr unsigned REG_CTRL_8BIT_PROC_BIT = 7; // 8-bit processor // Bits in address register constexpr unsigned REG_ADDR_HPIB_ADDR_BIT = 0; // HPIB address LSB constexpr unsigned REG_ADDR_LA_BIT = 5; // Listen always constexpr unsigned REG_ADDR_TA_BIT = 6; // Talk always constexpr unsigned REG_ADDR_ONLINE_BIT = 7; // Online // Interface commands constexpr uint8_t IFCMD_MASK = 0x7f; // Mask of interface commands constexpr uint8_t IFCMD_DCL = 0x14; // Device clear constexpr uint8_t IFCMD_GET = 0x08; // Group execute trigger constexpr uint8_t IFCMD_GTL = 0x01; // Go to local constexpr uint8_t IFCMD_LLO = 0x11; // Local lock-out constexpr uint8_t IFCMD_AG_MASK = 0x60; // Mask of bits identifying address group commands constexpr uint8_t IFCMD_ADDR_MASK = 0x1f; // Mask of address in AG commands constexpr uint8_t IFCMD_LAG_VALUE = 0x20; // Value of LAG commands constexpr uint8_t IFCMD_TAG_VALUE = 0x40; // Value of TAG commands constexpr uint8_t IFCMD_SCG_VALUE = 0x60; // Value of SCG commands constexpr uint8_t IFCMD_PPC = 0x05; // Parallel poll configure constexpr uint8_t IFCMD_PPX_MASK = 0x70; // Mask of PPE/PPD commands constexpr uint8_t IFCMD_PPE_VALUE = 0x60; // Parallel poll enable constexpr unsigned IFCMD_PPE_S_BIT = 3; // Position of "S" bit in PPE constexpr uint8_t IFCMD_PPE_PPR_MASK = 7; // Mask in PPE of PPR msg no. constexpr uint8_t IFCMD_PPD_VALUE = 0x70; // Parallel poll disable constexpr uint8_t IFCMD_PPU = 0x15; // Parallel poll unconfigure constexpr uint8_t IFCMD_SDC = 0x04; // Selected device clear constexpr uint8_t IFCMD_SPD = 0x19; // Serial poll disable constexpr uint8_t IFCMD_SPE = 0x18; // Serial poll enable constexpr uint8_t IFCMD_TCT = 0x09; // Take control constexpr uint8_t IFCMD_UNL = 0x3f; // Unlisten constexpr uint8_t IFCMD_UNT = 0x5f; // Untalk // Delays constexpr unsigned DELAY_T1 = 2000; // T1: 2 us constexpr unsigned DELAY_T7 = 500; // T7: 0.5 us constexpr unsigned DELAY_T9 = 1500; // T9: 1.5 us constexpr unsigned DELAY_T10 = 1500; // T10: 1.5 us // Controller address constexpr uint8_t CONTROLLER_ADDR = 0x1e; // PHI always has this address when it's a system controller // Device type definition DEFINE_DEVICE_TYPE(PHI, phi_device, "hp_phi", "HP Processor-to-HPIB Interface") // Constructors phi_device::phi_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock), m_dio_read_func(*this), m_dio_write_func(*this), m_signal_wr_fns{ devcb_write_line(*this), devcb_write_line(*this), devcb_write_line(*this), devcb_write_line(*this), devcb_write_line(*this), devcb_write_line(*this), devcb_write_line(*this), devcb_write_line(*this) }, m_int_write_func(*this), m_dmarq_write_func(*this), m_sys_cntrl_read_func(*this) { } phi_device::phi_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : phi_device(mconfig, PHI, tag, owner, clock) { } WRITE_LINE_MEMBER(phi_device::eoi_w) { set_ext_signal(PHI_488_EOI , state); } WRITE_LINE_MEMBER(phi_device::dav_w) { set_ext_signal(PHI_488_DAV , state); } WRITE_LINE_MEMBER(phi_device::nrfd_w) { set_ext_signal(PHI_488_NRFD , state); } WRITE_LINE_MEMBER(phi_device::ndac_w) { set_ext_signal(PHI_488_NDAC , state); } WRITE_LINE_MEMBER(phi_device::ifc_w) { set_ext_signal(PHI_488_IFC , state); } WRITE_LINE_MEMBER(phi_device::srq_w) { set_ext_signal(PHI_488_SRQ , state); } WRITE_LINE_MEMBER(phi_device::atn_w) { set_ext_signal(PHI_488_ATN , state); } WRITE_LINE_MEMBER(phi_device::ren_w) { set_ext_signal(PHI_488_REN , state); } WRITE8_MEMBER(phi_device::bus_dio_w) { update_pp(); } 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_NOISY("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 ] , m_ext_signals[ PHI_488_NDAC ] , m_ext_signals[ PHI_488_IFC ] , m_ext_signals[ PHI_488_SRQ ] , m_ext_signals[ PHI_488_ATN ] , m_ext_signals[ PHI_488_REN ]); update_fsm(); } } WRITE16_MEMBER(phi_device::reg16_w) { int_reg_w(offset , data & REG_ALL_MASK); } READ16_MEMBER(phi_device::reg16_r) { uint16_t res; switch (offset) { case REG_R_INT_COND: res = m_reg_int_cond & m_reg_int_mask; break; case REG_R_INT_MASK: res = m_reg_int_mask; break; case REG_R_INBOUND_FIFO: if (m_fifo_in.empty()) { if (m_c_state == PHI_C_CPPS) { res = get_pp_response(); } else { BIT_SET(m_reg_int_cond, REG_INT_PROC_ABORT_BIT); res = 0; } } else { res = m_fifo_in.dequeue(); } update_fsm(); break; case REG_R_STATUS: res = m_reg_status; break; case REG_R_CONTROL: res = m_reg_control; break; case REG_R_ADDRESS: res = m_reg_address; break; case REG_R_1ST_ID: res = m_reg_1st_id; break; case REG_R_2ND_ID: res = m_reg_2nd_id; break; default: res = 0; LOG("Reading from unmapped address (%u)\n", offset); break; }; if (offset != REG_R_STATUS) { // Store D0/D1 in top bits of status register m_reg_status = (m_reg_status & ~(3U << REG_STATUS_D0D1_BIT)) | ((res & REG_D0D1_MASK) >> (REG_D0D1_SHIFT - REG_STATUS_D0D1_BIT)); } LOG_REG("R %u=%04x\n" , offset , res); return res; } WRITE8_MEMBER(phi_device::reg8_w) { int_reg_w(offset , data); } READ8_MEMBER(phi_device::reg8_r) { return (uint8_t)reg16_r(space , offset , mem_mask); } void phi_device::device_start() { save_item(NAME(m_dio)); save_item(NAME(m_signals)); save_item(NAME(m_ext_signals)); save_item(NAME(m_sys_controller)); save_item(NAME(m_loopback)); save_item(NAME(m_id_enabled)); save_item(NAME(m_sh_state)); save_item(NAME(m_ah_state)); save_item(NAME(m_t_state)); save_item(NAME(m_t_spms)); save_item(NAME(m_l_state)); save_item(NAME(m_sr_state)); save_item(NAME(m_rl_rems)); save_item(NAME(m_pp_state)); save_item(NAME(m_ppr_msg)); save_item(NAME(m_s_sense)); save_item(NAME(m_c_state)); save_item(NAME(m_sa_state)); save_item(NAME(m_be_counter)); save_item(NAME(m_reg_status)); save_item(NAME(m_reg_int_cond)); save_item(NAME(m_reg_int_mask)); save_item(NAME(m_reg_1st_id)); save_item(NAME(m_reg_2nd_id)); save_item(NAME(m_reg_control)); save_item(NAME(m_reg_address)); save_item(NAME(m_nba_origin)); m_dio_read_func.resolve_safe(0xff); m_dio_write_func.resolve_safe(); for (auto& f : m_signal_wr_fns) { f.resolve_safe(); } m_int_write_func.resolve_safe(); m_dmarq_write_func.resolve_safe(); m_sys_cntrl_read_func.resolve_safe(0); m_sh_dly_timer = timer_alloc(SH_DELAY_TMR_ID); m_c_dly_timer = timer_alloc(C_DELAY_TMR_ID); } void phi_device::device_reset() { m_dio = 0; for (auto& s : m_signals) { s = false; } for (auto& s : m_ext_signals) { s = false; } m_no_recursion = false; // The following variables are set "true" because m_reg_address is set to 0 m_sys_controller = true; m_loopback = true; m_id_enabled = false; m_reg_status = 0; m_reg_int_cond = 0; m_reg_int_mask = 0; m_reg_1st_id = 0; m_reg_2nd_id = 0; m_reg_control = 0; m_reg_address = 0; m_fifo_in.clear(); m_fifo_out.clear(); m_int_line = false; m_int_write_func(false); m_dmarq_line = false; m_dmarq_write_func(false); pon_msg(); update_488(); } void phi_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { LOG_NOISY("tmr %d enabled %d\n" , id , timer.enabled()); update_fsm(); } void phi_device::int_reg_w(offs_t offset , uint16_t data) { if (BIT(m_reg_control , REG_CTRL_8BIT_PROC_BIT)) { // In 8-bit mode, D0/D1 come from status register data = (data & REG_D08D15_MASK) | ((m_reg_status << (REG_D0D1_SHIFT - REG_STATUS_D0D1_BIT)) & REG_D0D1_MASK); } LOG_REG("W %u=%04x\n" , offset , data); switch (offset) { case REG_W_INT_COND: // Bits D1/D8/D9/D15 only are clearable when written to 1 m_reg_int_cond &= ~(data & REG_INT_CLEARABLE_MASK); update_fsm(); break; case REG_W_INT_MASK: m_reg_int_mask = data; update_fsm(); break; case REG_W_OUTBOUND_FIFO: if (m_fifo_out.full()) { BIT_SET(m_reg_int_cond, REG_INT_PROC_ABORT_BIT); } else { m_fifo_out.enqueue(data); } update_fsm(); break; case REG_W_STATUS: // Copy D0/D1 access bits into status register m_reg_status = (m_reg_status & ~(3U << REG_STATUS_D0D1_BIT)) | (data & (3U << REG_STATUS_D0D1_BIT)); if (BIT(data , REG_STATUS_DATA_FREEZE_BIT) && m_fifo_in.empty()) { BIT_CLR(m_reg_status, REG_STATUS_DATA_FREEZE_BIT); } update_fsm(); break; case REG_W_CONTROL: // D0/D1/D15 are not mapped into register m_reg_control = data & 0xfe; if (BIT(data , REG_CTRL_INIT_OFIFO_BIT)) { m_fifo_out.clear(); if (m_c_state == PHI_C_CSBS) { // Take control asynchronously m_c_state = PHI_C_CSWS; m_c_dly_timer->adjust(attotime::from_nsec(DELAY_T7)); } } if (m_loopback) { // TODO: better? m_id_enabled = BIT(m_reg_control , REG_CTRL_PP_RESPONSE_BIT); } update_fsm(); break; case REG_W_ADDRESS: { // No D0/D1 in register data &= REG_D08D15_MASK; bool prev_ol = BIT(m_reg_address , REG_ADDR_ONLINE_BIT); m_reg_address = data; bool current_ol = BIT(m_reg_address , REG_ADDR_ONLINE_BIT); m_sys_controller = !current_ol || m_sys_cntrl_read_func(); m_loopback = !current_ol; if (!current_ol) { // IDENTIFY is enabled by PP_RESPONSE bit in control register m_id_enabled = BIT(m_reg_control , REG_CTRL_PP_RESPONSE_BIT); } else if (!prev_ol) { // Going on-line pon_msg(); m_id_enabled = BIT(m_reg_control , REG_CTRL_PP_RESPONSE_BIT); } configure_pp_response(); if (prev_ol != current_ol) { update_488(); } update_fsm(); } break; case REG_W_1ST_ID: // No D0/D1 in register m_reg_1st_id = data & REG_D08D15_MASK; update_fsm(); break; case REG_W_2ND_ID: // No D0/D1 in register m_reg_2nd_id = data & REG_D08D15_MASK; update_fsm(); break; default: LOG("Writing to unmapped address (%u)\n", offset); break; } } uint8_t phi_device::get_dio(void) { if (m_loopback) { return m_dio; } else { return ~m_dio_read_func(); } } void phi_device::set_dio(uint8_t data) { if (data != m_dio) { LOG_NOISY("DIO=%02x\n" , data); m_dio = data; if (!m_loopback) { m_dio_write_func(~data); } } } bool phi_device::get_signal(phi_488_signal_t signal) { if (m_loopback) { return m_signals[ signal ]; } else { return m_ext_signals[ signal ]; } } void phi_device::set_signal(phi_488_signal_t signal , bool state) { if (state != m_signals[ signal ]) { m_signals[ signal ] = state; LOG_NOISY("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 ] , m_signals[ PHI_488_NDAC ] , m_signals[ PHI_488_IFC ] , m_signals[ PHI_488_SRQ ] , m_signals[ PHI_488_ATN ] , m_signals[ PHI_488_REN ]); if (!m_loopback) { m_signal_wr_fns[ signal ](!state); } } } void phi_device::pon_msg(void) { m_sh_state = PHI_SH_SIDS; m_ah_state = PHI_AH_AIDS; m_t_state = PHI_T_TIDS; m_t_spms = false; m_l_state = PHI_L_LIDS; m_sr_state = PHI_SR_NPRS; m_rl_rems = false; m_pp_state = PHI_PP_PPIS; m_c_state = PHI_C_CIDS; m_sa_state = PHI_SA_NONE; m_be_counter = 0; m_nba_origin = NBA_NONE; } void phi_device::update_488(void) { if (m_loopback) { m_dio_write_func(~0); for (auto& f : m_signal_wr_fns) { f(1); } } else { m_dio_write_func(~m_dio); for (unsigned i = 0; i < PHI_488_SIGNAL_COUNT; i++) { m_signal_wr_fns[ i ](!m_signals[ i ]); } } } void phi_device::update_fsm(void) { if (m_no_recursion) { // Prevent recursion into this function whenever a signal change propagates through // set_ext_signal return; } m_no_recursion = true; set_signal(PHI_488_IFC , m_sys_controller && BIT(m_reg_control , REG_CTRL_IFC_BIT)); set_signal(PHI_488_REN , m_sys_controller && BIT(m_reg_control , REG_CTRL_REN_BIT)); // TODO: improve (see SR FSM) // This is not entirely correct but it works for now (on HP64K, the only system // where it's relevant) set_signal(PHI_488_SRQ , BIT(m_reg_control , REG_CTRL_SERVICE_REQ_BIT)); bool changed = true; int prev_state; uint8_t new_byte = 0; bool new_eoi = false; bool prev_cic = controller_in_charge(); // TODO: SR FSM // Loop until all changes settle while (changed) { LOG_NOISY("SH %d AH %d T %d SPMS %d L %d SR %d PP %d PPR %u S %d C %d SA %d\n" , m_sh_state , m_ah_state , m_t_state , m_t_spms , m_l_state , m_sr_state , m_pp_state , m_ppr_msg , m_s_sense , m_c_state , m_sa_state); LOG_NOISY("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 prev_state = m_sh_state; bool sh_reset = (get_signal(PHI_488_ATN) && !(m_c_state == PHI_C_CACS || m_c_state == PHI_C_CTRS)) || (!get_signal(PHI_488_ATN) && !(m_t_state == PHI_T_TACS || m_t_state == PHI_T_SPAS || m_t_state == PHI_T_ID2 || m_t_state == PHI_T_ID4)); if (sh_reset) { m_sh_state = PHI_SH_SIDS; m_sh_dly_timer->reset(); } else { switch (m_sh_state) { case PHI_SH_SIDS: if (m_t_state == PHI_T_TACS || m_t_state == PHI_T_SPAS || m_t_state == PHI_T_ID2 || m_t_state == PHI_T_ID4 || m_c_state == PHI_C_CACS) { m_sh_state = PHI_SH_SGNS; } break; case PHI_SH_SGNS: 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_NOISY("SH DLY enabled %d\n" , m_sh_dly_timer->enabled()); } break; case PHI_SH_SDYS: if (!get_signal(PHI_488_NRFD) && !m_sh_dly_timer->enabled()) { m_sh_state = PHI_SH_STRS; } break; case PHI_SH_STRS: if (!get_signal(PHI_488_NDAC)) { LOG("%.6f TX %02x/%d\n" , machine().time().as_double() , m_dio , m_signals[ PHI_488_EOI ]); m_sh_state = PHI_SH_SGNS; clear_nba((nba_origin_t)m_nba_origin); } break; default: logerror("Invalid SH state %d\n" , m_sh_state); m_sh_state = PHI_SH_SIDS; } } if (m_sh_state != prev_state) { changed = true; } // SH outputs // EOI is controlled by SH & C FSMs 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); dio_byte = new_byte; eoi_signal = new_eoi; } // AH FSM prev_state = m_ah_state; bool ah_reset = !(get_signal(PHI_488_ATN) || m_l_state == PHI_L_LADS || m_l_state == PHI_L_LACS || m_c_state == PHI_C_CSBS); if (ah_reset) { m_ah_state = PHI_AH_AIDS; } else { switch (m_ah_state) { case PHI_AH_AIDS: m_ah_state = PHI_AH_ANRS; break; case PHI_AH_ANRS: //if (!tcs_msg() && (get_signal(PHI_488_ATN) || rdy_msg())) { // According to standard either ATN or rdy should also be true, but rdy is always true in PHI if (!tcs_msg()) { m_ah_state = PHI_AH_ACRS; } break; case PHI_AH_ACRS: if (get_signal(PHI_488_DAV)) { m_ah_state = PHI_AH_ACDS; } // rdy is always true // } else if (!get_signal(PHI_488_ATN) && !rdy_msg()) { // m_ah_state = PHI_AH_ANRS; // } break; case PHI_AH_ACDS: // FSM stays in this state until the acceptor has // accepted the data byte or the interface command. if (get_signal(PHI_488_ATN)) { uint8_t if_cmd = get_dio(); bool parity_ok = odd_parity(if_cmd); if (!parity_ok) { BIT_SET(m_reg_int_cond , REG_INT_PARITY_ERR_BIT); } if (BIT(m_reg_control , REG_CTRL_PAR_FREEZE_BIT) && !parity_ok) { // With even parity and PARITY FREEZE set, command is ignored and // AH FSM freezes in ACDS m_ah_state = PHI_AH_ACDS_FROZEN; } else { // Clear parity bit & process command if_cmd &= IFCMD_MASK; if (if_cmd_received(if_cmd)) { m_ah_state = PHI_AH_AWNS; } } } else if (byte_received(get_dio() , get_signal(PHI_488_EOI))) { m_ah_state = PHI_AH_AWNS; } break; case PHI_AH_ACDS_FROZEN: case PHI_AH_AWNS: if (!get_signal(PHI_488_DAV)) { m_ah_state = PHI_AH_ANRS; } break; default: logerror("Invalid AH state %d\n" , m_ah_state); m_ah_state = PHI_AH_AIDS; } } if (m_ah_state != prev_state) { changed = true; } // AH outputs set_signal(PHI_488_NRFD , m_ah_state == PHI_AH_ANRS || m_ah_state == PHI_AH_ACDS || m_ah_state == PHI_AH_ACDS_FROZEN || m_ah_state == PHI_AH_AWNS); set_signal(PHI_488_NDAC , m_ah_state == PHI_AH_ANRS || m_ah_state == PHI_AH_ACRS || m_ah_state == PHI_AH_ACDS || m_ah_state == PHI_AH_ACDS_FROZEN); // T FSM prev_state = m_t_state; if (get_signal(PHI_488_IFC)) { m_t_state = PHI_T_TIDS; m_t_spms = false; } else { switch (m_t_state) { case PHI_T_TIDS: if (ton_msg()) { m_t_state = PHI_T_TADS; } break; case PHI_T_TADS: if (!get_signal(PHI_488_ATN)) { if (m_t_spms) { m_t_state = PHI_T_SPAS; } else { m_t_state = PHI_T_TACS; } } break; case PHI_T_SPAS: case PHI_T_TACS: if (get_signal(PHI_488_ATN)) { m_t_state = PHI_T_TADS; } break; case PHI_T_ID1: if (!get_signal(PHI_488_ATN)) { m_t_state = PHI_T_ID2; } break; case PHI_T_ID2: if (get_signal(PHI_488_ATN)) { m_t_state = PHI_T_ID1; } break; case PHI_T_ID3: if (!get_signal(PHI_488_ATN)) { m_t_state = PHI_T_ID4; } break; case PHI_T_ID4: if (get_signal(PHI_488_ATN)) { m_t_state = PHI_T_ID3; } break; case PHI_T_ID5: break; default: logerror("Invalid T state %d\n" , m_t_state); m_t_state = PHI_T_TIDS; } } if (m_t_state != prev_state) { changed = true; } // No direct T outputs // L FSM prev_state = m_l_state; if (get_signal(PHI_488_IFC)) { m_l_state = PHI_L_LIDS; } else { switch (m_l_state) { case PHI_L_LIDS: if (lon_msg()) { m_l_state = PHI_L_LADS; } break; case PHI_L_LADS: if (!get_signal(PHI_488_ATN)) { m_l_state = PHI_L_LACS; } break; case PHI_L_LACS: if (get_signal(PHI_488_ATN)) { m_l_state = PHI_L_LADS; } break; default: logerror("Invalid L state %d\n" , m_l_state); m_l_state = PHI_L_LIDS; } } if (m_l_state != prev_state) { changed = true; } // No direct L outputs // RL FSM if (!get_signal(PHI_488_REN) && m_rl_rems) { m_rl_rems = false; changed = true; } // No direct RL outputs // PP FSM prev_state = m_pp_state; switch (m_pp_state) { case PHI_PP_PPIS: break; case PHI_PP_PPSS: if (get_signal(PHI_488_ATN) && get_signal(PHI_488_EOI)) { m_pp_state = PHI_PP_PPAS; } break; case PHI_PP_PPAS: if (!get_signal(PHI_488_ATN) || !get_signal(PHI_488_EOI)) { m_pp_state = PHI_PP_PPSS; } break; default: logerror("Invalid PP state %d\n" , m_pp_state); m_pp_state = PHI_PP_PPIS; } if (m_pp_state != prev_state) { changed = true; } // PP outputs if (m_pp_state == PHI_PP_PPAS && m_s_sense == !!BIT(m_reg_control , REG_CTRL_PP_RESPONSE_BIT)) { LOG("%.6f PP %u\n" , machine().time().as_double() , m_ppr_msg); dio_byte |= BIT_MASK(m_ppr_msg); } // C FSM prev_state = m_c_state; if (!m_sys_controller && get_signal(PHI_488_IFC)) { m_c_state = PHI_C_CIDS; m_c_dly_timer->reset(); } else { switch (m_c_state) { case PHI_C_CIDS: if (m_sys_controller && get_signal(PHI_488_IFC)) { m_c_state = PHI_C_CADS; } break; case PHI_C_CADS: if (!get_signal(PHI_488_ATN)) { m_c_state = PHI_C_CACS; } break; case PHI_C_CACS: // If there are ifcmds to send, just stay in CACS // else wait for SH to finish its current transfer then decide what to do if (nba_msg(new_byte , new_eoi) != NBA_CMD_FROM_OFIFO && m_sh_state != PHI_SH_STRS && m_sh_state != PHI_SH_SDYS) { if (!m_fifo_out.empty()) { // Possible cases // D0/D1 Meaning of 1st word of OFIFO // ===================================== // x0 Counted transfer enable or byte to be sent // 11 Uncounted transfer enable // 01 Send interface command (already caught by nba_msg) m_c_state = PHI_C_CSBS; m_be_counter = 0; } else if (rpp_msg()) { // Start parallel polling m_c_state = PHI_C_CPPS; } // There's no third case: rpp_msg() is true when m_fifo_out.empty() is true } break; case PHI_C_CPPS: if (!rpp_msg()) { m_c_state = PHI_C_CAWS; m_c_dly_timer->adjust(attotime::from_nsec(DELAY_T9)); } break; case PHI_C_CSBS: if (tcs_msg() && m_ah_state == PHI_AH_ANRS) { m_c_state = PHI_C_CSHS; m_c_dly_timer->adjust(attotime::from_nsec(DELAY_T10)); } break; case PHI_C_CSHS: // tcs_msg cannot go false here if (!m_c_dly_timer->enabled()) { m_c_state = PHI_C_CSWS; m_c_dly_timer->adjust(attotime::from_nsec(DELAY_T7)); } break; case PHI_C_CAWS: if (rpp_msg()) { m_c_state = PHI_C_CPPS; m_c_dly_timer->reset(); } else if (!m_c_dly_timer->enabled()) { m_c_state = PHI_C_CACS; } break; case PHI_C_CTRS: if (m_sh_state != PHI_SH_STRS) { m_c_state = PHI_C_CIDS; } break; case PHI_C_CSWS: if (m_t_state == PHI_T_TADS || !m_c_dly_timer->enabled()) { m_c_state = PHI_C_CAWS; m_c_dly_timer->adjust(attotime::from_nsec(DELAY_T9)); } break; default: logerror("Invalid C state %d\n" , m_c_state); m_c_state = PHI_C_CIDS; } } if (m_c_state != prev_state) { changed = true; } // C outputs set_signal(PHI_488_ATN , m_c_state == PHI_C_CACS || m_c_state == PHI_C_CPPS || m_c_state == PHI_C_CSWS || m_c_state == PHI_C_CAWS || m_c_state == PHI_C_CTRS); eoi_signal = eoi_signal || m_c_state == PHI_C_CPPS; set_signal(PHI_488_EOI , eoi_signal); set_dio(dio_byte); } // Update status register m_reg_status &= ~REG_STATUS_STATE_MASK; if (m_l_state != PHI_L_LIDS) { BIT_SET(m_reg_status, REG_STATUS_LISTEN_BIT); } if (m_t_state != PHI_T_TIDS) { BIT_SET(m_reg_status, REG_STATUS_TALK_BIT); } if (m_sys_controller) { BIT_SET(m_reg_status, REG_STATUS_SYS_CTRL_BIT); } if (controller_in_charge()) { BIT_SET(m_reg_status, REG_STATUS_CONTROLLER_BIT); } if (m_rl_rems) { BIT_SET(m_reg_status, REG_STATUS_REMOTE_BIT); } // Update interrupting condition register and INT signal if (prev_cic != controller_in_charge()) { BIT_SET(m_reg_int_cond, REG_INT_STATUS_CH_BIT); } m_reg_int_cond &= ~REG_INT_STATE_MASK; if (m_fifo_out.empty()) { BIT_SET(m_reg_int_cond , REG_INT_FIFO_IDLE_BIT); } if (!m_fifo_in.empty()) { BIT_SET(m_reg_int_cond, REG_INT_FIFO_AV_BIT); } if (!m_fifo_out.full()) { BIT_SET(m_reg_int_cond, REG_INT_FIFO_ROOM_BIT); } if (controller_in_charge() && get_signal(PHI_488_SRQ)) { BIT_SET(m_reg_int_cond, REG_INT_SRQ_BIT); } update_pp(); update_interrupt(); update_dmarq(); m_no_recursion = false; } phi_device::nba_origin_t phi_device::nba_msg(uint8_t& new_byte , bool& new_eoi) const { if (controller_in_charge() && m_c_state == PHI_C_CACS && !m_fifo_out.empty()) { uint16_t word = m_fifo_out.peek(); if ((word & REG_D0D1_MASK) == REG_OFIFO_IFCMD_MASK) { // Controller sends an interface command new_byte = (uint8_t)(word & IFCMD_MASK); if (!odd_parity(new_byte)) { BIT_SET(new_byte, 7); } new_eoi = false; return NBA_CMD_FROM_OFIFO; } } switch (m_t_state) { case PHI_T_TACS: if (!BIT(m_reg_status , REG_STATUS_DATA_FREEZE_BIT) && !BIT(m_reg_int_cond , REG_INT_DEV_CLEAR_BIT) && !m_fifo_out.empty()) { uint16_t word = m_fifo_out.peek(); if (!BIT(word , REG_OFIFO_SPECIAL_BIT)) { // Talker sends a data byte new_byte = (uint8_t)word; new_eoi = BIT(word , REG_OFIFO_END_BIT); return NBA_BYTE_FROM_OFIFO; } } break; case PHI_T_SPAS: // Reply to serial poll: STB & RQS // TODO: check new_byte = m_sr_state == PHI_SR_NPRS ? 0x80 : 0x40; new_eoi = false; return NBA_FROM_SPAS; case PHI_T_ID2: // 1st byte of ID new_byte = (uint8_t)m_reg_1st_id; new_eoi = false; return NBA_FROM_ID2; case PHI_T_ID4: // 2nd byte of ID new_byte = (uint8_t)m_reg_2nd_id; new_eoi = true; return NBA_FROM_ID4; default: break; } return NBA_NONE; } void phi_device::clear_nba(nba_origin_t origin) { switch (origin) { case NBA_CMD_FROM_OFIFO: case NBA_BYTE_FROM_OFIFO: m_fifo_out.dequeue(); break; case NBA_FROM_ID2: m_t_state = PHI_T_ID3; break; case NBA_FROM_ID4: m_t_state = PHI_T_ID5; break; default: break; } } bool phi_device::if_cmd_received(uint8_t byte) { LOG("%.6f RX cmd: %02x\n" , machine().time().as_double() , byte); bool accepted = true; if ((byte & IFCMD_AG_MASK) != IFCMD_SCG_VALUE) { // Any PCG clears sec. address recognition // Exceptions are intercepted below m_sa_state = PHI_SA_NONE; } // TODO: IFC & non-reflection into controller switch (byte) { case IFCMD_GTL: // Go to local if (m_l_state == PHI_L_LADS) { m_rl_rems = false; } break; case IFCMD_SDC: // Selected device clear if (m_l_state == PHI_L_LADS && !controller_in_charge()) { BIT_SET(m_reg_int_cond, REG_INT_DEV_CLEAR_BIT); } break; case IFCMD_PPC: // Parallel poll configure if (m_l_state == PHI_L_LADS) { m_sa_state = PHI_SA_PACS; } break; case IFCMD_GET: // Group execute trigger // TODO: break; case IFCMD_TCT: // Take control if (m_c_state == PHI_C_CIDS && m_t_state == PHI_T_TADS) { // Take control m_c_state = PHI_C_CADS; } if (m_c_state == PHI_C_CACS && m_t_state != PHI_T_TADS) { // Give control to someone else m_c_state = PHI_C_CTRS; } break; case IFCMD_LLO: // Local lock-out // Ignored break; case IFCMD_DCL: // Device clear if (!controller_in_charge()) { BIT_SET(m_reg_int_cond, REG_INT_DEV_CLEAR_BIT); } break; case IFCMD_PPU: // Parallel poll unconfigure if (m_pp_state == PHI_PP_PPSS) { m_pp_state = PHI_PP_PPIS; } break; case IFCMD_SPE: // Serial poll enable m_t_spms = true; break; case IFCMD_SPD: // Serial poll disable m_t_spms = false; break; case IFCMD_UNL: // Unlisten if (!lon_msg()) { m_l_state = PHI_L_LIDS; } break; case IFCMD_UNT: // Untalk if (ton_msg()) { m_t_state = PHI_T_TADS; } else { m_t_state = PHI_T_TIDS; } if (m_id_enabled) { m_sa_state = PHI_SA_UNT; } break; default: { uint8_t address = byte & IFCMD_ADDR_MASK; uint8_t ag = byte & IFCMD_AG_MASK; bool my_addr = address == my_address(); if (ag == IFCMD_LAG_VALUE) { // LAG if (my_addr) { // MLA m_l_state = PHI_L_LADS; if (get_signal(PHI_488_REN)) { m_rl_rems = true; } m_sa_state = PHI_SA_LPAS; } } else if (ag == IFCMD_TAG_VALUE) { // TAG if (my_addr) { // MTA m_t_state = PHI_T_TADS; m_sa_state = PHI_SA_TPAS; } else if (!ton_msg()) { // OTA m_t_state = PHI_T_TIDS; } } else if (ag == IFCMD_SCG_VALUE) { // SCG switch (m_sa_state) { case PHI_SA_NONE: break; case PHI_SA_PACS: if ((byte & IFCMD_PPX_MASK) == IFCMD_PPE_VALUE && m_pp_state == PHI_PP_PPIS) { // PPE m_s_sense = BIT(byte , IFCMD_PPE_S_BIT); m_ppr_msg = byte & IFCMD_PPE_PPR_MASK; LOG("PPE s=%d ppr=%u\n" , m_s_sense , m_ppr_msg); m_pp_state = PHI_PP_PPSS; } else if ((byte & IFCMD_PPX_MASK) == IFCMD_PPD_VALUE && m_pp_state == PHI_PP_PPSS) { // PPD m_pp_state = PHI_PP_PPIS; } break; case PHI_SA_TPAS: case PHI_SA_LPAS: // command is a secondary address after MTA or MLA if (m_fifo_in.full() || BIT(m_reg_int_cond , REG_INT_DEV_CLEAR_BIT)) { // No room for secondary address in FIFO, stall handshake accepted = false; } else { uint16_t word = REG_IFIFO_2_ADDR_MASK | address; if (m_sa_state == PHI_SA_TPAS) { BIT_SET(word, REG_IFIFO_TALK_BIT); } rx_n_data_freeze(word); } break; case PHI_SA_UNT: if (my_addr) { // Start IDENTIFY sequence m_t_state = PHI_T_ID1; } else { // Unaddressed by OSA (== UNT) if_cmd_received(IFCMD_UNT); } } } } } return accepted; } bool phi_device::byte_received(uint8_t byte , bool eoi) { // Start with D0/D1 = 00 uint16_t word = byte; if (eoi) { // EOI -> D0/D1 = 11 word |= REG_IFIFO_LAST_MASK; } bool end_of_transfer = false; if (!m_fifo_out.empty() && m_c_state == PHI_C_CSBS && m_t_state != PHI_T_TACS) { uint16_t be_word = m_fifo_out.peek(); // Monitoring bytes being transferred on the bus if (eoi) { end_of_transfer = true; } else if (!BIT(be_word , REG_OFIFO_LF_INH_BIT) && byte == 0x0a) { // LF received -> D0/D1 = 11 word |= REG_IFIFO_LAST_MASK; end_of_transfer = true; } else if (be_word != REG_OFIFO_UNCNT_MASK && ((m_be_counter + 1) & 0xff) == (be_word & 0xff)) { // Byte count expired -> D0/D1 = 10 word |= REG_IFIFO_CNT_EXP_MASK; end_of_transfer = true; } } LOG("%.6f RX word:%04x\n" , machine().time().as_double() , word); 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_NOISY("..stalled\n"); return false; } else { LOG_NOISY("..OK\n"); rx_n_data_freeze(word); } } if (end_of_transfer) { LOG_NOISY("End of byte transfer enable\n"); m_fifo_out.dequeue(); m_be_counter = 0; } else { m_be_counter++; } return true; } void phi_device::rx_n_data_freeze(uint16_t word) { m_fifo_in.enqueue(word); if (!controller_in_charge() && m_sh_state != PHI_SH_STRS) { // If PHI didn't send this byte to itself, set data freeze BIT_SET(m_reg_status, REG_STATUS_DATA_FREEZE_BIT); } } bool phi_device::ton_msg(void) const { return BIT(m_reg_address , REG_ADDR_TA_BIT); } bool phi_device::lon_msg(void) const { return BIT(m_reg_address , REG_ADDR_LA_BIT); } bool phi_device::odd_parity(uint8_t byte) const { byte = (byte >> 4) ^ byte; byte = (byte >> 2) ^ byte; byte = (byte >> 1) ^ byte; return (byte & 1) != 0; } uint8_t phi_device::my_address(void) const { if (m_sys_controller) { return CONTROLLER_ADDR; } else { return (m_reg_address >> REG_ADDR_HPIB_ADDR_BIT) & 0x1f; } } bool phi_device::tcs_msg(void) const { uint8_t new_byte; bool new_eoi; // When the CIC takes back control synchronously: // * Request to start parallel poll is pending (i.e. OFIFO is empty) // * There's an interface command to be sent at head of OFIFO return (m_c_state == PHI_C_CSBS || m_c_state == PHI_C_CSHS || m_c_state == PHI_C_CSWS) && (rpp_msg() || nba_msg(new_byte , new_eoi) == NBA_CMD_FROM_OFIFO); } bool phi_device::rpp_msg(void) const { return m_fifo_out.empty(); } uint8_t phi_device::get_pp_response() { return (get_dio() ^ m_reg_2nd_id) & m_reg_1st_id; } bool phi_device::controller_in_charge(void) const { return m_c_state != PHI_C_CIDS; } void phi_device::configure_pp_response() { uint8_t addr = (m_reg_address >> REG_ADDR_HPIB_ADDR_BIT) & 0x1f; 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; } void phi_device::update_pp() { if (m_c_state == PHI_C_CPPS) { if (m_fifo_in.empty() && get_pp_response()) { BIT_SET(m_reg_int_cond , REG_INT_PP_RESPONSE_BIT); } else { BIT_CLR(m_reg_int_cond , REG_INT_PP_RESPONSE_BIT); } update_interrupt(); } } void phi_device::update_interrupt() { bool int_pending = (m_reg_int_cond & m_reg_int_mask) != 0; bool int_line = false; if (int_pending) { BIT_SET(m_reg_int_cond, REG_INT_PENDING_BIT); if (BIT(m_reg_int_mask , REG_INT_PENDING_BIT)) { int_line = true; } } else { BIT_CLR(m_reg_int_cond, REG_INT_PENDING_BIT); } if (int_line != m_int_line) { m_int_line = int_line; LOG_INT("INT %d\n" , m_int_line); m_int_write_func(m_int_line); } } void phi_device::update_dmarq() { bool new_dmarq_line; if (BIT(m_reg_control , REG_CTRL_DMA_FIFO_BIT)) { new_dmarq_line = BIT(m_reg_int_cond , REG_INT_FIFO_ROOM_BIT); } else { new_dmarq_line = BIT(m_reg_int_cond , REG_INT_FIFO_AV_BIT); } if (new_dmarq_line != m_dmarq_line) { m_dmarq_line = new_dmarq_line; LOG_INT("DRQ %d\n" , m_dmarq_line); m_dmarq_write_func(m_dmarq_line); } }