summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/pic17/pic17.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/pic17/pic17.cpp')
-rw-r--r--src/devices/cpu/pic17/pic17.cpp1107
1 files changed, 1107 insertions, 0 deletions
diff --git a/src/devices/cpu/pic17/pic17.cpp b/src/devices/cpu/pic17/pic17.cpp
new file mode 100644
index 00000000000..0258b052ca9
--- /dev/null
+++ b/src/devices/cpu/pic17/pic17.cpp
@@ -0,0 +1,1107 @@
+// license:BSD-3-Clause
+// copyright-holders:AJR
+/**********************************************************************
+
+ Microchip PIC17CXXX High-Performance 8-bit MCU family
+
+ Common features of this family include:
+ — Four oscillator options (maximum rate of 33 MHz except on
+ PIC17C42)
+ — 2048 to 16,384 16-bit words of internal EPROM program memory
+ in microcontroller modes (may be protected; certain models use
+ mask ROM instead); special “long” table writes are used to
+ program EPROM when Vpp is applied
+ — 64K 16-bit words of external address space in microprocessor
+ mode, readable and writable through TABPTR and TABLAT
+ — 232 or more bytes of internal data RAM
+ — Two registers for indirect access to entire register file, with
+ optional post-increment or post-decrement for each
+ — Independent bank selection for peripheral SFRs and general-
+ purpose RAM (up to 16 banks are selectable for each, though no
+ more than 8 of the former and 4 of the latter were implemented)
+ — All instructions execute in a single cycle except for program
+ transfers and table reads/writes
+ – Signed overflow flag and decimal adjustment for addition
+ — 8 x 8 unsigned multiplication in hardware (except PIC17C42)
+ — Single-bit instructions can manipulate any addressable register
+ — MOVFP and MOVPF can transfer data between registers without
+ touching the working register
+ — CALL and GOTO allow efficient program transfers within 8K page
+ — 16-level on-chip program stack (non-addressable)
+ — Multiple 8-bit and 16-bit on-chip timer/counters, with 16-bit
+ input capture and 10-bit PWM output
+ — USART serial I/O with dedicated 8-bit baud rate generator
+ — Watchdog timer with internal RC oscillator
+ — SLEEP mode with wake-up upon interrupt
+
+ Notable architectural quirks:
+ — PCH is copied to PCLATH whenever PCL is read unless the
+ operation on that register is of the read-modify-write type.
+ — MOVWF, CLRF, SETF, NEGW and DAW read the previous value of
+ the destination register before discarding and replacing it.
+ — A quirk of instruction decoding causes RETURN, SLEEP, CLRWDT
+ and RETFIE to perform dummy reads from PCL, PCLATH, ALUSTA
+ and T0STA respectively. This means that RETURN also copies PCH
+ to PCLATH before it pops the stack.
+ — The internal workings of the stack are fully specified, even
+ though no instruction can push or pop values from the stack,
+ adjust the stack pointer or even sense its current level
+ (aside from the crude heuristic of the STKAV flag).
+ — TABLWT and TABLRD access different implementations of TABLAT.
+ Neither half of a data word latched from external memory by
+ TABLRD will be written back by a following TABLWT. To ensure
+ correct data is present on both halves of the bus, TLWT must
+ precede TABLWT to refresh the alternate half of TABLAT.
+ — “Long” writes to internal EPROM (not emulated) can be aborted
+ by the setting of any interrupt flag, whether enabled or not.
+ — SFR bank 15 is “reserved for Microchip use.”
+
+**********************************************************************/
+
+#include "emu.h"
+#include "pic17.h"
+#include "pic17d.h"
+
+ALLOW_SAVE_TYPE(pic17_cpu_device::exec_phase);
+
+pic17_cpu_device::pic17_cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, u16 rom_size, address_map_constructor data_map)
+ : cpu_device(mconfig, type, tag, owner, clock)
+ , m_program_config("program", ENDIANNESS_LITTLE, 16, 16, -1, address_map_constructor(FUNC(pic17_cpu_device::program_map), this))
+ , m_data_config("data", ENDIANNESS_LITTLE, 8, 12, 0, data_map)
+ , m_rom_size(rom_size)
+ , m_mode(mode::EXTENDED_MICROCONTROLLER)
+ , m_pc(0)
+ , m_ppc(0)
+ , m_paddr(0)
+ , m_raddr(0)
+ , m_ir(0)
+ , m_tmp(0)
+ , m_execphase(exec_phase::Q1)
+ , m_execflags(0)
+ , m_icount(0)
+ , m_pclath(0)
+ , m_wreg(0)
+ , m_alusta(0)
+ , m_cpusta(0x3c)
+ , m_intsta(0)
+ , m_intsample(0)
+ , m_tblptr(0)
+ , m_tablat(0)
+ , m_tlwt(0)
+ , m_prod(0)
+ , m_fsr{0, 0}
+ , m_bsr(0)
+ , m_stkptr(0)
+ , m_t0sta(0)
+ , m_tmr0(0)
+ , m_ps(0)
+{
+ std::fill_n(&m_stack[0], 16, 0);
+}
+
+void pic17_cpu_device::program_map(address_map &map)
+{
+ if (m_mode != mode::MICROPROCESSOR)
+ {
+ map(0x0000, m_rom_size - 1).rom().region(DEVICE_SELF, 0);
+ //map(0xfe00, 0xffff).rom().region("config", 0);
+ }
+}
+
+void pic17_cpu_device::core_data_map(address_map &map)
+{
+ // Unbanked registers (INDF0 and INDF1 are not physical registers)
+ map(0x000, 0x000).lr8([]() { return 0; }, "0").nopw();
+ map(0x001, 0x001).rw(FUNC(pic17_cpu_device::fsr0_r), FUNC(pic17_cpu_device::fsr0_w));
+ map(0x002, 0x002).rw(FUNC(pic17_cpu_device::pcl_r), FUNC(pic17_cpu_device::pcl_w));
+ map(0x003, 0x003).rw(FUNC(pic17_cpu_device::pclath_r), FUNC(pic17_cpu_device::pclath_w));
+ map(0x004, 0x004).rw(FUNC(pic17_cpu_device::alusta_r), FUNC(pic17_cpu_device::alusta_w));
+ map(0x005, 0x005).rw(FUNC(pic17_cpu_device::t0sta_r), FUNC(pic17_cpu_device::t0sta_w));
+ map(0x006, 0x006).rw(FUNC(pic17_cpu_device::cpusta_r), FUNC(pic17_cpu_device::cpusta_w));
+ map(0x007, 0x007).rw(FUNC(pic17_cpu_device::intsta_r), FUNC(pic17_cpu_device::intsta_w));
+ map(0x008, 0x008).lr8([]() { return 0; }, "0").nopw();
+ map(0x009, 0x009).rw(FUNC(pic17_cpu_device::fsr1_r), FUNC(pic17_cpu_device::fsr1_w));
+ map(0x00a, 0x00a).rw(FUNC(pic17_cpu_device::wreg_r), FUNC(pic17_cpu_device::wreg_w));
+ map(0x00b, 0x00b).rw(FUNC(pic17_cpu_device::tmr0l_r), FUNC(pic17_cpu_device::tmr0l_w));
+ map(0x00c, 0x00c).rw(FUNC(pic17_cpu_device::tmr0h_r), FUNC(pic17_cpu_device::tmr0h_w));
+ map(0x00d, 0x00d).rw(FUNC(pic17_cpu_device::tblptrl_r), FUNC(pic17_cpu_device::tblptrl_w));
+ map(0x00e, 0x00e).rw(FUNC(pic17_cpu_device::tblptrh_r), FUNC(pic17_cpu_device::tblptrh_w));
+ map(0x00f, 0x00f).rw(FUNC(pic17_cpu_device::bsr_r), FUNC(pic17_cpu_device::bsr_w));
+ map(0x018, 0x018).rw(FUNC(pic17_cpu_device::prodl_r), FUNC(pic17_cpu_device::prodl_w)); // not on PIC17C42
+ map(0x019, 0x019).rw(FUNC(pic17_cpu_device::prodh_r), FUNC(pic17_cpu_device::prodh_w)); // not on PIC17C42
+}
+
+std::unique_ptr<util::disasm_interface> pic17_cpu_device::create_disassembler()
+{
+ return std::make_unique<pic17_disassembler>();
+}
+
+device_memory_interface::space_config_vector pic17_cpu_device::memory_space_config() const
+{
+ return space_config_vector {
+ std::make_pair(AS_PROGRAM, &m_program_config),
+ std::make_pair(AS_DATA, &m_data_config)
+ };
+}
+
+u8 pic17_cpu_device::pcl_r()
+{
+ // Note that this side effect is implicitly performed by the RETURN instruction
+ if (!machine().side_effects_disabled() && (m_execflags & REGWT) == 0)
+ m_pclath = m_pc >> 8;
+ return m_pc & 0x00ff;
+}
+
+void pic17_cpu_device::pcl_w(u8 data)
+{
+ m_pc = u8(m_pclath) << 8 | data;
+ m_execflags |= FORCENOP;
+}
+
+void pic17_cpu_device::debug_set_pc(u16 data)
+{
+ m_pc = m_ppc = data;
+ m_ir = m_cache.read_word(m_pc);
+}
+
+u8 pic17_cpu_device::pclath_r()
+{
+ return m_pclath;
+}
+
+void pic17_cpu_device::pclath_w(u8 data)
+{
+ m_pclath = data;
+}
+
+u8 pic17_cpu_device::wreg_r()
+{
+ return m_wreg;
+}
+
+void pic17_cpu_device::wreg_w(u8 data)
+{
+ m_wreg = data;
+}
+
+u8 pic17_cpu_device::alusta_r()
+{
+ return m_alusta;
+}
+
+void pic17_cpu_device::alusta_w(u8 data)
+{
+ m_alusta = data;
+}
+
+u8 pic17_cpu_device::cpusta_r()
+{
+ // STKAV is read as 0 when stack is full
+ if (m_stkptr == 0xf)
+ return m_cpusta & 0xdf;
+ else
+ return m_cpusta;
+}
+
+void pic17_cpu_device::cpusta_w(u8 data)
+{
+ // Only GLINTD is writable
+ if (BIT(data, 4))
+ {
+ m_cpusta |= 0x10;
+
+ // PIC17C42 lacks this failsafe, allowing an interrupt to be taken right after an instruction that sets GLINTD
+ m_execflags &= ~INTRPT;
+ }
+ else
+ m_cpusta &= 0xef;
+}
+
+u8 pic17_cpu_device::intsta_r()
+{
+ return m_intsta;
+}
+
+void pic17_cpu_device::intsta_w(u8 data)
+{
+ // PEIF is read-only
+ m_intsta = (m_intsta & 0x80) | (data & 0x7f);
+}
+
+void pic17_cpu_device::int_edge(bool rising)
+{
+ if (BIT(m_t0sta, 7) == rising)
+ m_intsta |= 0x10;
+}
+
+void pic17_cpu_device::t0cki_edge(bool rising)
+{
+ if (BIT(m_t0sta, 6) == rising)
+ {
+ m_intsta |= 0x40;
+ if (!BIT(m_t0sta, 5))
+ increment_tmr0();
+ }
+}
+
+void pic17_cpu_device::set_peif(bool state)
+{
+ if (state)
+ m_intsta |= 0x80;
+ else
+ {
+ m_intsta &= 0x7f;
+ if (((m_intsta >> 4) & m_intsta) == 0)
+ m_execflags &= ~INTRPT;
+ }
+}
+
+u8 pic17_cpu_device::t0sta_r()
+{
+ return m_t0sta;
+}
+
+void pic17_cpu_device::t0sta_w(u8 data)
+{
+ m_t0sta = data & 0xfe;
+}
+
+u8 pic17_cpu_device::tmr0l_r()
+{
+ return m_tmr0 & 0x00ff;
+}
+
+void pic17_cpu_device::tmr0l_w(u8 data)
+{
+ m_tmr0 = u8(data) | (m_tmr0 & 0xff00);
+ m_ps = 0;
+}
+
+u8 pic17_cpu_device::tmr0h_r()
+{
+ return m_tmr0 >> 8;
+}
+
+void pic17_cpu_device::tmr0h_w(u8 data)
+{
+ m_tmr0 = u8(data) << 8 | (m_tmr0 & 0x00ff);
+ m_ps = 0;
+}
+
+void pic17_cpu_device::increment_tmr0()
+{
+ // Prescaler is an 8-bit ripple counter
+ ++m_ps;
+
+ // Tap the prescaler output (PSOUT)
+ if ((m_ps & make_bitmask<u8>((m_t0sta & 0x1e) >> 1)) == 0)
+ {
+ // Set T0IF on overflow
+ if (m_tmr0 == 0xffff)
+ m_intsta |= 0x20;
+ ++m_tmr0;
+ }
+}
+
+u8 pic17_cpu_device::fsr0_r()
+{
+ return m_fsr[0];
+}
+
+void pic17_cpu_device::fsr0_w(u8 data)
+{
+ m_fsr[0] = data;
+}
+
+u8 pic17_cpu_device::fsr1_r()
+{
+ return m_fsr[1];
+}
+
+void pic17_cpu_device::fsr1_w(u8 data)
+{
+ m_fsr[1] = data;
+}
+
+u8 pic17_cpu_device::bsr_r()
+{
+ return m_bsr;
+}
+
+void pic17_cpu_device::bsr_w(u8 data)
+{
+ m_bsr = data;
+}
+
+u8 pic17_cpu_device::tblptrl_r()
+{
+ return m_tblptr & 0x00ff;
+}
+
+void pic17_cpu_device::tblptrl_w(u8 data)
+{
+ m_tblptr = (m_tblptr & 0xff00) | data;
+}
+
+u8 pic17_cpu_device::tblptrh_r()
+{
+ return m_tblptr >> 8;
+}
+
+void pic17_cpu_device::tblptrh_w(u8 data)
+{
+ m_tblptr = (m_tblptr & 0x00ff) | u16(data) << 8;
+}
+
+u8 pic17_cpu_device::prodl_r()
+{
+ return m_prod & 0x00ff;
+}
+
+void pic17_cpu_device::prodl_w(u8 data)
+{
+ m_prod = (m_prod & 0xff00) | data;
+}
+
+u8 pic17_cpu_device::prodh_r()
+{
+ return m_prod >> 8;
+}
+
+void pic17_cpu_device::prodh_w(u8 data)
+{
+ m_prod = (m_prod & 0x00ff) | u16(data) << 8;
+}
+
+void pic17_cpu_device::set_skip()
+{
+ m_execflags |= SKIP;
+}
+
+void pic17_cpu_device::clear_watchdog_timer()
+{
+ //logerror("%04X: Watchdog timer cleared\n", m_ppc);
+}
+
+u16 pic17_cpu_device::banked_register(u8 r)
+{
+ // INDF0 and INDF1 substitute contents of FSR0/FSR1
+ if ((r & 0xf7) == 0)
+ {
+ // Post auto-decrement, post auto-increment or no change
+ u8 fsmode = m_alusta >> (BIT(r, 3) ? 6 : 4);
+ if (BIT(fsmode, 1))
+ r = m_fsr[BIT(r, 3)];
+ else if (BIT(fsmode, 0))
+ r = m_fsr[BIT(r, 3)]++;
+ else
+ r = m_fsr[BIT(r, 3)]--;
+ }
+
+ if (r >= 0x20)
+ {
+ // Addresses from 20h to FFh use GPR banks
+ return u16(m_bsr & 0xf0) << 4 | r;
+ }
+ else if ((r & 0x18) == 0x10)
+ {
+ // Addresses from 10h to 17h use SFR banks
+ return u16(m_bsr & 0x0f) << 8 | r;
+ }
+ else
+ {
+ // Other addresses are unbanked
+ return r;
+ }
+}
+
+void pic17_cpu_device::stack_push(u16 addr)
+{
+ m_stack[m_stkptr] = addr;
+
+ // Check for stack overflow
+ if (m_stkptr == 0xf)
+ {
+ logerror("%04X: Stack overflow\n", m_pc);
+
+ // STKAV remains cleared until reset
+ m_cpusta &= 0xdf;
+ }
+
+ m_stkptr = (m_stkptr + 1) & 0xf;
+}
+
+u16 pic17_cpu_device::stack_pop()
+{
+ m_stkptr = (m_stkptr - 1) & 0xf;
+ return m_stack[m_stkptr];
+}
+
+u16 pic17_cpu_device::interrupt_vector()
+{
+ u8 active_ints = m_intsample & m_intsta;
+ if (BIT(active_ints, 0))
+ {
+ // Interrupt on RA0/INT pin edge
+ standard_irq_callback(0, m_pc);
+ m_intsta &= 0xef; // INTF is cleared
+ return 0x0008;
+ }
+ else if (BIT(active_ints, 1))
+ {
+ // Interrupt on TMR0 overflow
+ standard_irq_callback(1, m_pc);
+ m_intsta &= 0xdf; // T0IF is cleared
+ return 0x0010;
+ }
+ else if (BIT(active_ints, 2))
+ {
+ // Interrupt on RA1/T0CKI pin edge
+ standard_irq_callback(2, m_pc);
+ m_intsta &= 0xbf; // T0CKIF is cleared
+ return 0x0018;
+ }
+ else if (BIT(active_ints, 3))
+ {
+ // Peripheral interrupts (not further distinguished here, and PEIF is *not* cleared)
+ standard_irq_callback(3, m_pc);
+ return 0x0020;
+ }
+ else
+ {
+ // This may happen when interrupt enable flags are cleared at the wrong moment
+ logerror("%04X: Spurious interrupt taken\n", m_pc);
+ return 0x0000;
+ }
+}
+
+void pic17_cpu_device::set_zero(bool z)
+{
+ if (z)
+ m_alusta |= 0x04;
+ else
+ m_alusta &= 0xfb;
+}
+
+void pic17_cpu_device::set_carry(bool c)
+{
+ if (c)
+ m_alusta |= 0x01;
+ else
+ m_alusta &= 0xfe;
+}
+
+void pic17_cpu_device::add_with_carry(u8 augend, bool cin)
+{
+ // Carry out is inverted borrow for subtraction
+ bool cout = m_tmp + augend + cin >= 0x100;
+ bool dc = (m_tmp & 0x0f) + (augend & 0x0f) + cin >= 0x10;
+ s16 sum = s8(m_tmp) + s8(augend) + cin;
+ m_tmp = sum & 0x00ff;
+
+ // Set OV, Z, DC and C flags
+ m_alusta &= 0xf0;
+ if (sum < -128 || sum >= 128)
+ m_alusta |= 0x08;
+ if (m_tmp == 0)
+ m_alusta |= 0x04;
+ if (dc)
+ m_alusta |= 0x02;
+ if (cout)
+ m_alusta |= 0x01;
+}
+
+void pic17_cpu_device::decimal_adjust()
+{
+ u16 result = m_wreg;
+
+ if (BIT(m_alusta, 1) || (result & 0x0f) > 0x09)
+ {
+ result += 0x06;
+ if (result >= 0x100)
+ m_alusta |= 0x01;
+ }
+ if (BIT(m_alusta, 0) || (result & 0xf0) > 0x90)
+ {
+ result += 0x60;
+ if (result >= 0x100)
+ m_alusta |= 0x01;
+ }
+}
+
+void pic17_cpu_device::q1_decode()
+{
+ m_raddr = 0x000;
+ if (m_ir >= 0xc000)
+ {
+ // GOTO and CALL write to PCL (after setting PCLATH)
+ m_raddr = 0x002;
+ m_execflags |= REGWT;
+ }
+ else if (m_ir >= 0xb000)
+ {
+ if ((m_ir & 0x0f00) == 0x700)
+ {
+ // LCALL is a literal write to PCL
+ m_raddr = 0x002;
+ m_execflags |= REGWT;
+ }
+ else if ((m_ir & 0x0f00) == 0x800 || (m_ir & 0x0e00) == 0xa00)
+ {
+ // MOVLB and MOVLR operate on BSR
+ m_raddr = 0x00f;
+ m_execflags |= REGWT;
+ }
+ }
+ else if ((m_ir & 0xe000) == 0x4000)
+ {
+ // MOVPF reads register in 'p' field first
+ m_raddr = banked_register((m_ir & 0x1f00) >> 8);
+ }
+ else
+ {
+ if (m_ir != 0x0000)
+ m_raddr = banked_register(m_ir & 0x00ff);
+ if (m_ir < 0x3000)
+ {
+ // Byte-oriented register operations with d = 1
+ // CLRF, SETF, NEGW, DAW always copy their result to the register, even when s = 0
+ if (m_ir >= 0x2800 || BIT(m_ir, 8))
+ m_execflags |= REGWT;
+ }
+ else if ((m_ir & 0xf800) == 0x3800 || (m_ir & 0xf000) == 0x8000)
+ {
+ // Bitwise read-modify-write instructions
+ m_execflags |= REGWT;
+ }
+ else if ((m_ir & 0xf400) == 0xa000)
+ {
+ // TABLRD/TBRD
+ m_execflags |= REGWT;
+ }
+ }
+}
+
+void pic17_cpu_device::q2_read()
+{
+ if (m_ir >= 0xb000)
+ {
+ // Read literal data only
+ m_tmp = m_ir & 0x00ff;
+
+ // Set PCLATH for GOTO/CALL
+ if (m_ir >= 0xc000)
+ m_pclath = (m_pc & 0xe000) >> 8 | (m_ir & 0x1f00) >> 8;
+ }
+ else if ((m_ir & 0xa400) == 0xa000)
+ {
+ // Read TABLATH or TABLATL
+ if (BIT(m_ir, 9))
+ m_tmp = m_tablat >> 8;
+ else
+ m_tmp = m_tablat & 0x00ff;
+ }
+ else if (m_ir != 0x0000)
+ {
+ // Read register (dummy read for some instructions)
+ m_tmp = m_data.read_byte(m_raddr);
+
+ if ((m_ir & 0xc000) == 0x4000)
+ {
+ // Swap out address for MOVFP/MOVPF
+ u8 f = m_ir & 0x00ff;
+ u8 p = (m_ir & 0x1f00) >> 8;
+ if (p != f)
+ m_raddr = banked_register(BIT(m_ir, 13) ? p : f);
+
+ // Change from read to write mode
+ m_execflags |= REGWT;
+ }
+ }
+}
+
+void pic17_cpu_device::q3_execute()
+{
+ if (m_ir >= 0xe000)
+ stack_push(m_pc);
+ else switch (m_ir & 0xff00)
+ {
+ case 0x0000:
+ switch (m_ir & 0x00ff)
+ {
+ case 0x00: // NOP
+ break;
+
+ case 0x02: // RETURN
+ m_pc = stack_pop();
+ m_execflags |= FORCENOP;
+ break;
+
+ case 0x03: // SLEEP
+ clear_watchdog_timer();
+ m_cpusta |= 0x08; // set TO
+ m_cpusta &= 0xfb; // clear PD
+ m_execflags |= SLEEP;
+ break;
+
+ case 0x04: // CLRWDT
+ clear_watchdog_timer();
+ break;
+
+ case 0x05: // RETFIE
+ m_pc = stack_pop();
+ m_execflags |= FORCENOP;
+
+ // Clear global interrupt disable bit
+ m_cpusta &= 0xef;
+ break;
+ }
+ break;
+
+ case 0x0100: // MOVWF
+ m_tmp = m_wreg;
+ break;
+
+ case 0x0200: case 0x0300: // SUBWFB
+ add_with_carry(~m_wreg, BIT(m_alusta, 0));
+ break;
+
+ case 0x0400: case 0x0500: // SUBWF
+ case 0xb200: // SUBLW
+ add_with_carry(~m_wreg, true);
+ break;
+
+ case 0x0600: case 0x0700: // DECF
+ add_with_carry(0xff, false);
+ break;
+
+ case 0x0800: case 0x0900: // IORWF
+ case 0xb300: // IORLW
+ m_tmp |= m_wreg;
+ set_zero(m_tmp);
+ break;
+
+ case 0x0a00: case 0x0b00: // ANDWF
+ case 0xb500: // ANDLW
+ m_tmp &= m_wreg;
+ set_zero(m_tmp);
+ break;
+
+ case 0x0c00: case 0x0d00: // XORWF
+ case 0xb400: // XORLW
+ m_tmp ^= m_wreg;
+ set_zero(m_tmp);
+ break;
+
+ case 0x0e00: case 0x0f00: // ADDWF
+ case 0xb100: // ADDLW
+ add_with_carry(m_wreg, false);
+ break;
+
+ case 0x1000: case 0x1100: // ADDWFC
+ add_with_carry(m_wreg, BIT(m_alusta, 0));
+ break;
+
+ case 0x1200: case 0x1300: // COMF
+ m_tmp ^= 0xff;
+ set_zero(m_tmp);
+ break;
+
+ case 0x1400: case 0x1500: // INCF
+ add_with_carry(1, false);
+ break;
+
+ case 0x1600: case 0x1700: // DECFSZ
+ --m_tmp;
+ if (m_tmp == 0)
+ set_skip();
+ break;
+
+ case 0x1800: case 0x1900: // RRCF
+ set_carry(BIT(std::exchange(m_tmp, (m_tmp >> 1) | (m_alusta << 7)), 0));
+ break;
+
+ case 0x1a00: case 0x1b00: // RLCF
+ set_carry(BIT(std::exchange(m_tmp, (m_tmp << 1) | (m_alusta & 0x01)), 7));
+ break;
+
+ case 0x1c00: case 0x1d00: // SWAPF
+ m_tmp = (m_tmp << 4) | (m_tmp >> 4);
+ break;
+
+ case 0x1e00: case 0x1f00: // INCFSZ
+ ++m_tmp;
+ if (m_tmp == 0)
+ set_skip();
+ break;
+
+ case 0x2000: case 0x2100: // RRNCF
+ m_tmp = (m_tmp >> 1) | (m_tmp << 7);
+ break;
+
+ case 0x2200: case 0x2300: // RLNCF
+ m_tmp = (m_tmp << 1) | (m_tmp >> 7);
+ break;
+
+ case 0x2400: case 0x2500: // INFSNZ
+ ++m_tmp;
+ if (m_tmp != 0)
+ set_skip();
+ break;
+
+ case 0x2600: case 0x2700: // DCFSNZ
+ --m_tmp;
+ if (m_tmp != 0)
+ set_skip();
+ break;
+
+ case 0x2800: case 0x2900: // CLRF
+ m_tmp = 0;
+ break;
+
+ case 0x2a00: case 0x2b00: // SETF
+ m_tmp = 0xff;
+ break;
+
+ case 0x2c00: case 0x2d00: // NEGW
+ m_tmp = 0;
+ add_with_carry(~m_wreg, true);
+ break;
+
+ case 0x2e00: case 0x2f00: // DAW
+ decimal_adjust();
+ break;
+
+ case 0x3000: // CPFSLT (unsigned comparison)
+ if (m_tmp < m_wreg)
+ set_skip();
+ break;
+
+ case 0x3100: // CPFSEQ
+ if (m_tmp == m_wreg)
+ set_skip();
+ break;
+
+ case 0x3200: // CPFSGT (unsigned comparison)
+ if (m_tmp > m_wreg)
+ set_skip();
+ break;
+
+ case 0x3300: // TSTFSZ
+ if (m_tmp == 0)
+ set_skip();
+ break;
+
+ case 0x3400: // MULWF
+ case 0xbc00: // MULLW
+ m_prod = u16(m_wreg) * u16(m_tmp);
+ break;
+
+ case 0x3800: case 0x3900: case 0x3a00: case 0x3b00: case 0x3c00: case 0x3d00: case 0x3e00: case 0x3f00: // BTG
+ m_tmp ^= 1 << ((m_ir & 0x0700) >> 8);
+ break;
+
+ case 0x8000: case 0x8100: case 0x8200: case 0x8300: case 0x8400: case 0x8500: case 0x8600: case 0x8700: // BSF
+ m_tmp |= 1 << ((m_ir & 0x0700) >> 8);
+ break;
+
+ case 0x8800: case 0x8900: case 0x8a00: case 0x8b00: case 0x8c00: case 0x8d00: case 0x8e00: case 0x8f00: // BCF
+ m_tmp &= ~(1 << ((m_ir & 0x0700) >> 8));
+ break;
+
+ case 0x9000: case 0x9100: case 0x9200: case 0x9300: case 0x9400: case 0x9500: case 0x9600: case 0x9700: // BTFSS
+ if ((m_tmp & (1 << ((m_ir & 0x0700) >> 8))) != 0)
+ set_skip();
+ break;
+
+ case 0x9800: case 0x9900: case 0x9a00: case 0x9b00: case 0x9c00: case 0x9d00: case 0x9e00: case 0x9f00: // BTFSC
+ if ((m_tmp & (1 << ((m_ir & 0x0700) >> 8))) == 0)
+ set_skip();
+ break;
+
+ case 0xb600: // RETLW
+ m_pc = stack_pop();
+ m_execflags |= FORCENOP;
+ break;
+
+ case 0xb700: // LCALL
+ stack_push(m_pc);
+ break;
+
+ case 0xb800: // MOVLB
+ m_tmp = (m_bsr & 0xf0) | (m_tmp & 0x0f);
+ break;
+
+ case 0xba00: case 0xbb00: // MOVLR
+ m_tmp = (m_tmp & 0xf0) | (m_bsr & 0x0f);
+ break;
+ }
+}
+
+void pic17_cpu_device::q4_write()
+{
+ if ((m_execflags & REGWT) != 0)
+ {
+ m_data.write_byte(m_raddr, m_tmp);
+ m_execflags &= ~REGWT;
+ if ((m_ir & 0xfc00) == 0xa800)
+ {
+ m_execflags |= TABLRD;
+ if (BIT(m_ir, 8))
+ m_execflags |= TBLPTRI;
+ }
+ }
+ else if ((m_ir & 0xf400) == 0xa400)
+ {
+ // Set TABLATH or TABLATL for TABLWT/TLWT
+ if (BIT(m_ir, 9))
+ {
+ m_tablat = (m_tablat & 0x00ff) | u16(m_tmp) << 8;
+ m_tlwt = (m_tlwt & 0x00ff) | u16(m_tmp) << 8;
+ }
+ else
+ {
+ m_tablat = (m_tablat & 0xff00) | m_tmp;
+ m_tlwt = (m_tlwt & 0xff00) | m_tmp;
+ }
+ if (BIT(m_ir, 11))
+ {
+ m_execflags |= TABLWT;
+ if (BIT(m_ir, 8))
+ m_execflags |= TBLPTRI;
+ }
+ }
+
+ // MOVLW, ADDLW, SUBLW, IORLW, XORLW, ANDLW, RETLW always store result in W; other ALU instructions do so when d = 0
+ if ((m_ir >= 0x0200 && m_ir < 0x3000 && !BIT(m_ir, 8)) || (m_ir >= 0xb000 && m_ir < 0xb700))
+ m_wreg = m_tmp;
+}
+
+void pic17_cpu_device::increment_timers()
+{
+ // TMR0 increment is inhibited by count writes to TMR0L
+ if (BIT(m_t0sta, 5) && ((m_execflags & REGWT) == 0 || m_raddr != 0x00b))
+ increment_tmr0();
+}
+
+void pic17_cpu_device::execute_run()
+{
+ switch (m_execphase)
+ {
+ while (true)
+ {
+ [[fallthrough]];
+ case exec_phase::Q1:
+ if ((m_execflags & SLEEP) != 0)
+ {
+ if (((m_intsta >> 4) & m_intsta) != 0)
+ m_execflags &= ~SLEEP;
+ else
+ {
+ // Do nothing until time to wake up
+ m_icount = 0;
+ m_execphase = exec_phase::Q1;
+ return;
+ }
+ }
+ if (m_execflags == 0)
+ {
+ m_ppc = m_pc;
+ debugger_instruction_hook(m_pc);
+ ++m_pc;
+ q1_decode();
+ }
+ else if ((m_execflags & (TABLRD | TABLWT)) == 0)
+ {
+ if ((m_execflags & SKIP) != 0)
+ ++m_pc;
+ m_ir = 0;
+ m_execflags &= ~(SKIP | FORCENOP);
+ }
+ if (--m_icount <= 0)
+ {
+ m_execphase = exec_phase::Q2;
+ return;
+ }
+ [[fallthrough]];
+
+ case exec_phase::Q2:
+ if ((m_execflags & INTRPT) == 0)
+ m_intsample = BIT(m_cpusta, 4) ? 0 : m_intsta >> 4;
+ if ((m_execflags & (TABLRD | TABLWT)) != 0)
+ {
+ m_paddr = m_tblptr;
+ if ((m_execflags & TBLPTRI) != 0)
+ ++m_tblptr;
+ }
+ else
+ {
+ m_paddr = m_pc;
+ q2_read();
+ }
+ if (--m_icount <= 0)
+ {
+ m_execphase = exec_phase::Q3;
+ return;
+ }
+ [[fallthrough]];
+
+ case exec_phase::Q3:
+ if ((m_execflags & (TABLRD | TABLWT)) == 0)
+ {
+ if ((m_execflags & INTRPT) != 0)
+ {
+ m_execflags &= ~INTRPT;
+
+ // Call to interrupt vector
+ stack_push(m_pc);
+ m_pc = interrupt_vector();
+ m_execflags |= FORCENOP;
+
+ // Set global interrupt disable flag
+ m_cpusta |= 0x10;
+ m_intsample = 0;
+ }
+ else
+ q3_execute();
+ }
+ if (--m_icount <= 0)
+ {
+ m_execphase = exec_phase::Q4;
+ return;
+ }
+ [[fallthrough]];
+
+ case exec_phase::Q4:
+ if ((m_intsample & m_intsta) != 0)
+ m_execflags = (m_execflags | INTRPT) & ~SLEEP;
+ increment_timers();
+ if ((m_execflags & TABLWT) != 0)
+ {
+ // TABLAT value used here may not be from TABLRD, but must be written with TABLAT/TLWT.
+ // See PIC17C44 Rev. A Silicon Errata Sheet, DS30412C/44/E1A2.
+ m_program.write_word(m_paddr, m_tlwt);
+ m_execflags &= ~(TABLWT | TBLPTRI);
+ }
+ else if ((m_execflags & TABLRD) != 0)
+ {
+ m_tablat = m_program.read_word(m_paddr);
+ m_execflags &= ~(TABLRD | TBLPTRI);
+ }
+ else
+ {
+ u16 inst = m_cache.read_word(m_paddr);
+ q4_write();
+ m_ir = inst;
+ }
+ if (--m_icount <= 0)
+ {
+ m_execphase = exec_phase::Q1;
+ return;
+ }
+ }
+ }
+}
+
+void pic17_cpu_device::device_start()
+{
+ // Hook address spaces
+ space(AS_PROGRAM).cache(m_cache);
+ space(AS_PROGRAM).specific(m_program);
+ space(AS_DATA).specific(m_data);
+ set_icountptr(m_icount);
+
+ // Register debug state
+ state_add<u16>(PIC17_PC, "PC",
+ [this]() { return m_pc; },
+ [this](u16 data) { debug_set_pc(data); }
+ );
+ state_add<u16>(STATE_GENPC, "GENPC",
+ [this]() { return m_pc; },
+ [this](u16 data) { debug_set_pc(data); }
+ ).noshow();
+ state_add<u16>(STATE_GENPCBASE, "CURPC",
+ [this]() { return m_ppc; },
+ [this](u16 data) { debug_set_pc(data); }
+ ).noshow();
+ state_add(PIC17_PCLATH, "PCLATH", m_pclath);
+ state_add(PIC17_STKPTR, "STKPTR", m_stkptr).mask(0xf);
+ state_add<u16>(PIC17_TOS, "TOS",
+ [this]() { return m_stack[(m_stkptr - 1) & 0xf]; },
+ [this](u16 data) { m_stack[(m_stkptr - 1) & 0xf] = data; }
+ );
+ state_add(PIC17_WREG, "W", m_wreg);
+ state_add(PIC17_PROD, "PROD", m_prod); // not in PIC17C42
+ state_add(PIC17_ALUSTA, "ALUSTA", m_alusta);
+ state_add(STATE_GENFLAGS, "FLAGS", m_alusta).formatstr("%10s").noshow();
+ state_add(PIC17_CPUSTA, "CPUSTA", m_cpusta).mask(0x3c); // PIC17C75X also implements bits 1 and 0
+ state_add(PIC17_INTSTA, "INTSTA", m_intsta);
+ state_add(PIC17_FSR0, "FSR0", m_fsr[0]);
+ state_add(PIC17_FSR1, "FSR1", m_fsr[1]);
+ state_add(PIC17_BSR, "BSR", m_bsr);
+ state_add(PIC17_TBLPTR, "TBLPTR", m_tblptr);
+ state_add(PIC17_TABLAT, "TABLAT", m_tablat);
+ state_add(PIC17_TLWT, "TLWT", m_tlwt);
+ state_add(PIC17_T0STA, "T0STA", m_t0sta).mask(0xfe);
+ state_add(PIC17_TMR0, "TMR0", m_tmr0);
+ state_add(PIC17_PS, "PS", m_ps);
+
+ // Save state
+ save_item(NAME(m_pc));
+ save_item(NAME(m_ppc));
+ save_item(NAME(m_paddr));
+ save_item(NAME(m_raddr));
+ save_item(NAME(m_ir));
+ save_item(NAME(m_tmp));
+ save_item(NAME(m_execphase));
+ save_item(NAME(m_execflags));
+ save_item(NAME(m_pclath));
+ save_item(NAME(m_wreg));
+ save_item(NAME(m_alusta));
+ save_item(NAME(m_cpusta));
+ save_item(NAME(m_intsta));
+ save_item(NAME(m_intsample));
+ save_item(NAME(m_tblptr));
+ save_item(NAME(m_tablat));
+ save_item(NAME(m_tlwt));
+ save_item(NAME(m_prod));
+ save_item(NAME(m_fsr));
+ save_item(NAME(m_bsr));
+ save_item(NAME(m_stkptr));
+ save_item(NAME(m_stack));
+ save_item(NAME(m_t0sta));
+ save_item(NAME(m_tmr0));
+ save_item(NAME(m_ps));
+}
+
+void pic17_cpu_device::device_reset()
+{
+ // Reset CPU
+ m_pc = 0;
+ m_execphase = exec_phase::Q1;
+ m_execflags = FORCENOP;
+ m_alusta |= 0xf0;
+ m_t0sta = 0;
+ m_cpusta |= 0x3c;
+ m_intsta = 0;
+ m_intsample = 0;
+ m_tblptr = 0; // not cleared on PIC17C42
+ m_bsr = 0;
+ m_stkptr = 0;
+}
+
+void pic17_cpu_device::state_string_export(const device_state_entry &entry, std::string &str) const
+{
+ switch (entry.index())
+ {
+ case STATE_GENFLAGS:
+ str = string_format("1%c 0%c %c%c%c%c",
+ BIT(m_alusta, 7) ? '=' : BIT(m_alusta, 6) ? '+' : '-',
+ BIT(m_alusta, 5) ? '=' : BIT(m_alusta, 4) ? '+' : '-',
+ BIT(m_alusta, 3) ? 'V' : '.', // OV
+ BIT(m_alusta, 2) ? 'Z' : '.',
+ BIT(m_alusta, 1) ? 'D' : '.', // DC
+ BIT(m_alusta, 0) ? 'C' : '.');
+ break;
+ }
+}