diff options
Diffstat (limited to 'src/devices/cpu/pps41/pps41base.cpp')
-rw-r--r-- | src/devices/cpu/pps41/pps41base.cpp | 351 |
1 files changed, 351 insertions, 0 deletions
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; + } +} |