diff options
Diffstat (limited to 'src/devices/bus/multibus/isbc202.cpp')
-rw-r--r-- | src/devices/bus/multibus/isbc202.cpp | 1164 |
1 files changed, 1164 insertions, 0 deletions
diff --git a/src/devices/bus/multibus/isbc202.cpp b/src/devices/bus/multibus/isbc202.cpp new file mode 100644 index 00000000000..97b7bca71ae --- /dev/null +++ b/src/devices/bus/multibus/isbc202.cpp @@ -0,0 +1,1164 @@ +// license:BSD-3-Clause +// copyright-holders: F. Ulivi +/********************************************************************* + + isbc202.cpp + + Intel iSBC-202 SSDD 8" floppy disk controller + + This controller interfaces a standard 8/16-bit Multibus system + with up to 4 8" SSDD floppy drives. It was typically used to + expand a MDS-II system with double-density drives. + The ISIS-II OS identifies the drives as :F0: :F1: :F2: :F3: + The iSBC-202 controller is based on two boards: a "channel" + board with the actual controller and an "interface" board. + The latter handles the low-level aspects of the disks, especially + the encoding and decoding of MMFM modulated bits. This board + interfaces directly with standard Shugart SA-800-1 drives. + The drives are housed, two at time, in a (big) external box + that also holds their power supply. + The channel board is implemented with a 3000-series bit slice + processor. Microcode is stored on 4 512x8 bipolar PROMs. + The channel board is bus mastering, i.e. it can issue memory + read/write cycles to the main processor RAM. + Format of data on disk is entirely Intel proprietary. + Intel also designed a similar controller (iSBC-201) for FM + IBM-standard disks. AFAIK, this board shares the channel board + with iSBC-202 (but not the microcode) and has a different + interface board. + This table summarizes the main characteristics of the disks. + + | Bit cell size | 2 µs | + | Modulation | MMFM | + | Bit order | MS first | + | Sides | 1 | + | Tracks | 77 | + | Sectors/track | 52 | + | Sector size | 128 bytes| + | Formatted size | 500.5 kB | + | Rotation speed | 360 RPM | + + Special thanks to Eric Smith for dumping the microcode PROMs. + Without his work this driver wouldn't exist at all. + + Reference manuals. + - Intellec series II MDS double-density diskette subsystem + (schematic drawings) - Intel 1980 - 9800425-02 Rev. B + - Intellec double-density diskette operating system hardware + reference manual - Intel 1977 - 9800422A + - SBC 202 double-density diskette controller hardware reference + manual - Intel 1977 - 9800420A + + What follows is a list of things that I left out. They could be + implemented at a later time just for completeness sake as ISIS-II + doesn't rely on them at all. + - The STOP signal (it seems to be used in iSBC-201 only) + - Interrupt to CPU + - Head load/unload commands (MAME doesn't emulate head loading) + +*********************************************************************/ + +#include "emu.h" +#include "isbc202.h" +#include "formats/img_dsk.h" +#include "formats/fs_isis.h" + +// Debugging +#include "logmacro.h" +#define LOG_BUS_MASK (LOG_GENERAL << 1) +#define LOG_BUS(...) LOGMASKED(LOG_BUS_MASK, __VA_ARGS__) +#define LOG_RD_MASK (LOG_BUS_MASK << 1) +#define LOG_RD(...) LOGMASKED(LOG_RD_MASK, __VA_ARGS__) +#define LOG_WR_MASK (LOG_RD_MASK << 1) +#define LOG_WR(...) LOGMASKED(LOG_WR_MASK, __VA_ARGS__) +#define LOG_DR_MASK (LOG_WR_MASK << 1) +#define LOG_DR(...) LOGMASKED(LOG_DR_MASK, __VA_ARGS__) +#undef VERBOSE +//#define VERBOSE (LOG_GENERAL|LOG_BUS_MASK|LOG_RD_MASK|LOG_WR_MASK|LOG_DR_MASK) +#define VERBOSE LOG_GENERAL + +// Bit manipulation +namespace { + template<typename T> constexpr T BIT_MASK(unsigned n) + { + return (T)1U << n; + } + + template<typename T> void BIT_CLR(T& w , unsigned n) + { + w &= ~BIT_MASK<T>(n); + } + + template<typename T> void BIT_SET(T& w , unsigned n) + { + w |= BIT_MASK<T>(n); + } +} + +// Constants +constexpr unsigned TIMEOUT_MS = 10; // "timeout" timer: 10 ms +constexpr unsigned HALF_BIT_CELL_US = 1;// Half bit cell duration in µs +constexpr unsigned BIT_FREQUENCY = 500000; // Frequency of bit cells in Hz +constexpr uint16_t CRC_POLY = 0x1021; // CRC-CCITT + +// device type definition +DEFINE_DEVICE_TYPE(ISBC202, isbc202_device, "isbc202", "iSBC-202 floppy controller") + +// Microcode disassembler +class isbc202_disassembler : public util::disasm_interface +{ +public: + isbc202_disassembler(); + virtual ~isbc202_disassembler() = default; + + virtual u32 opcode_alignment() const override; + virtual offs_t disassemble(std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer ¶ms) override; +}; + +isbc202_disassembler::isbc202_disassembler() +{ +} + +u32 isbc202_disassembler::opcode_alignment() const +{ + return 1; +} + +offs_t isbc202_disassembler::disassemble(std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer ¶ms) +{ + uint32_t microcode = opcodes.r32(pc); + + // Decode address control instruction + uint8_t ac = uint8_t(microcode >> 25); + + if ((ac & 0b1100000) == 0b0000000) { + // JCC + util::stream_format(stream , "JCC $%03x" , ((uint16_t(ac) & 0b11111) << 4) | (pc & 0xf)); + } else if ((ac & 0b1110000) == 0b0100000) { + // JZR + util::stream_format(stream , "JZR $00%x" , ac & 0b1111); + } else if ((ac & 0b1110000) == 0b0110000) { + // JCR + util::stream_format(stream , "JCR $%03x" , (pc & 0b111110000) | (ac & 0b1111)); + } else if ((ac & 0b1111000) == 0b1110000) { + // JCE + util::stream_format(stream , "JCE $%03x" , (pc & 0b110001111) | ((ac & 0b111) << 4)); + } else if ((ac & 0b1110000) == 0b1000000) { + // JFL + util::stream_format(stream , "JFL $%03x" , (pc & 0b100001000) | ((ac & 0b1111) << 4) | 0b10); + } else if ((ac & 0b1111000) == 0b1010000) { + // JCF + util::stream_format(stream , "JCF $%03x" , (pc & 0b110001000) | ((ac & 0b111) << 4) | 0b10); + } else if ((ac & 0b1111000) == 0b1011000) { + // JZF + util::stream_format(stream , "JZF $%03x" , (pc & 0b110001000) | ((ac & 0b111) << 4) | 0b10); + } else if ((ac & 0b1111000) == 0b1100000) { + // JPR + util::stream_format(stream , "JPR $%02xx" , ((pc >> 4) & 0b11000) | (ac & 0b111)); + } else if ((ac & 0b1111000) == 0b1101000) { + // JLL + util::stream_format(stream , "JLL $%03x" , (pc & 0b110000000) | ((ac & 0b111) << 4) | 0b100); + } else if ((ac & 0b1111100) == 0b1111100) { + // JRL + util::stream_format(stream , "JRL $%03x" , (pc & 0b110000000) | ((ac & 0b11) << 4) | 0b001001100); + } else { + // JPX + util::stream_format(stream , "JPX $%02xx" , ((pc >> 4) & 0b11100) | (ac & 0b11)); + } + + // Decode input multiplexer + uint8_t in = uint8_t((microcode >> 13) & 7); + util::stream_format(stream , " I=%u" , in); + + // Decode function code + uint8_t fc = uint8_t((microcode >> 18) & 0x7f); + uint8_t fg; + uint8_t rg; + unsigned reg; + i3002_device::decode_fc(fc , fg , rg , reg); + util::stream_format(stream , " CPE=%u%u/%2s" , fg , rg + 1 , i3002_device::reg_name(reg)); + + // Decode flag control + stream << (BIT(microcode , 17) ? " FF1" : " FF0"); + stream << (BIT(microcode , 16) ? " HCZ" : " SCZ"); + + // Decode K + uint8_t slk = uint8_t((microcode >> 8) & 3); + uint8_t mask = uint8_t(microcode); + uint8_t kbus; + if (!BIT(slk , 0)) { + kbus = mask; + } else if (slk == 1) { + kbus = 0xff; + } else { + kbus = 0; + } + util::stream_format(stream , " K=$%02x" , kbus); + + // Decode OUT + util::stream_format(stream , " S=%u M=$%02x" , slk , mask); + if (BIT(slk , 0) && !BIT(mask , 7)) { + uint8_t out = uint8_t((microcode >> 10) & 7); + util::stream_format(stream , " O=%u" , out); + } + + return 1 | SUPPORTED; +} + +// isbc202_device +isbc202_device::isbc202_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : cpu_device(mconfig , ISBC202 , tag , owner , DERIVED_CLOCK(1, 4)) + , device_multibus_interface(mconfig , *this) + , m_mcu(*this , "mcu") + , m_cpes(*this , "cpe%u" , 0) + , m_drives(*this , "floppy%u" , 0) + , m_program_config("microprogram" , ENDIANNESS_BIG , 32 , 9 , -2) + , m_mem_space(nullptr) +{ +} + +isbc202_device::~isbc202_device() +{ +} + +uint8_t isbc202_device::io_r(address_space &space, offs_t offset) +{ + uint8_t res = 0; + + switch (offset) { + case 0: + // Read drive status & INT (auto XACK) + // Bit What + // 7 0 + // 6 Drive 3 ready + // 5 Drive 2 ready + // 4 1 + // 3 1 + // 2 Int pending + // 1 Drive 1 ready + // 0 Drive 0 ready + { + uint8_t ready = m_ready_in & m_ready_ff; + if (BIT(ready , 3)) { + BIT_SET(res , 6); + } + if (BIT(ready , 2)) { + BIT_SET(res , 5); + } + BIT_SET(res , 4); + BIT_SET(res , 3); + if (m_irq) { + BIT_SET(res , 2); + } + if (BIT(ready , 1)) { + BIT_SET(res , 1); + } + if (BIT(ready , 0)) { + BIT_SET(res , 0); + } + } + break; + + case 1: + // Read result type (auto XACK) + m_irq = false; + res = m_data_low_out; + break; + + case 3: + // Read result byte (no auto XACK) + if (!m_2nd_pass) { + set_start(3 , true); + } else { + m_2nd_pass = false; + res = m_data_low_out; + } + break; + + default: + LOG("RD from unknown reg!\n"); + break; + } + + LOG_BUS("IO R @%u=%02x\n" , offset , res); + return res; +} + +void isbc202_device::io_w(address_space &space, offs_t offset, uint8_t data) +{ + LOG_BUS("IO W @%u=%02x\n" , offset , data); + + switch (offset) { + case 0: + case 1: + // Write LSB address + case 2: + // Write MSB address & start op + case 4: + case 5: + case 6: + if (!m_2nd_pass) { + m_cpu_data = data; + set_start(offset , false); + } else { + m_2nd_pass = false; + } + break; + + case 7: + // Reset + pulse_input_line(INPUT_LINE_RESET , attotime::zero); + break; + + default: + LOG("WR to unknown reg!\n"); + break; + } +} + +void isbc202_device::co_w(int state) +{ + m_inputs[ IN_SEL_CO ] = state; + m_mcu->fi_w(state); + m_cpes[ 3 ]->li_w(state); +} + +uint8_t isbc202_device::px_r() +{ + if (BIT(m_px_s1s0 , 0)) { + return m_cmd & 7; + } else if (BIT(m_px_s1s0 , 1)) { + return (m_op_us & 7) | 8; + } else { + return 0; + } +} + +void isbc202_device::device_start() +{ + state_add(STATE_GENPC , "GENPC" , m_microcode_addr).noshow(); + state_add(STATE_GENPCBASE , "CURPC" , m_microcode_addr).noshow(); + state_add(STATE_GENFLAGS , "FLAGS" , m_flags).noshow().callimport().callexport().formatstr("%9s"); + + for (int i = 0; i < i3002_device::REG_COUNT; ++i) { + state_add(i , i3002_device::reg_name(i) , m_regs[ i ]).callimport().callexport(); + } + + save_item(NAME(m_flags)); + save_item(NAME(m_regs)); + save_item(NAME(m_microcode_addr)); + save_item(NAME(m_code_word)); + save_item(NAME(m_ac)); + save_item(NAME(m_fc)); + save_item(NAME(m_fc32)); + save_item(NAME(m_fc10)); + save_item(NAME(m_in_sel)); + save_item(NAME(m_out_sel)); + save_item(NAME(m_slk)); + save_item(NAME(m_mask)); + save_item(NAME(m_kbus)); + save_item(NAME(m_inputs)); + save_item(NAME(m_op_us)); + save_item(NAME(m_px_s1s0)); + save_item(NAME(m_cmd)); + save_item(NAME(m_2nd_pass)); + save_item(NAME(m_ready_in)); + save_item(NAME(m_ready_ff)); + save_item(NAME(m_gate_lower)); + save_item(NAME(m_irq)); + save_item(NAME(m_data_low_out)); + save_item(NAME(m_data_low_in)); + save_item(NAME(m_cpu_data)); + save_item(NAME(m_addr_low_out)); + save_item(NAME(m_mem_wrt)); + save_item(NAME(m_wrt_inh)); + save_item(NAME(m_direction)); + save_item(NAME(m_ibus_cached)); + save_item(NAME(m_ibus)); + save_item(NAME(m_crc)); + save_item(NAME(m_crc_enabled)); + save_item(NAME(m_crc_out)); + save_item(NAME(m_reading)); + save_item(NAME(m_writing)); + save_item(NAME(m_data_sr)); + save_item(NAME(m_last_data_bit)); + save_item(NAME(m_clock_sr)); + save_item(NAME(m_last_f_time)); + save_item(NAME(m_clock_gate)); + save_item(NAME(m_amwrt)); + save_item(NAME(m_dlyd_amwrt)); + + space(AS_PROGRAM).cache(m_cache); + set_icountptr(m_icount); + space(AS_PROGRAM).install_rom(0 , 0x1ff , memregion("microcode")->base()); + + for (auto& d : m_drives) { + d->get_device()->setup_ready_cb(floppy_image_device::ready_cb(&isbc202_device::floppy_ready_cb , this)); + d->get_device()->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&isbc202_device::floppy_index_cb , this)); + } + + m_timeout_timer = timer_alloc(FUNC(isbc202_device::timeout_tick), this); + m_byte_timer = timer_alloc(FUNC(isbc202_device::byte_tick), this); + m_f_timer = timer_alloc(FUNC(isbc202_device::f_tick), this); + + m_mem_space = &m_bus->space(AS_PROGRAM); + m_bus->space(AS_IO).install_readwrite_handler(0x78, 0x7f, read8m_delegate(*this, FUNC(isbc202_device::io_r)), write8m_delegate(*this, FUNC(isbc202_device::io_w))); +} + +void isbc202_device::device_reset() +{ + // Set start address + m_mcu->addr_w(0); + // Select drive #0 + m_op_us = 0; + m_current_drive = m_drives[ 0 ]->get_device(); + + // XFERREQ is always 1 because R/W in CPU memory is instantaneous + m_inputs[ IN_SEL_XFERQ ] = true; + + m_inputs[ IN_SEL_TIMEOUT ] = true; + m_inputs[ IN_SEL_F ] = false; + + m_2nd_pass = false; + + m_irq = false; + + m_reading = false; + m_writing = false; + + m_timeout_timer->reset(); + m_byte_timer->reset(); + m_f_timer->reset(); +} + +TIMER_CALLBACK_MEMBER(isbc202_device::timeout_tick) +{ + m_inputs[ IN_SEL_TIMEOUT ] = true; +} + +TIMER_CALLBACK_MEMBER(isbc202_device::byte_tick) +{ + m_inputs[ IN_SEL_F ] = true; + m_f_timer->adjust(attotime::from_usec(HALF_BIT_CELL_US * 2)); + m_dlyd_amwrt = m_amwrt; + if (m_reading) { + m_last_f_time = machine().time(); + rd_bits(8); + m_byte_timer->adjust(m_pll.ctime - machine().time()); + // Updating of AZ flag actually happens when F goes low + m_inputs[ IN_SEL_AZ ] = m_crc == 0; + } +} + +TIMER_CALLBACK_MEMBER(isbc202_device::f_tick) +{ + m_inputs[ IN_SEL_F ] = false; + if (m_writing) { + write_byte(); + m_data_sr = dbus_r(); + m_byte_timer->adjust(attotime::from_usec(HALF_BIT_CELL_US * 14)); + } +} + +ROM_START(isbc202) + ROM_REGION(0x800 , "microcode" , ROMREGION_32BIT | ROMREGION_BE) + ROM_LOAD32_BYTE("sbc202-a10-0230.bin" , 0x000 , 0x200 , CRC(e8fa3893) SHA1(88fab74b0466e8aac36eee46cd7536ed1b32a2c9)) + ROM_LOAD32_BYTE("sbc202-a11-0261.bin" , 0x001 , 0x200 , CRC(3ad01769) SHA1(4c22b8fc3ea599dd49684ff4dcafc29ec3425c4c)) + ROM_LOAD32_BYTE("sbc202-a12-0233.bin" , 0x002 , 0x200 , CRC(61496232) SHA1(b0473217944b2f6e966d97e97cf5ad8d883a09e4)) + ROM_LOAD32_BYTE("sbc202-a13-0232.bin" , 0x003 , 0x200 , CRC(c369ab86) SHA1(fc3b7f9c3e71ea1442827c51247a9944c6d40b37)) +ROM_END + +const tiny_rom_entry *isbc202_device::device_rom_region() const +{ + return ROM_NAME(isbc202); +} + +static void isbc202_floppies(device_slot_interface &device) +{ + device.option_add("8ssdd" , FLOPPY_8_SSDD); +} + +static void isbc202_floppy_formats(format_registration &fr) +{ + fr.add(FLOPPY_IMG_FORMAT); + fr.add(fs::ISIS); +}; + +void isbc202_device::device_add_mconfig(machine_config &config) +{ + I3001(config , m_mcu , 0); + + // Allocation of the bit-slices: + // m_cpes[ 0 ] Bits 0..1 + // m_cpes[ 1 ] Bits 2..3 + // m_cpes[ 2 ] Bits 4..5 + // m_cpes[ 3 ] Bits 6..7 + for (auto& finder : m_cpes) { + I3002(config , finder , 0); + } + + // Connect CO/CI signals + m_mcu->set_fo_w_cb(m_cpes[ 0 ] , FUNC(i3002_device::ci_w)); + m_cpes[ 0 ]->set_co_w_cb(m_cpes[ 1 ] , FUNC(i3002_device::ci_w)); + m_cpes[ 1 ]->set_co_w_cb(m_cpes[ 2 ] , FUNC(i3002_device::ci_w)); + m_cpes[ 2 ]->set_co_w_cb(m_cpes[ 3 ] , FUNC(i3002_device::ci_w)); + m_cpes[ 3 ]->set_co_w_cb(FUNC(isbc202_device::co_w)); + + // Connect RO/LI signals + m_cpes[ 0 ]->set_ro_w_cb(FUNC(isbc202_device::co_w)); + m_cpes[ 1 ]->set_ro_w_cb(m_cpes[ 0 ] , FUNC(i3002_device::li_w)); + m_cpes[ 2 ]->set_ro_w_cb(m_cpes[ 1 ] , FUNC(i3002_device::li_w)); + m_cpes[ 3 ]->set_ro_w_cb(m_cpes[ 2 ] , FUNC(i3002_device::li_w)); + + // Connect M-bus + m_cpes[ 0 ]->set_mbus_r_cb(NAME([this]() { return mbus_r(); })); + m_cpes[ 1 ]->set_mbus_r_cb(NAME([this]() { return mbus_r() >> 2; })); + m_cpes[ 2 ]->set_mbus_r_cb(NAME([this]() { return mbus_r() >> 4; })); + m_cpes[ 3 ]->set_mbus_r_cb(NAME([this]() { return mbus_r() >> 6; })); + + // Connect I-bus + m_cpes[ 0 ]->set_ibus_r_cb(NAME([this]() { return ibus_r(); })); + m_cpes[ 1 ]->set_ibus_r_cb(NAME([this]() { return ibus_r() >> 2; })); + m_cpes[ 2 ]->set_ibus_r_cb(NAME([this]() { return ibus_r() >> 4; })); + m_cpes[ 3 ]->set_ibus_r_cb(NAME([this]() { return ibus_r() >> 6; })); + + // Connect SX input + m_mcu->set_sx_r_cb(NAME([this]() { return m_microcode_addr & 0xf; })); + // Connect PX input + m_mcu->set_px_r_cb(FUNC(isbc202_device::px_r)); + + // Drives + for (auto& finder : m_drives) { + FLOPPY_CONNECTOR(config , finder , isbc202_floppies , "8ssdd" , isbc202_floppy_formats).set_fixed(true); + } +} + +void isbc202_device::execute_run() +{ + do { + m_microcode_addr = m_mcu->addr_r(); + debugger_instruction_hook(m_microcode_addr); + m_code_word = m_cache.read_dword(m_microcode_addr); + + // Unpack microcode into fields + // Bits Field + //================ + // 31..25 Address Control + // 24..18 Function Code + // 17 Flag Control bits 3 & 2 + // 16 Flag Control bits 1 & 0 + // 15..13 Input multiplexer selection + // 12..10 Output control + // 9..8 SLK field + // 7..0 Mask field + m_ac = uint8_t(m_code_word >> 25); + m_fc = uint8_t((m_code_word >> 18) & 0x7f); + m_fc32 = BIT(m_code_word , 17); + m_fc10 = BIT(m_code_word , 16); + m_in_sel = uint8_t((m_code_word >> 13) & 7); + m_out_sel = uint8_t((m_code_word >> 10) & 7); + m_slk = uint8_t((m_code_word >> 8) & 3); + m_mask = uint8_t(m_code_word); + + m_mcu->fc_w((m_fc32 ? 0b1100 : 0b0000) | (m_fc10 ? 0b0011 : 0b0000)); + + // Set outputs + if (BIT(m_slk , 0) && !BIT(m_mask , 7)) { + set_output(); + } + + // Compute K-bus + if (!BIT(m_slk , 0)) { + m_kbus = m_mask; + } else if (m_slk == 1) { + m_kbus = 0xff; + } else { + m_kbus = 0; + } + + m_ibus_cached = false; + + // Update CPEs + m_cpes[ 0 ]->fc_kbus_w(m_fc , m_kbus); + m_cpes[ 1 ]->fc_kbus_w(m_fc , m_kbus >> 2); + m_cpes[ 2 ]->fc_kbus_w(m_fc , m_kbus >> 4); + m_cpes[ 3 ]->fc_kbus_w(m_fc , m_kbus >> 6); + if (m_cpes[ 0 ]->update_ro()) { + // Data propagate to right (right-shift op) + m_cpes[ 3 ]->clk_w(1); + m_cpes[ 2 ]->clk_w(1); + m_cpes[ 1 ]->clk_w(1); + m_cpes[ 0 ]->clk_w(1); + } else { + // Data propagate to left (every op but right-shift) + m_cpes[ 0 ]->clk_w(1); + m_cpes[ 1 ]->clk_w(1); + m_cpes[ 2 ]->clk_w(1); + m_cpes[ 3 ]->clk_w(1); + } + + // Update MCU + if (m_in_sel == IN_SEL_AC0) { + m_mcu->ac_w(m_ac); + } else { + m_mcu->ac_w((m_ac & 0b1111110) | m_inputs[ m_in_sel ]); + } + m_mcu->clk_w(1); + + m_icount--; + } while (m_icount > 0); +} + +device_memory_interface::space_config_vector isbc202_device::memory_space_config() const +{ + return space_config_vector{ + std::make_pair(AS_PROGRAM , &m_program_config) + }; +} + +std::unique_ptr<util::disasm_interface> isbc202_device::create_disassembler() +{ + return std::make_unique<isbc202_disassembler>(); +} + +void isbc202_device::state_import(const device_state_entry &entry) +{ + switch (entry.index()) { + case i3002_device::REG_R0: + case i3002_device::REG_R1: + case i3002_device::REG_R2: + case i3002_device::REG_R3: + case i3002_device::REG_R4: + case i3002_device::REG_R5: + case i3002_device::REG_R6: + case i3002_device::REG_R7: + case i3002_device::REG_R8: + case i3002_device::REG_R9: + case i3002_device::REG_T: + case i3002_device::REG_AC: + case i3002_device::REG_MAR: + m_cpes[ 0 ]->get_reg(entry.index()) = m_regs[ entry.index() ] & i3002_device::WORD_MASK; + m_cpes[ 1 ]->get_reg(entry.index()) = (m_regs[ entry.index() ] >> 2) & i3002_device::WORD_MASK; + m_cpes[ 2 ]->get_reg(entry.index()) = (m_regs[ entry.index() ] >> 4) & i3002_device::WORD_MASK; + m_cpes[ 3 ]->get_reg(entry.index()) = (m_regs[ entry.index() ] >> 6) & i3002_device::WORD_MASK; + break; + + default: + break; + } +} + +void isbc202_device::state_export(const device_state_entry &entry) +{ + switch (entry.index()) { + case STATE_GENFLAGS: + m_flags = 0; + if (m_inputs[ IN_SEL_CO ]) { + BIT_SET(m_flags , 0); + } + if (m_mcu->fo_r()) { + BIT_SET(m_flags , 1); + } + if (m_mcu->carry_r()) { + BIT_SET(m_flags , 2); + } + if (m_mcu->zero_r()) { + BIT_SET(m_flags , 3); + } + break; + + case i3002_device::REG_R0: + case i3002_device::REG_R1: + case i3002_device::REG_R2: + case i3002_device::REG_R3: + case i3002_device::REG_R4: + case i3002_device::REG_R5: + case i3002_device::REG_R6: + case i3002_device::REG_R7: + case i3002_device::REG_R8: + case i3002_device::REG_R9: + case i3002_device::REG_T: + case i3002_device::REG_AC: + case i3002_device::REG_MAR: + m_regs[ entry.index() ] = m_cpes[ 3 ]->get_reg(entry.index()); + m_regs[ entry.index() ] <<= 2; + m_regs[ entry.index() ] |= m_cpes[ 2 ]->get_reg(entry.index()); + m_regs[ entry.index() ] <<= 2; + m_regs[ entry.index() ] |= m_cpes[ 1 ]->get_reg(entry.index()); + m_regs[ entry.index() ] <<= 2; + m_regs[ entry.index() ] |= m_cpes[ 0 ]->get_reg(entry.index()); + break; + + default: + break; + } +} + +void isbc202_device::state_string_export(const device_state_entry &entry, std::string &str) const +{ + switch (entry.index()) { + case STATE_GENFLAGS: + str = string_format("%c %c %s %s" , + BIT(m_flags , 3) ? 'Z' : '-' , + BIT(m_flags , 2) ? 'C' : '-' , + BIT(m_flags , 1) ? "FO" : "--" , + BIT(m_flags , 0) ? "FI" : "--"); + break; + + default: + break; + } +} + +void isbc202_device::set_output() +{ + switch (m_out_sel) { + case 0: + // Bit What + // 6..3 - + // 2 Head load + // 1 Start timeout + // 0 Step pulse + if (BIT(m_mask , 1)) { + m_inputs[ IN_SEL_TIMEOUT ] = false; + m_timeout_timer->adjust(attotime::from_msec(TIMEOUT_MS)); + } + if (BIT(m_mask , 0) && (!m_direction || m_current_drive->trk00_r())) { + LOG_DR("Step %s\n" , m_direction ? "OUT" : "IN"); + m_current_drive->dir_w(m_direction); + m_current_drive->stp_w(0); + m_current_drive->stp_w(1); + } + break; + + case 1: + // Bit What + // 6 Reset data overrun + // 5 Set XACK + // 4 Set write inhibit + // 3 Clear write inhibit + // 2 - + // 1 Reset RDY latches (0) + // 0 - + if (BIT(m_mask , 5)) { + // Release CPU from wait state + LOG_BUS("CPU out of wait state\n"); + xack_w(0); + // Ensure the MCU executes a few instruction before the CPU + machine().scheduler().add_quantum(attotime::from_usec(1) , attotime::from_usec(5)); + m_inputs[ IN_SEL_START ] = false; + } + if (BIT(m_mask , 4)) { + m_wrt_inh = true; + } + if (BIT(m_mask , 3)) { + m_wrt_inh = false; + } + if (!BIT(m_mask , 1)) { + m_ready_ff = 0xf; + } + break; + + case 2: + // Bit What + // 6..2 - + // 1..0 s1:s0 for PX input selection + m_px_s1s0 = m_mask & 3; + break; + + case 3: + // Bit What + // 6 Z2 + // 5 SR OUT (0 = DATA, 1 = CRC) + // 4 Write gate (0) + // 3 - + // 2 Z1 + // 1..0 - + m_crc_out = BIT(m_mask , 5); + set_rd_wr(m_reading, !BIT(m_mask , 4)); + break; + + case 4: + // Bit What + // 6 Stepping direction (1 = out, 0 = in) + // 5 CRC enable (0) + // 4..0 - + m_direction = BIT(m_mask , 6); + m_crc_enabled = !BIT(m_mask , 5); + break; + + case 5: + // Bit What + // 6..5 - + // 4 Mem write (0) + // 3 GATE LOWER (0) + // 2 - + // 1 INOP RESET (0) + // 0 AMWRT + m_mem_wrt = !BIT(m_mask , 4); + m_gate_lower = BIT(m_mask , 3); + m_amwrt = BIT(m_mask , 0); + break; + + case 6: + // Bit What + // 6 Latch data bus into A24/A25 + // 5 Set INT FF + // 4 Set Track > 43 + // 3 Latch D into A[7..0] + // 2 Latch D into D[7..0] + // 1 Latch D into D[F..8] + // 0 Set XFER REQ + if (BIT(m_mask , 6)) { + m_data_low_in = m_cpu_data; + } + if (BIT(m_mask , 5)) { + m_irq = true; + } + if (BIT(m_mask , 3)) { + m_addr_low_out = dbus_r(); + } + if (BIT(m_mask , 2)) { + m_data_low_out = dbus_r(); + } + if (BIT(m_mask , 0)) { + if (m_mem_wrt) { + if (!m_wrt_inh) { + // CPU memory write + uint16_t addr = m_addr_low_out | (uint16_t(abus_r()) << 8); + if (m_mem_space) { + LOG_BUS("MEM W %04x=%02x\n" , addr , m_data_low_out); + m_mem_space->write_byte(addr , m_data_low_out); + } else { + LOG("CPU AS not set!\n"); + } + } + } else { + // CPU memory read + uint16_t addr = m_addr_low_out | (uint16_t(abus_r()) << 8); + if (m_mem_space) { + m_data_low_in = m_mem_space->read_byte(addr); + LOG_BUS("MEM R %04x=%02x\n" , addr , m_data_low_in); + } else { + LOG("CPU AS not set!\n"); + } + } + } + break; + + case 7: + // Bit What + // 6 Latch OP & US + // 5 Reset INDEX + // 4 Set Track <= 43 + // 3 Clear START/STOP + // 2 Unload head + // 1 Set RESET READ + // 0 Clear RESET READ + if (BIT(m_mask , 6)) { + m_op_us = dbus_r() & 0x3f; + m_current_drive = m_drives[ selected_drive() ]->get_device(); + } + if (BIT(m_mask , 5)) { + m_inputs[ IN_SEL_INDEX ] = false; + } + if (BIT(m_mask , 3)) { + m_cmd = 0; + LOG_BUS("CLR ST\n"); + // TODO: more + } + if (BIT(m_mask , 1)) { + set_rd_wr(false, m_writing); + } + if (BIT(m_mask , 0)) { + set_rd_wr(true, m_writing); + } + break; + } +} + +unsigned isbc202_device::selected_drive() const +{ + return (m_op_us >> 4) & 3; +} + +unsigned isbc202_device::drive_idx(floppy_image_device *drive) +{ + for (unsigned i = 0; i < 4; ++i) { + if (drive == m_drives[ i ]->get_device()) { + return i; + } + } + LOG("Unknown drive!\n"); + return 0; +} + +void isbc202_device::floppy_ready_cb(floppy_image_device *floppy , int state) +{ + unsigned idx = drive_idx(floppy); + + uint8_t old_state = m_ready_in; + + if (!state) { + BIT_SET(m_ready_in , idx); + } else { + BIT_CLR(m_ready_in , idx); + } + + uint8_t gone_not_ready = ~m_ready_in & old_state; + + BIT_CLR(gone_not_ready, selected_drive()); + + m_ready_ff &= ~gone_not_ready; +} + +void isbc202_device::floppy_index_cb(floppy_image_device *floppy , int state) +{ + if (state && floppy == m_current_drive) { + LOG_DR("Index @%.6f\n" , machine().time().as_double()); + m_inputs[ IN_SEL_INDEX ] = true; + } +} + +uint8_t isbc202_device::dbus_r() const +{ + return m_cpes[ 0 ]->dbus_r() | + (m_cpes[ 1 ]->dbus_r() << 2) | + (m_cpes[ 2 ]->dbus_r() << 4) | + (m_cpes[ 3 ]->dbus_r() << 6); +} + +uint8_t isbc202_device::mbus_r() const +{ + return m_gate_lower ? 0 : m_data_low_in; +} + +uint8_t isbc202_device::ibus_r() +{ + if (!m_ibus_cached) { + m_ibus_cached = true; + m_ibus = 0xff; + if (BIT(m_slk , 0) && BIT(m_mask , 7)) { + if (BIT(m_mask , 5)) { + // Bit What + // 7 Drive 3/1 ready + // 6 Drive 2/0 ready + // 5 Track 0 + // 4 STOP + // 3..0 1 + m_ibus = 0x0f; + uint8_t ready = m_ready_in & m_ready_ff; + if (m_gate_lower) { + ready >>= 2; + } + if (BIT(ready , 1)) { + BIT_SET(m_ibus, 7); + } + if (BIT(ready , 0)) { + BIT_SET(m_ibus, 6); + } + if (!m_current_drive->trk00_r()) { + BIT_SET(m_ibus, 5); + } + // TODO: STOP bit + } else if (BIT(m_mask , 6)) { + // Bit What + // 7 Selected drive not ready + // 6 Write fault + // 5 Write protection + // 4 Data overrun + // 3..0 1 + m_ibus = 0x0f; + if (!BIT(m_ready_in , selected_drive())) { + BIT_SET(m_ibus, 7); + } + if (m_current_drive->wpt_r()) { + BIT_SET(m_ibus, 5); + } + } else if (BIT(m_mask , 3)) { + // Read clock SR + m_ibus = aligned_rd_data(m_clock_sr); + } else if (BIT(m_mask , 4)) { + // Read data SR + m_ibus = aligned_rd_data(m_data_sr); + } + } + } + + return m_ibus; +} + +uint8_t isbc202_device::abus_r() const +{ + return m_cpes[ 0 ]->abus_r() | + (m_cpes[ 1 ]->abus_r() << 2) | + (m_cpes[ 2 ]->abus_r() << 4) | + (m_cpes[ 3 ]->abus_r() << 6); +} + +void isbc202_device::set_start(uint8_t off , bool read) +{ + m_cmd = off; + m_inputs[ IN_SEL_START ] = true; + // Put CPU in wait state + xack_w(1); + m_cpu_rd = read; + m_2nd_pass = true; + LOG_BUS("CPU in wait state (rd=%d)\n" , read); +} + +void isbc202_device::set_rd_wr(bool new_rd , bool new_wr) +{ + if (!m_reading && new_rd) { + // Start reading + LOG_RD("Start RD @%.6f\n" , machine().time().as_double()); + m_pll.set_clock(attotime::from_usec(HALF_BIT_CELL_US)); + m_pll.read_reset(machine().time()); + + // Search for next SYNC (16x 1 and a 0) + m_byte_timer->reset(); + m_f_timer->reset(); + unsigned cnt_trans = 0; + attotime rot_period = attotime::from_hz(6); + while ((m_pll.ctime - machine().time()) < rot_period) { + attotime edge = m_current_drive->get_next_transition(m_pll.ctime); + if (edge.is_never()) { + break; + } + attotime tm; + bool bit = m_pll.feed_read_data(tm , edge , attotime::never); + if (cnt_trans < 32) { + if (!(BIT(cnt_trans , 0) ^ bit)) { + cnt_trans++; + } else { + cnt_trans = 0; + } + } else if (cnt_trans == 32) { + if (!bit) { + cnt_trans++; + } else { + cnt_trans = 0; + } + } else { + if (!bit) { + LOG_RD("SYNC found @%.6f\n" , m_pll.ctime.as_double()); + // GOT SYNC! + if (m_crc_enabled) { + // CRC shouldn't be enabled here or register won't get cleared + LOG("Huh? CRC enabled during SYNC scan?\n"); + } + m_crc = 0; + // Load the "0" bit into data/clock SR + m_data_sr = 0; + m_clock_sr = 0; + // Read 7 more bits to make a full byte + rd_bits(7); + // Timer to go off at end of 8th bit of AM byte (when F signal goes high) + m_byte_timer->adjust(m_pll.ctime - machine().time()); + break; + } else { + cnt_trans = 32; + } + } + } + } else if (m_reading && !new_rd) { + // Stop reading + LOG_RD("Stop RD\n"); + m_byte_timer->reset(); + m_f_timer->reset(); + m_inputs[ IN_SEL_F ] = false; + } + m_reading = new_rd; + + if (!m_writing && new_wr) { + // Start writing + LOG_WR("Start WR\n"); + m_pll.set_clock(attotime::from_usec(HALF_BIT_CELL_US)); + m_pll.start_writing(machine().time()); + m_pll.ctime = machine().time(); + m_last_data_bit = false; + m_byte_timer->adjust(attotime::from_usec(HALF_BIT_CELL_US * 14)); + } else if (m_writing && !new_wr) { + // Stop writing + LOG_WR("Stop WR\n"); + m_pll.stop_writing(m_current_drive , machine().time()); + m_byte_timer->reset(); + m_f_timer->reset(); + m_inputs[ IN_SEL_F ] = false; + } + m_writing = new_wr; +} + +uint8_t isbc202_device::aligned_rd_data(uint16_t sr) +{ + attotime tmp{ machine().time() - m_last_f_time }; + + // Compute how many bit cells have gone by since the last time F went high + unsigned bits = tmp.as_ticks(BIT_FREQUENCY); + if (bits) { + LOG_RD("Aligning by %u bits\n" , bits); + sr <<= bits; + } + return uint8_t(sr >> 8); +} + +void isbc202_device::rd_bits(unsigned n) +{ + while (n--) { + attotime edge = m_current_drive->get_next_transition(m_pll.ctime); + if (edge.is_never()) { + break; + } + attotime tm; + bool clock_bit = m_pll.feed_read_data(tm , edge , attotime::never); + edge = m_current_drive->get_next_transition(m_pll.ctime); + if (edge.is_never()) { + break; + } + bool data_bit = m_pll.feed_read_data(tm , edge , attotime::never); + + m_clock_sr = (m_clock_sr << 1) | clock_bit; + bool crc_bit = BIT(m_data_sr , 15); + m_data_sr = (m_data_sr << 1) | data_bit; + update_crc(crc_bit); + } + LOG_RD("CLK %04x DT %04x CRC %04x\n" , m_clock_sr , m_data_sr , m_crc); +} + +void isbc202_device::write_byte() +{ + LOG_WR("WR DT %02x CRC %04x CE %d CO %d AW %d @%.6f\n" , m_data_sr & 0xff , m_crc , m_crc_enabled , m_crc_out , m_dlyd_amwrt , machine().time().as_double()); + + for (unsigned i = 0; i < 8; i++) { + bool sr_bit = BIT(m_data_sr , 7); + bool crc_bit = update_crc(sr_bit); + bool data_bit = m_crc_out ? crc_bit : sr_bit; + bool clock_bit = m_clock_gate && !data_bit && !m_last_data_bit; + if (i <= 3 && m_dlyd_amwrt) { + m_clock_gate = true; + } else { + m_clock_gate = !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); + m_data_sr <<= 1; + m_last_data_bit = data_bit; + } + m_pll.commit(m_current_drive , machine().time()); + m_pll.ctime = machine().time(); +} + +bool isbc202_device::update_crc(bool bit) +{ + bool out = BIT(m_crc , 15); + + if (m_crc_enabled && (out ^ bit)) { + m_crc = (m_crc << 1) ^ CRC_POLY; + } else { + m_crc <<= 1; + } + + return out; +} |