summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/pps41
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/pps41')
-rw-r--r--src/devices/cpu/pps41/mm75.cpp27
-rw-r--r--src/devices/cpu/pps41/mm75.h56
-rw-r--r--src/devices/cpu/pps41/mm75op.cpp40
-rw-r--r--src/devices/cpu/pps41/mm76.cpp182
-rw-r--r--src/devices/cpu/pps41/mm76.h165
-rw-r--r--src/devices/cpu/pps41/mm76op.cpp473
-rw-r--r--src/devices/cpu/pps41/mm78.cpp217
-rw-r--r--src/devices/cpu/pps41/mm78.h128
-rw-r--r--src/devices/cpu/pps41/mm78la.cpp113
-rw-r--r--src/devices/cpu/pps41/mm78la.h98
-rw-r--r--src/devices/cpu/pps41/mm78laop.cpp73
-rw-r--r--src/devices/cpu/pps41/mm78op.cpp202
-rw-r--r--src/devices/cpu/pps41/pps41base.cpp351
-rw-r--r--src/devices/cpu/pps41/pps41base.h154
-rw-r--r--src/devices/cpu/pps41/pps41d.cpp210
-rw-r--r--src/devices/cpu/pps41/pps41d.h66
16 files changed, 2555 insertions, 0 deletions
diff --git a/src/devices/cpu/pps41/mm75.cpp b/src/devices/cpu/pps41/mm75.cpp
new file mode 100644
index 00000000000..83c5ae06368
--- /dev/null
+++ b/src/devices/cpu/pps41/mm75.cpp
@@ -0,0 +1,27 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM75 MCU
+
+*/
+
+#include "emu.h"
+#include "mm75.h"
+
+
+DEFINE_DEVICE_TYPE(MM75, mm75_device, "mm75", "Rockwell MM75 A7500") // stripped-down MM76 (no serial i/o, less pins)
+
+
+// constructor
+mm75_device::mm75_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm76_device(mconfig, MM75, tag, owner, clock, 10, address_map_constructor(FUNC(mm75_device::program_0_6k), this), 6, address_map_constructor(FUNC(mm75_device::data_48x4), this))
+{ }
+
+
+// initialize
+void mm75_device::device_start()
+{
+ mm76_device::device_start();
+ set_d_pins(9);
+}
diff --git a/src/devices/cpu/pps41/mm75.h b/src/devices/cpu/pps41/mm75.h
new file mode 100644
index 00000000000..92785d0d5ae
--- /dev/null
+++ b/src/devices/cpu/pps41/mm75.h
@@ -0,0 +1,56 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM75 MCU
+
+*/
+
+#ifndef MAME_CPU_PPS41_MM75_H
+#define MAME_CPU_PPS41_MM75_H
+
+#pragma once
+
+#include "mm76.h"
+
+// pinout reference
+
+/*
+ ____ ____
+ RIO8 1 |* \_/ | 28 RIO7
+ RIO1 2 | | 27 RIO6
+ RIO2 3 | | 26 RIO5
+ RIO3 4 | | 25 INT0
+ RIO4 5 | | 24 PO
+ DIO0 6 | | 23 PI4
+ DIO1 7 | MM75 | 22 PI3
+ DIO2 8 | | 21 PI2
+ DIO3 9 | | 20 PI1
+ DIO4 10 | | 19 TEST
+ DIO5 11 | | 18 Vdd
+ DIO6 12 | | 17 VC
+ DIO7 13 | | 16 A
+ Vss 14 |___________| 15 DIO8
+
+*/
+
+class mm75_device : public mm76_device
+{
+public:
+ mm75_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ virtual void device_start() override ATTR_COLD;
+
+ // opcode handlers
+ virtual void op_ios() override;
+ virtual void op_i2c() override;
+
+ virtual void op_ibm() override;
+ virtual void op_int1h() override;
+};
+
+
+DECLARE_DEVICE_TYPE(MM75, mm75_device)
+
+#endif // MAME_CPU_PPS41_MM75_H
diff --git a/src/devices/cpu/pps41/mm75op.cpp b/src/devices/cpu/pps41/mm75op.cpp
new file mode 100644
index 00000000000..445655ef31b
--- /dev/null
+++ b/src/devices/cpu/pps41/mm75op.cpp
@@ -0,0 +1,40 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+
+// MM75 opcode handlers
+
+#include "emu.h"
+#include "mm75.h"
+
+
+// unsupported opcodes
+
+void mm75_device::op_ios()
+{
+ // IOS: does not have serial I/O
+ op_illegal();
+}
+
+void mm75_device::op_i2c()
+{
+ // I2C: does not have PI5-8 pins
+ m_a = 0;
+ op_illegal();
+}
+
+
+// changed opcodes
+
+void mm75_device::op_ibm()
+{
+ // IBM: INT1 is shared with RIO8
+ mm76_device::op_ibm();
+ m_a &= ~(m_int_line[1] << 3);
+}
+
+void mm75_device::op_int1h()
+{
+ // INT1H: INT1 is shared with RIO8
+ int state = (m_read_r() & m_r_output) >> 7 & m_int_line[1];
+ m_skip = bool(state);
+}
diff --git a/src/devices/cpu/pps41/mm76.cpp b/src/devices/cpu/pps41/mm76.cpp
new file mode 100644
index 00000000000..d1eb8bac0ca
--- /dev/null
+++ b/src/devices/cpu/pps41/mm76.cpp
@@ -0,0 +1,182 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM76 MCU
+
+*/
+
+#include "emu.h"
+#include "mm76.h"
+
+#include "pps41d.h"
+
+
+DEFINE_DEVICE_TYPE(MM76, mm76_device, "mm76", "Rockwell MM76 A7600") // 640 bytes ROM, 48 nibbles RAM
+DEFINE_DEVICE_TYPE(MM76L, mm76l_device, "mm76l", "Rockwell MM76L B7600") // low-power
+DEFINE_DEVICE_TYPE(MM76E, mm76e_device, "mm76e", "Rockwell MM76E A8600") // ROM extended to 1KB
+DEFINE_DEVICE_TYPE(MM76EL, mm76el_device, "mm76el", "Rockwell MM76EL B8600") // low-power
+
+
+// constructor
+mm76_device::mm76_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm76_device(mconfig, MM76, tag, owner, clock, 10, address_map_constructor(FUNC(mm76_device::program_0_6k), this), 6, address_map_constructor(FUNC(mm76_device::data_48x4), this))
+{ }
+
+mm76_device::mm76_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data) :
+ pps41_base_device(mconfig, type, tag, owner, clock, prgwidth, program, datawidth, data)
+{ }
+
+mm76l_device::mm76l_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm76_device(mconfig, MM76L, tag, owner, clock, 10, address_map_constructor(FUNC(mm76l_device::program_0_6k), this), 6, address_map_constructor(FUNC(mm76l_device::data_48x4), this))
+{ }
+
+mm76e_device::mm76e_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm76e_device(mconfig, MM76E, tag, owner, clock, 10, address_map_constructor(FUNC(mm76e_device::program_1k), this), 6, address_map_constructor(FUNC(mm76e_device::data_48x4), this))
+{ }
+
+mm76e_device::mm76e_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data) :
+ mm76_device(mconfig, type, tag, owner, clock, prgwidth, program, datawidth, data)
+{ }
+
+mm76el_device::mm76el_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm76e_device(mconfig, MM76EL, tag, owner, clock, 10, address_map_constructor(FUNC(mm76el_device::program_1k), this), 6, address_map_constructor(FUNC(mm76el_device::data_48x4), this))
+{ }
+
+
+// internal memory maps
+void mm76_device::program_0_6k(address_map &map)
+{
+ map(0x000, 0x17f).mirror(0x200).rom();
+ map(0x180, 0x1ff).rom();
+ map(0x380, 0x3ff).rom();
+}
+
+void mm76e_device::program_1k(address_map &map)
+{
+ map(0x000, 0x3ff).rom();
+}
+
+void mm76_device::data_48x4(address_map &map)
+{
+ map(0x00, 0x2f).ram();
+ map(0x30, 0x3f).lr8(NAME([]() { return 0xf; })).nopw();
+}
+
+
+// machine config
+void mm76_device::device_add_mconfig(machine_config &config)
+{
+ PLA(config, "opla", 5, 8, 17).set_format(pla_device::FMT::BERKELEY);
+}
+
+
+// disasm
+std::unique_ptr<util::disasm_interface> mm76_device::create_disassembler()
+{
+ return std::make_unique<mm76_disassembler>();
+}
+
+
+// initialize
+void mm76_device::device_start()
+{
+ pps41_base_device::device_start();
+}
+
+void mm76_device::device_reset()
+{
+ pps41_base_device::device_reset();
+}
+
+
+//-------------------------------------------------
+// execute
+//-------------------------------------------------
+
+void mm76_device::execute_one()
+{
+ if (op_is_tr(m_prev_op))
+ {
+ // 2-byte opcodes
+ switch (m_op & 0xf0)
+ {
+ case 0x20: op_skbei(); break;
+ case 0x60: op_skaei(); break;
+
+ case 0x80: case 0x90: case 0xa0: case 0xb0: op_tml(); break;
+ case 0xc0: case 0xd0: case 0xe0: case 0xf0: op_tl(); break;
+
+ default: op_illegal(); break;
+ }
+ }
+ else
+ {
+ // standard opcodes
+ switch (m_op & 0xf0)
+ {
+ case 0x20: op_lb(); break;
+ case 0x30: op_tr(); break;
+ case 0x60: op_aisk(); break;
+ case 0x70: op_lai(); break;
+
+ case 0x80: case 0x90: case 0xa0: case 0xb0: op_tm(); break;
+ case 0xc0: case 0xd0: case 0xe0: case 0xf0: op_t(); break;
+
+ default:
+ switch (m_op & 0xfc)
+ {
+ case 0x08: op_skbf(); break;
+ case 0x10: op_sb(); break;
+ case 0x14: op_rb(); break;
+ case 0x1c: op_eob(); break;
+ case 0x50: op_l(); break;
+ case 0x54: op_xnsk(); break;
+ case 0x58: op_x(); break;
+ case 0x5c: op_xdsk(); break;
+
+ default:
+ switch (m_op)
+ {
+ case 0x00: op_nop(); break;
+ case 0x01: op_sknc(); break;
+ case 0x02: op_rt(); break;
+ case 0x03: op_rtsk(); break;
+ case 0x04: op_int0l(); break;
+ case 0x05: op_int1h(); break;
+ case 0x06: op_din1(); break;
+ case 0x07: op_din0(); break;
+ case 0x0c: op_sc(); break;
+ case 0x0d: op_rc(); break;
+ case 0x0e: op_seg1(); break;
+ case 0x0f: op_seg2(); break;
+
+ case 0x18: op_oa(); break;
+ case 0x19: op_ob(); break;
+ case 0x1a: op_iam(); break;
+ case 0x1b: op_ibm(); break;
+
+ case 0x40: op_ac(); break;
+ case 0x41: op_acsk(); break;
+ case 0x42: op_a(); break;
+ case 0x43: op_ask(); break;
+ case 0x44: op_lba(); break;
+ case 0x45: op_com(); break;
+ case 0x46: op_xab(); break;
+ case 0x47: op_skmea(); break;
+ case 0x4a: op_i1(); break;
+ case 0x4b: op_i2c(); break;
+ case 0x4c: op_lsa(); break;
+ case 0x4d: op_ios(); break;
+ case 0x4e: op_xas(); break;
+
+ default: op_illegal(); break;
+ }
+ break; // 0xff
+
+ }
+ break; // 0xfc
+
+ } // 0xf0
+ }
+}
diff --git a/src/devices/cpu/pps41/mm76.h b/src/devices/cpu/pps41/mm76.h
new file mode 100644
index 00000000000..b984ad9c713
--- /dev/null
+++ b/src/devices/cpu/pps41/mm76.h
@@ -0,0 +1,165 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM76 MCU
+
+*/
+
+#ifndef MAME_CPU_PPS41_MM76_H
+#define MAME_CPU_PPS41_MM76_H
+
+#pragma once
+
+#include "pps41base.h"
+
+// pinout reference
+
+/*
+ ____ ____ ____ ____
+ A 1 |* \_/ | 42 BP BP 1 |* \_/ | 40 A
+ EXCLK 2 | | 41 DIO9 VC 2 | | 39 DIO9
+ CLKIN 3 | | 40 DIO8 XTLIN 3 | | 38 DIO8
+ VC 4 | | 39 N/C XTLOUT 4 | | 37 DIO7
+ Vdd 5 | | 38 DIO7 Vdd 5 | | 36 DIO6
+ Vss 6 | | 37 DIO6 PI2 6 | | 35 DIO5
+ TEST 7 | | 36 DIO5 TEST 7 | | 34 DIO4
+ PI2 8 | | 35 DIO4 PI6 8 | | 33 DIO3
+ PI6 9 | | 34 DIO3 PI1 9 | | 32 DIO2
+ PI1 10 | MM76 | 33 DIO2 PI5 10 | MM76L | 31 DIO1
+ PI5 11 | MM76E | 32 DIO1 PI7 11 | MM76EL | 30 DIO0
+ PI7 12 | | 31 DIO0 PI3 12 | | 29 CLOCK
+ PI3 13 | | 30 CLOCK PI8 13 | | 28 DATAO
+ PI8 14 | | 29 DATAO PI4 14 | | 27 DATAI
+ PI4 15 | | 28 DATAI PO 15 | | 26 RIO4
+ Vdd 16 | | 27 RIO4 INT0 16 | | 25 RIO3
+ PO 17 | | 26 RIO3 INT1 17 | | 24 RIO2
+ INT0 18 | | 25 RIO2 RIO5 18 | | 23 RIO1
+ INT1 19 | | 24 RIO1 RIO6 19 | | 22 RIO8
+ RIO5 20 | | 23 RIO8 Vss 20 |___________| 21 RIO7
+ RIO6 21 |___________| 22 RIO7
+
+*/
+
+class mm76_device : public pps41_base_device
+{
+public:
+ mm76_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ mm76_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data);
+
+ // device-level overrides
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ // device_disasm_interface overrides
+ virtual std::unique_ptr<util::disasm_interface> create_disassembler() override;
+
+ // device_execute_interface overrides
+ virtual void execute_one() override;
+
+ void data_48x4(address_map &map) ATTR_COLD;
+ void program_0_6k(address_map &map) ATTR_COLD;
+
+ // opcode helpers
+ u8 ram_r();
+ void ram_w(u8 data);
+ void pop_pc();
+ void push_pc();
+ void op_illegal();
+ void op_todo();
+
+ virtual bool op_is_tr(u8 op) override { return (op & 0xf0) == 0x30; }
+ virtual bool op_is_eob(u8 op) { return (op & 0xfc) == 0x1c; }
+ virtual bool op_is_lb(u8 op) { return (op & 0xf0) == 0x20; }
+ virtual bool op_is_lai(u8 op) { return (op & 0xf0) == 0x70; }
+
+ // opcode handlers
+ virtual void op_xab();
+ virtual void op_lba();
+ virtual void op_lb();
+ virtual void op_eob();
+
+ virtual void op_sb();
+ virtual void op_rb();
+ virtual void op_skbf();
+
+ virtual void op_xas();
+ virtual void op_lsa();
+
+ virtual void op_l();
+ virtual void op_x();
+ virtual void op_xdsk();
+ virtual void op_xnsk();
+
+ virtual void op_a();
+ virtual void op_ac();
+ virtual void op_acsk();
+ virtual void op_ask();
+ virtual void op_com();
+ virtual void op_rc();
+ virtual void op_sc();
+ virtual void op_sknc();
+ virtual void op_lai();
+ virtual void op_aisk();
+
+ virtual void op_rt();
+ virtual void op_rtsk();
+ virtual void op_t();
+ virtual void op_tl();
+ virtual void op_tm();
+ virtual void op_tml();
+ virtual void op_tr();
+ virtual void op_nop();
+
+ virtual void op_skmea();
+ virtual void op_skbei();
+ virtual void op_skaei();
+
+ virtual void op_ibm();
+ virtual void op_ob();
+ virtual void op_iam();
+ virtual void op_oa();
+ virtual void op_ios();
+ virtual void op_i1();
+ virtual void op_i2c();
+ virtual void op_int1h();
+ virtual void op_din1();
+ virtual void op_int0l();
+ virtual void op_din0();
+ virtual void op_seg1();
+ virtual void op_seg2();
+};
+
+class mm76l_device : public mm76_device
+{
+public:
+ mm76l_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+};
+
+class mm76e_device : public mm76_device
+{
+public:
+ mm76e_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ mm76e_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data);
+
+ void program_1k(address_map &map) ATTR_COLD;
+};
+
+class mm76el_device : public mm76e_device
+{
+public:
+ mm76el_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+};
+
+
+DECLARE_DEVICE_TYPE(MM76, mm76_device)
+DECLARE_DEVICE_TYPE(MM76L, mm76l_device)
+DECLARE_DEVICE_TYPE(MM76E, mm76e_device)
+DECLARE_DEVICE_TYPE(MM76EL, mm76el_device)
+
+#endif // MAME_CPU_PPS41_MM76_H
diff --git a/src/devices/cpu/pps41/mm76op.cpp b/src/devices/cpu/pps41/mm76op.cpp
new file mode 100644
index 00000000000..6cef3dae06d
--- /dev/null
+++ b/src/devices/cpu/pps41/mm76op.cpp
@@ -0,0 +1,473 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+
+// MM76/shared opcode handlers
+
+#include "emu.h"
+#include "mm76.h"
+
+
+// internal helpers
+
+u8 mm76_device::ram_r()
+{
+ return m_data->read_byte(m_ram_addr & m_datamask) & 0xf;
+}
+
+void mm76_device::ram_w(u8 data)
+{
+ m_data->write_byte(m_ram_addr & m_datamask, data & 0xf);
+}
+
+void mm76_device::pop_pc()
+{
+ m_pc = m_stack[0] & m_prgmask;
+ for (int i = 0; i < m_stack_levels-1; i++)
+ m_stack[i] = m_stack[i+1];
+}
+
+void mm76_device::push_pc()
+{
+ for (int i = m_stack_levels-1; i >= 1; i--)
+ m_stack[i] = m_stack[i-1];
+ m_stack[0] = m_pc;
+}
+
+void mm76_device::op_illegal()
+{
+ logerror("unknown opcode $%02X at $%03X\n", m_op, m_prev_pc);
+}
+
+void mm76_device::op_todo()
+{
+ logerror("unimplemented opcode $%02X at $%03X\n", m_op, m_prev_pc);
+}
+
+
+// opcodes
+
+// RAM addressing instructions
+
+void mm76_device::op_xab()
+{
+ // XAB: exchange A with Bl
+ u8 a = m_a;
+ m_a = m_b & 0xf;
+ m_b = (m_b & ~0xf) | a;
+ m_ram_delay = true;
+}
+
+void mm76_device::op_lba()
+{
+ // LBA: load Bl from A
+ m_b = (m_b & ~0xf) | m_a;
+ m_ram_delay = true;
+}
+
+void mm76_device::op_lb()
+{
+ // LB x: load B from x (successive LB/EOB are ignored)
+ if (!(op_is_lb(m_prev_op) && !op_is_tr(m_prev2_op)) && !op_is_eob(m_prev_op))
+ m_b = m_op & 0xf;
+}
+
+void mm76_device::op_eob()
+{
+ // EOB x: XOR Bu with x (successive LB/EOB are ignored, except after first executed LB)
+ bool first_lb = (op_is_lb(m_prev_op) && !op_is_tr(m_prev2_op)) && !(op_is_lb(m_prev2_op) && !op_is_tr(m_prev3_op)) && !op_is_eob(m_prev2_op);
+ if ((!(op_is_lb(m_prev_op) && !op_is_tr(m_prev2_op)) && !op_is_eob(m_prev_op)) || first_lb)
+ m_b ^= m_op << 4 & m_datamask;
+}
+
+
+// bit manipulation instructions
+
+void mm76_device::op_sb()
+{
+ // SB x: set memory bit / SOS: set output
+
+ // Bu rising: opcode is invalid
+ if ((m_prev2_b & 0x30) != 0x30 && (m_prev_b & 0x30) == 0x30)
+ {
+ logerror("SB/SOS invalid access at $%03X\n", m_prev_pc);
+ return;
+ }
+
+ // Bu falling or Bu == 3: SOS
+ if (((m_prev2_b & 0x30) == 0x30 && (m_prev_b & 0x30) != 0x30) || (m_prev_b & 0x30) == 0x30)
+ {
+ u8 bl = m_ram_addr & 0xf;
+ if (bl > m_d_pins)
+ logerror("SOS invalid pin %d at $%03X\n", bl, m_prev_pc);
+ else
+ {
+ m_d_output = (m_d_output | (1 << bl)) & m_d_mask;
+ m_write_d(m_d_output);
+ }
+ }
+
+ // Bu != 3: SB
+ if ((m_prev_b & 0x30) != 0x30)
+ ram_w(ram_r() | (1 << (m_op & 3)));
+}
+
+void mm76_device::op_rb()
+{
+ // RB x: reset memory bit / ROS: reset output
+
+ // Bu rising: opcode is invalid
+ if ((m_prev2_b & 0x30) != 0x30 && (m_prev_b & 0x30) == 0x30)
+ {
+ logerror("RB/ROS invalid access at $%03X\n", m_prev_pc);
+ return;
+ }
+
+ // Bu falling or Bu == 3: ROS
+ if (((m_prev2_b & 0x30) == 0x30 && (m_prev_b & 0x30) != 0x30) || (m_prev_b & 0x30) == 0x30)
+ {
+ u8 bl = m_ram_addr & 0xf;
+ if (bl > m_d_pins)
+ logerror("ROS invalid pin %d at $%03X\n", bl, m_prev_pc);
+ else
+ {
+ m_d_output = m_d_output & ~(1 << bl);
+ m_write_d(m_d_output);
+ }
+ }
+
+ // Bu != 3: RB
+ if ((m_prev_b & 0x30) != 0x30)
+ ram_w(ram_r() & ~(1 << (m_op & 3)));
+}
+
+void mm76_device::op_skbf()
+{
+ // SKBF x: test memory bit / SKISL: test input
+
+ // Bu rising: opcode is invalid
+ if ((m_prev2_b & 0x30) != 0x30 && (m_prev_b & 0x30) == 0x30)
+ {
+ logerror("SKBF/SKISL invalid access at $%03X\n", m_prev_pc);
+ return;
+ }
+
+ // Bu falling or Bu == 3: SKISL
+ if (((m_prev2_b & 0x30) == 0x30 && (m_prev_b & 0x30) != 0x30) || (m_prev_b & 0x30) == 0x30)
+ {
+ u8 bl = m_ram_addr & 0xf;
+ if (bl > m_d_pins)
+ logerror("SKISL invalid pin %d at $%03X\n", bl, m_prev_pc);
+ else
+ m_skip = !BIT((m_d_output | m_read_d()) & m_d_mask, bl);
+ }
+
+ // Bu != 3: SKBF
+ if ((m_prev_b & 0x30) != 0x30)
+ m_skip = m_skip || !BIT(ram_r(), m_op & 3);
+}
+
+
+// register to register instructions
+
+void mm76_device::op_xas()
+{
+ // XAS: exchange A with S
+ u8 a = m_a;
+ m_a = m_s;
+ m_s = a;
+ m_write_sdo(BIT(m_s, 3));
+}
+
+void mm76_device::op_lsa()
+{
+ // LSA: load S from A
+ m_s = m_a;
+ m_write_sdo(BIT(m_s, 3));
+}
+
+
+// register memory instructions
+
+void mm76_device::op_l()
+{
+ // L x: load A from memory, XOR Bu with x
+ m_a = ram_r();
+ m_b ^= m_op << 4 & 0x30;
+}
+
+void mm76_device::op_x()
+{
+ // X x: exchange A with memory, XOR Bu with x
+ u8 a = m_a;
+ m_a = ram_r();
+ ram_w(a);
+ m_b ^= m_op << 4 & 0x30;
+}
+
+void mm76_device::op_xdsk()
+{
+ // XDSK x: X x + decrement Bl
+ op_x();
+ m_b = (m_b & ~0xf) | ((m_b - 1) & 0xf);
+ m_skip = (m_b & 0xf) == 0xf;
+ m_ram_delay = true;
+}
+
+void mm76_device::op_xnsk()
+{
+ // XNSK x: X x + increment Bl
+ op_x();
+ m_b = (m_b & ~0xf) | ((m_b + 1) & 0xf);
+ m_skip = (m_b & 0xf) == 0;
+ m_ram_delay = true;
+}
+
+
+// arithmetic instructions
+
+void mm76_device::op_a()
+{
+ // A: add memory to A
+ m_a = (m_a + ram_r()) & 0xf;
+}
+
+void mm76_device::op_ac()
+{
+ // AC: add memory and carry to A
+ m_a += ram_r() + m_c_in;
+ m_c = m_a >> 4 & 1;
+ m_a &= 0xf;
+ m_c_delay = true;
+}
+
+void mm76_device::op_acsk()
+{
+ // ACSK: AC + skip on no overflow
+ op_ac();
+ m_skip = !m_c;
+}
+
+void mm76_device::op_ask()
+{
+ // ASK: A + skip on no overflow
+ u8 a = m_a;
+ op_a();
+ m_skip = m_a >= a;
+}
+
+void mm76_device::op_com()
+{
+ // COM: complement A
+ m_a ^= 0xf;
+}
+
+void mm76_device::op_rc()
+{
+ // RC: reset carry
+ m_c = 0;
+}
+
+void mm76_device::op_sc()
+{
+ // SC: set carry
+ m_c = 1;
+}
+
+void mm76_device::op_sknc()
+{
+ // SKNC: skip on no carry
+ m_skip = !m_c_in;
+}
+
+void mm76_device::op_lai()
+{
+ // LAI x: load A from x (successive LAI are ignored)
+ if (!(op_is_lai(m_prev_op) && !op_is_tr(m_prev2_op)))
+ m_a = m_op & 0xf;
+}
+
+void mm76_device::op_aisk()
+{
+ // AISK x: add x to A, skip on no overflow
+ m_a += m_op & 0xf;
+ m_skip = !(m_a & 0x10);
+ m_a &= 0xf;
+}
+
+
+// ROM addressing instructions
+
+void mm76_device::op_rt()
+{
+ // RT: return from subroutine
+ cycle();
+ pop_pc();
+}
+
+void mm76_device::op_rtsk()
+{
+ // RTSK: RT + skip next instruction
+ op_rt();
+ m_skip = true;
+}
+
+void mm76_device::op_t()
+{
+ // T x: transfer on-page
+ cycle();
+
+ // jumps from subroutine pages reset page to SR1
+ u16 mask = m_prgmask & ~0x7f;
+ if ((m_pc & mask) == mask)
+ m_pc &= ~0x40;
+
+ m_pc = (m_pc & ~0x3f) | (~m_op & 0x3f);
+}
+
+void mm76_device::op_tl()
+{
+ // TL x: transfer long off-page
+ cycle();
+ m_pc = (~m_prev_op & 0xf) << 6 | (~m_op & 0x3f);
+}
+
+void mm76_device::op_tm()
+{
+ // TM x: transfer and mark to SR0
+ cycle();
+
+ // calls from subroutine pages don't push PC
+ u16 mask = m_prgmask & ~0x7f;
+ if ((m_pc & mask) != mask)
+ push_pc();
+
+ m_pc = ((m_prgmask & ~0x3f) | (~m_op & 0x3f));
+}
+
+void mm76_device::op_tml()
+{
+ // TML x: transfer and mark long
+ cycle();
+ push_pc();
+ m_pc = (~m_prev_op & 0xf) << 6 | (~m_op & 0x3f);
+}
+
+void mm76_device::op_tr()
+{
+ // TR x: prefix for extended opcode
+}
+
+void mm76_device::op_nop()
+{
+ // NOP: no operation
+}
+
+
+// logical comparison instructions
+
+void mm76_device::op_skmea()
+{
+ // SKMEA: skip on memory equals A
+ m_skip = m_a == ram_r();
+}
+
+void mm76_device::op_skbei()
+{
+ // SKBEI x: skip on Bl equals x
+ m_skip = (m_b & 0xf) == (m_op & 0xf);
+}
+
+void mm76_device::op_skaei()
+{
+ // SKAEI x: skip on A equals X
+ m_skip = m_a == (~m_op & 0xf);
+}
+
+
+// input/output instructions
+
+void mm76_device::op_ibm()
+{
+ // IBM: input channel B to A
+ m_a &= (m_read_r() & m_r_output) >> 4;
+}
+
+void mm76_device::op_ob()
+{
+ // OB: output from A to channel B
+ m_r_output = (m_r_output & 0xf) | m_a << 4;
+ m_write_r(m_r_output);
+}
+
+void mm76_device::op_iam()
+{
+ // IAM: input channel A to A
+ m_a &= m_read_r() & m_r_output;
+}
+
+void mm76_device::op_oa()
+{
+ // OA: output from A to channel A
+ m_r_output = (m_r_output & ~0xf) | m_a;
+ m_write_r(m_r_output);
+}
+
+void mm76_device::op_ios()
+{
+ // IOS: start serial I/O (2 cycles per shift, 1st shift at IOS+3)
+ m_sclock_count = 9;
+}
+
+void mm76_device::op_i1()
+{
+ // I1: input channel 1 to A
+ m_a = m_read_p() & 0xf;
+}
+
+void mm76_device::op_i2c()
+{
+ // I2C: input channel 2 to A
+ m_a = ~m_read_p() >> 4 & 0xf;
+}
+
+void mm76_device::op_int1h()
+{
+ // INT1H: skip on INT1 high
+ m_skip = bool(m_int_line[1]);
+}
+
+void mm76_device::op_din1()
+{
+ // DIN1: test INT1 flip-flop
+ m_skip = !m_int_ff[1];
+ m_int_ff[1] = 1;
+}
+
+void mm76_device::op_int0l()
+{
+ // INT0L: skip on INT0 low
+ m_skip = !m_int_line[0];
+}
+
+void mm76_device::op_din0()
+{
+ // DIN0: test INT0 flip-flop
+ m_skip = !m_int_ff[0];
+ m_int_ff[0] = 1;
+}
+
+void mm76_device::op_seg1()
+{
+ // SEG1: output A+carry through PLA to channel A
+ u8 out = bitswap<8>(m_opla->read((m_c_in << 4 | (ram_r() & ~m_a)) ^ 0x1f), 7,5,3,1,0,2,4,6);
+ m_r_output = (m_r_output & ~0xf) | (out & 0xf);
+ m_write_r(m_r_output);
+}
+
+void mm76_device::op_seg2()
+{
+ // SEG2: output A+carry through PLA to channel B
+ u8 out = bitswap<8>(m_opla->read((m_c_in << 4 | (ram_r() & ~m_a)) ^ 0x1f), 7,5,3,1,0,2,4,6);
+ m_r_output = (m_r_output & 0xf) | (out & 0xf0);
+ m_write_r(m_r_output);
+}
diff --git a/src/devices/cpu/pps41/mm78.cpp b/src/devices/cpu/pps41/mm78.cpp
new file mode 100644
index 00000000000..dd875551fc8
--- /dev/null
+++ b/src/devices/cpu/pps41/mm78.cpp
@@ -0,0 +1,217 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM77/MM78 MCU
+
+*/
+
+#include "emu.h"
+#include "mm78.h"
+
+#include "pps41d.h"
+
+
+DEFINE_DEVICE_TYPE(MM78, mm78_device, "mm78", "Rockwell MM78 A7800") // 2KB ROM, 128 nibbles RAM
+DEFINE_DEVICE_TYPE(MM78L, mm78l_device, "mm78l", "Rockwell MM78L B7800") // low-power
+DEFINE_DEVICE_TYPE(MM77, mm77_device, "mm77", "Rockwell MM77 A7700") // 1.3KB ROM, 96 nibbles RAM
+DEFINE_DEVICE_TYPE(MM77L, mm77l_device, "mm77l", "Rockwell MM77L B7700") // 1.5KB ROM, low-power
+
+
+// constructor
+mm78_device::mm78_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm78_device(mconfig, MM78, tag, owner, clock, 11, address_map_constructor(FUNC(mm78_device::program_2k), this), 7, address_map_constructor(FUNC(mm78_device::data_128x4), this))
+{ }
+
+mm78_device::mm78_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data) :
+ mm76_device(mconfig, type, tag, owner, clock, prgwidth, program, datawidth, data)
+{ }
+
+mm78l_device::mm78l_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm78_device(mconfig, MM78L, tag, owner, clock, 11, address_map_constructor(FUNC(mm78l_device::program_2k), this), 7, address_map_constructor(FUNC(mm78l_device::data_128x4), this))
+{ }
+
+mm77_device::mm77_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm77_device(mconfig, MM77, tag, owner, clock, 11, address_map_constructor(FUNC(mm77_device::program_1_3k), this), 7, address_map_constructor(FUNC(mm77_device::data_96x4), this))
+{ }
+
+mm77_device::mm77_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data) :
+ mm78_device(mconfig, type, tag, owner, clock, prgwidth, program, datawidth, data)
+{ }
+
+mm77l_device::mm77l_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm77_device(mconfig, MM77L, tag, owner, clock, 11, address_map_constructor(FUNC(mm77l_device::program_1_5k), this), 7, address_map_constructor(FUNC(mm77l_device::data_96x4), this))
+{ }
+
+
+// internal memory maps
+void mm78_device::program_2k(address_map &map)
+{
+ map(0x000, 0x7ff).rom();
+}
+
+void mm78_device::program_1_3k(address_map &map)
+{
+ map(0x040, 0x1ff).rom();
+ map(0x240, 0x3ff).rom();
+ map(0x640, 0x7ff).rom();
+}
+
+void mm78_device::program_1_5k(address_map &map)
+{
+ map(0x000, 0x3ff).rom();
+ map(0x400, 0x5ff).mirror(0x200).rom();
+}
+
+void mm78_device::data_128x4(address_map &map)
+{
+ map(0x00, 0x7f).ram();
+}
+
+void mm78_device::data_96x4(address_map &map)
+{
+ map(0x00, 0x3f).ram();
+ map(0x40, 0x47).mirror(0x18).ram(); // not to 0x50
+ map(0x50, 0x57).ram();
+ map(0x60, 0x67).mirror(0x18).ram(); // not to 0x70
+ map(0x70, 0x77).ram();
+}
+
+
+// disasm
+std::unique_ptr<util::disasm_interface> mm78_device::create_disassembler()
+{
+ return std::make_unique<mm78_disassembler>();
+}
+
+
+// initialize
+void mm78_device::device_start()
+{
+ mm76_device::device_start();
+ m_stack_levels = 2;
+
+ state_add(++m_state_count, "X", m_x).formatstr("%01X"); // 6
+}
+
+void mm78_device::device_reset()
+{
+ mm76_device::device_reset();
+}
+
+
+//-------------------------------------------------
+// execute
+//-------------------------------------------------
+
+void mm78_device::execute_one()
+{
+ if (op_is_tr(m_prev_op) && op_is_tr(m_prev2_op))
+ {
+ // 3-byte opcodes
+ switch (m_op & 0xf0)
+ {
+ case 0x80: case 0x90: case 0xa0: case 0xb0: op_tmlb(); break;
+ case 0xc0: case 0xd0: case 0xe0: case 0xf0: op_tlb(); break;
+
+ default: op_illegal(); break;
+ }
+ }
+ else if (op_is_tr(m_prev_op))
+ {
+ // 2-byte opcodes
+ switch (m_op & 0xf0)
+ {
+ case 0x30: op_tr(); break;
+ case 0x40: op_skbei(); break;
+ case 0x60:
+ if (m_op != 0x60)
+ op_skaei();
+ else
+ op_illegal();
+ break;
+
+ case 0x80: case 0x90: case 0xa0: case 0xb0: op_tml(); break;
+ case 0xc0: case 0xd0: case 0xe0: case 0xf0: op_tl(); break;
+
+ default: op_illegal(); break;
+ }
+ }
+ else
+ {
+ // standard opcodes
+ switch (m_op & 0xf0)
+ {
+ case 0x10: op_lb(); break;
+ case 0x30: op_tr(); break;
+ case 0x40: op_lai(); break;
+ case 0x60:
+ if (m_op != 0x60)
+ op_aisk();
+ else
+ op_i1sk();
+ break;
+
+ case 0x80: case 0x90: case 0xa0: case 0xb0: op_tm(); break;
+ case 0xc0: case 0xd0: case 0xe0: case 0xf0: op_t(); break;
+
+ default:
+ switch (m_op & 0xfc)
+ {
+ case 0x08: case 0x0c: op_eob(); break;
+ case 0x20: op_sb(); break;
+ case 0x24: op_rb(); break;
+ case 0x28: op_skbf(); break;
+ case 0x50: op_l(); break;
+ case 0x54: op_xnsk(); break;
+ case 0x58: op_xdsk(); break;
+ case 0x5c: op_x(); break;
+
+ default:
+ switch (m_op)
+ {
+ case 0x00: op_nop(); break;
+ case 0x01: op_skisl(); break;
+ case 0x02: op_sknc(); break;
+ case 0x03: op_int0h(); break;
+ case 0x04: op_int1l(); break;
+ case 0x05: op_rc(); break;
+ case 0x06: op_sc(); break;
+ case 0x07: op_sag(); break;
+
+ case 0x2c: break; // TAB
+ case 0x2d: op_ios(); break;
+ case 0x2e: op_rtsk(); break;
+ case 0x2f: op_rt(); break;
+
+ case 0x70: op_sos(); break;
+ case 0x71: op_ros(); break;
+ case 0x72: op_ix(); break;
+ case 0x73: op_ox(); break;
+ case 0x74: op_xas(); break;
+ case 0x75: op_lxa(); break;
+ case 0x76: op_lba(); break;
+ case 0x77: op_com(); break;
+ case 0x78: op_i2c(); break;
+ case 0x79: op_xax(); break;
+ case 0x7a: op_xab(); break;
+ case 0x7b: op_ioa(); break;
+ case 0x7c: op_ac(); break;
+ case 0x7d: op_acsk(); break;
+ case 0x7e: op_a(); break;
+ case 0x7f: op_skmea(); break;
+
+ default: op_illegal(); break; // won't happen
+ }
+ break; // 0xff
+
+ }
+ break; // 0xfc
+
+ } // 0xf0
+ }
+
+ // TAB is delayed by 1 opcode (including a single TR)
+ if (m_prev_op == 0x2c)
+ op_tab();
+}
diff --git a/src/devices/cpu/pps41/mm78.h b/src/devices/cpu/pps41/mm78.h
new file mode 100644
index 00000000000..7676cd30226
--- /dev/null
+++ b/src/devices/cpu/pps41/mm78.h
@@ -0,0 +1,128 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM77/MM78 MCU
+
+*/
+
+#ifndef MAME_CPU_PPS41_MM78_H
+#define MAME_CPU_PPS41_MM78_H
+
+#pragma once
+
+#include "mm76.h"
+
+// pinout reference
+
+/*
+ ____ ____ ____ ____
+ BP 1 |* \_/ | 42 DIO9 BP 1 |* \_/ | 40 DIO9
+ A 2 | | 41 DIO8 A 2 | | 39 DIO8
+ CLKIN 3 | | 40 DIO7 XTLOUT 3 | | 38 DIO7
+ EXCLK 4 | | 39 DIO6 XTLIN 4 | | 37 DIO6
+ VC 5 | | 38 DIO5 VC 5 | | 36 DIO5
+ Vdd 6 | | 37 DIO4 Vdd 6 | | 35 DIO4
+ Vss 7 | | 36 DIO3 Vss 7 | | 34 DIO3
+ N/C 8 | | 35 DIO2 TEST 8 | | 33 DIO2
+ TEST 9 | | 34 DIO1 PI4 9 | | 32 DIO1
+ PI4 10 | MM77 | 33 DIO0 PI8 10 | MM77L | 31 DIO0
+ PI8 11 | MM78 | 32 INT1 PI3 11 | MM78L | 30 INT1
+ PI3 12 | | 31 INT0 PI7 12 | | 29 INT0
+ PI7 13 | | 30 DATAI PI6 13 | | 28 DATAI
+ PI6 14 | | 29 DATAO PI2 14 | | 27 DATAO
+ PI2 15 | | 28 CLOCK PI5 15 | | 26 CLOCK
+ PI5 16 | | 27 RIO4 PI1 16 | | 25 RIO4
+ Vdd 17 | | 26 RIO3 PO 17 | | 24 RIO3
+ PI1 18 | | 25 RIO2 RIO5 18 | | 23 RIO2
+ PO 19 | | 24 RIO1 RIO6 19 | | 22 RIO1
+ RIO5 20 | | 23 RIO8 RIO7 20 |___________| 21 RIO8
+ RIO6 21 |___________| 22 RIO7
+
+ MM78 also exists as 40-pin DIP and can have the same pinout as MM78L
+*/
+
+class mm78_device : public mm76_device
+{
+public:
+ mm78_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ mm78_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data);
+
+ // device-level overrides
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override { ; }
+
+ // device_disasm_interface overrides
+ virtual std::unique_ptr<util::disasm_interface> create_disassembler() override;
+
+ // device_execute_interface overrides
+ virtual void execute_one() override;
+
+ void data_96x4(address_map &map) ATTR_COLD;
+ void data_128x4(address_map &map) ATTR_COLD;
+ void program_1_3k(address_map &map) ATTR_COLD;
+ void program_1_5k(address_map &map) ATTR_COLD;
+ void program_2k(address_map &map) ATTR_COLD;
+
+ // opcode helpers
+ virtual bool op_is_eob(u8 op) override { return (op & 0xf8) == 0x08; }
+ virtual bool op_is_lb(u8 op) override { return (op & 0xf0) == 0x10; }
+ virtual bool op_is_lai(u8 op) override { return (op & 0xf0) == 0x40; }
+
+ // opcode handlers
+ virtual void op_lba() override;
+ virtual void op_acsk() override;
+ virtual void op_aisk() override;
+ virtual void op_sb() override;
+ virtual void op_rb() override;
+ virtual void op_skbf() override;
+
+ virtual void op_sos();
+ virtual void op_ros();
+ virtual void op_skisl();
+
+ virtual void op_sag();
+ virtual void op_lxa();
+ virtual void op_xax();
+ virtual void op_tlb();
+ virtual void op_tmlb();
+ virtual void op_tab();
+ virtual void op_ix();
+ virtual void op_ox();
+ virtual void op_ioa();
+ virtual void op_i1sk();
+ virtual void op_int0h();
+ virtual void op_int1l();
+};
+
+class mm78l_device : public mm78_device
+{
+public:
+ mm78l_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+};
+
+class mm77_device : public mm78_device
+{
+public:
+ mm77_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ mm77_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data);
+};
+
+class mm77l_device : public mm77_device
+{
+public:
+ mm77l_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+};
+
+
+DECLARE_DEVICE_TYPE(MM78, mm78_device)
+DECLARE_DEVICE_TYPE(MM78L, mm78l_device)
+DECLARE_DEVICE_TYPE(MM77, mm77_device)
+DECLARE_DEVICE_TYPE(MM77L, mm77l_device)
+
+#endif // MAME_CPU_PPS41_MM78_H
diff --git a/src/devices/cpu/pps41/mm78la.cpp b/src/devices/cpu/pps41/mm78la.cpp
new file mode 100644
index 00000000000..0e03e77935d
--- /dev/null
+++ b/src/devices/cpu/pps41/mm78la.cpp
@@ -0,0 +1,113 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM77LA/MM78LA MCU
+
+*/
+
+#include "emu.h"
+#include "mm78la.h"
+
+
+DEFINE_DEVICE_TYPE(MM78LA, mm78la_device, "mm78la", "Rockwell MM78LA B9000") // MM78L + output PLA and tone generator, no serial i/o
+DEFINE_DEVICE_TYPE(MM77LA, mm77la_device, "mm77la", "Rockwell MM77LA B8000") // MM77L + "
+
+
+// constructor
+mm78la_device::mm78la_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm78la_device(mconfig, MM78LA, tag, owner, clock, 11, address_map_constructor(FUNC(mm78la_device::program_2k), this), 7, address_map_constructor(FUNC(mm78la_device::data_128x4), this))
+{ }
+
+mm78la_device::mm78la_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data) :
+ mm78_device(mconfig, type, tag, owner, clock, prgwidth, program, datawidth, data)
+{ }
+
+mm77la_device::mm77la_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ mm78la_device(mconfig, MM77LA, tag, owner, clock, 11, address_map_constructor(FUNC(mm77la_device::program_1_5k), this), 7, address_map_constructor(FUNC(mm77la_device::data_96x4), this))
+{ }
+
+
+// machine config
+void mm78la_device::device_add_mconfig(machine_config &config)
+{
+ PLA(config, "opla", 4, 2*14, 16).set_format(pla_device::FMT::BERKELEY);
+}
+
+void mm77la_device::device_add_mconfig(machine_config &config)
+{
+ PLA(config, "opla", 4, 10, 16).set_format(pla_device::FMT::BERKELEY);
+}
+
+
+// initialize
+void mm78la_device::device_start()
+{
+ mm78_device::device_start();
+
+ set_d_pins(12);
+ set_r_pins(14);
+
+ // zerofill
+ m_tone_on = 0;
+ m_tone_freq = 0;
+ m_tone_count = 1;
+ m_spk_output = 2;
+ m_ios_state = 0;
+
+ // register for savestates
+ save_item(NAME(m_tone_on));
+ save_item(NAME(m_tone_freq));
+ save_item(NAME(m_tone_count));
+ save_item(NAME(m_spk_output));
+ save_item(NAME(m_ios_state));
+}
+
+void mm78la_device::device_reset()
+{
+ m_r_mask = 0;
+ mm78_device::device_reset();
+
+ // reset speaker
+ m_ios_state = 0;
+ m_tone_on = false;
+ m_write_spk(m_spk_output);
+}
+
+void mm77la_device::device_start()
+{
+ mm78la_device::device_start();
+ set_r_pins(10);
+}
+
+void mm77la_device::device_reset()
+{
+ mm78la_device::device_reset();
+}
+
+
+// speaker
+void mm78la_device::cycle()
+{
+ mm78_device::cycle();
+
+ m_tone_count++;
+
+ // toggle when counter matches
+ if (m_tone_on && m_tone_count == m_tone_freq)
+ {
+ toggle_speaker();
+ reset_tone_count();
+ }
+}
+
+void mm78la_device::reset_tone_count()
+{
+ m_tone_count = 1;
+}
+
+void mm78la_device::toggle_speaker()
+{
+ m_spk_output ^= 3;
+ m_write_spk(m_spk_output);
+}
diff --git a/src/devices/cpu/pps41/mm78la.h b/src/devices/cpu/pps41/mm78la.h
new file mode 100644
index 00000000000..cdbcb1ac275
--- /dev/null
+++ b/src/devices/cpu/pps41/mm78la.h
@@ -0,0 +1,98 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell MM77LA/MM78LA MCU
+
+*/
+
+#ifndef MAME_CPU_PPS41_MM78LA_H
+#define MAME_CPU_PPS41_MM78LA_H
+
+#pragma once
+
+#include "mm78.h"
+
+// pinout reference
+
+/*
+ ____ ____ ____ ____
+ BP 1 |* \_/ | 42 DIO9 DIO9 1 |* \_/ | 40 DIO8
+ A 2 | | 41 DIO8 BP 2 | | 39 DIO7
+ Vdd 3 | | 40 DIO7 A 3 | | 38 DIO6
+ VC 4 | | 39 DIO6 VC 4 | | 37 DIO5
+ TEST 5 | | 38 DIO5 Vdd 5 | | 36 DIO4
+ Vss 6 | | 37 DIO4 Vss 6 | | 35 DIO3
+ PI4 7 | | 36 DIO3 TEST 7 | | 34 DIO2
+ PI8 8 | | 35 DIO2 PI4 8 | | 33 DIO1
+ PI3 9 | | 34 DIO1 PI8 9 | | 32 DIO0
+ PI7 10 | MM95 | 33 DIO0 PI3 10 | B8000 | 31 DIO10
+ PI6 11 | MM78LA | 32 Vdd SPK PI7 11 | | 30 DIO11
+ PI2 12 | | 31 SPK R2 PI6 12 | | 29 SPK R2
+ PI5 13 | | 30 SPK R1 PI2 13 | | 28 Vdd SPK
+ PI1 14 | | 29 RO01 PI5 14 | | 27 SPK R1
+ PO 15 | | 28 RO02 PI1 15 | | 26 RO01
+ RO14 16 | | 27 RO03 PO 16 | | 25 RO02
+ RO13 17 | | 26 RO04 RO10 17 | | 24 RO03
+ RO12 18 | | 25 RO05 RO09 18 | | 23 RO04
+ RO11 19 | | 24 RO06 RO08 19 | | 22 RO05
+ RO10 20 | | 23 RO07 RO07 20 |___________| 21 RO06
+ RO09 21 |___________| 22 RO08
+
+ MM78LA = aka MM95, speaker pins can be used for 2 more DIO when speaker is not used.
+ For B8000 (I call it MM77LA), no documentation is known, pinout might not be accurate.
+*/
+
+class mm78la_device : public mm78_device
+{
+public:
+ mm78la_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ mm78la_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data);
+
+ // device-level overrides
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ // opcode handlers
+ virtual void op_ioa() override;
+ virtual void op_ox() override;
+ virtual void op_ix() override;
+ virtual void op_ios() override;
+ virtual void op_int0h() override;
+ virtual void op_int1l() override;
+
+ // speaker
+ bool m_tone_on;
+ u8 m_tone_freq;
+ u8 m_tone_count;
+ u8 m_spk_output;
+ u8 m_ios_state;
+
+ virtual void cycle() override;
+ void reset_tone_count();
+ void toggle_speaker();
+};
+
+class mm77la_device : public mm78la_device
+{
+public:
+ mm77la_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ // device-level overrides
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+ virtual void device_add_mconfig(machine_config &config) override ATTR_COLD;
+
+ // opcode handlers
+ virtual void op_ix() override;
+};
+
+
+DECLARE_DEVICE_TYPE(MM78LA, mm78la_device)
+DECLARE_DEVICE_TYPE(MM77LA, mm77la_device)
+
+#endif // MAME_CPU_PPS41_MM78LA_H
diff --git a/src/devices/cpu/pps41/mm78laop.cpp b/src/devices/cpu/pps41/mm78laop.cpp
new file mode 100644
index 00000000000..88b59a12e0c
--- /dev/null
+++ b/src/devices/cpu/pps41/mm78laop.cpp
@@ -0,0 +1,73 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+
+// MM77LA/MM78LA opcode handlers
+
+#include "emu.h"
+#include "mm78la.h"
+
+
+// changed opcodes
+
+void mm78la_device::op_ioa()
+{
+ // IOA: output A+carry to lower half of RO pins
+ u16 mask = (1 << (m_r_pins / 2)) - 1;
+ m_r_output = (m_r_output & ~mask) | (m_c_in << 4 | m_a);
+ m_write_r(m_r_output);
+}
+
+void mm78la_device::op_ox()
+{
+ // OX: output A+carry to upper half of RO pins
+ u16 mask = (1 << (m_r_pins / 2)) - 1;
+ m_r_output = (m_r_output & mask) | (m_c_in << 4 | m_a) << (m_r_pins / 2);
+ m_write_r(m_r_output);
+}
+
+void mm78la_device::op_ix()
+{
+ // IX: output A+carry to RO pins through PLA (MM78LA)
+ u32 out = ~m_opla->read(m_a) & 0x0fff'ffff;
+ out = bitswap<28>(out, 26,22,18,14,10,6,2,1,5,9,13,17,21,25, 27,23,19,15,11,7,3,0,4,8,12,16,20,24);
+ m_r_output = out >> (m_c_in ? 0 : 14) & 0x3fff;
+ m_write_r(m_r_output);
+}
+
+void mm77la_device::op_ix()
+{
+ // IX: output A to RO pins through PLA (MM77LA)
+ u16 out = ~m_opla->read(m_a) & 0x3ff;
+ m_r_output = bitswap<10>(out, 9,7,5,3,1,0,2,4,6,8);
+ m_write_r(m_r_output);
+}
+
+void mm78la_device::op_ios()
+{
+ // IOS: set tone frequency
+ m_tone_freq = m_tone_freq >> 4 | (m_a << 4);
+
+ // state 1->2: turn on
+ if (m_ios_state == 1)
+ {
+ m_tone_on = true;
+ reset_tone_count();
+ }
+ else
+ m_tone_on = false;
+
+ // increment state machine
+ m_ios_state = (m_ios_state + 1) % 3;
+}
+
+void mm78la_device::op_int0h()
+{
+ // INT0H: toggle speaker
+ toggle_speaker();
+}
+
+void mm78la_device::op_int1l()
+{
+ // INT1L: ?
+ op_todo();
+}
diff --git a/src/devices/cpu/pps41/mm78op.cpp b/src/devices/cpu/pps41/mm78op.cpp
new file mode 100644
index 00000000000..ddf7221a682
--- /dev/null
+++ b/src/devices/cpu/pps41/mm78op.cpp
@@ -0,0 +1,202 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+
+// MM77/MM78 opcode handlers
+
+#include "emu.h"
+#include "mm78.h"
+
+
+// changed opcodes
+
+void mm78_device::op_lba()
+{
+ // LBA: no RAM delay
+ mm76_device::op_lba();
+ m_ram_delay = false;
+}
+
+void mm78_device::op_acsk()
+{
+ // ACSK: skip logic is inverted
+ mm76_device::op_acsk();
+ m_skip = !m_skip;
+}
+
+void mm78_device::op_aisk()
+{
+ // AISK x: don't skip if x=6 (aka DC opcode)
+ mm76_device::op_aisk();
+ if ((m_op & 0xf) == 6)
+ m_skip = false;
+}
+
+void mm78_device::op_sb()
+{
+ // SB x: SB/SOS opcodes are separated
+ ram_w(ram_r() | (1 << (m_op & 3)));
+}
+
+void mm78_device::op_rb()
+{
+ // RB x: RB/ROS opcodes are separated
+ ram_w(ram_r() & ~(1 << (m_op & 3)));
+}
+
+void mm78_device::op_skbf()
+{
+ // SKBF x: SKBF/SKISL opcodes are separated
+ m_skip = !BIT(ram_r(), m_op & 3);
+}
+
+void mm78_device::op_sos()
+{
+ // SOS: SB/SOS opcodes are separated
+
+ // B7 must be low
+ if (m_ram_addr & 0x40)
+ {
+ logerror("SOS invalid access at $%03X\n", m_prev_pc);
+ return;
+ }
+
+ u8 bl = m_ram_addr & 0xf;
+ if (bl < m_d_pins)
+ {
+ m_d_output = (m_d_output | (1 << bl)) & m_d_mask;
+ m_write_d(m_d_output);
+ }
+ else if (bl < 12)
+ m_int_ff[~bl & 1] = 1;
+ else
+ logerror("SOS invalid pin %d at $%03X\n", bl, m_prev_pc);
+}
+
+void mm78_device::op_ros()
+{
+ // ROS: RB/ROS opcodes are separated
+
+ // B7 must be low
+ if (m_ram_addr & 0x40)
+ {
+ logerror("ROS invalid access at $%03X\n", m_prev_pc);
+ return;
+ }
+
+ u8 bl = m_ram_addr & 0xf;
+ if (bl < m_d_pins)
+ {
+ m_d_output = m_d_output & ~(1 << bl);
+ m_write_d(m_d_output);
+ }
+ else if (bl < 12)
+ m_int_ff[~bl & 1] = 0;
+ else
+ logerror("ROS invalid pin %d at $%03X\n", bl, m_prev_pc);
+}
+
+void mm78_device::op_skisl()
+{
+ // SKISL: SKBF/SKISL opcodes are separated
+
+ // B7 must be low
+ if (m_ram_addr & 0x40)
+ {
+ logerror("SKISL invalid access at $%03X\n", m_prev_pc);
+ return;
+ }
+
+ u8 bl = m_ram_addr & 0xf;
+ if (bl < m_d_pins)
+ m_skip = !BIT((m_d_output | m_read_d()) & m_d_mask, bl);
+ else if (bl < 12)
+ m_skip = !m_int_ff[~bl & 1];
+ else
+ logerror("SKISL invalid pin %d at $%03X\n", bl, m_prev_pc);
+}
+
+
+// new opcodes
+
+void mm78_device::op_sag()
+{
+ // SAG: set Bu to 3 for the next cycle
+ m_sag = true;
+}
+
+void mm78_device::op_lxa()
+{
+ // LXA: load X from A
+ m_x = m_a;
+}
+
+void mm78_device::op_xax()
+{
+ // XAX: exchange A with X
+ u8 a = m_a;
+ m_a = m_x;
+ m_x = a;
+}
+
+void mm78_device::op_tlb()
+{
+ // TLB x: transfer long banked
+ op_tl();
+ m_pc |= 0x400;
+}
+
+void mm78_device::op_tmlb()
+{
+ // TMLB x: transfer and mark long banked
+ op_tml();
+ m_pc |= 0x400;
+}
+
+void mm78_device::op_tab()
+{
+ // TAB: table look up transfer
+ m_skip_count = m_a + 1;
+ m_a = 0xf;
+}
+
+void mm78_device::op_ix()
+{
+ // IX: input to X from channel X(aka B)
+ m_x = (m_read_r() & m_r_output) >> 4 & 0xf;
+}
+
+void mm78_device::op_ox()
+{
+ // OX: output from X to channel X(aka B)
+ m_r_output = (m_r_output & 0xf) | m_x << 4;
+ m_write_r(m_r_output);
+}
+
+void mm78_device::op_ioa()
+{
+ // IOA: exchange A with channel A
+ u8 a = m_read_r() & m_r_output & 0xf;
+ m_r_output = (m_r_output & ~0xf) | m_a;
+ m_write_r(m_r_output);
+ m_a = a;
+}
+
+void mm78_device::op_i1sk()
+{
+ // I1SK: add channel 1 to A, skip on no overflow
+ m_a += m_read_p() & 0xf;
+ m_skip = !(m_a & 0x10);
+ m_a &= 0xf;
+}
+
+void mm78_device::op_int0h()
+{
+ // INT0H: skip on INT0 high
+ m_skip = bool(m_int_line[0]);
+}
+
+void mm78_device::op_int1l()
+{
+ // INT1L: skip on INT1 low
+ m_skip = !m_int_line[1];
+}
diff --git a/src/devices/cpu/pps41/pps41base.cpp b/src/devices/cpu/pps41/pps41base.cpp
new file mode 100644
index 00000000000..dc37d66bf5f
--- /dev/null
+++ b/src/devices/cpu/pps41/pps41base.cpp
@@ -0,0 +1,351 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell PPS-4/1 MCU cores
+
+This is the single-chip evolution of Rockwell's older PPS-4 CPU. It is similar,
+but a lot of things were simplified, the ALU instructions are less diverse.
+It also has a lot in common with Rockwell's previous MCU series (A/B5000).
+
+Part numbers:
+- A75xx = MM75 - 28 pin dip
+- A76xx = MM76 - 42 pin spider
+- A77xx = MM77 - 42 pin spider
+- A78xx = MM78 - 42 pin spider
+- A79xx = MM76C - 52 pin spider - high-speed counter
+- A??xx = MM76D - 52 pin spider - 12-bit ADC
+- A86xx = MM76E - 42 pin spider - extended ROM
+- B76xx = MM76L - 40 pin dip
+- B77xx = MM77L - 40 pin dip
+- B80xx = MM77LA? - 40 pin dip
+- B78xx = MM78L - 40 pin dip
+- B86xx = MM76EL - 40 pin dip
+- B90xx = MM78LA - 42 pin spider
+
+"spider" = 2 rows of pins on each side, just like standard PPS-4 CPUs.
+"L" main difference is low-power
+
+Internal clock is 4-phase (4 subcycles per 1-byte opcode), and when running
+from an external oscillator, it is divided by 2 first. It also has an internal
+oscillator which can be enabled with a resistor wired to VC.
+
+References:
+- Series MM76 Product Description
+- Series MM77 Product Description
+- MM76 Microcomputer Programming Manual
+- MM77 Microcomputer Programming Manual
+
+TODO:
+- add extended opcodes to disasm? it's easy to add there, but the emulation goes
+ through prefixes 1 cycle at the time which means the live disasm gets messy
+- documentation discourages long jumps to the subroutine pages, but does not
+ explain what would happen. Scrabble Sensor does it, so it's probably ok.
+- documentation discourages use of some extended opcodes when in subroutine pages,
+ but again does not explain why
+- allowed opcode after TAB should be limited
+- add MCU mask options, there's one for inverting interrupts
+- does MM78LA support interrupts? the sparse documentation available says it does
+- MM78LA mnemonics for changed opcodes is unknown
+- no known documentation exists for MM77LA, mcu name is guessed (maybe it was
+ designed in collaboration with Mattel, and later evolved into MM78LA)
+
+*/
+
+#include "emu.h"
+#include "pps41base.h"
+
+
+pps41_base_device::pps41_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data) :
+ cpu_device(mconfig, type, tag, owner, clock),
+ m_program_config("program", ENDIANNESS_LITTLE, 8, prgwidth, 0, program),
+ m_data_config("data", ENDIANNESS_LITTLE, 8, datawidth, 0, data),
+ m_prgwidth(prgwidth),
+ m_datawidth(datawidth),
+ m_opla(*this, "opla"),
+ m_read_p(*this, 0xff),
+ m_read_d(*this, 0),
+ m_write_d(*this),
+ m_read_r(*this, 0xff),
+ m_write_r(*this),
+ m_read_sdi(*this, 1),
+ m_write_sdo(*this),
+ m_write_ssc(*this),
+ m_write_spk(*this)
+{ }
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void pps41_base_device::device_start()
+{
+ m_program = &space(AS_PROGRAM);
+ m_data = &space(AS_DATA);
+ m_prgmask = (1 << m_prgwidth) - 1;
+ m_datamask = (1 << m_datawidth) - 1;
+
+ // init RAM with 0xf
+ for (int i = 0; i <= m_datamask; i++)
+ m_data->write_byte(i, 0xf);
+
+ // zerofill
+ m_pc = 0;
+ m_prev_pc = 0;
+ m_op = 0;
+ m_prev_op = 0;
+ m_prev2_op = 0;
+ m_prev3_op = 0;
+ memset(m_stack, 0, sizeof(m_stack));
+ m_stack_levels = 1;
+
+ m_a = 0;
+ m_b = 0;
+ m_prev_b = 0;
+ m_prev2_b = 0;
+ m_ram_addr = 0;
+ m_ram_delay = false;
+ m_sag = false;
+ m_c = 0;
+ m_prev_c = 0;
+ m_c_in = 0;
+ m_c_delay = false;
+ m_x = 0;
+ m_skip = false;
+ m_skip_count = 0;
+
+ m_s = 0;
+ m_sclock_in = 0;
+ m_sclock_count = 0;
+
+ set_d_pins(10);
+ m_d_output = 0;
+ set_r_pins(8);
+ m_r_output = 0;
+ m_int_line[0] = m_int_line[1] = 1; // GND = 1
+ m_int_ff[0] = m_int_ff[1] = 0;
+
+ // register for savestates
+ save_item(NAME(m_pc));
+ save_item(NAME(m_prev_pc));
+ save_item(NAME(m_op));
+ save_item(NAME(m_prev_op));
+ save_item(NAME(m_prev2_op));
+ save_item(NAME(m_prev3_op));
+ save_item(NAME(m_stack));
+
+ save_item(NAME(m_a));
+ save_item(NAME(m_b));
+ save_item(NAME(m_prev_b));
+ save_item(NAME(m_prev2_b));
+ save_item(NAME(m_ram_addr));
+ save_item(NAME(m_ram_delay));
+ save_item(NAME(m_sag));
+ save_item(NAME(m_c));
+ save_item(NAME(m_prev_c));
+ save_item(NAME(m_c_in));
+ save_item(NAME(m_c_delay));
+ save_item(NAME(m_x));
+ save_item(NAME(m_skip));
+ save_item(NAME(m_skip_count));
+
+ save_item(NAME(m_s));
+ save_item(NAME(m_sclock_in));
+ save_item(NAME(m_sclock_count));
+
+ save_item(NAME(m_d_output));
+ save_item(NAME(m_r_output));
+ save_item(NAME(m_int_line));
+ save_item(NAME(m_int_ff));
+
+ // register state for debugger
+ state_add(STATE_GENPC, "GENPC", m_pc).formatstr("%03X").noshow();
+ state_add(STATE_GENPCBASE, "CURPC", m_prev_pc).formatstr("%03X").noshow();
+
+ m_state_count = 0;
+ state_add(++m_state_count, "PC", m_pc).formatstr("%03X"); // 1
+ state_add(++m_state_count, "A", m_a).formatstr("%01X"); // 2
+ state_add(++m_state_count, "C", m_c_in).formatstr("%01X"); // 3
+ state_add(++m_state_count, "B", m_b).formatstr("%02X"); // 4
+ state_add(++m_state_count, "S", m_s).formatstr("%01X"); // 5
+
+ set_icountptr(m_icount);
+}
+
+device_memory_interface::space_config_vector pps41_base_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)
+ };
+}
+
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+
+void pps41_base_device::device_reset()
+{
+ m_op = m_prev_op = m_prev2_op = 0;
+ m_pc = m_prgmask >> 1 & ~0x3f;
+ m_skip = false;
+ m_skip_count = 0;
+
+ // clear outputs
+ m_write_r(m_r_output = m_r_mask);
+ m_write_d(m_d_output = 0);
+
+ m_s = 0;
+ m_sclock_count = 0;
+ m_write_sdo(0);
+ m_write_ssc(0);
+}
+
+
+//-------------------------------------------------
+// interrupt handling
+//-------------------------------------------------
+
+void pps41_base_device::execute_set_input(int line, int state)
+{
+ state = (state) ? 1 : 0;
+
+ switch (line)
+ {
+ case PPS41_INPUT_LINE_INT0:
+ // reset flip-flop on rising edge
+ if (state && !m_int_line[0])
+ m_int_ff[0] = 0;
+ m_int_line[0] = state;
+ break;
+
+ case PPS41_INPUT_LINE_INT1:
+ // reset flip-flop on falling edge
+ if (!state && m_int_line[1])
+ m_int_ff[1] = 0;
+ m_int_line[1] = state;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// serial i/o
+//-------------------------------------------------
+
+void pps41_base_device::ssc_w(int state)
+{
+ state = (state) ? 1 : 0;
+
+ // serial shift on falling edge
+ if (!state && m_sclock_in)
+ serial_shift(m_read_sdi());
+ m_sclock_in = state;
+}
+
+void pps41_base_device::serial_shift(int state)
+{
+ state = (state) ? 1 : 0;
+ m_s = (m_s << 1 | state) & 0xf;
+ m_write_sdo(BIT(m_s, 3));
+}
+
+void pps41_base_device::serial_clock()
+{
+ // internal serial clock cycle
+ int i = m_read_sdi();
+ m_sclock_count--;
+ m_write_ssc(m_sclock_count & 1);
+
+ if (~m_sclock_count & 1 && m_sclock_count < 8)
+ serial_shift(i);
+}
+
+
+//-------------------------------------------------
+// execute
+//-------------------------------------------------
+
+void pps41_base_device::cycle()
+{
+ m_icount--;
+
+ // clock serial i/o
+ if (m_sclock_count)
+ serial_clock();
+}
+
+void pps41_base_device::increment_pc()
+{
+ // low part is LFSR
+ int feed = ((m_pc & 0x3e) == 0) ? 1 : 0;
+ feed ^= (m_pc >> 1 ^ m_pc) & 1;
+ m_pc = (m_pc & ~0x3f) | (m_pc >> 1 & 0x1f) | (feed << 5);
+}
+
+void pps41_base_device::execute_run()
+{
+ while (m_icount > 0)
+ {
+ // remember previous state
+ m_prev3_op = m_prev2_op;
+ m_prev2_op = m_prev_op;
+ m_prev_op = m_op;
+ m_prev_pc = m_pc;
+
+ m_prev2_b = m_prev_b;
+ m_prev_b = m_b;
+ m_prev_c = m_c;
+
+ // fetch next opcode
+ if (!m_skip && !m_skip_count)
+ debugger_instruction_hook(m_pc);
+ m_op = m_program->read_byte(m_pc);
+ increment_pc();
+ cycle();
+
+ // handle opcode if it's not skipped
+ if (m_skip)
+ {
+ // still skip through prefix(es)
+ m_skip = op_is_tr(m_op);
+ m_op = 0; // fake nop
+ }
+ else if (m_skip_count)
+ {
+ m_skip_count--;
+
+ // restore opcode state
+ m_op = m_prev_op;
+ m_prev_op = m_prev2_op;
+ m_prev2_op = m_prev3_op;
+ }
+ else
+ execute_one();
+
+ // some opcodes delay RAM address(Bl part) adjustment for 1 cycle
+ m_ram_addr = m_b;
+
+ if (m_ram_delay)
+ {
+ m_ram_addr = (m_ram_addr & ~0xf) | (m_prev_b & 0xf);
+ m_ram_delay = false;
+ }
+
+ // SAG sets RAM address(Bu part) to 3 for the next cycle
+ if (m_sag)
+ {
+ m_ram_addr = (m_ram_addr & 0xf) | 0x30;
+ m_sag = false;
+ }
+
+ // and some opcodes delay carry adjustment for 1 cycle
+ m_c_in = m_c_delay ? m_prev_c : m_c;
+ m_c_delay = false;
+ }
+}
diff --git a/src/devices/cpu/pps41/pps41base.h b/src/devices/cpu/pps41/pps41base.h
new file mode 100644
index 00000000000..e97a8d39f33
--- /dev/null
+++ b/src/devices/cpu/pps41/pps41base.h
@@ -0,0 +1,154 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell PPS-4/1 MCU cores
+
+ Don't include this file, include the specific device header instead,
+ for example mm76.h
+
+*/
+
+#ifndef MAME_CPU_PPS41_PPS41BASE_H
+#define MAME_CPU_PPS41_PPS41BASE_H
+
+#pragma once
+
+#include "machine/pla.h"
+
+enum
+{
+ PPS41_INPUT_LINE_INT0 = 0,
+ PPS41_INPUT_LINE_INT1
+};
+
+
+class pps41_base_device : public cpu_device
+{
+public:
+ // configuration helpers
+ // I/O ports:
+
+ // 8-bit P(parallel) input
+ auto read_p() { return m_read_p.bind(); }
+
+ // 10/12-bit D(discrete) I/O
+ auto read_d() { return m_read_d.bind(); }
+ auto write_d() { return m_write_d.bind(); }
+
+ // 8/10/14-bit R I/O
+ auto read_r() { return m_read_r.bind(); }
+ auto write_r() { return m_write_r.bind(); }
+
+ // serial data/clock
+ auto read_sdi() { return m_read_sdi.bind(); }
+ auto write_sdo() { return m_write_sdo.bind(); }
+ auto write_ssc() { return m_write_ssc.bind(); }
+
+ // speaker output
+ auto write_spk() { return m_write_spk.bind(); }
+
+ // I/O access
+ u16 d_output_r() { return m_d_output; }
+ u16 r_output_r() { return m_r_output; }
+ int sdo_r() { return BIT(m_s, 3); }
+ void ssc_w(int state);
+
+protected:
+ // construction/destruction
+ pps41_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, int prgwidth, address_map_constructor program, int datawidth, address_map_constructor data);
+
+ // device-level overrides
+ virtual void device_start() override ATTR_COLD;
+ virtual void device_reset() override ATTR_COLD;
+
+ // device_execute_interface overrides
+ virtual u64 execute_clocks_to_cycles(u64 clocks) const noexcept override { return (clocks + 4 - 1) / 4; }
+ virtual u64 execute_cycles_to_clocks(u64 cycles) const noexcept override { return (cycles * 4); }
+ virtual u32 execute_min_cycles() const noexcept override { return 1; }
+ virtual u32 execute_max_cycles() const noexcept override { return 2; }
+ virtual void execute_set_input(int line, int state) override;
+ virtual void execute_run() override;
+ virtual void execute_one() = 0;
+
+ // device_memory_interface overrides
+ virtual space_config_vector memory_space_config() const override;
+
+ address_space_config m_program_config;
+ address_space_config m_data_config;
+ address_space *m_program;
+ address_space *m_data;
+
+ int m_icount;
+ int m_state_count;
+
+ // fixed settings or mask options
+ int m_prgwidth; // ROM/RAM address size
+ int m_datawidth; // "
+ u16 m_prgmask; // "
+ u16 m_datamask; // "
+
+ optional_device<pla_device> m_opla; // segment output PLA
+
+ // i/o handlers
+ devcb_read8 m_read_p;
+ devcb_read16 m_read_d;
+ devcb_write16 m_write_d;
+ devcb_read16 m_read_r;
+ devcb_write16 m_write_r;
+ devcb_read_line m_read_sdi;
+ devcb_write_line m_write_sdo;
+ devcb_write_line m_write_ssc;
+ devcb_write8 m_write_spk;
+
+ // internal state, regs
+ u16 m_pc;
+ u16 m_prev_pc;
+ u8 m_op;
+ u8 m_prev_op;
+ u8 m_prev2_op;
+ u8 m_prev3_op;
+ int m_stack_levels;
+ u16 m_stack[2]; // max 2
+
+ u8 m_a;
+ u8 m_b;
+ u8 m_prev_b;
+ u8 m_prev2_b;
+ u8 m_ram_addr;
+ bool m_ram_delay;
+ bool m_sag;
+ int m_c;
+ int m_prev_c;
+ int m_c_in;
+ bool m_c_delay;
+ u8 m_x;
+ bool m_skip;
+ int m_skip_count;
+
+ u8 m_s;
+ int m_sclock_in;
+ int m_sclock_count;
+
+ int m_d_pins;
+ u16 m_d_mask;
+ u16 m_d_output;
+ int m_r_pins;
+ u16 m_r_mask;
+ u16 m_r_output;
+ int m_int_line[2];
+ int m_int_ff[2];
+
+ // misc handlers
+ void set_d_pins(u8 p) { m_d_pins = p; m_d_mask = (1 << p) - 1; }
+ void set_r_pins(u8 p) { m_r_pins = p; m_r_mask = (1 << p) - 1; }
+ virtual bool op_is_tr(u8 op) = 0;
+
+ void serial_shift(int state);
+ void serial_clock();
+ virtual void cycle();
+ void increment_pc();
+};
+
+
+#endif // MAME_CPU_PPS41_PPS41BASE_H
diff --git a/src/devices/cpu/pps41/pps41d.cpp b/src/devices/cpu/pps41/pps41d.cpp
new file mode 100644
index 00000000000..70e1401f87a
--- /dev/null
+++ b/src/devices/cpu/pps41/pps41d.cpp
@@ -0,0 +1,210 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell PPS-4/1 disassembler
+
+*/
+
+#include "emu.h"
+#include "pps41d.h"
+
+// constructor
+
+pps41_common_disassembler::pps41_common_disassembler()
+{
+ // init lfsr pc lut
+ for (u32 i = 0, pc = 0; i < 0x40; i++)
+ {
+ m_l2r[i] = pc;
+ m_r2l[pc] = i;
+ pc = increment_pc(pc);
+ }
+}
+
+offs_t pps41_common_disassembler::increment_pc(offs_t pc)
+{
+ int feed = ((pc & 0x3e) == 0) ? 1 : 0;
+ feed ^= (pc >> 1 ^ pc) & 1;
+ return (pc & ~0x3f) | (pc >> 1 & 0x1f) | (feed << 5);
+}
+
+
+// common lookup tables
+
+enum pps41_common_disassembler::e_mnemonics : unsigned
+{
+ // MM76/shared
+ mILL /* 0! */,
+ mXAB, mLBA, mLB, mEOB2,
+ mSB, mRB, mSKBF,
+ mXAS, mLSA,
+ mL, mX, mXDSK, mXNSK,
+ mA, mAC, mACSK, mASK, mCOM, mRC, mSC, mSKNC, mLAI, mAISK,
+ mRT, mRTSK, mT, mNOP, mTL, mTM, mTML, mTR,
+ mSKMEA, mSKBEI, mSKAEI,
+ mSOS, mROS, mSKISL, mIBM, mOB, mIAM, mOA, mIOS, mI1, mI2C, mINT1H, mDIN1, mINT0L, mDIN0, mSEG1, mSEG2,
+
+ // MM78 differences
+ mINT0H, mINT1L, mSAG, mEOB3, mTAB,
+ mI1SK, mIX, mOX, mLXA, mXAX, mIOA,
+ mTLB, mTMLB
+};
+
+const char *const pps41_common_disassembler::s_name[] =
+{
+ // MM76
+ "?",
+ "XAB", "LBA", "LB", "EOB",
+ "SB", "RB", "SKBF",
+ "XAS", "LSA",
+ "L", "X", "XDSK", "XNSK",
+ "A", "AC", "ACSK", "ASK", "COM", "RC", "SC", "SKNC", "LAI", "AISK",
+ "RT", "RTSK", "T", "NOP", "TL", "TM", "TML", "TR",
+ "SKMEA", "SKBEI", "SKAEI",
+ "SOS", "ROS", "SKISL", "IBM", "OB", "IAM", "OA", "IOS", "I1", "I2C", "INT1H", "DIN1", "INT0L", "DIN0", "SEG1", "SEG2",
+
+ // MM78
+ "INT0H", "INT1L", "SAG", "EOB", "TAB",
+ "I1SK", "IX", "OX", "LXA", "XAX", "IOA",
+ "TLB", "TMLB"
+};
+
+// number of bits per opcode parameter
+// note: d4 means hex, d5 means inverted, d6 means extended, d7 means double extended
+const u8 pps41_common_disassembler::s_bits[] =
+{
+ // MM76
+ 0,
+ 0, 0, 4, 2,
+ 2, 2, 2,
+ 0, 0,
+ 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 4, 4,
+ 0, 0, 0x36, 0, 0x76, 0x36, 0x76, 0x34,
+ 0, 0x40, 0x60,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ // MM78
+ 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0xf6, 0xf6
+};
+
+const u32 pps41_common_disassembler::s_flags[] =
+{
+ // MM76
+ 0,
+ 0, 0, 0, 0,
+ 0, 0, STEP_COND,
+ 0, 0,
+ 0, 0, STEP_COND, STEP_COND,
+ 0, 0, STEP_COND, STEP_COND, 0, 0, 0, STEP_COND, 0, STEP_COND,
+ STEP_OUT, STEP_OUT, 0, 0, 0, STEP_OVER, STEP_OVER, 0,
+ STEP_COND, STEP_COND, STEP_COND,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, STEP_COND, STEP_COND, STEP_COND, STEP_COND, 0, 0,
+
+ // MM78
+ STEP_COND, STEP_COND, 0, 0, 0,
+ STEP_COND, 0, 0, 0, 0, 0,
+ 0, STEP_OVER
+};
+
+
+// common disasm
+
+offs_t pps41_common_disassembler::common_disasm(const u8 *lut_opmap, std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer &params)
+{
+ // get raw opcode
+ u8 op = opcodes.r8(pc);
+ u8 instr = lut_opmap[op];
+
+ // get parameter
+ u8 bits = s_bits[instr];
+ u16 mask = (1 << (bits & 0xf)) - 1;
+ bits &= 0xf0;
+
+ u16 param = op & mask;
+ if (bits & 0x20)
+ param ^= mask;
+
+ // memory bit opcodes are 1,2,3,4
+ if (instr == mSB || instr == mRB || instr == mSKBF)
+ param++;
+
+ // disassemble it
+ util::stream_format(stream, "%-8s", s_name[instr]);
+ if (mask > 0)
+ {
+ if (bits & 0x10)
+ util::stream_format(stream, (mask & ~0xf) ? "$%02X" : "$%X", param);
+ else
+ util::stream_format(stream, "%d", param);
+ }
+
+ return 1 | s_flags[instr] | SUPPORTED;
+}
+
+
+// MM76 disasm
+
+const u8 mm76_disassembler::mm76_opmap[0x100] =
+{
+// 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ mNOP, mSKNC, mRT, mRTSK, mINT0L,mINT1H,mDIN1, mDIN0, mSKBF, mSKBF, mSKBF, mSKBF, mSC, mRC, mSEG1, mSEG2, // 0
+ mSB, mSB, mSB, mSB, mRB, mRB, mRB, mRB, mOA, mOB, mIAM, mIBM, mEOB2, mEOB2, mEOB2, mEOB2, // 1
+ mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, // 2
+ mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, // 3
+
+ mAC, mACSK, mA, mASK, mLBA, mCOM, mXAB, mSKMEA,mILL, mILL, mI1, mI2C, mLSA, mIOS, mXAS, mILL, // 4
+ mL, mL, mL, mL, mXNSK, mXNSK, mXNSK, mXNSK, mX, mX, mX, mX, mXDSK, mXDSK, mXDSK, mXDSK, // 5
+ mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, // 6
+ mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, // 7
+
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // 8
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // 9
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // A
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // B
+
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, // C
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, // D
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, // E
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT // F
+};
+
+offs_t mm76_disassembler::disassemble(std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer &params)
+{
+ return common_disasm(mm76_opmap, stream, pc, opcodes, params);
+}
+
+
+// MM78 disasm
+
+const u8 mm78_disassembler::mm78_opmap[0x100] =
+{
+// 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ mNOP, mSKISL,mSKNC, mINT0H,mINT1L,mRC, mSC, mSAG, mEOB3, mEOB3, mEOB3, mEOB3, mEOB3, mEOB3, mEOB3, mEOB3, // 0
+ mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, mLB, // 1
+ mSB, mSB, mSB, mSB, mRB, mRB, mRB, mRB, mSKBF, mSKBF, mSKBF, mSKBF, mTAB, mIOS, mRTSK, mRT, // 2
+ mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, mTR, // 3
+
+ mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, mLAI, // 4
+ mL, mL, mL, mL, mXNSK, mXNSK, mXNSK, mXNSK, mXDSK, mXDSK, mXDSK, mXDSK, mX, mX, mX, mX, // 5
+ mI1SK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, mAISK, // 6
+ mSOS, mROS, mIX, mOX, mXAS, mLXA, mLBA, mCOM, mI2C, mXAX, mXAB, mIOA, mAC, mACSK, mA, mSKMEA,// 7
+
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // 8
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // 9
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // A
+ mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, mTM, // B
+
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, // C
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, // D
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, // E
+ mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT, mT // F
+};
+
+offs_t mm78_disassembler::disassemble(std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer &params)
+{
+ return common_disasm(mm78_opmap, stream, pc, opcodes, params);
+}
diff --git a/src/devices/cpu/pps41/pps41d.h b/src/devices/cpu/pps41/pps41d.h
new file mode 100644
index 00000000000..5d61b5ebf7b
--- /dev/null
+++ b/src/devices/cpu/pps41/pps41d.h
@@ -0,0 +1,66 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+ Rockwell PPS-4/1 disassembler
+
+*/
+
+#ifndef MAME_CPU_PPS41_PPS41D_H
+#define MAME_CPU_PPS41_PPS41D_H
+
+#pragma once
+
+
+class pps41_common_disassembler : public util::disasm_interface
+{
+public:
+ pps41_common_disassembler();
+ virtual ~pps41_common_disassembler() = default;
+
+ virtual u32 opcode_alignment() const override { return 1; }
+ virtual u32 interface_flags() const override { return NONLINEAR_PC | PAGED; }
+ virtual u32 page_address_bits() const override { return 6; }
+ virtual offs_t pc_linear_to_real(offs_t pc) const override { return (pc & ~0x3f) | m_l2r[pc & 0x3f]; }
+ virtual offs_t pc_real_to_linear(offs_t pc) const override { return (pc & ~0x3f) | m_r2l[pc & 0x3f]; }
+
+protected:
+ enum e_mnemonics : unsigned;
+ static const char *const s_name[];
+ static const u8 s_bits[];
+ static const u32 s_flags[];
+
+ u8 m_l2r[0x40];
+ u8 m_r2l[0x40];
+
+ offs_t increment_pc(offs_t pc);
+ offs_t common_disasm(const u8 *lut_opmap, std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer &params);
+};
+
+class mm76_disassembler : public pps41_common_disassembler
+{
+public:
+ mm76_disassembler() = default;
+ virtual ~mm76_disassembler() = default;
+
+ virtual offs_t disassemble(std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer &params) override;
+
+private:
+ static const u8 mm76_opmap[0x100];
+
+};
+
+class mm78_disassembler : public pps41_common_disassembler
+{
+public:
+ mm78_disassembler() = default;
+ virtual ~mm78_disassembler() = default;
+
+ virtual offs_t disassemble(std::ostream &stream, offs_t pc, const data_buffer &opcodes, const data_buffer &params) override;
+
+private:
+ static const u8 mm78_opmap[0x100];
+
+};
+
+#endif // MAME_CPU_PPS41_PPS41D_H