// license:BSD-3-Clause // copyright-holders:Carl // Peripheral code from rmnimbus driver by Phill Harvey-Smith which is // based on the Leland sound driver by Aaron Giles and Paul Leaman // Note: the X1 input (typically an XTAL) is divided by 2 internally. // The device clock should therefore be twice the desired operating // frequency (and twice the speed rating suffixed to the part number). // Some high-performance AMD versions, however, generate the system // clock with a PLL and only support XTAL dividers other than 1 for // compatibility and power-saving modes. #include "emu.h" #include "i186.h" #include "i86inline.h" #define LOG_PORTS (1U << 1) #define LOG_INTERRUPTS (1U << 2) #define LOG_INTERRUPTS_EXT (1U << 3) #define LOG_TIMER (1U << 4) #define LOG_DMA (1U << 5) #define LOG_DMA_HIFREQ (1U << 6) #define VERBOSE (0) #include "logmacro.h" /* external int priority masks */ #define EXTINT_CTRL_PRI_MASK 0x07 #define EXTINT_CTRL_MSK 0x08 #define EXTINT_CTRL_LTM 0x10 #define EXTINT_CTRL_CASCADE 0x20 #define EXTINT_CTRL_SFNM 0x40 /* DMA control register */ #define DEST_MIO 0x8000 #define DEST_DECREMENT 0x4000 #define DEST_INCREMENT 0x2000 #define DEST_NO_CHANGE (DEST_DECREMENT | DEST_INCREMENT) #define DEST_INCDEC_MASK (DEST_DECREMENT | DEST_INCREMENT) #define SRC_MIO 0x1000 #define SRC_DECREMENT 0x0800 #define SRC_INCREMENT 0x0400 #define SRC_NO_CHANGE (SRC_DECREMENT | SRC_INCREMENT) #define SRC_INCDEC_MASK (SRC_DECREMENT | SRC_INCREMENT) #define TERMINATE_ON_ZERO 0x0200 #define INTERRUPT_ON_ZERO 0x0100 #define SYNC_MASK 0x00C0 #define SYNC_SOURCE 0x0040 #define SYNC_DEST 0x0080 #define CHANNEL_PRIORITY 0x0020 #define TIMER_DRQ 0x0010 #define CHG_NOCHG 0x0004 #define ST_STOP 0x0002 #define BYTE_WORD 0x0001 /* these come from the Intel 80186 datasheet */ const uint8_t i80186_cpu_device::m_i80186_timing[] = { 45,28, /* exception, IRET */ 0, 2, 4, 3, /* INTs */ 2, /* segment overrides */ 2, 2, 3, /* flag operations */ 8, 7,19,15, /* arithmetic adjusts */ 4, 4, /* decimal adjusts */ 2, 4, /* sign extension */ 2,18, 6, 2, 6,11, /* misc */ 14,14,14, /* direct JMPs */ 11,17,26, /* indirect JMPs */ 15,23, /* direct CALLs */ 13,19,38, /* indirect CALLs */ 16,22,18,25, /* returns */ 4,13, 5,15, /* conditional JMPs */ 6,16, 6,16, /* loops */ 10,10, 8, 8, /* port reads */ 9, 9, 7, 7, /* port writes */ 2, 9,12, /* move, 8-bit */ 3,12, /* move, 8-bit immediate */ 2, 9,12, /* move, 16-bit */ 4,13, /* move, 16-bit immediate */ 8, 8, 9, 9, /* move, AL/AX memory */ 2,11, 2,11, /* move, segment registers */ 4,17, /* exchange, 8-bit */ 4,17, 3, /* exchange, 16-bit */ 10,16, 9, 9, /* pushes */ 10,20, 8, 8, /* pops */ 3,10,10, /* ALU ops, 8-bit */ 4,16,10, /* ALU ops, 8-bit immediate */ 3,10,10, /* ALU ops, 16-bit */ 4,16,10, /* ALU ops, 16-bit immediate */ 4,16,10, /* ALU ops, 16-bit w/8-bit immediate */ 26,35,32,41, /* MUL */ 25,34,31,40, /* IMUL */ 29,38,35,44, /* DIV */ 44,53,50,59, /* IDIV */ 3, 3,15,15, /* INC/DEC */ 3, 3,10,10, /* NEG/NOT */ 2, 5, 1, /* reg shift/rotate */ 15,17, 1, /* m8 shift/rotate */ 15,17, 1, /* m16 shift/rotate */ 22, 5,22, /* CMPS 8-bit */ 22, 5,22, /* CMPS 16-bit */ 15, 5,15, /* SCAS 8-bit */ 15, 5,15, /* SCAS 16-bit */ 12, 6,11, /* LODS 8-bit */ 12, 6,11, /* LODS 16-bit */ 10, 6, 9, /* STOS 8-bit */ 10, 6, 9, /* STOS 16-bit */ 14, 8, 8, /* MOVS 8-bit */ 14, 8, 8, /* MOVS 16-bit */ 14, 8, 8, /* (80186) INS 8-bit */ 14, 8, 8, /* (80186) INS 16-bit */ 14, 8, 8, /* (80186) OUTS 8-bit */ 14, 8, 8, /* (80186) OUTS 16-bit */ 14,68,83, /* (80186) PUSH immediate, PUSHA/POPA */ 22,29, /* (80186) IMUL immediate 8-bit */ 25,32, /* (80186) IMUL immediate 16-bit */ 15,25,4,16, 8, /* (80186) ENTER/LEAVE */ 33, /* (80186) BOUND */ }; DEFINE_DEVICE_TYPE(I80186, i80186_cpu_device, "i80186", "Intel 80186") DEFINE_DEVICE_TYPE(I80188, i80188_cpu_device, "i80188", "Intel 80188") DEFINE_DEVICE_TYPE(AM186EM, am186em_device, "am186em", "AMD Am186EM") DEFINE_DEVICE_TYPE(AM188EM, am188em_device, "am188em", "AMD Am188EM") i80186_cpu_device::i80186_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i80186_cpu_device(mconfig, I80186, tag, owner, clock, 16) { } i80188_cpu_device::i80188_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i80186_cpu_device(mconfig, I80188, tag, owner, clock, 8) { } am186em_device::am186em_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i80186_cpu_device(mconfig, AM186EM, tag, owner, clock, 16) { } am188em_device::am188em_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i80186_cpu_device(mconfig, AM188EM, tag, owner, clock, 8) { } i80186_cpu_device::i80186_cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, int data_bus_size) : i8086_common_cpu_device(mconfig, type, tag, owner, clock) , m_program_config("program", ENDIANNESS_LITTLE, data_bus_size, 20, 0) , m_opcodes_config("opcodes", ENDIANNESS_LITTLE, data_bus_size, 20, 0) , m_io_config("io", ENDIANNESS_LITTLE, data_bus_size, 16, 0) , m_read_slave_ack_func(*this, 0) , m_out_chip_select_func(*this) , m_out_tmrout0_func(*this) , m_out_tmrout1_func(*this) , m_irmx_irq_cb(*this) , m_irqa_cb(*this) , m_irmx_irq_ack(*this) { memcpy(m_timing, m_i80186_timing, sizeof(m_i80186_timing)); set_irq_acknowledge_callback(*this, FUNC(i80186_cpu_device::inta_callback)); } device_memory_interface::space_config_vector i80186_cpu_device::memory_space_config() const { if (has_configured_map(AS_OPCODES)) return space_config_vector { std::make_pair(AS_PROGRAM, &m_program_config), std::make_pair(AS_OPCODES, &m_opcodes_config), std::make_pair(AS_IO, &m_io_config) }; else return space_config_vector { std::make_pair(AS_PROGRAM, &m_program_config), std::make_pair(AS_IO, &m_io_config) }; } uint8_t i80186_cpu_device::fetch() { uint8_t data = m_or8(update_pc()); m_ip++; return data; } void i80186_cpu_device::execute_run() { while (m_icount > 0) { if ((m_dma[0].drq_state && (m_dma[0].control & ST_STOP)) || (m_dma[1].drq_state && (m_dma[1].control & ST_STOP))) { int channel = m_last_dma ? 0 : 1; m_last_dma = !m_last_dma; if (!(m_dma[1].drq_state && (m_dma[1].control & ST_STOP))) channel = 0; else if (!(m_dma[0].drq_state && (m_dma[0].control & ST_STOP))) channel = 1; else if ((m_dma[0].control & CHANNEL_PRIORITY) && !(m_dma[1].control & CHANNEL_PRIORITY)) channel = 0; else if ((m_dma[1].control & CHANNEL_PRIORITY) && !(m_dma[0].control & CHANNEL_PRIORITY)) channel = 1; m_icount--; drq_callback(channel); continue; } if (m_seg_prefix_next) { m_seg_prefix = true; m_seg_prefix_next = false; } else { m_prev_ip = m_ip; m_seg_prefix = false; /* Dispatch IRQ */ if (m_pending_irq && m_no_interrupt == 0) { if (m_pending_irq & NMI_IRQ) { interrupt(2); m_pending_irq &= ~NMI_IRQ; m_halt = false; } else if (m_IF) { /* the actual vector is retrieved after pushing flags */ /* and clearing the IF */ interrupt(-1); m_halt = false; } } if (m_halt) { debugger_wait_hook(); m_icount = 0; return; } /* No interrupt allowed between last instruction and this one */ if (m_no_interrupt) { m_no_interrupt--; } /* trap should allow one instruction to be executed */ if (m_fire_trap) { if (m_fire_trap >= 2) { interrupt(1); m_fire_trap = 0; } else { m_fire_trap++; } } } debugger_instruction_hook( update_pc() ); uint8_t op = fetch_op(); switch(op) { case 0x60: // i_pusha { uint32_t tmp = m_regs.w[SP]; PUSH(m_regs.w[AX]); PUSH(m_regs.w[CX]); PUSH(m_regs.w[DX]); PUSH(m_regs.w[BX]); PUSH(tmp); PUSH(m_regs.w[BP]); PUSH(m_regs.w[SI]); PUSH(m_regs.w[DI]); CLK(PUSHA); } break; case 0x61: // i_popa m_regs.w[DI] = POP(); m_regs.w[SI] = POP(); m_regs.w[BP] = POP(); POP(); m_regs.w[BX] = POP(); m_regs.w[DX] = POP(); m_regs.w[CX] = POP(); m_regs.w[AX] = POP(); CLK(POPA); break; case 0x62: // i_bound { m_modrm = fetch(); uint32_t low = GetRMWord(); uint32_t high = GetnextRMWord(); uint32_t tmp = RegWord(); if (tmp < low || tmp > high) interrupt(5); CLK(BOUND); logerror("%06x: bound %04x high %04x low %04x tmp\n", m_pc, high, low, tmp); } break; case 0x68: // i_push_d16 PUSH(fetch_word()); CLK(PUSH_IMM); break; case 0x69: // i_imul_d16 { DEF_r16w(); uint32_t tmp = fetch_word(); m_dst = (int32_t)((int16_t)m_src) * (int32_t)((int16_t)tmp); m_CarryVal = m_OverVal = (((int32_t)m_dst) >> 15 != 0) && (((int32_t)m_dst) >> 15 != -1); RegWord(m_dst); CLKM(IMUL_RRI16, IMUL_RMI16); } break; case 0x6a: // i_push_d8 PUSH((uint16_t)((int16_t)((int8_t)fetch()))); CLK(PUSH_IMM); break; case 0x6b: // i_imul_d8 { DEF_r16w(); uint32_t src2 = (uint16_t)(int8_t)fetch(); m_dst = (int32_t)((int16_t)m_src) * (int32_t)((int16_t)src2); m_CarryVal = m_OverVal = (((int32_t)m_dst) >> 15 != 0) && (((int32_t)m_dst) >> 15 != -1); RegWord(m_dst); CLKM(IMUL_RRI8, IMUL_RMI8); } break; case 0x6c: // i_insb i_insb(); break; case 0x6d: // i_insw i_insw(); break; case 0x6e: // i_outsb i_outsb(); break; case 0x6f: // i_outsw i_outsw(); break; case 0x8e: // i_mov_sregw m_modrm = fetch(); m_src = GetRMWord(); CLKM(MOV_SR, MOV_SM); switch (m_modrm & 0x38) { case 0x00: /* mov es,ew */ m_sregs[ES] = m_src; break; case 0x10: /* mov ss,ew */ m_sregs[SS] = m_src; m_no_interrupt = 1; break; case 0x18: /* mov ds,ew */ m_sregs[DS] = m_src; break; default: logerror("%06x: Mov Sreg - Invalid register\n", m_pc); m_ip = m_prev_ip; interrupt(6); break; } break; case 0xc0: // i_rotshft_bd8 { m_modrm = fetch(); m_src = GetRMByte(); m_dst = m_src; uint8_t c = fetch() & 0x1f; CLKM(ROT_REG_BASE, ROT_M8_BASE); m_icount -= m_timing[ROT_REG_BIT] * c; if (c) { switch (m_modrm & 0x38) { case 0x00: do { ROL_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x08: do { ROR_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x10: do { ROLC_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x18: do { RORC_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x30: case 0x20: SHL_BYTE(c); break; case 0x28: SHR_BYTE(c); break; case 0x38: SHRA_BYTE(c); break; } } } break; case 0xc1: // i_rotshft_wd8 { m_modrm = fetch(); m_src = GetRMWord(); m_dst = m_src; uint8_t c = fetch() & 0x1f; CLKM(ROT_REG_BASE, ROT_M16_BASE); m_icount -= m_timing[ROT_REG_BIT] * c; if (c) { switch (m_modrm & 0x38) { case 0x00: do { ROL_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x08: do { ROR_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x10: do { ROLC_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x18: do { RORC_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x30: case 0x20: SHL_WORD(c); break; case 0x28: SHR_WORD(c); break; case 0x38: SHRA_WORD(c); break; } } } break; case 0xc8: // i_enter { uint16_t nb = fetch(); nb |= fetch() << 8; uint32_t level = fetch(); CLK(!level ? ENTER0 : (level == 1) ? ENTER1 : ENTER_BASE); if (level > 1) m_icount -= level * m_timing[ENTER_COUNT]; PUSH(m_regs.w[BP]); m_regs.w[BP] = m_regs.w[SP]; m_regs.w[SP] -= nb; for (int i = 1; i < level; i++) { PUSH(GetMemW(SS, m_regs.w[BP] - i * 2)); } if (level) { PUSH(m_regs.w[BP]); } } break; case 0xc9: // i_leave m_regs.w[SP] = m_regs.w[BP]; m_regs.w[BP] = POP(); CLK(LEAVE); break; case 0xd2: // i_rotshft_bcl { m_modrm = fetch(); m_src = GetRMByte(); m_dst = m_src; uint8_t c = m_regs.b[CL] & 0x1f; CLKM(ROT_REG_BASE, ROT_M16_BASE); m_icount -= m_timing[ROT_REG_BIT] * c; if (c) { switch (m_modrm & 0x38) { case 0x00: do { ROL_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x08: do { ROR_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x10: do { ROLC_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x18: do { RORC_BYTE(); c--; } while (c > 0); PutbackRMByte(m_dst); break; case 0x30: case 0x20: SHL_BYTE(c); break; case 0x28: SHR_BYTE(c); break; case 0x38: SHRA_BYTE(c); break; } } } break; case 0xd3: // i_rotshft_wcl { m_modrm = fetch(); m_src = GetRMWord(); m_dst = m_src; uint8_t c = m_regs.b[CL] & 0x1f; CLKM(ROT_REG_BASE, ROT_M16_BASE); m_icount -= m_timing[ROT_REG_BIT] * c; if (c) { switch (m_modrm & 0x38) { case 0x00: do { ROL_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x08: do { ROR_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x10: do { ROLC_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x18: do { RORC_WORD(); c--; } while (c > 0); PutbackRMWord(m_dst); break; case 0x30: case 0x20: SHL_WORD(c); break; case 0x28: SHR_WORD(c); break; case 0x38: SHRA_WORD(c); break; } } } break; case 0xd8: // i_esc case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: if (m_reloc & 0x8000) { m_ip = m_prev_ip; interrupt(7); break; } m_modrm = fetch(); GetRMByte(); CLK(NOP); // The 80187 has the FSTSW AX instruction if ((m_modrm == 0xe0) && (op == 0xdf)) m_regs.w[AX] = 0xffff; // FPU not present break; case 0xe6: // i_outal write_port_byte_al(fetch()); CLK(OUT_IMM8); break; case 0xee: // i_outdxal write_port_byte_al(m_regs.w[DX]); CLK(OUT_DX8); break; case 0xf2: // i_repne case 0xf3: { bool pass = false; uint8_t next = repx_op(); uint16_t c = m_regs.w[CX]; switch (next) { case 0x6c: CLK(OVERRIDE); if (c) { do { i_insb(); c--; } while (c > 0 && m_icount > 0); } m_regs.w[CX] = c; m_seg_prefix = false; m_seg_prefix_next = false; break; case 0x6d: CLK(OVERRIDE); if (c) { do { i_insw(); c--; } while (c > 0 && m_icount > 0); } m_regs.w[CX] = c; m_seg_prefix = false; m_seg_prefix_next = false; break; case 0x6e: CLK(OVERRIDE); if (c) { do { i_outsb(); c--; } while (c > 0 && m_icount > 0); } m_regs.w[CX] = c; m_seg_prefix = false; m_seg_prefix_next = false; break; case 0x6f: CLK(OVERRIDE); if (c) { do { i_outsw(); c--; } while (c > 0 && m_icount > 0); } m_regs.w[CX] = c; m_seg_prefix = false; m_seg_prefix_next = false; break; default: // Decrement IP and pass on m_ip -= 1 + (m_seg_prefix_next ? 1 : 0); pass = true; break; } if (!pass) { if (c) m_ip = m_prev_ip; break; } } [[fallthrough]]; default: if (!common_op(op)) { m_icount -= 10; // UD fault timing? logerror("%06x: Invalid Opcode %02x\n", m_pc, op); m_ip = m_prev_ip; interrupt(6); // 80186 has #UD break; } } } } void i80186_cpu_device::device_start() { i8086_common_cpu_device::device_start(); state_add( I8086_ES, "ES", m_sregs[ES] ).formatstr("%04X"); state_add( I8086_CS, "CS", m_sregs[CS] ).callimport().formatstr("%04X"); state_add( I8086_SS, "SS", m_sregs[SS] ).formatstr("%04X"); state_add( I8086_DS, "DS", m_sregs[DS] ).formatstr("%04X"); state_add( I8086_VECTOR, "V", m_int_vector).formatstr("%02X"); state_add( I8086_PC, "PC", m_pc ).callimport().formatstr("%05X"); state_add( STATE_GENPCBASE, "CURPC", [this] { return (m_sregs[CS] << 4) + m_prev_ip; }).mask(0xfffff).noshow(); state_add( I8086_HALT, "HALT", m_halt ).mask(1); // Most of these mnemonics are borrowed from the Intel 80C186EA/80C188EA User's Manual. // (The 80C186EA/80C188EA's peripheral block is mapped incompatibly but mostly functionally analogous.) state_add( I80186_RELREG, "RELREG", m_reloc ).formatstr("%04X"); state_add( I80186_UMCS, "UMCS", m_mem.upper ).formatstr("%04X"); state_add( I80186_LMCS, "LMCS", m_mem.lower ).formatstr("%04X"); state_add( I80186_PACS, "PACS", m_mem.peripheral ).formatstr("%04X"); state_add( I80186_MMCS, "MMCS", m_mem.middle ).formatstr("%04X"); state_add( I80186_MPCS, "MPCS", m_mem.middle_size ).formatstr("%04X"); state_add( I80186_DxSRC + 0, "D0SRC", m_dma[0].source ).formatstr("%05X").mask(0xfffff); state_add( I80186_DxDST + 0, "D0DST", m_dma[0].dest ).formatstr("%05X").mask(0xfffff); state_add( I80186_DxTC + 0, "D0TC", m_dma[0].count ).formatstr("%04X"); state_add( I80186_DxCON + 0, "D0CON", m_dma[0].control ).formatstr("%04X"); state_add( I80186_DxSRC + 1, "D1SRC", m_dma[1].source ).formatstr("%05X").mask(0xfffff); state_add( I80186_DxDST + 1, "D1DST", m_dma[1].dest ).formatstr("%05X").mask(0xfffff); state_add( I80186_DxTC + 1, "D1TC", m_dma[1].count ).formatstr("%04X"); state_add( I80186_DxCON + 1, "D1CON", m_dma[1].control ).formatstr("%04X"); state_add( I80186_TxCNT + 0, "T0CNT", m_timer[0].count ).formatstr("%04X"); state_add( I80186_TxCMPA + 0, "T0CMPA", m_timer[0].maxA ).formatstr("%04X"); state_add( I80186_TxCMPB + 0, "T0CMPB", m_timer[0].maxB ).formatstr("%04X"); state_add( I80186_TxCON + 0, "T0CON", m_timer[0].control ).formatstr("%04X"); state_add( I80186_TxCNT + 1, "T1CNT", m_timer[1].count ).formatstr("%04X"); state_add( I80186_TxCMPA + 1, "T1CMPA", m_timer[1].maxA ).formatstr("%04X"); state_add( I80186_TxCMPB + 1, "T1CMPB", m_timer[1].maxB ).formatstr("%04X"); state_add( I80186_TxCON + 1, "T1CON", m_timer[1].control ).formatstr("%04X"); state_add( I80186_TxCNT + 2, "T2CNT", m_timer[2].count ).formatstr("%04X"); state_add( I80186_TxCMPA + 2, "T2CMPA", m_timer[2].maxA ).formatstr("%04X"); state_add( I80186_TxCON + 2, "T2CON", m_timer[2].control ).formatstr("%04X"); state_add( I80186_INSERV, "INSERV", m_intr.in_service ).formatstr("%04X"); state_add( I80186_REQST, "REQST", m_intr.request ).formatstr("%04X"); state_add( I80186_PRIMSK, "PRIMSK", m_intr.priority_mask ).formatstr("%04X"); state_add( I80186_INTSTS, "INTSTS", m_intr.status ).formatstr("%04X"); state_add( I80186_TCUCON, "TCUCON", m_intr.timer[0] ).formatstr("%04X"); state_add( I80186_DMA0CON, "DMA0CON", m_intr.dma[0] ).formatstr("%04X"); state_add( I80186_DMA1CON, "DMA1CON", m_intr.dma[1] ).formatstr("%04X"); state_add( I80186_I0CON, "I0CON", m_intr.ext[0] ).formatstr("%04X"); state_add( I80186_I1CON, "I1CON", m_intr.ext[1] ).formatstr("%04X"); state_add( I80186_I2CON, "I2CON", m_intr.ext[2] ).formatstr("%04X"); state_add( I80186_I3CON, "I3CON", m_intr.ext[3] ).formatstr("%04X"); state_add( I80186_POLLSTS, "POLLSTS", m_intr.poll_status ).formatstr("%04X"); // register for savestates save_item(STRUCT_MEMBER(m_timer, control)); save_item(STRUCT_MEMBER(m_timer, maxA)); save_item(STRUCT_MEMBER(m_timer, maxB)); save_item(STRUCT_MEMBER(m_timer, count)); save_item(STRUCT_MEMBER(m_dma, source)); save_item(STRUCT_MEMBER(m_dma, dest)); save_item(STRUCT_MEMBER(m_dma, count)); save_item(STRUCT_MEMBER(m_dma, control)); save_item(NAME(m_intr.vector)); save_item(NAME(m_intr.pending)); save_item(NAME(m_intr.ack_mask)); save_item(NAME(m_intr.priority_mask)); save_item(NAME(m_intr.in_service)); save_item(NAME(m_intr.request)); save_item(NAME(m_intr.status)); save_item(NAME(m_intr.poll_status)); save_item(NAME(m_intr.timer)); save_item(NAME(m_intr.dma)); save_item(NAME(m_intr.ext)); save_item(NAME(m_intr.ext_state)); save_item(NAME(m_mem.lower)); save_item(NAME(m_mem.upper)); save_item(NAME(m_mem.middle)); save_item(NAME(m_mem.middle_size)); save_item(NAME(m_mem.peripheral)); save_item(NAME(m_reloc)); save_item(NAME(m_last_dma)); // zerofill memset(m_timer, 0, sizeof(m_timer)); memset(m_dma, 0, sizeof(m_dma)); memset(&m_intr, 0, sizeof(intr_state)); memset(&m_mem, 0, sizeof(mem_state)); m_reloc = 0; m_last_dma = 0; m_timer[0].int_timer = timer_alloc(FUNC(i80186_cpu_device::timer_elapsed), this); m_timer[1].int_timer = timer_alloc(FUNC(i80186_cpu_device::timer_elapsed), this); m_timer[2].int_timer = timer_alloc(FUNC(i80186_cpu_device::timer_elapsed), this); m_irmx_irq_ack.resolve_safe(0); } void i80186_cpu_device::device_reset() { i8086_common_cpu_device::device_reset(); /* reset the interrupt state */ m_intr.priority_mask = 0x0007; m_intr.timer[0] = 0x000f; m_intr.timer[1] = 0x000f; m_intr.timer[2] = 0x000f; m_intr.dma[0] = 0x000f; m_intr.dma[1] = 0x000f; m_intr.ext[0] = 0x000f; m_intr.ext[1] = 0x000f; m_intr.ext[2] = 0x000f; m_intr.ext[3] = 0x000f; m_intr.in_service = 0x0000; m_intr.pending = 0x0000; m_intr.ack_mask = 0x0000; m_intr.request = 0x0000; m_intr.status = 0x0000; m_intr.poll_status = 0x0000; m_intr.ext_state = 0x00; m_reloc = 0x20ff; m_mem.upper = 0xfffb; for (dma_state &elem : m_dma) { elem.drq_state = false; elem.control = 0; } for (timer_state &elem : m_timer) { elem.control = 0; elem.maxA = 0; elem.maxB = 0; elem.count = 0; } } uint8_t i80186_cpu_device::read_port_byte(uint16_t port) { if (!(m_reloc & 0x1000) && (port >> 8) == (m_reloc & 0xff)) { if (port & 1) return internal_port_r((port >> 1) & 0x7f, 0xff00) >> 8; else return internal_port_r((port >> 1) & 0x7f, 0x00ff) & 0xff; } return m_io->read_byte(port); } uint16_t i80186_cpu_device::read_port_word(uint16_t port) { if (!(m_reloc & 0x1000) && (port >> 8) == (m_reloc & 0xff)) { // Unaligned reads from the internal bus are swapped rather than split if (port & 1) return swapendian_int16(internal_port_r((port >> 1) & 0x7f)); else return internal_port_r((port >> 1) & 0x7f); } return m_io->read_word_unaligned(port); } void i80186_cpu_device::write_port_byte(uint16_t port, uint8_t data) { if (!(m_reloc & 0x1000) && (port >> 8) == (m_reloc & 0xff)) { if (port & 1) internal_port_w((port >> 1) & 0x7f, data << 8); else internal_port_w((port >> 1) & 0x7f, data); } else m_io->write_byte(port, data); } void i80186_cpu_device::write_port_byte_al(uint16_t port) { if (!(m_reloc & 0x1000) && (port >> 8) == (m_reloc & 0xff)) { // Both AH and AL are written onto the internal bus if (port & 1) internal_port_w((port >> 1) & 0x7f, swapendian_int16(m_regs.w[AX])); else internal_port_w((port >> 1) & 0x7f, m_regs.w[AX]); } else m_io->write_byte(port, m_regs.w[AL]); } void i80186_cpu_device::write_port_word(uint16_t port, uint16_t data) { if (!(m_reloc & 0x1000) && (port >> 8) == (m_reloc & 0xff)) { // Unaligned writes to the internal bus are swapped rather than split if (port & 1) internal_port_w((port >> 1) & 0x7f, swapendian_int16(data)); else internal_port_w((port >> 1) & 0x7f, data); } else m_io->write_word_unaligned(port, data); } uint8_t i80186_cpu_device::read_byte(uint32_t addr) { if ((m_reloc & 0x1000) && ((addr >> 8) & 0xfff) == (m_reloc & 0xfff)) { uint16_t ret = internal_port_r((addr >> 1) & 0x7f, (addr & 1) ? 0xff00 : 0x00ff); return (addr & 1) ? (ret >> 8) : (ret & 0xff); } return m_program->read_byte(addr); } uint16_t i80186_cpu_device::read_word(uint32_t addr) { if ((m_reloc & 0x1000) && ((addr >> 8) & 0xfff) == (m_reloc & 0xfff)) { // Unaligned reads from the internal bus are swapped rather than split if (addr & 1) return swapendian_int16(internal_port_r((addr >> 1) & 0x7f)); else return internal_port_r((addr >> 1) & 0x7f); } return m_program->read_word_unaligned(addr); } void i80186_cpu_device::write_byte(uint32_t addr, uint8_t data) { if ((m_reloc & 0x1000) && ((addr >> 8) & 0xfff) == (m_reloc & 0xfff)) internal_port_w((addr >> 1) & 0x7f, (addr & 1) ? (data << 8) : data); else m_program->write_byte(addr, data); } void i80186_cpu_device::write_word(uint32_t addr, uint16_t data) { if ((m_reloc & 0x1000) && ((addr >> 8) & 0xfff) == (m_reloc & 0xfff)) { // Unaligned writes from the internal bus are swapped rather than split if (addr & 1) internal_port_w((addr >> 1) & 0x7f, swapendian_int16(data)); else internal_port_w((addr >> 1) & 0x7f, data); } else m_program->write_word_unaligned(addr, data); } /************************************* * * 80186 interrupt controller * *************************************/ IRQ_CALLBACK_MEMBER(i80186_cpu_device::inta_callback) { // s-state 0 is irqack m_irqa_cb(ASSERT_LINE); m_irqa_cb(CLEAR_LINE); if (BIT(m_reloc, 14)) return m_irmx_irq_ack(device, irqline); return int_callback(device, irqline); } IRQ_CALLBACK_MEMBER(i80186_cpu_device::int_callback) { LOGMASKED(LOG_INTERRUPTS, "(%f) **** Acknowledged interrupt vector %02X\n", machine().time().as_double(), m_intr.poll_status & 0x1f); /* clear the interrupt */ set_input_line(0, CLEAR_LINE); m_intr.pending = 0; uint16_t oldreq = m_intr.request; /* clear the request and set the in-service bit */ if (m_intr.ack_mask & 0xf0) { int i; for (i = 0; i < 4; i++) if (BIT(m_intr.ack_mask, i + 4)) break; if (!(m_intr.ext[i] & EXTINT_CTRL_LTM)) m_intr.request &= ~m_intr.ack_mask; } else m_intr.request &= ~m_intr.ack_mask; if (m_intr.request != oldreq) LOGMASKED(LOG_INTERRUPTS, "intr.request changed from %02X to %02X\n", oldreq, m_intr.request); uint16_t old = m_intr.in_service; m_intr.in_service |= m_intr.ack_mask; if (m_intr.in_service != old) LOGMASKED(LOG_INTERRUPTS, "intr.in_service changed from %02X to %02X\n",old,m_intr.in_service); uint8_t vector; if (!BIT(m_reloc, 14)) { if (m_intr.ack_mask == 0x0001) { switch (m_intr.poll_status & 0x1f) { case 0x08: m_intr.status &= ~0x01; break; case 0x12: m_intr.status &= ~0x02; break; case 0x13: m_intr.status &= ~0x04; break; } } /* return the vector */ switch(m_intr.poll_status & 0x1F) { case 0x0C: vector = (m_intr.ext[0] & EXTINT_CTRL_CASCADE) ? m_read_slave_ack_func(0) : (m_intr.poll_status & 0x1f); break; case 0x0D: vector = (m_intr.ext[1] & EXTINT_CTRL_CASCADE) ? m_read_slave_ack_func(1) : (m_intr.poll_status & 0x1f); break; default: vector = m_intr.poll_status & 0x1f; break; } } else { if (m_intr.ack_mask & 0x31) { if (m_intr.ack_mask & 1) m_intr.status &= ~0x01; else if (m_intr.ack_mask & 0x10) m_intr.status &= ~0x02; else if (m_intr.ack_mask & 0x20) m_intr.status &= ~0x04; } vector = m_intr.poll_status & 0xff; } m_intr.ack_mask = 0; /* a request no longer pending */ m_intr.poll_status &= ~0x8000; LOGMASKED(LOG_INTERRUPTS, "intr.ext[0]=%04X intr.ext[1]=%04X\n", m_intr.ext[0], m_intr.ext[1]); LOGMASKED(LOG_INTERRUPTS, "Int %02X Calling vector %02X\n", m_intr.poll_status, vector); return vector; } void i80186_cpu_device::update_interrupt_state() { int new_vector = 0; LOGMASKED(LOG_INTERRUPTS, "update_interrupt_status: req=%04X stat=%04X serv=%04X priority_mask=%4X\n", m_intr.request, m_intr.status, m_intr.in_service, m_intr.priority_mask); /* loop over priorities */ for (int priority = 0; priority <= m_intr.priority_mask; priority++) { /* note: by checking 4 bits, we also verify that the mask is off */ if (BIT(m_reloc, 14)) { for (int int_num = 0; int_num < 3; int_num++) { if ((m_intr.timer[int_num] & 0x0f) == priority) { int irq = (1 << int_num); /* if we're already servicing something at this level, don't generate anything new */ if (m_intr.in_service & 0x01) return; /* if there's something pending, generate an interrupt */ if (m_intr.status & irq) { new_vector = m_intr.vector | priority; /* set the clear mask and generate the int */ m_intr.ack_mask = int_num ? (8 << int_num) : 1; goto generate_int; } } } } else { if ((m_intr.timer[0] & 0x0f) == priority) { /* if we're already servicing something at this level, don't generate anything new */ if (m_intr.in_service & 0x01) return; /* if there's something pending, generate an interrupt */ if (m_intr.status & 0x07) { if (m_intr.status & 1) new_vector = 0x08; else if (m_intr.status & 2) new_vector = 0x12; else if (m_intr.status & 4) new_vector = 0x13; else logerror("Invalid timer interrupt!\n"); /* set the clear mask and generate the int */ m_intr.ack_mask = 0x0001; goto generate_int; } } } /* check DMA interrupts */ for (int int_num = 0; int_num < 2; int_num++) if ((m_intr.dma[int_num] & 0x0F) == priority) { /* if we're already servicing something at this level, don't generate anything new */ if (m_intr.in_service & (0x04 << int_num)) return; /* if there's something pending, generate an interrupt */ if (m_intr.request & (0x04 << int_num)) { if (BIT(m_reloc, 14)) new_vector = m_intr.vector | priority; else new_vector = 0x0a + int_num; /* set the clear mask and generate the int */ m_intr.ack_mask = 0x0004 << int_num; goto generate_int; } } if (BIT(m_reloc, 14)) continue; /* check external interrupts */ for (int int_num = 0; int_num < 4; int_num++) { if ((m_intr.ext[int_num] & 0x0f) == priority) { LOGMASKED(LOG_INTERRUPTS, "Int%d priority=%d\n", int_num, priority); /* if we're already servicing something at this level, don't generate anything new */ if ((m_intr.in_service & (0x10 << int_num)) && !(m_intr.ext[int_num] & EXTINT_CTRL_SFNM)) return; /* if there's something pending, generate an interrupt */ if (m_intr.request & (0x10 << int_num)) { if((int_num >= 2) && (m_intr.ext[int_num - 2] & EXTINT_CTRL_CASCADE)) { logerror("i186: %06x: irq %d use when set for cascade mode\n", m_pc, int_num); m_intr.request &= ~(0x10 << int_num); continue; } /* otherwise, generate an interrupt for this request */ new_vector = 0x0c + int_num; /* set the clear mask and generate the int */ m_intr.ack_mask = 0x0010 << int_num; goto generate_int; } else if ((m_intr.in_service & (0x10 << int_num)) && (m_intr.ext[int_num] & EXTINT_CTRL_SFNM)) return; // if an irq is in service and sfnm is enabled, stop here } } } m_intr.pending = 0; if (!BIT(m_reloc, 14)) set_input_line(0, CLEAR_LINE); else m_irmx_irq_cb(CLEAR_LINE); return; generate_int: /* generate the appropriate interrupt */ m_intr.poll_status = 0x8000 | new_vector; if (!m_intr.pending) { if (!BIT(m_reloc, 14)) set_input_line(0, ASSERT_LINE); else m_irmx_irq_cb(ASSERT_LINE); } m_intr.pending = 1; LOGMASKED(LOG_INTERRUPTS, "(%f) **** Requesting interrupt vector %02X\n", machine().time().as_double(), new_vector); } void i80186_cpu_device::handle_eoi(int data) { bool handled = false; /* specific case */ if (!(data & 0x8000)) { /* turn off the appropriate in-service bit */ switch (data & 0x1f) { case 0x08: m_intr.in_service &= ~0x01; break; case 0x12: m_intr.in_service &= ~0x01; break; case 0x13: m_intr.in_service &= ~0x01; break; case 0x0a: m_intr.in_service &= ~0x04; break; case 0x0b: m_intr.in_service &= ~0x08; break; case 0x0c: m_intr.in_service &= ~0x10; break; case 0x0d: m_intr.in_service &= ~0x20; break; case 0x0e: m_intr.in_service &= ~0x40; break; case 0x0f: m_intr.in_service &= ~0x80; break; default: logerror("%05X:ERROR - 80186 EOI with unknown vector %02X\n", m_pc, data & 0x1f); } LOGMASKED(LOG_INTERRUPTS, "(%f) **** Got EOI for vector %02X\n", machine().time().as_double(), data & 0x1f); } /* non-specific case */ else { /* loop over priorities */ for (int priority = 0; priority <= 7 && !handled; priority++) { /* check for in-service timers */ if (BIT(m_reloc, 14)) { for (int int_num = 0; int_num < 2 && !handled; int_num++) { int mask = int_num ? (8 << int_num) : 1; if ((m_intr.timer[int_num] & 0x07) == priority && (m_intr.in_service & mask)) { m_intr.in_service &= ~mask; LOGMASKED(LOG_INTERRUPTS, "(%f) **** Got EOI for timer%d\n", machine().time().as_double(), int_num); handled = true; } } } else { if ((m_intr.timer[0] & 0x07) == priority && (m_intr.in_service & 0x01)) { m_intr.in_service &= ~0x01; LOGMASKED(LOG_INTERRUPTS, "(%f) **** Got EOI for timer\n", machine().time().as_double()); handled = true; } } /* check for in-service DMA interrupts */ for (int int_num = 0; int_num < 2 && !handled; int_num++) if ((m_intr.dma[int_num] & 0x07) == priority && (m_intr.in_service & (0x04 << int_num))) { m_intr.in_service &= ~(0x04 << int_num); LOGMASKED(LOG_INTERRUPTS, "(%f) **** Got EOI for DMA%d\n", machine().time().as_double(), int_num); handled = true; } if (BIT(m_reloc, 14)) continue; /* check external interrupts */ for (int int_num = 0; int_num < 4 && !handled; int_num++) if ((m_intr.ext[int_num] & 0x07) == priority && (m_intr.in_service & (0x10 << int_num))) { m_intr.in_service &= ~(0x10 << int_num); LOGMASKED(LOG_INTERRUPTS, "(%f) **** Got EOI for INT%d\n", machine().time().as_double(), int_num); handled = true; } } } update_interrupt_state(); } /* Trigger an external interrupt, optionally supplying the vector to take */ void i80186_cpu_device::external_int(uint16_t int_num, int state) { if (BIT(m_reloc, 14)) { if (!int_num) { set_input_line(0, state); return; } logerror("irq to line %d in irmx mode\n", int_num); return; } if (!(m_intr.ext_state & (1 << int_num)) == !state) return; LOGMASKED(LOG_INTERRUPTS_EXT, "generating external int %02X\n", int_num); if (!state) { m_intr.request &= ~(0x10 << int_num); m_intr.ack_mask &= ~(0x10 << int_num); m_intr.ext_state &= ~(1 << int_num); } else // Turn on the requested request bit and handle interrupt { m_intr.request |= 0x10 << int_num; m_intr.ext_state |= 1 << int_num; } update_interrupt_state(); } /************************************* * * 80186 internal timers * *************************************/ TIMER_CALLBACK_MEMBER(i80186_cpu_device::timer_elapsed) { int which = param; timer_state *t = &m_timer[which]; LOGMASKED(LOG_TIMER, "Hit interrupt callback for timer %d\n", which); /* set the max count bit */ t->control |= 0x0020; /* request an interrupt */ if (t->control & 0x2000) { m_intr.status |= 0x01 << which; update_interrupt_state(); LOGMASKED(LOG_TIMER, " Generating timer interrupt\n"); } if (which == 2) { if ((m_dma[0].control & (TIMER_DRQ | ST_STOP)) == (TIMER_DRQ | ST_STOP)) drq_callback(0); if ((m_dma[1].control & (TIMER_DRQ | ST_STOP)) == (TIMER_DRQ | ST_STOP)) drq_callback(1); if ((m_timer[0].control & 0x800c) == 0x8008) inc_timer(0); if ((m_timer[1].control & 0x800c) == 0x8008) inc_timer(1); } else { if (!(t->control & 2)) { if (which) m_out_tmrout1_func(1); else m_out_tmrout0_func(1); } else { if (which) m_out_tmrout1_func((t->control & 0x1000) ? 1 : 0); else m_out_tmrout0_func((t->control & 0x1000) ? 1 : 0); } } /* if we're continuous or altcounting, reset */ if ((t->control & 1) || ((t->control & 2) && !(t->control & 0x1000))) { if ((t->control & 2) && !(t->control & 0x1000)) t->control |= 0x1000; else t->control &= ~0x1000; restart_timer(which); LOGMASKED(LOG_TIMER, " Repriming interrupt\n"); } else { t->int_timer->adjust(attotime::never, which); t->control &= ~0x9000; } t->count = 0; } void i80186_cpu_device::restart_timer(int which) { timer_state *t = &m_timer[which]; /* Only run timer 0,1 when not incremented via timer 2 pre-scaler */ if (which != 2 && (t->control & 0x800c) == 0x8008) return; int count = (t->control & 0x1000) ? t->maxB : t->maxA; if (!(t->control & 4)) t->int_timer->adjust(cycles_to_attotime(4 * (count ? count : 0x10000)), which); } void i80186_cpu_device::internal_timer_sync(int which) { timer_state *t = &m_timer[which]; /* if we have a timing timer running, adjust the count */ if ((t->control & 0x8000) && !(t->control & 0x0c) && t->int_timer->enabled()) t->count = ((t->control & 0x1000) ? t->maxB : t->maxA) - attotime_to_cycles(t->int_timer->remaining()) / 4; } void i80186_cpu_device::inc_timer(int which) { timer_state *t = &m_timer[which]; t->count++; if (t->control & 2) { if (t->count == ((t->control & 0x1000) ? t->maxB : t->maxA)) timer_elapsed(which); } else if (t->count == t->maxA) timer_elapsed(which); } void i80186_cpu_device::internal_timer_update(int which, int new_count, int new_maxA, int new_maxB, int new_control) { timer_state *t = &m_timer[which]; bool update_int_timer = false; LOGMASKED(LOG_TIMER, "internal_timer_update: %d, new_count=%d, new_maxA=%d, new_maxB=%d, new_control=%d\n", which, new_count, new_maxA, new_maxB, new_control); /* if we have a new count and we're on, update things */ if (new_count != -1) { if (t->control & 0x8000) { internal_timer_sync(which); update_int_timer = true; } t->count = new_count; } /* if we have a new max and we're on, update things */ if (new_maxA != -1 && new_maxA != t->maxA) { if (t->control & 0x8000) { internal_timer_sync(which); update_int_timer = true; } t->maxA = new_maxA; if (new_maxA == 0) { new_maxA = 0x10000; } } /* if we have a new max and we're on, update things */ if (new_maxB != -1 && new_maxB != t->maxB) { if (t->control & 0x8000) { internal_timer_sync(which); update_int_timer = true; } t->maxB = new_maxB; if (new_maxB == 0) { new_maxB = 0x10000; } } /* handle control changes */ if (new_control != -1) { uint16_t resbits = (which == 2) ? 0x1fde : 0x1fc0; /* merge back in the bits we don't modify */ new_control = (new_control & ~resbits) | (t->control & resbits); /* handle the /INH bit */ if (!(new_control & 0x4000)) new_control = (new_control & ~0x8000) | (t->control & 0x8000); new_control &= ~0x4000; /* if we have real changes, update things */ uint16_t diff = new_control ^ t->control; if (diff != 0) { /* if we're going off, make sure our timers are gone */ if ((diff & 0x8000) && !(new_control & 0x8000)) { /* compute the final count */ internal_timer_sync(which); update_int_timer = true; } /* if we're going on, start the timers running except with external clock or prescale */ else if ((diff & 0x8000) && (new_control & 0x8000) && !(new_control & 0xc)) { update_int_timer = true; } /* if something about the interrupt timer changed, force an update */ if (!(diff & 0x8000) && (diff & 0x2000)) { internal_timer_sync(which); update_int_timer = true; } } /* RIU is cleared whenever ALT = 0 */ if (!(new_control & 0x0002)) new_control &= ~0x1000; /* set the new control register */ t->control = new_control; } /* update the interrupt timer */ if (update_int_timer) { if ((t->control & 0x8004) == 0x8000 && (!(t->control & 0x10) || t->int_timer->enabled())) { int diff = ((t->control & 0x1000) ? t->maxB : t->maxA) - t->count; if (diff <= 0) diff += 0x10000; t->int_timer->adjust(cycles_to_attotime(4 * diff), which); LOGMASKED(LOG_TIMER, "Set interrupt timer for %d\n", which); } else { t->int_timer->adjust(attotime::never, which); } } } void i80186_cpu_device::external_tmrin(int which, int state) { // TODO: make this an actual edge trigger if (state) { if ((m_timer[which].control & 0x8004) == 0x8004) inc_timer(which); else if ((m_timer[which].control & 0x8014) == 0x8010) { m_timer[which].count = 0; restart_timer(which); LOGMASKED(LOG_TIMER, "Retriggered timer %d\n", which); } } } /************************************* * * 80186 internal DMA * *************************************/ void i80186_cpu_device::update_dma_control(int which, int new_control) { dma_state *d = &m_dma[which]; /* handle the CHG bit */ if (!(new_control & CHG_NOCHG)) new_control = (new_control & ~ST_STOP) | (d->control & ST_STOP); new_control &= ~CHG_NOCHG; LOGMASKED(LOG_DMA, "Initiated DMA %d - count = %04X, source = %04X, dest = %04X\n", which, d->count, d->source, d->dest); /* set the new control register */ d->control = new_control; } void i80186_cpu_device::drq_callback(int which) { dma_state *dma = &m_dma[which]; uint16_t dma_word; uint8_t dma_byte; uint8_t incdec_size; LOGMASKED(LOG_DMA_HIFREQ, "Control=%04X, src=%05X, dest=%05X, count=%04X\n", dma->control, dma->source, dma->dest, dma->count); if (!(dma->control & ST_STOP)) { LOGMASKED(LOG_DMA, "%05X:ERROR! - drq%d with dma channel stopped\n", m_pc, which); return; } address_space *dest_space = (dma->control & DEST_MIO) ? m_program : m_io; address_space *src_space = (dma->control & SRC_MIO) ? m_program : m_io; // Do the transfer, 80188 is incapable of word transfers if ((dma->control & BYTE_WORD) && (m_program->data_width() == 16)) { dma_word = src_space->read_word_unaligned(dma->source); dest_space->write_word_unaligned(dma->dest, dma_word); incdec_size = 2; } else { dma_byte = src_space->read_byte(dma->source); dest_space->write_byte(dma->dest, dma_byte); incdec_size = 1; } // Increment or Decrement destination and source pointers as needed switch (dma->control & DEST_INCDEC_MASK) { case DEST_DECREMENT: dma->dest -= incdec_size; break; case DEST_INCREMENT: dma->dest += incdec_size; break; } switch (dma->control & SRC_INCDEC_MASK) { case SRC_DECREMENT: dma->source -= incdec_size; break; case SRC_INCREMENT: dma->source += incdec_size; break; } // decrement count dma->count -= 1; // Terminate if count is zero, and terminate flag set if (((dma->control & TERMINATE_ON_ZERO) || !(dma->control & SYNC_MASK)) && dma->count == 0) { dma->control &= ~ST_STOP; LOGMASKED(LOG_DMA, "DMA terminated\n"); } // Interrupt if count is zero, and interrupt flag set if ((dma->control & INTERRUPT_ON_ZERO) && dma->count == 0) { LOGMASKED(LOG_DMA_HIFREQ, "DMA%d - requesting interrupt: count = %04X, source = %04X\n", which, dma->count, dma->source); m_intr.request |= 0x04 << which; update_interrupt_state(); } } uint16_t i80186_cpu_device::internal_port_r(offs_t offset, uint16_t mem_mask) { int temp, which; switch (offset) { case 0x10: LOGMASKED(LOG_PORTS, "%05X:read interrupt vector\n", m_pc); return m_intr.vector; case 0x11: LOGMASKED(LOG_PORTS, "%05X:ERROR - read from 80186 EOI\n", m_pc); break; case 0x12: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt poll\n", m_pc); if (m_intr.poll_status & 0x8000) inta_callback(*this, 0); return m_intr.poll_status; case 0x13: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt poll status\n", m_pc); return m_intr.poll_status; case 0x14: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt mask\n", m_pc); temp = (m_intr.timer[0] >> 3) & 0x01; temp |= (m_intr.dma[0] >> 1) & 0x04; temp |= (m_intr.dma[1] >> 0) & 0x08; if (BIT(m_reloc, 14)) { temp |= (m_intr.timer[1] << 1) & 0x10; temp |= (m_intr.timer[2] << 2) & 0x20; } else { temp |= (m_intr.ext[0] << 1) & 0x10; temp |= (m_intr.ext[1] << 2) & 0x20; temp |= (m_intr.ext[2] << 3) & 0x40; temp |= (m_intr.ext[3] << 4) & 0x80; } return temp; case 0x15: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt priority mask\n", m_pc); return m_intr.priority_mask; case 0x16: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt in-service\n", m_pc); return m_intr.in_service; case 0x17: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt request\n", m_pc); temp = m_intr.request & ~0x0001; if (m_intr.status & 0x0007) temp |= 1; return temp; case 0x18: LOGMASKED(LOG_PORTS, "%05X:read 80186 interrupt status\n", m_pc); return m_intr.status; case 0x19: LOGMASKED(LOG_PORTS, "%05X:read 80186 timer interrupt control\n", m_pc); return m_intr.timer[0]; case 0x1a: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA 0 interrupt control\n", m_pc); return m_intr.dma[0]; case 0x1b: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA 1 interrupt control\n", m_pc); return m_intr.dma[1]; case 0x1c: LOGMASKED(LOG_PORTS, "%05X:read 80186 INT 0 interrupt control\n", m_pc); if (BIT(m_reloc, 14)) return m_intr.timer[1]; else return m_intr.ext[0]; case 0x1d: LOGMASKED(LOG_PORTS, "%05X:read 80186 INT 1 interrupt control\n", m_pc); if (BIT(m_reloc, 14)) return m_intr.timer[2]; else return m_intr.ext[1]; case 0x1e: LOGMASKED(LOG_PORTS, "%05X:read 80186 INT 2 interrupt control\n", m_pc); if (BIT(m_reloc, 14)) return 0; else return m_intr.ext[2]; case 0x1f: LOGMASKED(LOG_PORTS, "%05X:read 80186 INT 3 interrupt control\n", m_pc); if (BIT(m_reloc, 14)) return 0; else return m_intr.ext[3]; case 0x28: case 0x2c: case 0x30: LOGMASKED(LOG_PORTS, "%05X:read 80186 Timer %d count\n", m_pc, (offset - 0x28) / 4); which = (offset - 0x28) / 4; if (ACCESSING_BITS_0_7) internal_timer_sync(which); return m_timer[which].count; case 0x29: case 0x2d: case 0x31: LOGMASKED(LOG_PORTS, "%05X:read 80186 Timer %d max A\n", m_pc, (offset - 0x29) / 4); which = (offset - 0x29) / 4; return m_timer[which].maxA; case 0x2a: case 0x2e: LOGMASKED(LOG_PORTS, "%05X:read 80186 Timer %d max B\n", m_pc, (offset - 0x2a) / 4); which = (offset - 0x2a) / 4; return m_timer[which].maxB; case 0x2b: case 0x2f: case 0x33: LOGMASKED(LOG_PORTS, "%05X:read 80186 Timer %d control\n", m_pc, (offset - 0x2b) / 4); which = (offset - 0x2b) / 4; return m_timer[which].control; case 0x50: LOGMASKED(LOG_PORTS, "%05X:read 80186 upper chip select\n", m_pc); return m_mem.upper; case 0x51: LOGMASKED(LOG_PORTS, "%05X:read 80186 lower chip select\n", m_pc); return m_mem.lower; case 0x52: LOGMASKED(LOG_PORTS, "%05X:read 80186 peripheral chip select\n", m_pc); return m_mem.peripheral; case 0x53: LOGMASKED(LOG_PORTS, "%05X:read 80186 middle chip select\n", m_pc); return m_mem.middle; case 0x54: LOGMASKED(LOG_PORTS, "%05X:read 80186 middle P chip select\n", m_pc); return m_mem.middle_size; case 0x60: case 0x68: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA%d lower source address\n", m_pc, (offset - 0x60) / 8); which = (offset - 0x60) / 8; return m_dma[which].source; case 0x61: case 0x69: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA%d upper source address\n", m_pc, (offset - 0x61) / 8); which = (offset - 0x61) / 8; return m_dma[which].source >> 16; case 0x62: case 0x6a: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA%d lower dest address\n", m_pc, (offset - 0x62) / 8); which = (offset - 0x62) / 8; return m_dma[which].dest; case 0x63: case 0x6b: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA%d upper dest address\n", m_pc, (offset - 0x63) / 8); which = (offset - 0x63) / 8; return m_dma[which].dest >> 16; case 0x64: case 0x6c: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA%d transfer count\n", m_pc, (offset - 0x64) / 8); which = (offset - 0x64) / 8; return m_dma[which].count; case 0x65: case 0x6d: LOGMASKED(LOG_PORTS, "%05X:read 80186 DMA%d control\n", m_pc, (offset - 0x65) / 8); which = (offset - 0x65) / 8; return m_dma[which].control; case 0x7f: return m_reloc; default: LOGMASKED(LOG_PORTS, "%05X:read 80186 port %02X\n", m_pc, offset); break; } return 0x0000; } /************************************* * * 80186 internal I/O writes * *************************************/ void i80186_cpu_device::internal_port_w(offs_t offset, uint16_t data) { int which; switch (offset) { case 0x10: LOGMASKED(LOG_PORTS, "%05X:write interrupt vector = %04X\n", m_pc, data); m_intr.vector = data & 0xf8; break; case 0x11: LOGMASKED(LOG_PORTS, "%05X:80186 EOI = %04X\n", m_pc, data); handle_eoi(data); update_interrupt_state(); break; case 0x12: LOGMASKED(LOG_PORTS, "%05X:ERROR - write to 80186 interrupt poll = %04X\n", m_pc, data); break; case 0x13: LOGMASKED(LOG_PORTS, "%05X:ERROR - write to 80186 interrupt poll status = %04X\n", m_pc, data); break; case 0x14: LOGMASKED(LOG_PORTS, "%05X:80186 interrupt mask = %04X\n", m_pc, data); m_intr.timer[0] = (m_intr.timer[0] & ~0x08) | ((data << 3) & 0x08); m_intr.dma[0] = (m_intr.dma[0] & ~0x08) | ((data << 1) & 0x08); m_intr.dma[1] = (m_intr.dma[1] & ~0x08) | ((data << 0) & 0x08); if (BIT(m_reloc, 14)) { m_intr.timer[1] = (m_intr.timer[1] & ~0x08) | ((data >> 1) & 0x08); m_intr.timer[2] = (m_intr.timer[2] & ~0x08) | ((data >> 2) & 0x08); } else { m_intr.ext[0] = (m_intr.ext[0] & ~0x08) | ((data >> 1) & 0x08); m_intr.ext[1] = (m_intr.ext[1] & ~0x08) | ((data >> 2) & 0x08); m_intr.ext[2] = (m_intr.ext[2] & ~0x08) | ((data >> 3) & 0x08); m_intr.ext[3] = (m_intr.ext[3] & ~0x08) | ((data >> 4) & 0x08); } update_interrupt_state(); break; case 0x15: LOGMASKED(LOG_PORTS, "%05X:80186 interrupt priority mask = %04X\n", m_pc, data); m_intr.priority_mask = data & 0x0007; update_interrupt_state(); break; case 0x16: LOGMASKED(LOG_PORTS, "%05X:80186 interrupt in-service = %04X\n", m_pc, data); m_intr.in_service = data & 0x00ff; update_interrupt_state(); break; case 0x17: LOGMASKED(LOG_PORTS, "%05X:80186 interrupt request = %04X\n", m_pc, data); m_intr.request = (m_intr.request & ~0x000c) | (data & 0x000c); update_interrupt_state(); break; case 0x18: LOGMASKED(LOG_PORTS, "%05X:WARNING - wrote to 80186 interrupt status = %04X\n", m_pc, data); m_intr.status = (m_intr.status & ~0x8007) | (data & 0x8007); update_interrupt_state(); break; case 0x19: LOGMASKED(LOG_PORTS, "%05X:80186 timer interrupt contol = %04X\n", m_pc, data); m_intr.timer[0] = data & 0x000f; update_interrupt_state(); break; case 0x1a: LOGMASKED(LOG_PORTS, "%05X:80186 DMA 0 interrupt control = %04X\n", m_pc, data); m_intr.dma[0] = data & 0x000f; update_interrupt_state(); break; case 0x1b: LOGMASKED(LOG_PORTS, "%05X:80186 DMA 1 interrupt control = %04X\n", m_pc, data); m_intr.dma[1] = data & 0x000f; update_interrupt_state(); break; case 0x1c: LOGMASKED(LOG_PORTS, "%05X:80186 INT 0 interrupt control = %04X\n", m_pc, data); if (BIT(m_reloc, 14)) m_intr.timer[1] = data & 0x000f; else m_intr.ext[0] = data & 0x007f; update_interrupt_state(); break; case 0x1d: LOGMASKED(LOG_PORTS, "%05X:80186 INT 1 interrupt control = %04X\n", m_pc, data); if (BIT(m_reloc, 14)) m_intr.timer[2] = data & 0x000f; else m_intr.ext[1] = data & 0x007f; update_interrupt_state(); break; case 0x1e: LOGMASKED(LOG_PORTS, "%05X:80186 INT 2 interrupt control = %04X\n", m_pc, data); if (!BIT(m_reloc, 14)) m_intr.ext[2] = data & 0x001f; update_interrupt_state(); break; case 0x1f: LOGMASKED(LOG_PORTS, "%05X:80186 INT 3 interrupt control = %04X\n", m_pc, data); if (!BIT(m_reloc, 14)) m_intr.ext[3] = data & 0x001f; update_interrupt_state(); break; case 0x28: case 0x2c: case 0x30: LOGMASKED(LOG_PORTS, "%05X:80186 Timer %d count = %04X\n", m_pc, (offset - 0x28) / 4, data); which = (offset - 0x28) / 4; internal_timer_update(which, data, -1, -1, -1); break; case 0x29: case 0x2d: case 0x31: LOGMASKED(LOG_PORTS, "%05X:80186 Timer %d max A = %04X\n", m_pc, (offset - 0x29) / 4, data); which = (offset - 0x29) / 4; internal_timer_update(which, -1, data, -1, -1); break; case 0x2a: case 0x2e: LOGMASKED(LOG_PORTS, "%05X:80186 Timer %d max B = %04X\n", m_pc, (offset - 0x2a) / 4, data); which = (offset - 0x2a) / 4; internal_timer_update(which, -1, -1, data, -1); break; case 0x2b: case 0x2f: case 0x33: LOGMASKED(LOG_PORTS, "%05X:80186 Timer %d control = %04X\n", m_pc, (offset - 0x2b) / 4, data); which = (offset - 0x2b) / 4; internal_timer_update(which, -1, -1, -1, data); break; case 0x50: LOGMASKED(LOG_PORTS, "%05X:80186 upper chip select = %04X\n", m_pc, data); m_mem.upper = data | 0xc038; m_out_chip_select_func(0, m_mem.upper, 0xffff); break; case 0x51: LOGMASKED(LOG_PORTS, "%05X:80186 lower chip select = %04X\n", m_pc, data); m_mem.lower = (data & 0x3fff) | 0x0038; m_out_chip_select_func(1, m_mem.lower, 0xffff); break; case 0x52: LOGMASKED(LOG_PORTS, "%05X:80186 peripheral chip select = %04X\n", m_pc, data); m_mem.peripheral = data | 0x0038; m_out_chip_select_func(2, m_mem.peripheral, 0xffff); break; case 0x53: LOGMASKED(LOG_PORTS, "%05X:80186 middle chip select = %04X\n", m_pc, data); m_mem.middle = data | 0x01f8; m_out_chip_select_func(3, m_mem.middle, 0xffff); break; case 0x54: LOGMASKED(LOG_PORTS, "%05X:80186 middle P chip select = %04X\n", m_pc, data); m_mem.middle_size = data | 0x8038; m_out_chip_select_func(4, m_mem.middle_size, 0xffff); break; case 0x60: case 0x68: LOGMASKED(LOG_PORTS, "%05X:80186 DMA%d lower source address = %04X\n", m_pc, (offset - 0x60) / 8, data); which = (offset - 0x60) / 8; m_dma[which].source = (m_dma[which].source & ~0x0ffff) | (data & 0x0ffff); break; case 0x61: case 0x69: LOGMASKED(LOG_PORTS, "%05X:80186 DMA%d upper source address = %04X\n", m_pc, (offset - 0x61) / 8, data); which = (offset - 0x61) / 8; m_dma[which].source = (m_dma[which].source & ~0xf0000) | ((data << 16) & 0xf0000); break; case 0x62: case 0x6a: LOGMASKED(LOG_PORTS, "%05X:80186 DMA%d lower dest address = %04X\n", m_pc, (offset - 0x62) / 8, data); which = (offset - 0x62) / 8; m_dma[which].dest = (m_dma[which].dest & ~0x0ffff) | (data & 0x0ffff); break; case 0x63: case 0x6b: LOGMASKED(LOG_PORTS, "%05X:80186 DMA%d upper dest address = %04X\n", m_pc, (offset - 0x63) / 8, data); which = (offset - 0x63) / 8; m_dma[which].dest = (m_dma[which].dest & ~0xf0000) | ((data << 16) & 0xf0000); break; case 0x64: case 0x6c: LOGMASKED(LOG_PORTS, "%05X:80186 DMA%d transfer count = %04X\n", m_pc, (offset - 0x64) / 8, data); which = (offset - 0x64) / 8; m_dma[which].count = data; break; case 0x65: case 0x6d: LOGMASKED(LOG_PORTS, "%05X:80186 DMA%d control = %04X\n", m_pc, (offset - 0x65) / 8, data); which = (offset - 0x65) / 8; update_dma_control(which, data); if((m_dma[which].control & (SYNC_MASK | ST_STOP | TIMER_DRQ)) == ST_STOP) { // TODO: don't do this while(m_dma[which].control & ST_STOP) drq_callback(which); } break; case 0x7f: // 80188 byte output to this port is *not* masked! LOGMASKED(LOG_PORTS, "%05X:80186 relocation register = %04X\n", m_pc, data); m_reloc = data; break; default: LOGMASKED(LOG_PORTS, "%05X:80186 port %02X = %04X\n", m_pc, offset, data); break; } }