// license:BSD-3-Clause // copyright-holders:Michael Zapf /* Texas Instruments TMS9900 +--------------------+ V_BB | 1 o 64| /HOLD V_CC | 2 63| /MEMEN WAIT | 3 62| READY /LOAD | 4 61| /WE HOLDA | 5 60| CRUCLK /RESET | 6 59| V_CC IAQ | 7 58| - PHI1 | 8 57| - PHI2 | 9 56| D15 -+ LSB LSB +- A14 |10 55| D14 | | A13 |11 54| D13 | | A12 |12 53| D12 | | A11 |13 52| D11 | Address | A10 |14 +--------+ 51| D10 | Data bus | A9 |15 | | 50| D9 | bus 32K * | A8 |16 | | 49| D8 | 16 bit 16bit | A7 |17 | | 48| D7 | | A6 |18 | | 47| D6 | | A5 |19 +--------+ 46| D5 | | A4 |20 45| D4 | | A3 |21 44| D3 | | A2 |22 43| D2 | | A1 |23 42| D1 | MSB +- A0 |24 41| D0 -+ MSB PHI4 |25 40| V_SS V_SS |26 39| - V_DD |27 38| - PHI3 |28 37| - DBIN |29 36| IC0 -+ MSB CRUOUT |30 35| IC1 | Interrupt CRUIN |31 34| IC2 | level /INTREQ |32 33| IC3 -+ LSB +--------------------+ WAIT out Processor in wait state /LOAD in Non-maskable interrupt HOLDA out Hold acknowledge /RESET in Reset IAQ out Instruction acquisition PHI1-4 in Clock phase inputs DBIN out Data bus in input mode CRUOUT out Communication register unit data output CRUIN in Communication register unit data input /INTREQ in Interrupt request CRUCLK out Communication register unit clock output /WE out Data available for memory write READY in Memory ready for access /MEMEN out Address bus contains memory address /HOLD in External device acquires address and data bus lines V_BB -5V supply V_CC +5V supply (pins 2 and 59 connected in parallel) V_DD +12V supply V_SS 0V Ground reference (pins 26 and 40 connected in parallel) A0-A14 out Address bus (32768 words of 16 bit width) D0-A15 i/o Data bus IC0-IC3 in Interrupt level (0-15) Note that Texas Instruments' bit numberings define bit 0 as the most significant bit (different to most other systems). Also, the system uses big-endian memory organisation: Storing the word 0x1234 at address 0x0000 means that the byte 0x12 is stored at 0x0000 and byte 0x34 is stored at 0x0001. The processor also knows byte-oriented operations (like add byte (AB), move byte (MOVB)). This makes it necessary for the CPU to read the word from the target memory location first, change the respective byte, and write it back. See the TI-99/4A driver for an application of the TMS9900 processor within an 8-bit data bus board layout (using a data bus multiplexer). Subcycle handling In this implementation we try to emulate the internal operations as precisely as possible, following the technical specifications. We need not try to be clock-precise with every tick; it suffices to perform the proper number of operations within a given time span. For each command the CPU executes a microprogram which requires some amount of cycles to complete. During this time the external clock continues to issue pulses which can be used to control wait state creation. As we do not emulate external clocks this implementation offers an extra output "clock_out" (which, however, is available for the TMS9995) which pulses at a rate of 3 MHz. External devices (e.g. memory controllers) may count the pulses and pull down the READY line (with set_ready) as needed. Another possibility for creating wait states is to pull down the line for some time set by a timer. This is done, for example, by circuits like GROMs or speech synthesis processors (TMS52xx). TODO: - Fine-tune cycles - State save - HOLD state should be tested; I don't have test cases yet Michael Zapf, June 2012 */ #include "tms9900.h" /* tms9900 ST register bits. */ enum { ST_LH = 0x8000, // Logical higher (unsigned comparison) ST_AGT = 0x4000, // Arithmetical greater than (signed comparison) ST_EQ = 0x2000, // Equal ST_C = 0x1000, // Carry ST_OV = 0x0800, // Overflow (when using signed operations) ST_OP = 0x0400, // Odd parity (used with byte operations) ST_X = 0x0200, // XOP ST_IM = 0x000f // Interrupt mask }; /* The following defines can be set to 0 or 1 to disable or enable certain output in the log. */ // Emulation setup #define TRACE_SETUP 0 // Emulation details #define TRACE_EMU 0 // Location and command #define TRACE_EXEC 0 // Memory operation #define TRACE_MEM 0 // Address bus operation #define TRACE_ADDRESSBUS 0 // Cycle count #define TRACE_CYCLES 0 // Clock ticks #define TRACE_CLOCK 0 // Wait states #define TRACE_WAIT 0 // Interrupts #define TRACE_INT 0 // CRU operation #define TRACE_CRU 0 // Status register #define TRACE_STATUS 0 // ALU details #define TRACE_ALU 0 // Microinstruction level #define TRACE_MICRO 0 /**************************************************************************** Common constructor for TMS9900 and TMS9980A The CRU mask is related to the bits, not to their addresses which are twice their number. Accordingly, the TMS9900 has a CRU bitmask 0x0fff. ****************************************************************************/ tms99xx_device::tms99xx_device(const machine_config &mconfig, device_type type, std::string name, std::string tag, int databus_width, int prg_addr_bits, int cru_addr_bits, device_t *owner, UINT32 clock, std::string shortname, std::string source) : cpu_device(mconfig, type, name, tag, owner, clock, shortname, source), m_program_config("program", ENDIANNESS_BIG, databus_width, prg_addr_bits), m_io_config("cru", ENDIANNESS_BIG, 8, cru_addr_bits), m_prgspace(nullptr), m_cru(nullptr), m_prgaddr_mask((1<> 1) & 0x7fff; } } str.assign(flags); } /**************************************************************************/ UINT16 tms99xx_device::read_workspace_register_debug(int reg) { int temp = m_icount; m_prgspace->set_debugger_access(true); UINT16 value = m_prgspace->read_word((WP+(reg<<1)) & m_prgaddr_mask & 0xfffe); m_prgspace->set_debugger_access(false); m_icount = temp; return value; } void tms99xx_device::write_workspace_register_debug(int reg, UINT16 data) { int temp = m_icount; m_prgspace->set_debugger_access(true); m_prgspace->write_word((WP+(reg<<1)) & m_prgaddr_mask & 0xfffe, data); m_prgspace->set_debugger_access(false); m_icount = temp; } const address_space_config *tms99xx_device::memory_space_config(address_spacenum spacenum) const { switch (spacenum) { case AS_PROGRAM: return &m_program_config; case AS_IO: return &m_io_config; default: return nullptr; } } /************************************************************************** Microprograms for the CPU instructions The actions which are specific to the respective instruction are invoked by repeated calls of ALU_xxx; each call increases a state variable so that on the next call, the next part can be processed. This saves us a lot of additional functions. **************************************************************************/ /* Define the indices for the micro-operation table. This is done for the sake of a simpler microprogram definition as an UINT8[]. */ enum { IAQ = 0, MEMORY_READ, MEMORY_WRITE, REG_READ, REG_WRITE, CRU_INPUT, CRU_OUTPUT, DATA_DERIVE, RET, ABORT, END, ALU_NOP, ALU_CLR, ALU_SETADDR, ALU_ADDONE, ALU_SETADDR_ADDONE, ALU_PCADDR_ADVANCE, ALU_SOURCE, ALU_ADDREG, ALU_IMM, ALU_REG, ALU_F1, ALU_COMP, ALU_F3, ALU_MPY, ALU_DIV, ALU_XOP, ALU_CLR_SWPB, ALU_ABS, ALU_X, ALU_B, ALU_BLWP, ALU_LDCR, ALU_STCR, ALU_SBZ_SBO, ALU_TB, ALU_JMP, ALU_SHIFT, ALU_AI_ORI, ALU_CI, ALU_LI, ALU_LWPI, ALU_LIMI, ALU_STWP_STST, ALU_EXT, ALU_RTWP, ALU_INT }; #define MICROPROGRAM(_MP) \ static const UINT8 _MP[] = /* This is a kind of subroutine with 6 variants. Might be done in countless better ways, but will suffice for now. Each variant has at most 8 steps RET will return to the caller. The padding simplifies the calculation of the start address: We just take the Ts field as an index. In the last two cases we add an offset of 8 if we have an indexed (resp. a byte) operation. */ MICROPROGRAM(data_derivation) { REG_READ, RET, 0, 0, 0, 0, 0, 0, // Rx (00) 0, 0, 0, 0, 0, 0, 0, 0, REG_READ, ALU_SETADDR, MEMORY_READ, RET, 0, 0, 0, 0, // *Rx (01) 0, 0, 0, 0, 0, 0, 0, 0, ALU_CLR, ALU_PCADDR_ADVANCE, MEMORY_READ, ALU_ADDREG, MEMORY_READ, RET, 0, 0, // @sym (10) REG_READ, ALU_PCADDR_ADVANCE, MEMORY_READ, ALU_ADDREG, MEMORY_READ, RET, 0, 0, // @sym(Rx) (10) REG_READ, ALU_SETADDR_ADDONE, ALU_ADDONE, REG_WRITE, MEMORY_READ, RET, 0, 0, // *Rx+ (word) (11) REG_READ, ALU_SETADDR_ADDONE, REG_WRITE, MEMORY_READ, RET, 0, 0, 0 // *Rx+ (byte) (11) }; MICROPROGRAM(f1_mp) { ALU_NOP, DATA_DERIVE, ALU_SOURCE, // Store the word DATA_DERIVE, ALU_F1, MEMORY_WRITE, END }; MICROPROGRAM(comp_mp) { ALU_NOP, DATA_DERIVE, ALU_SOURCE, DATA_DERIVE, ALU_COMP, ALU_NOP, // Compare operations do not write back any data END }; MICROPROGRAM(f3_mp) { ALU_NOP, DATA_DERIVE, ALU_F3, MEMORY_READ, // We have to distinguish this from the C/CB microprogram above ALU_F3, ALU_NOP, // Compare operations do not write back any data END }; MICROPROGRAM(xor_mp) { ALU_NOP, DATA_DERIVE, ALU_F3, MEMORY_READ, ALU_F3, MEMORY_WRITE, // XOR again must write back data, cannot reuse f3_mp END }; MICROPROGRAM(mult_mp) { ALU_NOP, DATA_DERIVE, ALU_MPY, // Save the value; put register number in m_regnumber MEMORY_READ, ALU_MPY, // 18 cycles for multiplication MEMORY_WRITE, // Write the high word ALU_MPY, // Get low word, increase m_address MEMORY_WRITE, END }; MICROPROGRAM(div_mp) { ALU_NOP, DATA_DERIVE, // Get divisor ALU_DIV, // 0 Store divisor and get register number MEMORY_READ, // Read register ALU_DIV, // 1 Check overflow, increase address (or abort here) ABORT, MEMORY_READ, // Read subsequent word (if reg=15 this is behind the workspace) ALU_DIV, // 2 Calculate quotient (takes variable amount of cycles; at least 32 machine cycles), set register number MEMORY_WRITE, // Write quotient into register ALU_DIV, // 3 Get remainder MEMORY_WRITE, // Write remainder END }; MICROPROGRAM(xop_mp) { ALU_NOP, DATA_DERIVE, // Get argument ALU_XOP, // 0 Save the address of the source operand, set address = 0x0040 + xopNr*4, 6 cycles MEMORY_READ, // Read the new WP ALU_XOP, // 1 Save old WP, set new WP, get the source operand address MEMORY_WRITE, // Write the address of the source operand into the new R11 ALU_XOP, // 2 MEMORY_WRITE, // Write the ST into the new R15 ALU_XOP, // 3 MEMORY_WRITE, // Write the PC into the new R14 ALU_XOP, // 4 MEMORY_WRITE, // Write the WP into the new R13 ALU_XOP, // 5 Set the X bit in the ST MEMORY_READ, // Read the new PC ALU_XOP, // 6 Set the new PC END }; MICROPROGRAM(clr_swpb_mp) { ALU_NOP, DATA_DERIVE, ALU_CLR_SWPB, MEMORY_WRITE, END }; MICROPROGRAM(abs_mp) { ALU_NOP, DATA_DERIVE, ALU_ABS, // two cycles MEMORY_WRITE, // skipped when ABS is not performed ALU_NOP, END }; MICROPROGRAM(x_mp) { ALU_NOP, DATA_DERIVE, ALU_X, END }; MICROPROGRAM(b_mp) // Branch { ALU_NOP, DATA_DERIVE, ALU_B, END }; MICROPROGRAM(bl_mp) // Branch and Link { ALU_NOP, DATA_DERIVE, ALU_B, ALU_NOP, MEMORY_WRITE, END }; MICROPROGRAM(blwp_mp) // Branch and Load WP { ALU_NOP, DATA_DERIVE, // Get argument ALU_BLWP, // 0 Save old WP, set new WP, save position ALU_NOP, MEMORY_WRITE, // write ST to R15 ALU_BLWP, // 1 MEMORY_WRITE, // write PC to R14 ALU_BLWP, // 2 MEMORY_WRITE, // write WP to R13 ALU_BLWP, // 3 Get saved position MEMORY_READ, // Read new PC ALU_BLWP, // 4 Set new PC END }; MICROPROGRAM(ldcr_mp) { ALU_NOP, DATA_DERIVE, ALU_SOURCE, ALU_NOP, ALU_LDCR, ALU_NOP, MEMORY_READ, ALU_LDCR, CRU_OUTPUT, ALU_NOP, END }; MICROPROGRAM(stcr_mp) { ALU_NOP, DATA_DERIVE, ALU_SOURCE, // Store address and value ALU_STCR, // 0 Set register_number = 12; 0 cycles (already done before) MEMORY_READ, ALU_STCR, // 1 Prepare CRU access ALU_NOP, CRU_INPUT, ALU_STCR, // 2 Create result; Cycles = 5 + (8-#C-1) or + (16-#C) ALU_NOP, ALU_NOP, ALU_NOP, MEMORY_WRITE, END }; MICROPROGRAM(sbz_sbo_mp) { ALU_SBZ_SBO, ALU_NOP, MEMORY_READ, ALU_SBZ_SBO, CRU_OUTPUT, END }; MICROPROGRAM(tb_mp) { ALU_TB, MEMORY_READ, ALU_TB, CRU_INPUT, ALU_TB, END }; MICROPROGRAM(jmp_mp) { ALU_NOP, ALU_JMP, ALU_JMP, ALU_NOP, END }; MICROPROGRAM(shift_mp) { ALU_SHIFT, MEMORY_READ, ALU_SHIFT, // 2 cycles if count != 0, else 4 MEMORY_READ, // skipped if count != 0 ALU_SHIFT, // skipped if count != 0 (4 cycles) ALU_SHIFT, MEMORY_WRITE, ALU_NOP, END }; MICROPROGRAM(ai_ori_mp) { ALU_REG, MEMORY_READ, ALU_IMM, MEMORY_READ, ALU_AI_ORI, MEMORY_WRITE, END }; MICROPROGRAM(ci_mp) { ALU_REG, MEMORY_READ, ALU_IMM, MEMORY_READ, ALU_CI, ALU_NOP, END }; MICROPROGRAM(li_mp) { ALU_IMM, MEMORY_READ, ALU_LI, // sets status bits ALU_REG, // set register number MEMORY_WRITE, END }; MICROPROGRAM(lwpi_mp) { ALU_IMM, MEMORY_READ, ALU_NOP, ALU_LWPI, // sets WP END }; MICROPROGRAM(limi_mp) { ALU_IMM, MEMORY_READ, ALU_NOP, ALU_LIMI, // sets interrupt mask in ST ALU_NOP, ALU_NOP, END }; MICROPROGRAM(stwp_stst_mp) { ALU_STWP_STST, ALU_REG, MEMORY_WRITE, END }; MICROPROGRAM(external_mp) { ALU_NOP, ALU_NOP, ALU_EXT, ALU_NOP, ALU_NOP, END }; MICROPROGRAM(rtwp_mp) { ALU_NOP, ALU_RTWP, MEMORY_READ, ALU_RTWP, // no cycles MEMORY_READ, ALU_RTWP, // no cycles MEMORY_READ, ALU_RTWP, END }; MICROPROGRAM(int_mp) { ALU_NOP, ALU_INT, // 0 Set address = 0 MEMORY_READ, ALU_INT, // 1 Save old WP, set new WP, save position MEMORY_WRITE, // write ST to R15 ALU_INT, // 2 MEMORY_WRITE, // write PC to R14 ALU_INT, // 3 MEMORY_WRITE, // write WP to R13 ALU_INT, // 4 Get saved position MEMORY_READ, // Read new PC ALU_INT, // 5 Set new PC END }; const tms99xx_device::ophandler tms99xx_device::s_microoperation[] = { &tms99xx_device::acquire_instruction, &tms99xx_device::mem_read, &tms99xx_device::mem_write, &tms99xx_device::register_read, &tms99xx_device::register_write, &tms99xx_device::cru_input_operation, &tms99xx_device::cru_output_operation, &tms99xx_device::data_derivation_subprogram, &tms99xx_device::return_from_subprogram, &tms99xx_device::abort_operation, &tms99xx_device::command_completed, &tms99xx_device::alu_nop, &tms99xx_device::alu_clear, &tms99xx_device::alu_setaddr, &tms99xx_device::alu_addone, &tms99xx_device::alu_setaddr_addone, &tms99xx_device::alu_pcaddr_advance, &tms99xx_device::alu_source, &tms99xx_device::alu_add_register, &tms99xx_device::alu_imm, &tms99xx_device::alu_reg, &tms99xx_device::alu_f1, &tms99xx_device::alu_comp, &tms99xx_device::alu_f3, &tms99xx_device::alu_multiply, &tms99xx_device::alu_divide, &tms99xx_device::alu_xop, &tms99xx_device::alu_clr_swpb, &tms99xx_device::alu_abs, &tms99xx_device::alu_x, &tms99xx_device::alu_b, &tms99xx_device::alu_blwp, &tms99xx_device::alu_ldcr, &tms99xx_device::alu_stcr, &tms99xx_device::alu_sbz_sbo, &tms99xx_device::alu_tb, &tms99xx_device::alu_jmp, &tms99xx_device::alu_shift, &tms99xx_device::alu_ai_ori, &tms99xx_device::alu_ci, &tms99xx_device::alu_li, &tms99xx_device::alu_lwpi, &tms99xx_device::alu_limi, &tms99xx_device::alu_stwp_stst, &tms99xx_device::alu_external, &tms99xx_device::alu_rtwp, &tms99xx_device::alu_int }; /***************************************************************************** CPU instructions *****************************************************************************/ /* Available instructions */ enum { ILL=0, A, AB, ABS, AI, ANDI, B, BL, BLWP, C, CB, CI, CKOF, CKON, CLR, COC, CZC, DEC, DECT, DIV, IDLE, INC, INCT, INV, JEQ, JGT, JH, JHE, JL, JLE, JLT, JMP, JNC, JNE, JNO, JOC, JOP, LDCR, LI, LIMI, LREX, LWPI, MOV, MOVB, MPY, NEG, ORI, RSET, RTWP, S, SB, SBO, SBZ, SETO, SLA, SOC, SOCB, SRA, SRC, SRL, STCR, STST, STWP, SWPB, SZC, SZCB, TB, X, XOP, XOR, INTR }; /* Formats: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ----+------------------------------------------------+ 1 | Opcode | B | Td | RegNr | Ts | RegNr | +--------+---+----+------------+----+------------+ 2 | Opcode | Displacement | +-----------------------+------------------------+ 3 | Opcode | RegNr | Ts | RegNr | +-----------------+------------+----+------------+ 4 | Opcode | Count | Ts | RegNr | +-----------------+------------+----+------------+ 5 | Opcode | Count | RegNr | +-----------------------+-----------+------------+ 6 | Opcode | Ts | RegNr | +------------------------------+----+------------+ 7 | Opcode |0| 0| 0| 0| 0 | +---------------------------------+-+--+--+--+---+ 8 | Opcode |0| RegNr | +---------------------------------+-+------------+ 9 | Opcode | Reg/Nr | Ts | RegNr | +-----------------+------------+----+------------+ */ /* Defines the number of bits from the left which are significant for the command in the respective format. */ static const int format_mask_len[] = { 0, 4, 8, 6, 6, 8, 10, 16, 12, 6 }; const tms99xx_device::tms_instruction tms99xx_device::s_command[] = { // Opcode, ID, format, microprg { 0x0200, LI, 8, li_mp }, { 0x0220, AI, 8, ai_ori_mp }, { 0x0240, ANDI, 8, ai_ori_mp }, { 0x0260, ORI, 8, ai_ori_mp }, { 0x0280, CI, 8, ci_mp }, { 0x02a0, STWP, 8, stwp_stst_mp }, { 0x02c0, STST, 8, stwp_stst_mp }, { 0x02e0, LWPI, 8, lwpi_mp }, { 0x0300, LIMI, 8, limi_mp }, { 0x0340, IDLE, 7, external_mp }, { 0x0360, RSET, 7, external_mp }, { 0x0380, RTWP, 7, rtwp_mp }, { 0x03a0, CKON, 7, external_mp }, { 0x03c0, CKOF, 7, external_mp }, { 0x03e0, LREX, 7, external_mp }, { 0x0400, BLWP, 6, blwp_mp }, { 0x0440, B, 6, b_mp }, { 0x0480, X, 6, x_mp }, { 0x04c0, CLR, 6, clr_swpb_mp }, { 0x0500, NEG, 6, clr_swpb_mp }, { 0x0540, INV, 6, clr_swpb_mp }, { 0x0580, INC, 6, clr_swpb_mp }, { 0x05c0, INCT, 6, clr_swpb_mp }, { 0x0600, DEC, 6, clr_swpb_mp }, { 0x0640, DECT, 6, clr_swpb_mp }, { 0x0680, BL, 6, bl_mp }, { 0x06c0, SWPB, 6, clr_swpb_mp }, { 0x0700, SETO, 6, clr_swpb_mp }, { 0x0740, ABS, 6, abs_mp }, { 0x0800, SRA, 5, shift_mp }, { 0x0900, SRL, 5, shift_mp }, { 0x0a00, SLA, 5, shift_mp }, { 0x0b00, SRC, 5, shift_mp }, { 0x1000, JMP, 2, jmp_mp }, { 0x1100, JLT, 2, jmp_mp }, { 0x1200, JLE, 2, jmp_mp }, { 0x1300, JEQ, 2, jmp_mp }, { 0x1400, JHE, 2, jmp_mp }, { 0x1500, JGT, 2, jmp_mp }, { 0x1600, JNE, 2, jmp_mp }, { 0x1700, JNC, 2, jmp_mp }, { 0x1800, JOC, 2, jmp_mp }, { 0x1900, JNO, 2, jmp_mp }, { 0x1a00, JL, 2, jmp_mp }, { 0x1b00, JH, 2, jmp_mp }, { 0x1c00, JOP, 2, jmp_mp }, { 0x1d00, SBO, 2, sbz_sbo_mp }, { 0x1e00, SBZ, 2, sbz_sbo_mp }, { 0x1f00, TB, 2, tb_mp }, { 0x2000, COC, 3, f3_mp }, { 0x2400, CZC, 3, f3_mp }, { 0x2800, XOR, 3, xor_mp }, { 0x2c00, XOP, 3, xop_mp }, { 0x3000, LDCR, 4, ldcr_mp }, { 0x3400, STCR, 4, stcr_mp }, { 0x3800, MPY, 9, mult_mp }, { 0x3c00, DIV, 9, div_mp }, { 0x4000, SZC, 1, f1_mp }, { 0x5000, SZCB, 1, f1_mp }, { 0x6000, S, 1, f1_mp }, { 0x7000, SB, 1, f1_mp }, { 0x8000, C, 1, comp_mp }, { 0x9000, CB, 1, comp_mp }, { 0xa000, A, 1, f1_mp }, { 0xb000, AB, 1, f1_mp }, { 0xc000, MOV, 1, f1_mp }, { 0xd000, MOVB, 1, f1_mp }, { 0xe000, SOC, 1, f1_mp }, { 0xf000, SOCB, 1, f1_mp } }; /* Create a B-tree for looking up the commands. Each node can carry up to 16 entries, indexed by 4 consecutive bits in the opcode. Works as follows: Opcode = 0201 (Load immediate value into register 1) Opcode = 0284 (Compare immediate value with register 4) Table: [ Table0, table1, table2, ... tableF ] | +-------+ v table0: [ table00, table01, table02, ... table0f ] | +-------------------------+ v table02: [ table020, table021, ... table028, ... table02f ] | | | v v v Entry NULL Entry for LI for CI For each level in the tree, four more bits are compared. The search terminates when the number of compared bits is equal or higher than the number of significant bits of the format of this opcode. The entry points to the respective line in s_command. This way we can decode all format 1 commands by a single pass (including the most frequent command MOV), and almost all commands by less than four passes. The disadvantage is that we have to build these tables from the opcode list at runtime, and many positions are empty. But we do not need more than 20 tables for the TMS command set. */ void tms99xx_device::build_command_lookup_table() { int i = 0; int cmdindex; int bitcount; const tms_instruction *inst; UINT16 opcode; int k = 0; m_command_lookup_table = new lookup_entry[16]; // We use lotables as a list of allocated tables - to be able to delete them // at the end. m_lotables[k++] = m_command_lookup_table; lookup_entry* table = m_command_lookup_table; for (int j=0; j < 16; j++) { table[j].entry = nullptr; table[j].next_digit = nullptr; } do { inst = &s_command[i]; table = m_command_lookup_table; if (TRACE_SETUP) logerror("tms99xx: === opcode=%04x, len=%d\n", inst->opcode, format_mask_len[inst->format]); bitcount = 4; opcode = inst->opcode; cmdindex = (opcode>>12) & 0x000f; while (bitcount < format_mask_len[inst->format]) { // Descend if (table[cmdindex].next_digit == nullptr) { if (TRACE_SETUP) logerror("tms99xx: create new table at bitcount=%d for index=%d\n", bitcount, cmdindex); table[cmdindex].next_digit = new lookup_entry[16]; m_lotables[k++] = table[cmdindex].next_digit; for (int j=0; j < 16; j++) { table[cmdindex].next_digit[j].next_digit = nullptr; table[cmdindex].next_digit[j].entry = nullptr; } } else { if (TRACE_SETUP) logerror("tms99xx: found a table at bitcount=%d\n", bitcount); } table = table[cmdindex].next_digit; bitcount = bitcount+4; opcode <<= 4; cmdindex = (opcode>>12) & 0x000f; if (TRACE_SETUP) logerror("tms99xx: next index=%x\n", cmdindex); } if (TRACE_SETUP) logerror("tms99xx: bitcount=%d\n", bitcount); // We are at the target level // Need to fill in the same entry for all values in the bitcount // (if a command needs 10 bits we have to copy it four // times for all combinations with 12 bits) for (int j=0; j < (1<<(bitcount-format_mask_len[inst->format])); j++) { if (TRACE_SETUP) logerror("tms99xx: opcode=%04x at position %d\n", inst->opcode, cmdindex+j); table[cmdindex+j].entry = inst; } i++; } while (inst->opcode != 0xf000); m_lotables[k++] = nullptr; if (TRACE_SETUP) logerror("tms99xx: Allocated %d tables\n", k); } /* Main execution loop For each invocation of execute_run, a number of loop iterations has been calculated before (m_icount). Each loop iteration is one clock cycle. The loop must be executed for the number of times that corresponds to the time until the next timer event. In this implementation, each loop iteration also causes the clock line to pulse once. External devices may use this pulse to decrement counters which control the READY line. Machine cycles to clock input: +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ | | | | | | | | | | | | | | | | | | clock (1 of 4 phases) +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ + |-------|-------|-------|-------|---- cycles (2 clock pulses each) Wait states only have effect for memory operations. They are processed as follows: 1) The CPU sets the address bus for reading. If READY is low, the CPU waits for the next clock tick repeatedly until READY is high again. When this is the case, the data bus is sampled on the next clock tick and the read operation is complete. As we do not have a split-phase read operation in this emulation we actually read the data bus instantly but wait for the READY line to be high again. 2) The CPU sets the address bus for writing. In the same moment, the data bus is loaded with the word to be written. On the next clock tick, the CPU checks the READY line and waits until it is high. When READY is high at a clock tick, the operation is complete on the next clock tick. */ void tms99xx_device::execute_run() { if (m_reset) service_interrupt(); if (TRACE_EMU) logerror("tms99xx: calling execute_run for %d cycles\n", m_icount); do { // Only when last instruction has completed if (m_program == nullptr) { if (m_load_state) { logerror("tms99xx: LOAD interrupt\n"); m_irq_level = LOAD_INT; m_irq_state = false; service_interrupt(); } else { // Interrupts are serviced when // - an interrupt condition is signaled over INTREQ and // - the level indicated by IC0-IC3 is lower than the interrupt mask value and // - the previous instruction is not an XOP or BLWP if (m_irq_state && (m_irq_level <= (ST & 0x000f)) && (m_command != XOP && m_command != BLWP)) service_interrupt(); } } if (m_program == nullptr && m_idle_state) { if (TRACE_WAIT) logerror("tms99xx: idle state\n"); pulse_clock(1); if (!m_external_operation.isnull()) { m_external_operation(IDLE_OP, 0, 0xff); m_external_operation(IDLE_OP, 1, 0xff); } } else { // Handle HOLD // A HOLD request is signalled through the input line HOLD. // The hold state will be entered with the next non-memory access cycle. if (m_hold_state && (m_program==nullptr || (m_program[MPC] != IAQ && m_program[MPC] != MEMORY_READ && m_program[MPC] != MEMORY_WRITE && m_program[MPC] != REG_READ && m_program[MPC] != REG_WRITE))) { if (TRACE_WAIT) logerror("tms99xx: hold\n"); if (!m_hold_acknowledged) acknowledge_hold(); pulse_clock(1); } else { // Normal operation if (m_check_ready && m_ready == false) { // We are in a wait state set_wait_state(true); if (TRACE_WAIT) logerror("tms99xx: wait\n"); // The clock output should be used to change the state of an outer // device which operates the READY line pulse_clock(1); } else { set_wait_state(false); m_check_ready = false; if (m_program==nullptr) m_op = IAQ; else { m_op = m_program[MPC]; } if (TRACE_MICRO) logerror("tms99xx: MPC = %d, m_op = %d\n", MPC, m_op); // Call the operation of the microprogram (this->*s_microoperation[m_op])(); // If we have multiple passes (as in the TMS9980) m_pass--; if (m_pass<=0) { m_pass = 1; MPC++; m_mem_phase = 1; if (!m_iaq_line.isnull()) m_iaq_line(CLEAR_LINE); } } } } } while (m_icount>0 && !m_reset); if (TRACE_EMU) logerror("tms99xx: cycles expired; will return soon.\n"); } /**************************************************************************/ /* Interrupt input */ void tms99xx_device::execute_set_input(int irqline, int state) { if (irqline==INT_9900_RESET && state==ASSERT_LINE) { m_reset = true; } else { if (irqline == INT_9900_LOAD) { m_load_state = (state==ASSERT_LINE); m_irq_level = -1; m_reset = false; } else { m_irq_state = (state==ASSERT_LINE); if (state==ASSERT_LINE) { m_irq_level = get_intlevel(state); if (TRACE_INT) logerror("tms99xx: /INT asserted, level=%d, ST=%04x\n", m_irq_level, ST); } else { if (TRACE_INT) logerror("tms99xx: /INT cleared\n"); } } } } /* This can be overloaded by variants of TMS99xx. */ int tms99xx_device::get_intlevel(int state) { if (!m_get_intlevel.isnull()) return m_get_intlevel(0); return 0; } void tms99xx_device::service_interrupt() { m_program = int_mp; m_command = INTR; m_idle_state = false; if (!m_external_operation.isnull()) m_external_operation(IDLE_OP, 0, 0xff); m_state = 0; if (!m_dbin_line.isnull()) m_dbin_line(ASSERT_LINE); // If reset, we just start with execution, otherwise we put the MPC // on the first microinstruction, which also means that the main loop shall // leave it where it is. So we pretend we have another pass to do. m_pass = m_reset? 1 : 2; if (m_reset) { m_irq_level = RESET_INT; m_ready_bufd = true; m_ready = true; m_load_state = false; m_hold_state = false; m_hold_acknowledged = false; m_wait_state = false; IR = 0; ST = 0; m_mem_phase = 1; m_reset = false; } if (TRACE_INT) { switch (m_irq_level) { case RESET_INT: logerror("tms99xx: **** triggered a RESET interrupt\n"); break; case LOAD_INT: logerror("tms99xx: **** triggered a LOAD (NMI) interrupt\n"); break; default: logerror("tms99xx: ** triggered an interrupt on level %d\n", m_irq_level); break; } } MPC = 0; m_first_cycle = m_icount; } /* Issue a pulse on the clock line. */ void tms99xx_device::pulse_clock(int count) { for (int i=0; i < count; i++) { if (!m_clock_out_line.isnull()) m_clock_out_line(ASSERT_LINE); m_ready = m_ready_bufd; // get the latched READY state if (!m_clock_out_line.isnull()) m_clock_out_line(CLEAR_LINE); m_icount--; // This is the only location where we count down the cycles. if (TRACE_CLOCK) { if (m_check_ready) logerror("tms99xx: pulse_clock, READY=%d\n", m_ready? 1:0); else logerror("tms99xx: pulse_clock\n"); } } } /* Enter the hold state. */ void tms99xx_device::set_hold(int state) { m_hold_state = (state==ASSERT_LINE); if (!m_hold_state) { m_hold_acknowledged = false; if (!m_holda_line.isnull()) m_holda_line(CLEAR_LINE); } } /* Acknowledge the HOLD request. */ inline void tms99xx_device::acknowledge_hold() { m_hold_acknowledged = true; if (!m_holda_line.isnull()) m_holda_line(ASSERT_LINE); } /* Signal READY to the CPU. When cleared, the CPU enters wait states. This becomes effective on a clock pulse. */ void tms99xx_device::set_ready(int state) { m_ready_bufd = (state==ASSERT_LINE); } void tms99xx_device::abort_operation() { command_completed(); } /* Enter or leave the wait state. We only operate the WAIT line when there is a change. */ inline void tms99xx_device::set_wait_state(bool state) { if (m_wait_state != state) if (!m_wait_line.isnull()) m_wait_line(state? ASSERT_LINE : CLEAR_LINE); m_wait_state = state; } /* Acquire the next word as an instruction. The program counter advances by one word. */ void tms99xx_device::decode(UINT16 inst) { int index = 0; lookup_entry* table = m_command_lookup_table; UINT16 opcode = inst; bool complete = false; const tms_instruction *decoded; m_state = 0; IR = inst; m_get_destination = false; m_byteop = false; while (!complete) { index = (opcode >> 12) & 0x000f; if (TRACE_MICRO) logerror("tms99xx: Check next hex digit of instruction %x\n", index); if (table[index].next_digit != nullptr) { table = table[index].next_digit; opcode = opcode << 4; } else complete = true; } decoded = table[index].entry; if (decoded == nullptr) { // not found logerror("tms99xx: Illegal opcode %04x\n", inst); IR = 0; // This will cause another instruction acquisition in the next machine cycle // with an asserted IAQ line (can be used to indicate this illegal opcode detection). m_program = nullptr; } else { m_program = decoded->prog; MPC = -1; m_command = decoded->id; if (TRACE_MICRO) logerror("tms99xx: Command decoded as id %d, %s, base opcode %04x\n", m_command, opname[m_command], decoded->opcode); // Byte operations are either format 1 with the byte flag set // or format 4 (CRU multi bit operations) with 1-8 bits to transfer. m_byteop = ((decoded->format==1 && ((IR & 0x1000)!=0)) || (decoded->format==4 && (((IR >> 6)&0x000f) > 0) && (((IR >> 6)&0x000f) > 9))); } m_pass = 1; } inline bool tms99xx_device::byte_operation() { return (IR & 0x1000)!=0; } void tms99xx_device::acquire_instruction() { if (m_mem_phase == 1) { if (!m_iaq_line.isnull()) m_iaq_line(ASSERT_LINE); m_address = PC; m_first_cycle = m_icount; } mem_read(); if (m_mem_phase == 1) { decode(m_current_value); if (TRACE_EXEC) logerror("tms99xx: %04x: %04x (%s)\n", PC, IR, opname[m_command]); debugger_instruction_hook(this, PC); PC = (PC + 2) & 0xfffe & m_prgaddr_mask; // IAQ will be cleared in the main loop } } /* Memory read Clock cycles: 2 + W, W = number of wait states */ void tms99xx_device::mem_read() { // After set_address, any device attached to the address bus may pull down // READY in order to put the CPU into wait state before the read_word // operation will be performed // set_address and read_word should pass the same address as argument if (m_mem_phase==1) { if (!m_dbin_line.isnull()) m_dbin_line(ASSERT_LINE); m_prgspace->set_address(m_address & m_prgaddr_mask & 0xfffe); m_check_ready = true; m_mem_phase = 2; m_pass = 2; if (TRACE_ADDRESSBUS) logerror("tms99xx: set address (r) %04x\n", m_address); pulse_clock(1); // Concludes the first cycle // If READY has been found to be low, the CPU will now stay in the wait state loop } else { // Second phase (after READY was raised again) m_current_value = m_prgspace->read_word(m_address & m_prgaddr_mask & 0xfffe); pulse_clock(1); if (!m_dbin_line.isnull()) m_dbin_line(CLEAR_LINE); m_mem_phase = 1; // reset to phase 1 if (TRACE_MEM) logerror("tms99xx: mem r %04x -> %04x\n", m_address, m_current_value); } } void tms99xx_device::mem_write() { if (m_mem_phase==1) { if (!m_dbin_line.isnull()) m_dbin_line(CLEAR_LINE); // When writing, the data bus is asserted immediately after the address bus if (TRACE_ADDRESSBUS) logerror("tms99xx: set address (w) %04x\n", m_address); m_prgspace->set_address(m_address & m_prgaddr_mask & 0xfffe); if (TRACE_MEM) logerror("tms99xx: mem w %04x <- %04x\n", m_address, m_current_value); m_prgspace->write_word(m_address & m_prgaddr_mask & 0xfffe, m_current_value); m_check_ready = true; m_mem_phase = 2; m_pass = 2; pulse_clock(1); } else { // Second phase (we arrive here when the wait states are over) pulse_clock(1); } } void tms99xx_device::register_read() { // Need to set m_address for F1/F3 (we don't know what the data_derive did) if (m_mem_phase==1) { m_address = WP + (m_regnumber<<1); } mem_read(); if (m_mem_phase==1) { m_register_contents = m_current_value; } } /* Memory write: Clock cycles: 2 + W, W = number of wait states */ void tms99xx_device::register_write() { // This will be called twice; m_pass is set by the embedded mem_write UINT16 addr_save = m_address; m_address = (WP + (m_regnumber<<1)) & m_prgaddr_mask & 0xfffe; mem_write(); m_address = addr_save; } /* CRU support code The CRU bus is a 1-bit-wide I/O bus. The CPU can read or write bits at random address. Special instructions are dedicated to reading and writing one or several consecutive bits. The CRU uses the same address bus as the normal memory access. For writing, the CRUCLK line is pulsed, but not for reading where CRUCLK stays cleared. This means that each normal memory access also causes read accesses on the CRU side. The /MEMEN line may be used to distinguish the kinds of accesses as it stays cleared during CRU operations. We do not emulate this here as it seems there are no real applications of this side effect. Real designs must ensure that CRU read operations are idempotent (i.e. they must not change the state of the queried device). Read returns the number of consecutive CRU bits, with increasing CRU address from the least significant to the most significant bit; right-aligned There seems to be no handling of wait states during CRU operations on the TMS9900. The TMS9995, in contrast, respects wait states during the transmission of each single bit. Usage of this method: CRU write: First bit is at rightmost position of m_value. */ void tms99xx_device::cru_input_operation() { int value, value1; int offset, location; location = (m_cru_address >> 4) & (m_cruaddr_mask>>3); offset = (m_cru_address>>1) & 0x07; // Read 8 bits (containing the desired bits) value = m_cru->read_byte(location); if ((offset + m_count) > 8) // spans two 8 bit cluster { // Read next 8 bits location = (location + 1) & (m_cruaddr_mask>>3); value1 = m_cru->read_byte(location); value |= (value1 << 8); if ((offset + m_count) > 16) // spans three 8 bit cluster { // Read next 8 bits location = (location + 1) & (m_cruaddr_mask>>3); value1 = m_cru->read_byte(location); value |= (value1 << 16); } } // On each machine cycle (2 clocks) only one CRU bit is transmitted pulse_clock(m_count<<1); // Shift back the bits so that the first bit is at the rightmost place m_value = (value >> offset); // Mask out what we want m_value &= (0x0000ffff >> (16-m_count)); } void tms99xx_device::cru_output_operation() { int value; int location; location = (m_cru_address >> 1) & m_cruaddr_mask; value = m_value; // Write m_count bits from cru_address for (int i=0; i < m_count; i++) { if (TRACE_CRU) logerror("tms99xx: CRU output operation, address %04x, value %d\n", location<<1, value & 0x01); m_cru->write_byte(location, (value & 0x01)); value >>= 1; location = (location + 1) & m_cruaddr_mask; pulse_clock(2); } } void tms99xx_device::return_from_subprogram() { // Return from data derivation // The result should be in m_current_value // and the address in m_address m_program = m_caller; MPC = m_caller_MPC; // will be increased on return } void tms99xx_device::command_completed() { // Pseudo state at the end of the current instruction cycle sequence if (TRACE_CYCLES) { logerror("tms99xx: ------"); int cycles = m_first_cycle - m_icount; // Avoid nonsense values due to expired and resumed main loop if (cycles > 0 && cycles < 10000) logerror(" %d cycles", cycles); logerror("\n"); } m_program = nullptr; } /* This is a switch to a subprogram; there is only one, the data derivation. In terms of cycles, it does not take any time; execution continues with the first instruction of the subprogram. */ void tms99xx_device::data_derivation_subprogram() { UINT16 ircopy = IR; // Save the return program and position m_caller = m_program; m_caller_MPC = MPC; // Source or destination argument? if (m_get_destination) ircopy >>= 6; m_regnumber = ircopy & 0x000f; m_program = (UINT8*)data_derivation; MPC = ircopy & 0x0030; if (((MPC == 0x0020) && (m_regnumber != 0)) // indexed || ((MPC == 0x0030) && m_byteop)) // byte operation { MPC += 8; // the second option } m_get_destination = true; // when we call this the second time before END it's the destination m_pass = 2; } /************************************************************************** Status bit operations **************************************************************************/ inline void tms99xx_device::set_status_bit(int bit, bool state) { if (state) ST |= bit; else ST &= ~bit; } void tms99xx_device::set_status_parity(UINT8 value) { int count = 0; for (int i=0; i < 8; i++) { if ((value & 0x80)!=0) count++; value <<= 1; } set_status_bit(ST_OP, (count & 1)!=0); } inline void tms99xx_device::compare_and_set_lae(UINT16 value1, UINT16 value2) { set_status_bit(ST_EQ, value1 == value2); set_status_bit(ST_LH, value1 > value2); set_status_bit(ST_AGT, (INT16)value1 > (INT16)value2); if (TRACE_STATUS) logerror("tms99xx: ST = %04x (val1=%04x, val2=%04x)\n", ST, value1, value2); } /************************************************************************** ALU operations **************************************************************************/ void tms99xx_device::alu_nop() { // Do nothing (or nothing that is externally visible) pulse_clock(2); return; } void tms99xx_device::alu_source() { // Copy the current value into the source data register m_source_even = ((m_address & 1)==0); m_source_value = m_current_value; m_source_address = m_address; pulse_clock(2); } void tms99xx_device::alu_clear() { // Clears the register contents m_register_contents = 0; pulse_clock(2); } void tms99xx_device::alu_setaddr() { // Load the current value into the address register m_address = m_current_value; pulse_clock(2); } void tms99xx_device::alu_addone() { m_current_value++; pulse_clock(2); } void tms99xx_device::alu_setaddr_addone() { // Set the address register and increase the recent value m_address = m_current_value; m_current_value++; pulse_clock(2); } void tms99xx_device::alu_pcaddr_advance() { // Set PC as new read address, increase by 2 m_address = PC; PC = (PC + 2) & 0xfffe & m_prgaddr_mask; pulse_clock(2); } void tms99xx_device::alu_add_register() { // Add the register contents to the current value and set as address m_address = m_current_value + m_register_contents; pulse_clock(2); } void tms99xx_device::alu_imm() { m_value_copy = m_current_value; m_address_copy = m_address; m_address = PC; PC = (PC + 2) & 0xfffe & m_prgaddr_mask; pulse_clock(2); } void tms99xx_device::alu_reg() { m_address = (WP + ((IR & 0x000f)<<1)) & m_prgaddr_mask; pulse_clock(2); } void tms99xx_device::alu_f1() { UINT32 dest_new = 0; // Save the destination value UINT16 prev_dest_value = m_current_value; m_destination_even = ((m_address & 1)==0); // this is the destination address; the source address has already been saved bool byteop = byte_operation(); if (byteop) { if (!m_destination_even) m_current_value <<= 8; if (!m_source_even) m_source_value <<= 8; // We have to strip away the low byte, or byte operations may fail // e.g. 0x10ff + 0x0101 = 0x1200 // or 0x2000 - 0x0101 = 0x1eff m_source_value &= 0xff00; m_current_value &= 0xff00; } switch (m_command) { case A: case AB: // Add the contents of the source data to the destination data // May exceed 0xffff (for carry check) dest_new = m_current_value + m_source_value; // 1000 + e000 = f000 (L) // c000 + c000 = 8000 (LC) // 7000 + 4000 = b000 (LO) // 2000 + f000 = 1000 (LAC) // c000 + b000 = 7000 (LACO) // 2000 + e000 = 0000 (EC) // 8000 + 8000 = 0000 (ECO) // When adding, a carry occurs when we exceed the 0xffff value. set_status_bit(ST_C, (dest_new & 0x10000) != 0); // If the result has a sign bit that is different from both arguments, we have an overflow // (i.e. getting a negative value from two positive values and vice versa) set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_source_value) & 0x8000)!=0); break; case S: case SB: // Subtract the contents of the source data from the destination data dest_new = m_current_value + ((~m_source_value) & 0xffff) + 1; // LAECO(P) // 8000 - 8000 = 0000 (EC) // 2000 - 8000 = a000 (LO) // 8000 - 2000 = 6000 (LACO) // 2000 - 1000 = 1000 (LAC) // 1000 - 2000 = f000 (L) // 1000 - 1000 = 0000 (EC) // 1000 - f000 = 2000 (LA) // f000 - 2000 = d000 (LC) // Subtraction means adding the 2s complement, so the carry bit // is set whenever adding the 2s complement exceeds ffff // In fact the CPU adds the one's complement, then adds a one. This // explains why subtracting 0 sets the carry bit. set_status_bit(ST_C, (dest_new & 0x10000) != 0); // If the arguments have different sign bits and the result has a // sign bit different from the destination value, we have an overflow // e.g. value1 = 0x7fff, value2 = 0xffff; value1-value2 = 0x8000 // or value1 = 0x8000, value2 = 0x0001; value1-value2 = 0x7fff // value1 is the destination value set_status_bit(ST_OV, (m_current_value ^ m_source_value) & (m_current_value ^ dest_new) & 0x8000); break; case SOC: case SOCB: // OR the contents of the source data on the destination data dest_new = m_current_value | m_source_value; break; case SZC: case SZCB: // AND the one's complement of the contents of the source data on the destination data dest_new = m_current_value & ~m_source_value; break; case MOV: case MOVB: // Copy the source data to the destination data dest_new = m_source_value; break; } if (byteop) { set_status_parity((UINT8)(dest_new>>8)); // destnew is the new value to be written (high byte); needs to be // merged with the existing word if (m_destination_even) m_current_value = (prev_dest_value & 0x00ff) | (dest_new & 0xff00); else m_current_value = (prev_dest_value & 0xff00) | ((dest_new >> 8) & 0x00ff); compare_and_set_lae((UINT16)(dest_new & 0xff00), 0); } else { m_current_value = (UINT16)(dest_new & 0xffff); compare_and_set_lae((UINT16)(dest_new & 0xffff), 0); } pulse_clock(2); } void tms99xx_device::alu_comp() { m_destination_even = ((m_address & 1)==0); // this is the destination address; the source address has already been saved if (byte_operation()) { if (!m_destination_even) m_current_value <<= 8; if (!m_source_even) m_source_value <<= 8; set_status_parity((UINT8)(m_source_value>>8)); compare_and_set_lae(m_source_value & 0xff00, m_current_value & 0xff00); } else compare_and_set_lae(m_source_value, m_current_value); pulse_clock(2); } void tms99xx_device::alu_f3() { switch (m_state) { case 0: // Get register address m_address = WP + ((IR >> 5) & 0x001e); m_source_value = m_current_value; break; case 1: if (m_command == COC) { set_status_bit(ST_EQ, (m_current_value & m_source_value) == m_source_value); } else { if (m_command == CZC) { set_status_bit(ST_EQ, (~m_current_value & m_source_value) == m_source_value); } else { // XOR // The workspace register address is still in m_address m_current_value = (m_current_value ^ m_source_value); compare_and_set_lae(m_current_value, 0); } } if (TRACE_STATUS) logerror("tms99xx: ST = %04x\n", ST); break; } m_state++; pulse_clock(2); } void tms99xx_device::alu_multiply() { UINT32 result; switch (m_state) { case 0: // After data derivation m_source_value = m_current_value; m_address = ((IR >> 5) & 0x001e) + WP; break; case 1: // After reading the register (multiplier) if (TRACE_ALU) logerror("tms99xx: Multiply %04x by %04x\n", m_current_value, m_source_value); result = (m_source_value & 0x0000ffff) * (m_current_value & 0x0000ffff); m_current_value = (result >> 16) & 0xffff; m_value_copy = result & 0xffff; pulse_clock(34); // add 36 clock cycles (18 machine cycles); last one in main loop break; case 2: // After writing the high word to the destination register m_current_value = m_value_copy; // Prepare to save low word m_address = (m_address + 2) & m_prgaddr_mask; break; } pulse_clock(2); m_state++; } void tms99xx_device::alu_divide() { // Format is DIV Divisor,REG(dividend) UINT32 uval32; bool overflow = true; UINT16 value1; switch (m_state) { case 0: m_source_value = m_current_value; // store divisor // Set address of register m_address = WP + ((IR >> 5) & 0x001e); m_address_copy = m_address; break; case 1: // We have an overflow when the quotient cannot be stored in 16 bits // This is the case when the dividend / divisor >= 0x10000, // or equivalently, dividend / 0x10000 >= divisor if (m_current_value < m_source_value) // also if source=0 { MPC++; // skip the abort overflow = false; } set_status_bit(ST_OV, overflow); m_value_copy = m_current_value; // Save the high word m_address = (m_address + 2) & m_prgaddr_mask; // Read next word break; case 2: // W2 is in m_current_value // Create full word and perform division uval32 = (m_value_copy << 16) | m_current_value; if (TRACE_ALU) logerror("tms99xx: Dividing %08x by %04x\n", uval32, m_source_value); m_current_value = uval32 / m_source_value; m_value_copy = uval32 % m_source_value; if (TRACE_ALU) logerror("tms99xx: Quotient %04x, remainder %04x\n", m_current_value, m_value_copy); m_address = m_address_copy; // The number of ALU cycles depends on the number of steps in // the division algorithm. The number of cycles is between 32 and // 48 (*2 for clock cycles) // As I don't have a description of the actual algorithm, I'll use // the following heuristic: We use 32 ALU cycles in general, then // we need as many cycles as it takes to // shift away the dividend. Thus, bigger dividends need more cycles. pulse_clock(62); // one pulse is at the start, one at the end value1 = m_value_copy & 0xffff; while (value1 != 0) { value1 = (value1 >> 1) & 0xffff; pulse_clock(2); } // We still have m_regnumber; this is where m_current_value will go to break; case 3: // Prepare to write the remainder m_current_value = m_value_copy; m_address = m_address + 2; if (TRACE_STATUS) logerror("tms99xx: ST = %04x (div)\n", ST); break; } pulse_clock(2); m_state++; } void tms99xx_device::alu_xop() { switch (m_state) { case 0: // We have the effective address of the source operand in m_address m_address_saved = m_address; // Now we take the XOP number from the instruction register // and calculate the vector location // [0010 11xx xx tt SSSS] shift 6 right, then *4 => shift 4 right m_address = 0x0040 + ((IR >> 4) & 0x003c); // Takes some additional cycles pulse_clock(4); break; case 1: m_value_copy = WP; // save the old WP WP = m_current_value & m_prgaddr_mask & 0xfffe; // the new WP has been read in the previous microoperation m_current_value = m_address_saved; // we saved the address of the source operand; retrieve it m_address = WP + 0x0016; // Next register is R11 break; case 2: m_address = WP + 0x001e; m_current_value = ST; break; case 3: m_address = WP + 0x001c; m_current_value = PC; break; case 4: m_address = WP + 0x001a; m_current_value = m_value_copy; // old WP into new R13 break; case 5: m_address = 0x0042 + ((IR >> 4) & 0x003c); // location of new PC set_status_bit(ST_X, true); break; case 6: PC = m_current_value & m_prgaddr_mask & 0xfffe; break; } pulse_clock(2); m_state++; } void tms99xx_device::alu_clr_swpb() { UINT32 dest_new = 0; UINT32 src_val = m_current_value & 0x0000ffff; UINT16 sign = 0; bool setstatus = true; bool check_ov = true; switch (m_command) { case CLR: // no status bits m_current_value = 0x0000; setstatus = false; break; case SETO: // no status bits m_current_value = 0xffff; setstatus = false; break; case INV: // LAE dest_new = ~src_val & 0xffff; check_ov = false; break; case NEG: // LAECO // Overflow occurs for value=0x8000 dest_new = ((~src_val) & 0x0000ffff) + 1; check_ov = false; set_status_bit(ST_OV, src_val == 0x8000); break; case INC: // LAECO // Overflow for result value = 0x8000 // Carry for result value = 0x0000 dest_new = src_val + 1; break; case INCT: // LAECO // Overflow for result value = 0x8000 / 0x8001 // Carry for result value = 0x0000 / 0x0001 dest_new = src_val + 2; break; case DEC: // LAECO // Carry for result value != 0xffff // Overflow for result value == 0x7fff dest_new = src_val + 0xffff; sign = 0x8000; break; case DECT: // Carry for result value != 0xffff / 0xfffe // Overflow for result value = 0x7fff / 0x7ffe dest_new = src_val + 0xfffe; sign = 0x8000; break; case SWPB: m_current_value = ((m_current_value << 8) | (m_current_value >> 8)) & 0xffff; setstatus = false; break; } if (setstatus) { if (check_ov) set_status_bit(ST_OV, ((src_val & 0x8000)==sign) && ((dest_new & 0x8000)!=sign)); set_status_bit(ST_C, (dest_new & 0x10000) != 0); m_current_value = dest_new & 0xffff; compare_and_set_lae(m_current_value, 0); } pulse_clock(2); // No states here } void tms99xx_device::alu_abs() { // LAECO (from original word!) // O if >8000 // C is alwas reset set_status_bit(ST_OV, m_current_value == 0x8000); set_status_bit(ST_C, false); compare_and_set_lae(m_current_value, 0); if ((m_current_value & 0x8000)!=0) { m_current_value = (((~m_current_value) & 0x0000ffff) + 1) & 0xffff; pulse_clock(2); // If ABS is performed it takes one machine cycle more } else { MPC++; // skips over the next micro operation (MEMORY_WRITE) } pulse_clock(2); } void tms99xx_device::alu_x() { if (TRACE_ALU) logerror("tms99xx: Substituting current command by %04x\n", m_current_value); decode(m_current_value); pulse_clock(2); } /* Also used by other microprograms */ void tms99xx_device::alu_b() { // no status bits // Although we got the contents of the source data, we do not use them // but directly branch there. That is, we are only interested in the // address of the source data. // If we have a B *R5 and R5 contains the value 0xa000, the CPU actually // retrieves the value at 0xa000, but in fact it will load the PC // with the address 0xa000 m_current_value = PC; PC = m_address & m_prgaddr_mask & 0xfffe; m_address = WP + 22; if (TRACE_ALU) logerror("tms99xx: Set new PC = %04x\n", PC); pulse_clock(2); } void tms99xx_device::alu_blwp() { switch (m_state) { case 0: m_value_copy = WP; WP = m_current_value & m_prgaddr_mask & 0xfffe; // set new WP (*m_destination) m_address_saved = (m_address + 2) & m_prgaddr_mask; // Save the location of the WP m_address = WP + 30; m_current_value = ST; // get status register break; case 1: m_current_value = PC; // get program counter m_address = m_address - 2; break; case 2: m_current_value = m_value_copy; // retrieve the old WP m_address = m_address - 2; break; case 3: m_address = m_address_saved; // point to PC component of branch vector break; case 4: PC = m_current_value & m_prgaddr_mask & 0xfffe; if (TRACE_ALU) logerror("tms9900: Context switch complete; WP=%04x, PC=%04x, ST=%04x\n", WP, PC, ST); break; } pulse_clock(2); m_state++; } void tms99xx_device::alu_ldcr() { UINT16 value; // Spec: "If the source operand address is odd, the address is truncated // to an even address prior to data transfer." // (Editor/Assembler, page 151) // This refers to transfers with more than 8 bits. In this case, for // LDCR the first bit is taken from the least significant bit of the // source word. If the address is odd (e.g. 0x1001), it is // treated as 0x1000, that is, truncated to an even address. // For transfers with 1-8 bits, the first bit is the least significant // bit of the source byte (any address). if (m_state == 0) { m_address = WP + 24; } else { value = m_source_value; // copied by ALU_SOURCE m_count = (IR >> 6) & 0x000f; if (m_count == 0) m_count = 16; if (m_count <= 8) { if (m_source_even) value>>=8; set_status_parity((UINT8)(value & 0xff)); compare_and_set_lae(value<<8, 0); } else { compare_and_set_lae(value, 0); } m_cru_address = m_current_value; m_value = value; if (TRACE_CRU) logerror("tms99xx: Load CRU address %04x (%d bits), value = %04x\n", m_cru_address, m_count, m_value); } m_state++; pulse_clock(2); } void tms99xx_device::alu_stcr() { UINT16 value; int n = 2; // For STCR transfers with more than 8 bits, the first CRU bit is // always put into the least significant bit of the destination word. // If the address is odd (e.g. 0x1001), it is treated as 0x1000, that is, // truncated to an even boundary. // For transfers with 1-8 bits, the destination address is handled as // in MOVB operations, i.e. the other byte of the word is kept unchanged. switch (m_state) { case 0: // After getting the destination operand and saving the address/value m_address = WP + 24; n = 0; break; case 1: // After getting R12 m_cru_address = m_current_value; m_count = (IR >> 6) & 0x000f; if (m_count == 0) m_count = 16; break; case 2: // After the cru operation; value starts at LSB of m_value value = m_value & 0xffff; if (m_count < 9) { if (TRACE_CRU) logerror("tms99xx: Store CRU at %04x (%d bits) in %04x, result = %02x\n", m_cru_address, m_count, m_source_address, value); set_status_parity((UINT8)(value & 0xff)); compare_and_set_lae(value<<8, 0); if (m_source_even) m_current_value = (m_source_value & 0x00ff) | (value<<8); else m_current_value = (m_source_value & 0xff00) | (value & 0xff); pulse_clock(2*(5 + (8-m_count))); } else { if (TRACE_CRU) logerror("tms99xx: Store CRU at %04x (%d bits) in %04x, result = %04x\n", m_cru_address, m_count, m_source_address, value); m_current_value = value; compare_and_set_lae(value, 0); pulse_clock(2*(5 + (16-m_count))); } m_address = m_source_address; break; } m_state++; pulse_clock(n); } void tms99xx_device::alu_sbz_sbo() { INT8 displacement; if (m_state==0) { m_address = WP + 24; } else { m_value = (m_command==SBO)? 1 : 0; displacement = (INT8)(IR & 0xff); m_cru_address = m_current_value + (displacement<<1); m_count = 1; } m_state++; pulse_clock(2); } void tms99xx_device::alu_tb() { INT8 displacement; switch (m_state) { case 0: m_address = WP + 24; break; case 1: displacement = (INT8)(IR & 0xff); m_cru_address = m_current_value + (displacement<<1); m_count = 1; break; case 2: set_status_bit(ST_EQ, m_value!=0); if (TRACE_STATUS) logerror("tms99xx: ST = %04x\n", ST); break; } m_state++; pulse_clock(2); } void tms99xx_device::alu_jmp() { INT8 displacement; bool cond = false; if (m_state==0) { switch (m_command) { case JMP: cond = true; break; case JLT: // LAECOP == x00xxx cond = ((ST & (ST_AGT | ST_EQ))==0); break; case JLE: // LAECOP == 0xxxxx cond = ((ST & ST_LH)==0); break; case JEQ: // LAECOP == xx1xxx cond = ((ST & ST_EQ)!=0); break; case JHE: // LAECOP == 1x0xxx, 0x1xxx cond = ((ST & (ST_LH | ST_EQ)) != 0); break; case JGT: // LAECOP == x1xxxx cond = ((ST & ST_AGT)!=0); break; case JNE: // LAECOP == xx0xxx cond = ((ST & ST_EQ)==0); break; case JNC: // LAECOP == xxx0xx cond = ((ST & ST_C)==0); break; case JOC: // LAECOP == xxx1xx cond = ((ST & ST_C)!=0); break; case JNO: // LAECOP == xxxx0x cond = ((ST & ST_OV)==0); break; case JL: // LAECOP == 0x0xxx cond = ((ST & (ST_LH | ST_EQ)) == 0); break; case JH: // LAECOP == 1xxxxx cond = ((ST & ST_LH)!=0); break; case JOP: // LAECOP == xxxxx1 cond = ((ST & ST_OP)!=0); break; } if (!cond) { if (TRACE_ALU) logerror("tms99xx: Jump condition false\n"); MPC+=1; // skip next ALU call } else if (TRACE_ALU) logerror("tms99xx: Jump condition true\n"); } else { displacement = (IR & 0xff); PC = (PC + (displacement<<1)) & m_prgaddr_mask & 0xfffe; } m_state++; pulse_clock(2); } void tms99xx_device::alu_shift() { bool carry = false; bool overflow = false; UINT16 sign = 0; UINT32 value; int count; switch (m_state) { case 0: m_address = WP + ((IR & 0x000f)<<1); pulse_clock(2); break; case 1: // we have the value of the register in m_current_value // Save it (we may have to read R0) m_value_copy = m_current_value; m_address_saved = m_address; m_address = WP; m_current_value = (IR >> 4) & 0x000f; if (m_current_value != 0) { // skip the next read and ALU operation MPC = MPC+2; m_state++; } else { if (TRACE_ALU) logerror("tms99xx: Shift operation gets count from R0\n"); pulse_clock(2); } pulse_clock(2); break; case 2: // after READ pulse_clock(2); pulse_clock(2); break; case 3: count = m_current_value & 0x000f; // from the instruction or from R0 if (count==0) count = 16; value = m_value_copy; // we are re-implementing the shift operations because we have to pulse // the clock at each single shift anyway. // Also, it is easier to implement the status bit setting. // Note that count is never 0 if (m_command == SRA) sign = value & 0x8000; for (int i=0; i < count; i++) { switch (m_command) { case SRL: case SRA: carry = ((value & 1)!=0); value = (value >> 1) | sign; break; case SLA: carry = ((value & 0x8000)!=0); value <<= 1; if (carry != ((value&0x8000)!=0)) overflow = true; break; case SRC: carry = ((value & 1)!=0); value = (value>>1) | (carry? 0x8000 : 0x0000); break; } pulse_clock(2); } m_current_value = value & 0xffff; set_status_bit(ST_C, carry); set_status_bit(ST_OV, overflow); compare_and_set_lae(m_current_value, 0); m_address = m_address_saved; // Register address if (TRACE_STATUS) logerror("tms99xx: ST = %04x (val=%04x)\n", ST, m_current_value); break; } m_state++; } void tms99xx_device::alu_ai_ori() { UINT32 dest_new = 0; switch (m_command) { case AI: dest_new = m_current_value + m_value_copy; // See status bit handling for Add set_status_bit(ST_C, (dest_new & 0x10000) != 0); set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_value_copy) & 0x8000)!=0); break; case ANDI: dest_new = m_current_value & m_value_copy; break; case ORI: dest_new = m_current_value | m_value_copy; break; } m_current_value = dest_new & 0xffff; m_address = m_address_copy; compare_and_set_lae(m_current_value, 0); pulse_clock(2); } void tms99xx_device::alu_ci() { compare_and_set_lae(m_value_copy, m_current_value); pulse_clock(2); } void tms99xx_device::alu_li() { compare_and_set_lae(m_current_value, 0); pulse_clock(2); } void tms99xx_device::alu_lwpi() { WP = m_current_value & m_prgaddr_mask & 0xfffe; pulse_clock(2); } void tms99xx_device::alu_limi() { ST = (ST & 0xfff0) | (m_current_value & 0x000f); if (TRACE_STATUS) logerror("tms99xx: ST = %04x\n", ST); pulse_clock(2); } void tms99xx_device::alu_stwp_stst() { if (m_command==STST) m_current_value = ST; else m_current_value = WP; pulse_clock(2); } void tms99xx_device::alu_external() { // Call some possibly attached external device // We pass the bit pattern of the address bus to the external function // IDLE = 0000 0011 0100 0000 // RSET = 0000 0011 0110 0000 // CKON = 0000 0011 1010 0000 // CKOF = 0000 0011 1100 0000 // LREX = 0000 0011 1110 0000 // --- if (m_command == IDLE) m_idle_state = true; if (!m_external_operation.isnull()) m_external_operation((IR >> 5) & 0x07, 1, 0xff); pulse_clock(2); } void tms99xx_device::alu_rtwp() { switch (m_state) { case 0: m_address = WP + 30; // R15 pulse_clock(2); break; case 1: ST = m_current_value; m_address -= 2; // R14 break; case 2: PC = m_current_value & m_prgaddr_mask & 0xfffe; m_address -= 2; // R13 break; case 3: WP = m_current_value & m_prgaddr_mask & 0xfffe; pulse_clock(2); break; } m_state++; } void tms99xx_device::alu_int() { if (TRACE_EMU) logerror("tms99xx: INT state %d; irq_level %d\n", m_state, m_irq_level); switch (m_state) { case 0: if (m_irq_level == RESET_INT) { m_address = 0; pulse_clock(2); } else { if (m_irq_level == LOAD_INT) m_address = 0xfffc; // will be truncated for TMS9980 else { m_address = (m_irq_level << 2); } } break; case 1: m_address_copy = m_address; m_value_copy = WP; // old WP WP = m_current_value & m_prgaddr_mask & 0xfffe; // new WP m_current_value = ST; m_address = (WP + 30) & m_prgaddr_mask; break; case 2: m_current_value = PC; m_address = (WP + 28) & m_prgaddr_mask; break; case 3: m_current_value = m_value_copy; // old WP m_address = (WP + 26) & m_prgaddr_mask; break; case 4: m_address = (m_address_copy + 2) & 0xfffe & m_prgaddr_mask; if (TRACE_ALU) logerror("tms99xx: read from %04x\n", m_address); break; case 5: PC = m_current_value & m_prgaddr_mask & 0xfffe; if (m_irq_level > 0 ) { ST = (ST & 0xfff0) | (m_irq_level - 1); } break; } m_state++; pulse_clock(2); } /**************************************************************************/ UINT32 tms99xx_device::execute_min_cycles() const { return 2; } // TODO: Compute this value, just a wild guess for the average UINT32 tms99xx_device::execute_max_cycles() const { return 10; } UINT32 tms99xx_device::execute_input_lines() const { return 2; } // clocks to cycles, cycles to clocks = id // execute_default_irq_vector = 0 // execute_burn = nop // device_disasm_interface overrides UINT32 tms99xx_device::disasm_min_opcode_bytes() const { return 2; } UINT32 tms99xx_device::disasm_max_opcode_bytes() const { return 6; } offs_t tms99xx_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options) { extern CPU_DISASSEMBLE( tms9900 ); return CPU_DISASSEMBLE_NAME(tms9900)(this, buffer, pc, oprom, opram, options); } const device_type TMS9900 = &device_creator;