// license:BSD-3-Clause // copyright-holders:Juergen Buchmueller /***************************************************************************** * * pps4.c * * Rockwell Parallel Processing System (PPS-4) Microcomputer * * Introduced in 1972, the PPS-4 was a 4-bit PMOS CPU that ran at 256kHz. * The improved PPS-4/2, released in 1975, doubled the width of discrete * output and added an internal clock generator (intended for use with a * 3.579545MHz NTSC XTAL), but the latter could only manage 200kHz. The * chipset later evolved into the PPS-4/1 (MM76, MM78, etc.) series of * MCUs which Rockwell continued to produce through the early 1980s. * * Part numbers are 10660 (original), 11660 (PPS-4/2), 12660. * * List of memory chips: * 10432 RAM (256 x 4) * 10932 RAM (512 x 4) * A05XX ROM (1K x 8) * A52XX ROM (2K x 8) * A66XX ROM (4K x 8) * A88XX ROM (8K x 8) * A08XX ROM/RAM (704 x 8/72 x 4) * A07XX ROM/RAM (1K x 8/116 x 4) * A20XX ROM/RAM (1.5K x 8/128 x 4) * A17XX ROM/RAM + I/O (2K x 8/128 x 4/16 x 1) * A23XX ROM/RAM + I/O (1K x 8/128 x 4/16 x 1) * * List of support / peripheral chips: * 10706 4-phase clock generator * 10738 Bus interface * 11049 Interval timer * 10686 General purpose I/O * 10696 General purpose I/O * 10731 Telecommunications data interface * 10736 dot matrix printer controller * 10788 keyboard/display controller * 10789 printer controller * 10815 keyboard/printer controller * 10930 Serial data controller * 15380 dot matrix printer controller * 11696 Parallel input/output * * All of the above devices, except those providing 4-bit RAM, were also * compatible with the failed PPS-8 series of 8-bit PMOS CPUs. * * Pinouts: * 10660 11660 * * +--------\ /--------+ +--------\ /--------+ * 1 [| DIB-3 ++ DIA-3 |] 42 1 [| DIO-4 DIO-3 |] 42 * 2 [-| DIA-2 DIB-4 |-] 41 2 [-| DIA-4 DIO-2 |-] 41 * 3 [| DIB-2 DIA-4 |] 40 3 [| DIA-3 DIO-1 |] 40 * 4 [-| DIA-1 NC |-] 39 4 [-| DIA-2 Vdd |-] 39 * 5 [| DIB-1 A/B-1 |] 38 5 [| DIA-1 A/B-1 |] 38 * 6 [-| Vdd A/B-2 |-] 37 6 [-| I/O-5 A/B-2 |-] 37 * 7 [| I/D-5 A/B-3 |] 36 7 [| I/O-6 A/B-3 |] 36 * 8 [-| I/D-6 A/B-4 |-] 35 8 [-| I/O-7 A/B-4 |-] 35 * 9 [| I/D-7 A/B-5 |] 34 9 [| I/O-8 A/B-5 |] 34 * 10 [-| I/D-8 A/B-6 |-] 33 10 [-| I/O-1 A/B-6 |-] 33 * 11 [| I/D-1 A/B-7 |] 32 11 [| I/O-4 A/B-7 |] 32 * 12 [-| I/D-4 A/B-8 |-] 31 12 [-| I/O-2 A/B-8 |-] 31 * 13 [| I/D-2 A/B-9 |] 30 13 [| I/O-3 A/B-9 |] 30 * 14 [-| I/D-3 A/B-10 |-] 29 14 [-| W/IO A/B-10 |-] 29 * 15 [| W/IO A/B-11 |] 28 15 [| CLK ~B A/B-11 |] 28 * 16 [-| CLK ~B A/B-12 |-] 27 16 [-| CLK A A/B-12 |-] 27 * 17 [| CLK A NC |] 26 17 [| VCLK DO-4 |] 26 * 18 [-| PO DO-3 |-] 25 18 [-| Xtal1 DO-3 |-] 25 * 19 [| SPO DO-4 |] 24 19 [| Xtal2 DO-2 |] 24 * 20 [-| DO-2 NC |-] 23 20 [-| Vss DO-1 |-] 23 * 21 [| DO-1 Vss |] 22 21 [| SPO TC1-14 |] 22 * +--------------------+ +--------------------+ * *****************************************************************************/ #include "emu.h" #include "pps4.h" #include "pps4dasm.h" #include "debugger.h" #define VERBOSE 0 //!< set to 1 to log certain instruction conditions #include "logmacro.h" DEFINE_DEVICE_TYPE(PPS4, pps4_device, "pps4", "Rockwell PPS4-4") DEFINE_DEVICE_TYPE(PPS4_2, pps4_2_device, "pps4_2", "Rockwell PPS-4/2") pps4_device::pps4_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : cpu_device(mconfig, type, tag, owner, clock) , m_program_config("program", ENDIANNESS_LITTLE, 8, 12) , m_data_config("data", ENDIANNESS_LITTLE, 8, 12) // 4bit RAM , m_io_config("io", ENDIANNESS_LITTLE, 8, 8) // 4bit IO , m_dia_cb(*this) , m_dib_cb(*this) , m_do_cb(*this) { } pps4_device::pps4_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : pps4_device(mconfig, PPS4, tag, owner, clock) { } pps4_2_device::pps4_2_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : pps4_device(mconfig, PPS4_2, tag, owner, clock) { } device_memory_interface::space_config_vector pps4_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), std::make_pair(AS_IO, &m_io_config) }; } /** * @brief pps4_device::M Return the memory at address B * @return ROM/RAM(B) */ u8 pps4_device::M() { u8 ret = m_data->read_byte(m_B & ~m_SAG); m_SAG = 0; return ret; } /** * @brief pps4_device::W Write to the memory address at B * @return ROM/RAM(B) */ void pps4_device::W(u8 data) { m_data->write_byte(m_B & ~m_SAG, data); m_SAG = 0; } std::unique_ptr pps4_device::create_disassembler() { return std::make_unique(); } /** * @brief pps4_device::ROP Read the next opcode (instruction) * The previous opcode mask (upper four bits) is set from the * previous instruction. The new opcode is fetched and the * program counter is incremented. The icount is decremented. * @return m_I the next opcode */ inline u8 pps4_device::ROP() { const u8 op = m_direct->read_byte(m_P & 0xFFF); m_Ip = m_I1; // save previous opcode m_P = (m_P + 1) & 0xFFF; m_icount -= 1; return op; } /** * @brief pps4_device::ARG Read the next argument (instruction 2) * The byte at program counter is read from the unencrypted * direct space. The program count is incremented and the * icount is decremented. * @return m_I2 the next argument */ inline u8 pps4_device::ARG() { const u8 arg = m_direct->read_byte(m_P & 0xFFF); m_P = (m_P + 1) & 0xFFF; m_icount -= 1; return arg; } /** * @brief Note3 * * Instructions ADI, LD, EX, EXD, LDI, LB and LBL have a numeric * value coded as part of the instruction in the immediate field. * This numeric value must be in complementary form on the bus. * All of these immediate fields which are inverted are shown * in brackets. * For example: ADI 1, as written by the programmer who wishes * to add one to the value in the accumulator, is converted to * 0x6E = 01001 [1110]; the bracketed binary value is the value * as seen on the data bus. * If the programmer is using the Rockwell Assembler he does not * have to manually determine the proper inverted value as the * assembler does this for him. * * [And we do in MAME as well :-] */ /** * @brief pps4_device::iAD Add * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0b 0000 1011 1 AD * * Symbolic equation * ---------------------------------- * C, A <- A + M * * The result of the binary addition of contents of accumulator * and 4-bit contents of RAM currently addressed by B register, * replaces the contents of the accumulator. The resulting * carry-out is loaded into C flip-flop. */ void pps4_device::iAD() { m_A = m_A + M(); m_C = (m_A >> 4) & 1; m_A = m_A & 15; } /** * @brief pps4_device::iADC Add with carry-in * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0a 0000 1010 1 ADC * * Symbolic equation * ---------------------------------- * C, A <- A + M + C * * Same as AD except the C flip-flop serves as a carry-in * to the adder. */ void pps4_device::iADC() { m_A = m_A + M() + m_C; m_C = m_A >> 4; m_A = m_A & 15; } /** * @brief pps4_device::iADSK Add and skip if carry-out * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x09 0000 1001 1 ADSK * * Symbolic equation * ---------------------------------- * C, A <- A + M * Skip if C = 1 * * Same as AD except the next ROM word will be * skipped (ignored) if a carry-out is generated. */ void pps4_device::iADSK() { m_A = m_A + M(); m_C = m_A >> 4; m_Skip = m_C; m_A = m_A & 15; } /** * @brief pps4_device::iADCSK Add with carry-in and skip if carry-out * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x08 0000 1000 1 ADCSK * * Symbolic equation * ---------------------------------- * C, A <- A + M + C * Skip if C = 1 * * Same as ADC except the next ROM word will be * skipped (ignored) if a carry-out is generated. */ void pps4_device::iADCSK() { m_A = m_A + M() + m_C; m_C = m_A >> 4; m_Skip = m_C; m_A = m_A & 15; } /** * @brief pps4_device::iADI Add immediate * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x6* 0110 xxxx 1 ADI x * * Symbolic equation * ---------------------------------- * A <- A + [I(4:1)] * * The result of the binary addition of contents of * accumulator and 4-bit immediate field of instruction * word replaces the contents of accumulator. * The next ROM word will be skipped (ignored) if a * carry-out is generated. * __ The instruction does not use or change the C flip-flop. __ * The immediate field I(4:1) of this instruction may not * be equal to binary 0 (CYS) or 0101 (DC) * * See %Note3 */ void pps4_device::iADI() { const u8 imm = ~m_I1 & 15; m_A = m_A + imm; m_Skip = (m_A >> 4) & 1; m_A = m_A & 15; } /** * @brief pps4_device::iDC Decimal correction * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x65 0110 0101 1 DC * * Symbolic equation * ---------------------------------- * A <- A + 1010 * * Decimal correction of accumulator. * Binary 1010 is added to the contents of the accumulator. * Result is stored in accumulator. Instruction does not * use or change carry flip-flop or skip. */ void pps4_device::iDC() { m_A = m_A + 10; } /** * @brief pps4_device::iAND Logical AND * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0d 0000 1101 1 AND * * Symbolic equation * ---------------------------------- * A <- A & M * * The result of logical AND of accumulator and * 4-bit contents of RAM currently addressed by * B register replaces contents of accumulator. */ void pps4_device::iAND() { m_A = m_A & M(); } /** * @brief pps4_device::iOR Logical OR * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0f 0000 1111 1 OR * * Symbolic equation * ---------------------------------- * A <- A | M * * The result of logical OR of accumulator and * 4-bit contents of RAM currently addressed by * B register replaces contents of accumulator. */ void pps4_device::iOR() { m_A = m_A | M(); } /** * @brief pps4_device::iEOR Logical exclusive-OR * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0c 0000 1100 1 EOR * * Symbolic equation * ---------------------------------- * A <- A ^ M * * The result of logical exclusive-OR of * accumulator and 4-bit contents of RAM * currently addressed by B register * replaces contents of accumulator. */ void pps4_device::iEOR() { m_A = m_A ^ M(); } /** * @brief pps4_device::iCOMP Complement * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0e 0000 1110 1 COMP * * Symbolic equation * ---------------------------------- * A <- ~A * * Each bit of the accumulator is logically * complemented and placed in accumulator. */ void pps4_device::iCOMP() { m_A = m_A ^ 15; } /** * @brief pps4_device::iSC Set carry flip-flop * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x20 0010 0000 1 SC * * Symbolic equation * ---------------------------------- * C <- 1 * * The C flip-flop is set to 1. */ void pps4_device::iSC() { m_C = 1; } /** * @brief pps4_device::iRC Reset carry flip-flop * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x28 0010 0100 1 RC * * Symbolic equation * ---------------------------------- * C <- 0 * * The C flip-flop is set to 0. */ void pps4_device::iRC() { m_C = 0; } /** * @brief pps4_device::iSF1 Set flip-flop FF1 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x22 0010 0010 1 SF1 * * Symbolic equation * ---------------------------------- * FF1 <- 1 * * The Flip-flop FF1 is set to 1. */ void pps4_device::iSF1() { m_FF1 = 1; } /** * @brief pps4_device::iRF1 Reset flip-flop FF1 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x26 0010 0110 1 RF1 * * Symbolic equation * ---------------------------------- * FF1 <- 0 * * The Flip-flop FF1 is set to 0. */ void pps4_device::iRF1() { m_FF1 = 0; } /** * @brief pps4_device::iSF2 Set flip-flop FF2 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x21 0010 0001 1 SF2 * * Symbolic equation * ---------------------------------- * FF2 <- 1 * * The Flip-flop FF2 is set to 1. */ void pps4_device::iSF2() { m_FF2 = 1; } /** * @brief pps4_device::iRF2 Reset flip-flop FF2 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x25 0010 0101 1 RF2 * * Symbolic equation * ---------------------------------- * FF2 <- 0 * * The flip-flop FF2 is set to 0. */ void pps4_device::iRF2() { m_FF2 = 0; } /** * @brief pps4_device::iLD Load accumulator from memory * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x30+ 0011 0xxx 1 LD x * * Symbolic equation * ---------------------------------- * A <- M * B(7:5) <- B(7:5) ^ [I(3:1)] * * The 4-bit contents of RAM currently addressed * by B register are placed in the accumulator. * The RAM address in the B register is then * modified by the result of an exclusive-OR of * the 3-b it immediate field I(3:1) and B(7:5) * * See %Note3 */ void pps4_device::iLD() { const u16 i3c = ~m_I1 & 7; m_A = M(); m_B = m_B ^ (i3c << 4); } /** * @brief pps4_device::iEX Exchange accumulator and memory * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x38+ 0011 1xxx 1 EX x * * Symbolic equation * ---------------------------------- * A <-> M * B(7:5) <- B(7:5) ^ [I(3:1)] * * The same as LD except the contents of accumulator * are also placed in currently addressed RAM location. * * See %Note3 */ void pps4_device::iEX() { const u16 i3c = ~m_I1 & 7; const u8 mem = M(); W(m_A); m_A = mem; m_B = m_B ^ (i3c << 4); } /** * @brief pps4_device::iEXD Exchange accumulator and memory and decrement BL * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x28+ 0010 1xxx 1 EXD x * * Symbolic equation * ---------------------------------- * A <-> M * B(7:5) <- B(7:5) ^ [I(3:1)] * BL <- BL - 1 * Skip on BL = 1111b * * The same as EX except RAM address in B register * is further modified by decrementing BL by 1. * If the new contents of BL is 1111, the next * ROM word will be ignored. * * See %Note3 */ void pps4_device::iEXD() { const u8 i3c = ~m_I1 & 7; const u8 mem = M(); u8 bl = m_B & 15; W(m_A); m_A = mem; m_B = m_B ^ (i3c << 4); // if decrement BL wraps to 1111b if (0 == bl) { bl = 15; m_Skip = 1; } else { bl = bl - 1; } m_B = (m_B & ~15) | bl; } /** * @brief pps4_device::iLDI Load accumualtor immediate * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x7* 0111 xxxx 1 LDI x * * Symbolic equation * ---------------------------------- * A <- [I(4:1)] * * The 4-bit contents, immediate field I(4:1), * of the instruction are placed in the accumulator. * * Note: Only the first occurrence of an LDI in a consecutive * string of LDIs will be executed. The program will ignore * remaining LDIs and execute next valid instruction. * * See %Note3 */ void pps4_device::iLDI() { // previous LDI instruction? if (0x70 == (m_Ip & 0xf0)) { LOG("%s: skip prev:%02x op:%02x\n", __FUNCTION__, m_Ip, m_I1); return; } m_A = ~m_I1 & 15; } /** * @brief pps4_device::iLAX * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x12 0001 0010 1 LAX * * Symbolic equation * ---------------------------------- * A <- X * * The 4-bit contents of the X register are * placed in the accumulator. */ void pps4_device::iLAX() { m_A = m_X; } /** * @brief pps4_device::iLXA * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x1b 0001 1011 1 LXA * * Symbolic equation * ---------------------------------- * X <- A * * The contents of the accumulator are * tansferred to the X register. */ void pps4_device::iLXA() { m_X = m_A; } /** * @brief pps4_device::iLABL * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x11 0001 0001 1 LABL * * Symbolic equation * ---------------------------------- * A <- BL * * The contents of BL register are * tansferred to the accumulator. */ void pps4_device::iLABL() { m_A = m_B & 15; } /** * @brief pps4_device::iLBMX * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x10 0001 0000 1 LBMX * * Symbolic equation * ---------------------------------- * BM <- X * * The contents of X register are * tansferred to BM register. */ void pps4_device::iLBMX() { m_B = (m_B & ~(15 << 4)) | (m_X << 4); } /** * @brief pps4_device::iLBUA * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x08 0000 0100 1 LBUA * * Symbolic equation * ---------------------------------- * BU <- A * A <- M * * The contents of accumulator are tansferred to * BU register. Also, the contents of the currently * addressed RAM are transferred to accumulator. */ void pps4_device::iLBUA() { m_B = (m_B & ~(15 << 8)) | (m_A << 8); m_A = M(); } /** * @brief pps4_device::iXABL * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x19 0001 1001 1 XABL * * Symbolic equation * ---------------------------------- * A <-> BL * * The contents of accumulator and BL register * are exchanged. */ void pps4_device::iXABL() { // swap A and BL u8 bl = m_B & 15; m_B = (m_B & ~15) | m_A; m_A = bl; } /** * @brief pps4_device::iXMBX * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x18 0001 1000 1 XMBX * * Symbolic equation * ---------------------------------- * X <-> BM * * The contents of accumulator and BL register * are exchanged. */ void pps4_device::iXBMX() { // swap X and BM const u8 bm = (m_B >> 4) & 15; m_B = (m_B & ~(15 << 4)) | (m_X << 4); m_X = bm; } /** * @brief pps4_device::iXAX * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x1a 0001 1010 1 XAX * * Symbolic equation * ---------------------------------- * A <-> X * * The contents of accumulator and X register * are exchanged. */ void pps4_device::iXAX() { // swap A and X m_A ^= m_X; m_X ^= m_A; m_A ^= m_X; } /** * @brief pps4_device::iXS * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x06 0000 0110 1 XS * * Symbolic equation * ---------------------------------- * SA <-> SB * * The 12-bit contents of SA and SB register * are exchanged. */ void pps4_device::iXS() { // swap SA and SB m_SA ^= m_SB; m_SB ^= m_SA; m_SA ^= m_SB; } /** * @brief pps4_device::iCYS * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x6f 0110 1111 1 CYS * * Symbolic equation * ---------------------------------- * A <- SA(4:1) * SA(4:1) <- SA(8:5) * SA(8:5) <- SA(12:9) * SA(12:9) <- A * * A 4-bit right shift of the SA register takes place * with the four bits which are shifted off the end * of SA being transferred into the accumulator. * The contents of the accumulator are placed in the * left end of the SA register * */ void pps4_device::iCYS() { const u16 sa = (m_SA >> 4) | (m_A << 8); m_A = m_SA & 15; m_SA = sa; } /** * @brief pps4_device::iLB Load B indirect * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0xc* 1100 xxxx 2 LB x * * Symbolic equation * ---------------------------------- * SB <- SA, SA <- P * P(12:5) <- 0000 1100 * P(4:1) <- I(4:1) * * BU <- 0000 * B(8:1) <- [I2(8:1)] * P <- SA, SA <-> SB * * Sixteen consecutive locations on ROM page 3 (I2) contain * data which can be loaded into the eight least significant * bits of the B register by use of any LB instruction. * The four most significant bits of B register will be loaded * with zeros. The contents of the SB register will be destroyed. * This instruction takes two cycles to execute but occupies * only one ROM word. (Automatic return) * * Only the first occurrence of an LB or LBL instruction in a * consecutive string of LB or LBL will be executed. The * program will ignore the remaining LB or LBL and execute * the next valid instruction. Within subroutines the LB * instruction must be used with caution because the contents * of SB have been modified. * * See %Note3 and %Note4 */ void pps4_device::iLB() { // previous LB or LBL instruction? if (0xc0 == (m_Ip & 0xf0) || 0x00 == m_Ip) { LOG("%s: skip prev:%02x op:%02x\n", __FUNCTION__, m_Ip, m_I1); return; } m_SB = m_SA; m_SA = (m_P + 1) & 0xFFF; m_P = (3 << 6) | (m_I1 & 15); m_B = ~ARG() & 255; m_P = m_SA; // swap SA and SB m_SA ^= m_SB; m_SB ^= m_SA; m_SA ^= m_SB; } /** * @brief pps4_device::iLBL Load B long * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x00 0000 0000 2 LBL * * Symbolic equation * ---------------------------------- * BU <- 0000 * B(8:1) <- [I2(8:1)] * * This instruction occupies two ROM words, the second of * which will be loaded into the eight least significant * bits of the B register. The four most significant bits * of B (BU) will be loaded with zeroes. * * Only the first occurrence of an LB or LBL instruction in a * consecutive string of LB or LBL will be executed. The * program will ignore the remaining LB or LBL and execute * the next valid instruction. * * See %Note3 */ void pps4_device::iLBL() { m_I2 = ARG(); // previous LB or LBL instruction? if (0xc0 == (m_Ip & 0xf0) || 0x00 == m_Ip) { LOG("%s: skip prev:%02x op:%02x\n", __FUNCTION__, m_Ip, m_I1); return; } m_B = ~m_I2 & 255; // Note: immediate is 1's complement } /** * @brief pps4_device::INCB Increment B lower, skip if 0000 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x17 0001 0111 1 INCB * * Symbolic equation * ---------------------------------- * BL <- BL + 1 * Skip on BL = 0000 * * BL register (least significant four bits of B register) * is incremented by 1. If the new contents of BL is 0000b, * then the next ROM word will be ignored. */ void pps4_device::iINCB() { u8 bl = m_B & 15; bl = (bl + 1) & 15; if (0 == bl) { LOG("%s: skip BL=%x\n", __FUNCTION__, bl); m_Skip = 1; } m_B = (m_B & ~15) | bl; } /** * @brief pps4_device::iDECB Decrement B lower, skip if 1111 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x1f 0001 1111 1 DECB * * Symbolic equation * ---------------------------------- * BL <- BL - 1 * Skip on BL = 1111 * * BL register is decremented by 1. If the new * contents of BL is 1111b, then the next ROM * word will be ignored. */ void pps4_device::iDECB() { u8 bl = m_B & 15; bl = (bl - 1) & 15; if (15 == bl) { LOG("%s: skip BL=%x\n", __FUNCTION__, bl); m_Skip = 1; } m_B = (m_B & ~15) | bl; } /** * @brief pps4_device::iT Transfer * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x80+ 10xx xxxx 1 T *xx * * Symbolic equation * ---------------------------------- * P(6:1) <- I(6:1) * * An unconditional transfer to a ROM word on the current * page takes place. The least significant 6-bits of P * register P(6:1) are replaced by six bit immediate * field I(6:1) */ void pps4_device::iT() { const u16 p = (m_P & ~63) | (m_I1 & 63); LOG("%s: P=%03x I=%02x -> P=%03x\n", __FUNCTION__, m_P, m_I1, p); m_P = p; } /** * @brief pps4_device::iTM Transfer and mark indirect * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0xc0+ 11xx xxxx 2 TM x * yyyy yyyy from page 3 * * Symbolic equation * ---------------------------------- * SB <- SA, SA <- P * P(12:7) <- 000011 * P(6:1) <- I1(6:1) * * P(12:9) <- 0001 * P(8:1) <- I2(8:1) * * 48 consecutive locations on ROM page 3 contains pointer data * which indentify subroutine entry addresses. These subroutine * entry addresses are limited to pages 4 through 7. This TM * instruction will save the address of the next ROM word in * the SA register after loading the original contents of SA * into SB. A transfer then occurs to one of the subroutine * entry addresses. This instruction occupies one ROM word * but takes two cycles for execution. */ void pps4_device::iTM() { m_SB = m_SA; m_SA = m_P; m_P = (3 << 6) | (m_I1 & 63); m_I2 = ARG(); m_P = (1 << 8) | m_I2; } /** * @brief pps4_device::iTL Transfer long * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x5x 0101 xxxx 2 TL xyy * yyyy yyyy * * Symbolic equation * ---------------------------------- * P(12:9) <- I1(4:1) * P(8:1) <- I2(8:1) * * The instruction executes a transfer to any ROM word on any * page. It occupies two ROM words an requires two cycles for * execution. The first byte loads P(12:9) with field I1(4:1) * and then the second byte I2(8:1) is placed in P(8:1). */ void pps4_device::iTL() { m_I2 = ARG(); m_P = ((m_I1 & 15) << 8) | m_I2; } /** * @brief pps4_device::iTML Transfer and mark long * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x0* 0000 xxxx 2 TML xyy * yyyy yyyy * * Symbolic equation * ---------------------------------- * SB <- SA, SA <- P * P(12:9) <- I1(4:1) * P(8:1) <- I2(8:1) * * Note I1(2:1) != 00 * * This instruction executes a transfer and mark to any * location on ROM pages 4 through 15. It occupies two * ROM words and requires two cycle times for execution. */ void pps4_device::iTML() { m_I2 = ARG(); m_SB = m_SA; m_SA = m_P; m_P = ((m_I1 & 15) << 8) | m_I2; } /** * @brief pps4_device::iSKC Skip on carry flip-flop * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x15 0001 0101 1 SKC * * Symbolic equation * ---------------------------------- * Skip if C = 1 * * The next ROM word will be ignored if C flip-flop is 1. */ void pps4_device::iSKC() { m_Skip = m_C; } /** * @brief pps4_device::iSKC Skip on carry flip-flop * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x1e 0001 1110 1 SKZ * * Symbolic equation * ---------------------------------- * Skip if A = 0 * * The next ROM word will be ignored if C flip-flop is 1. */ void pps4_device::iSKZ() { m_Skip = (0 == m_A) ? 1 : 0; } /** * @brief pps4_device::iSKBI Skip if BL equal to immediate * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x4* 0100 xxxx 1 SKBI x * * Symbolic equation * ---------------------------------- * Skip if BL = I(4:1) * * The next ROM word will be ignored if the least significant * four bits of B register (BL) is equal to the 4-bit immediate * field I(4:1) of instruction. */ void pps4_device::iSKBI() { const u8 i4 = m_I1 & 15; const u8 bl = m_B & 15; m_Skip = bl == i4 ? 1 : 0; } /** * @brief pps4_device::iSKF1 Skip if FF1 equals 1 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x16 0001 0110 1 SKF1 * * Symbolic equation * ---------------------------------- * Skip if FF1 = 1 */ void pps4_device::iSKF1() { m_Skip = m_FF1; } /** * @brief pps4_device::iSKF2 Skip if FF2 equals 1 * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x14 0001 0100 1 SKF2 * * Symbolic equation * ---------------------------------- * Skip if FF2 = 1 */ void pps4_device::iSKF2() { m_Skip = m_FF2; } /** * @brief pps4_device::iRTN Return * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x05 0000 0101 1 RTN * * Symbolic equation * ---------------------------------- * P <- SA, SA <-> SB * * This instruction executes a return from subroutine * by loading contents of SA register into P register * and interchanges the SB and SA registers. */ void pps4_device::iRTN() { m_P = m_SA & 0xFFF; // swap SA and SB m_SA ^= m_SB; m_SB ^= m_SA; m_SA ^= m_SB; } /** * @brief pps4_device::iRTNSK Return and skip * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x07 0000 0111 1 RTNSK * * Symbolic equation * ---------------------------------- * P <- SA, SA <-> SB * P <- P + 1 * * Same as RTN except the first ROM word encountered * after the return from subroutine is skipped. */ void pps4_device::iRTNSK() { m_P = m_SA & 0xFFF; // swap SA and SB m_SA ^= m_SB; m_SB ^= m_SA; m_SA ^= m_SB; m_Skip = 1; // next opcode is ignored } /** * @brief pps4_device::IOL Input / Output Long * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x1c 0001 1100 2 IOL yy * yyyy yyyy * * Symbolic equation * ---------------------------------- * ~A -> Data Bus * A <- ~Data Bus * I2 -> I/O device * * This instruction occupies two ROM words and requires two * cycles for execution. The first ROM word is received by * the CPU and sets up the I/O enable signal. The second * ROM word is then received by the I/O devices and decoded * for address and command. The contents of the accumulator * inverted are placed on the data lines [I/D:4-1] for * acceptance by the I/O. At the same time, input data * received by the I/O device [on I/D:8-5] is transferred * to the accumulator inverted. * * The RAM address register (B) is placed on the address bus * during the I/O request cycle. The original RAM chip ignores * this and leaves the data bus alone at this time, but the * A17xx uses the value of BL to address one of the sixteen * input/output lines. */ void pps4_device::iIOL() { u8 ac = (~m_A & 15); m_I2 = ARG(); m_io->write_byte(m_I2, ac); LOG("%s: port:%02x <- %x\n", __FUNCTION__, m_I2, ac); ac = m_io->read_byte(m_I2) & 15; LOG("%s: port:%02x -> %x\n", __FUNCTION__, m_I2, ac); m_A = ~ac & 15; } /** * @brief pps4_device::iDIA Discrete input group A * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x27 0010 0111 1 DIA * * Symbolic equation * ---------------------------------- * A <- DIA * * Data at the inputs to discrete group A is * transferred to the accumulator. */ void pps4_device::iDIA() { m_A = m_dia_cb() & 15; } /** * @brief pps4_device::iDIB Discrete input group B * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x23 0010 0011 1 DIB * * Symbolic equation * ---------------------------------- * A <- DIB * * Data at the inputs to discrete group B is * transferred to the accumulator. */ void pps4_device::iDIB() { m_A = m_dib_cb() & 15; } void pps4_2_device::iDIB() { // PPS-4/2 can write zeros onto bidirectional DIO pins to mask open-drain inputs m_A = m_dib_cb() & m_DIO; } /** * @brief pps4_device::iDOA Discrete output * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x1d 0001 1101 1 DOA * * Symbolic equation * ---------------------------------- * DOA <- A * * The contents of the accumulator are transferred * to the discrete output register. */ void pps4_device::iDOA() { m_do_cb(m_A); } void pps4_2_device::iDOA() { // DOA also transfers contents of X to DIO on PPS-4/2 m_DIO = m_X; m_do_cb(m_A | (m_X << 4)); } /** * @brief pps4_device::iSAG Special address generation * HEX BINARY CYCLES MNEMONIC * ---------------------------------- * 0x2d 0001 0011 1 SAG * * Symbolic equation * ---------------------------------- * A/B Bus (12:5) <- 0000 0000 * A/B Bus (4:1) <- BL(4:1) * Contents of B remains unchanged * * The instruction causes the eight most significant bits * of the RAM address output to be zeroed during the next * cycle only. Note that this instruction does not alter * the contents of the B register. */ void pps4_device::iSAG() { // mask bits 12:5 on next memory access m_SAG = 0xff0; } /*************************************************************************** COMMON EXECUTION ***************************************************************************/ void pps4_device::execute_one() { m_I1 = ROP(); if (m_Skip) { m_Skip = 0; LOG("%s: skip op:%02x\n", __FUNCTION__, m_I1); return; } switch (m_I1) { case 0x00: iLBL(); break; case 0x01: iTML(); break; case 0x02: iTML(); break; case 0x03: iTML(); break; case 0x04: iLBUA(); break; case 0x05: iRTN(); break; case 0x06: iXS(); break; case 0x07: iRTNSK(); break; case 0x08: iADCSK(); break; case 0x09: iADSK(); break; case 0x0a: iADC(); break; case 0x0b: iAD(); break; case 0x0c: iEOR(); break; case 0x0d: iAND(); break; case 0x0e: iCOMP(); break; case 0x0f: iOR(); break; case 0x10: iLBMX(); break; case 0x11: iLABL(); break; case 0x12: iLAX(); break; case 0x13: iSAG(); break; case 0x14: iSKF2(); break; case 0x15: iSKC(); break; case 0x16: iSKF1(); break; case 0x17: iINCB(); break; case 0x18: iXBMX(); break; case 0x19: iXABL(); break; case 0x1a: iXAX(); break; case 0x1b: iLXA(); break; case 0x1c: iIOL(); break; case 0x1d: iDOA(); break; case 0x1e: iSKZ(); break; case 0x1f: iDECB(); break; case 0x20: iSC(); break; case 0x21: iSF2(); break; case 0x22: iSF1(); break; case 0x23: iDIB(); break; case 0x24: iRC(); break; case 0x25: iRF2(); break; case 0x26: iRF1(); break; case 0x27: iDIA(); break; case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: iEXD(); break; case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: iLD(); break; case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: iEX(); break; case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: iSKBI(); break; case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: iTL(); break; case 0x65: iDC(); break; case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: iADI(); break; case 0x6f: iCYS(); break; case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: iLDI(); break; case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf: case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf: iT(); break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: iLB(); break; default: iTM(); } } void pps4_device::execute_run() { do { debugger_instruction_hook(this, m_P); execute_one(); } while (m_icount > 0); } /*************************************************************************** CORE INITIALIZATION ***************************************************************************/ void pps4_device::device_start() { m_program = &space(AS_PROGRAM); m_direct = m_program->direct<0>(); m_data = &space(AS_DATA); m_io = &space(AS_IO); save_item(NAME(m_A)); save_item(NAME(m_X)); save_item(NAME(m_P)); save_item(NAME(m_SA)); save_item(NAME(m_SB)); save_item(NAME(m_Skip)); save_item(NAME(m_SAG)); save_item(NAME(m_B)); save_item(NAME(m_C)); save_item(NAME(m_FF1)); save_item(NAME(m_FF2)); save_item(NAME(m_I1)); save_item(NAME(m_I2)); save_item(NAME(m_Ip)); state_add( PPS4_PC, "PC", m_P ).mask(0xFFF).formatstr("%03X"); state_add( PPS4_A, "A", m_A ).formatstr("%01X"); state_add( PPS4_X, "X", m_X ).formatstr("%01X"); state_add( PPS4_SA, "SA", m_SA ).formatstr("%03X"); state_add( PPS4_SB, "SB", m_SB ).formatstr("%03X"); state_add( PPS4_Skip, "Skip", m_Skip ).formatstr("%01X"); state_add( PPS4_SAG, "SAG", m_SAG ).formatstr("%03X"); state_add( PPS4_B, "B", m_B ).formatstr("%03X"); state_add( PPS4_I1, "I1", m_I1 ).formatstr("%02X").noshow(); state_add( PPS4_I2, "I2", m_I2 ).formatstr("%02X").noshow(); state_add( PPS4_Ip, "Ip", m_Ip ).formatstr("%02X").noshow(); state_add( STATE_GENPC, "GENPC", m_P ).noshow(); state_add( STATE_GENPCBASE,"CURPC", m_P ).noshow(); state_add( STATE_GENFLAGS, "GENFLAGS", m_C).formatstr("%3s").noshow(); m_icountptr = &m_icount; m_dia_cb.resolve_safe(0); m_dib_cb.resolve_safe(0); m_do_cb.resolve_safe(); } void pps4_2_device::device_start() { pps4_device::device_start(); save_item(NAME(m_DIO)); } void pps4_device::state_string_export(const device_state_entry &entry, std::string &str) const { switch (entry.index()) { case STATE_GENFLAGS: str = string_format("%c%c%c", m_C ? 'C':'.', m_FF1 ? '1':'.', m_FF2 ? '2':'.'); break; } } /*************************************************************************** COMMON RESET ***************************************************************************/ void pps4_device::device_reset() { m_A = 0; // Accumulator A(4:1) m_X = 0; // X register X(4:1) m_P = 0; // program counter P(12:1) m_SA = 0; // Shift register SA(12:1) m_SB = 0; // Shift register SB(12:1) m_SAG = 0; // Special address generation mask m_B = 0; // B address register B(12:1) (BL, BM and BU) m_C = 0; // Carry flip-flop m_FF1 = 0; // Flip-flop 1 m_FF2 = 0; // Flip-flop 2 m_I1 = 0; // Most recent instruction I(8:1) m_I2 = 0; // Most recent parameter I2(8:1) m_Ip = 0; // Previous instruction I(8:1) } void pps4_2_device::device_reset() { pps4_device::device_reset(); m_DIO = 15; // DIO clamp } READ16_MEMBER(pps4_device::address_bus_r) { if (&space == m_io || &space == m_data) return m_B; else if (&space == m_program) return m_P; else return 0; }