diff options
Diffstat (limited to 'src/devices/cpu/tms1000/tms1k_base.cpp')
-rw-r--r-- | src/devices/cpu/tms1000/tms1k_base.cpp | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/src/devices/cpu/tms1000/tms1k_base.cpp b/src/devices/cpu/tms1000/tms1k_base.cpp new file mode 100644 index 00000000000..10e041b5104 --- /dev/null +++ b/src/devices/cpu/tms1000/tms1k_base.cpp @@ -0,0 +1,635 @@ +// license:BSD-3-Clause +// copyright-holders:Wilbert Pol, hap +/* + + TMS1000 family - base/shared + + TODO: + - fix debugger disasm view + + +The TMS0980 and TMS1000-family MCU cores are very similar. The TMS0980 has a +slightly bigger addressable area and uses 9bit instructions where the TMS1000 +family uses 8bit instruction. The instruction set themselves are very similar +though. + +Each instruction takes 12 cycles to execute in 2 phases: a fetch phase and an +execution phase. The execution phase takes place at the same time as the fetch +phase of the next instruction. So, during execution there are both fetch and +execution operations taking place. The operation can be split up as follows: +cycle #0 + - Fetch: + 1. ROM address 0 + - Execute: + 1. Read RAM + 2. Clear ALU inputs + 3. Execute BRANCH/CALL/RETN part #2 + 4. K input valid +cycle #1 + - Fetch: + 1. ROM address 1 + - Execute: + 1. Update ALU inputs +cycle #2 + - Fetch: + 1. nothing/wait(?) + - Execute: + 1. Perform ALU operation + 2. Write RAM +cycle #3 + - Fetch: + 1. Fetch/Update PC/RAM address #1 + - Execute: + 1. Register store part #1 +cycle #4 + - Fetch: + 1. Fetch/Update PC/RAM address #2 + - Execute: + 1. Register store part #2 +cycle #5 + - Fetch: + 1. Instruction decode + - Execute: + 1. Execute BRANCH/CALL/RETN part #1 + +The MCU cores contains a set of fixed instructions and a set of +instructions created using microinstructions. A subset of the +instruction set could be defined from the microinstructions by +TI customers. + +cycle #0: 15TN, ATN, CIN, CKN, CKP, DMTP, MTN, MTP, NATN, NDMTP, YTP +cycle #2: C8(?), CKM, NE(?), STO +cycle #3,#4: AUTA, AUTY + +unknown cycle: CME, SSE, SSS + +*/ + +#include "tms1k_base.h" +#include "debugger.h" + +// disasm +void tms1k_base_device::state_string_export(const device_state_entry &entry, std::string &str) const +{ + switch (entry.index()) + { + case STATE_GENPC: + str = string_format("%03X", m_rom_address << ((m_byte_bits > 8) ? 1 : 0)); + break; + } +} + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +enum +{ + TMS1XXX_PC=1, TMS1XXX_SR, TMS1XXX_PA, TMS1XXX_PB, + TMS1XXX_A, TMS1XXX_X, TMS1XXX_Y, TMS1XXX_STATUS +}; + +void tms1k_base_device::device_start() +{ + m_program = &space(AS_PROGRAM); + m_data = &space(AS_DATA); + + m_o_mask = (1 << m_o_pins) - 1; + m_r_mask = (1 << m_r_pins) - 1; + m_pc_mask = (1 << m_pc_bits) - 1; + m_x_mask = (1 << m_x_bits) - 1; + + // resolve callbacks + m_read_k.resolve_safe(0); + m_write_o.resolve_safe(); + m_write_r.resolve_safe(); + m_power_off.resolve_safe(); + + // zerofill + m_pc = 0; + m_sr = 0; + m_pa = 0; + m_pb = 0; + m_ps = 0; + m_a = 0; + m_x = 0; + m_y = 0; + m_ca = 0; + m_cb = 0; + m_cs = 0; + m_r = 0; + m_o = 0; + m_cki_bus = 0; + m_c4 = 0; + m_p = 0; + m_n = 0; + m_adder_out = 0; + m_carry_in = 0; + m_carry_out = 0; + m_status = 0; + m_status_latch = 0; + m_eac = 0; + m_clatch = 0; + m_add = 0; + m_bl = 0; + + m_ram_in = 0; + m_dam_in = 0; + m_ram_out = 0; + m_ram_address = 0; + m_rom_address = 0; + m_opcode = 0; + m_fixed = 0; + m_micro = 0; + m_subcycle = 0; + + // register for savestates + save_item(NAME(m_pc)); + save_item(NAME(m_sr)); + save_item(NAME(m_pa)); + save_item(NAME(m_pb)); + save_item(NAME(m_ps)); + save_item(NAME(m_a)); + save_item(NAME(m_x)); + save_item(NAME(m_y)); + save_item(NAME(m_ca)); + save_item(NAME(m_cb)); + save_item(NAME(m_cs)); + save_item(NAME(m_r)); + save_item(NAME(m_o)); + save_item(NAME(m_cki_bus)); + save_item(NAME(m_c4)); + save_item(NAME(m_p)); + save_item(NAME(m_n)); + save_item(NAME(m_adder_out)); + save_item(NAME(m_carry_in)); + save_item(NAME(m_carry_out)); + save_item(NAME(m_status)); + save_item(NAME(m_status_latch)); + save_item(NAME(m_eac)); + save_item(NAME(m_clatch)); + save_item(NAME(m_add)); + save_item(NAME(m_bl)); + + save_item(NAME(m_ram_in)); + save_item(NAME(m_dam_in)); + save_item(NAME(m_ram_out)); + save_item(NAME(m_ram_address)); + save_item(NAME(m_rom_address)); + save_item(NAME(m_opcode)); + save_item(NAME(m_fixed)); + save_item(NAME(m_micro)); + save_item(NAME(m_subcycle)); + + // register state for debugger + state_add(TMS1XXX_PC, "PC", m_pc ).formatstr("%02X"); + state_add(TMS1XXX_SR, "SR", m_sr ).formatstr("%01X"); + state_add(TMS1XXX_PA, "PA", m_pa ).formatstr("%01X"); + state_add(TMS1XXX_PB, "PB", m_pb ).formatstr("%01X"); + state_add(TMS1XXX_A, "A", m_a ).formatstr("%01X"); + state_add(TMS1XXX_X, "X", m_x ).formatstr("%01X"); + state_add(TMS1XXX_Y, "Y", m_y ).formatstr("%01X"); + state_add(TMS1XXX_STATUS, "STATUS", m_status).formatstr("%01X"); + + state_add(STATE_GENPC, "curpc", m_rom_address).formatstr("%03X").noshow(); + state_add(STATE_GENFLAGS, "GENFLAGS", m_sr).formatstr("%8s").noshow(); + + m_icountptr = &m_icount; +} + + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void tms1k_base_device::device_reset() +{ + m_pa = 0xf; + m_pb = 0xf; + m_pc = 0; + m_ca = 0; + m_cb = 0; + m_cs = 0; + + m_eac = 0; + m_bl = 0; + m_add = 0; + + m_opcode = 0; + m_micro = 0; + m_fixed = 0; + + m_subcycle = 0; + + // clear outputs + m_r = 0; + m_write_r(0, m_r & m_r_mask, 0xffff); + write_o_output(0); + m_write_r(0, m_r & m_r_mask, 0xffff); + m_power_off(0); +} + + + +//------------------------------------------------- +// program counter/opcode decode +//------------------------------------------------- + +void tms1k_base_device::next_pc() +{ + // The program counter is a LFSR. To put it simply, the feedback bit is a XOR of the two highest bits, + // but it makes an exception when all low bits are set (eg. in TMS1000 case, when PC is 0x1f or 0x3f). + int high = 1 << (m_pc_bits - 1); + int fb = (m_pc << 1 & high) == (m_pc & high); + + if (m_pc == (m_pc_mask >> 1)) + fb = 1; + else if (m_pc == m_pc_mask) + fb = 0; + + m_pc = (m_pc << 1 | fb) & m_pc_mask; +} + +void tms1k_base_device::read_opcode() +{ + debugger_instruction_hook(this, m_rom_address); + m_opcode = m_program->read_byte(m_rom_address); + m_c4 = BITSWAP8(m_opcode,7,6,5,4,0,1,2,3) & 0xf; // opcode operand is bitswapped for most opcodes + + m_fixed = m_fixed_decode[m_opcode]; + m_micro = m_micro_decode[m_opcode]; + + next_pc(); +} + + + +//------------------------------------------------- +// i/o handling +//------------------------------------------------- + +void tms1k_base_device::write_o_output(UINT8 index) +{ + // a hardcoded table is supported if the output pla is unknown + m_o = (m_output_pla_table == nullptr) ? m_opla->read(index) : m_output_pla_table[index]; + m_write_o(0, m_o & m_o_mask, 0xffff); +} + +UINT8 tms1k_base_device::read_k_input() +{ + // K1,2,4,8 (KC test pin is not emulated) + return m_read_k(0, 0xff) & 0xf; +} + +void tms1k_base_device::set_cki_bus() +{ + switch (m_opcode & 0xf8) + { + // 00001XXX: K-inputs + case 0x08: + m_cki_bus = read_k_input(); + break; + + // 0011XXXX: select bit + case 0x30: case 0x38: + m_cki_bus = 1 << (m_c4 >> 2) ^ 0xf; + break; + + // 01XXXXXX: constant + case 0x00: // R2,3,4 are NANDed with eachother, and then ORed with R1, making 00000XXX valid too + case 0x40: case 0x48: case 0x50: case 0x58: case 0x60: case 0x68: case 0x70: case 0x78: + m_cki_bus = m_c4; + break; + + default: + m_cki_bus = 0; + break; + } +} + + + +//------------------------------------------------- +// fixed opcode set +//------------------------------------------------- + +// handle branches: + +// TMS1000/common +// note: add(latch) and bl(branch latch) are specific to 0980 series, +// c(chapter) bits are specific to 1100(and 1400) series + +void tms1k_base_device::op_br() +{ + // BR/BL: conditional branch + if (m_status) + { + if (m_clatch == 0) + m_pa = m_pb; + m_ca = m_cb; + m_pc = m_opcode & m_pc_mask; + } +} + +void tms1k_base_device::op_call() +{ + // CALL/CALLL: conditional call + if (m_status) + { + UINT8 prev_pa = m_pa; + + if (m_clatch == 0) + { + m_clatch = 1; + m_sr = m_pc; + m_pa = m_pb; + m_cs = m_ca; + } + m_ca = m_cb; + m_pb = prev_pa; + m_pc = m_opcode & m_pc_mask; + } +} + +void tms1k_base_device::op_retn() +{ + // RETN: return from subroutine + if (m_clatch == 1) + { + m_clatch = 0; + m_pc = m_sr; + m_ca = m_cs; + } + m_add = 0; + m_bl = 0; + m_pa = m_pb; +} + + +// handle other: + +// TMS1000/common + +void tms1k_base_device::op_sbit() +{ + // SBIT: set memory bit + if (m_ram_out == -1) + m_ram_out = m_ram_in; + m_ram_out |= (m_cki_bus ^ 0xf); +} + +void tms1k_base_device::op_rbit() +{ + // RBIT: reset memory bit + if (m_ram_out == -1) + m_ram_out = m_ram_in; + m_ram_out &= m_cki_bus; +} + +void tms1k_base_device::op_setr() +{ + // SETR: set one R-output line + m_r = m_r | (1 << m_y); + m_write_r(0, m_r & m_r_mask, 0xffff); +} + +void tms1k_base_device::op_rstr() +{ + // RSTR: reset one R-output line + m_r = m_r & ~(1 << m_y); + m_write_r(0, m_r & m_r_mask, 0xffff); +} + +void tms1k_base_device::op_tdo() +{ + // TDO: transfer accumulator and status latch to O-output + write_o_output(m_status_latch << 4 | m_a); +} + +void tms1k_base_device::op_clo() +{ + // CLO: clear O-output + write_o_output(0); +} + +void tms1k_base_device::op_ldx() +{ + // LDX: load X register with (x_bits) constant + m_x = m_c4 >> (4-m_x_bits); +} + +void tms1k_base_device::op_comx() +{ + // COMX: complement X register + m_x ^= m_x_mask; +} + +void tms1k_base_device::op_comx8() +{ + // COMX8: complement MSB of X register + // note: on TMS1100, the mnemonic is simply called "COMX" + m_x ^= 1 << (m_x_bits-1); +} + +void tms1k_base_device::op_ldp() +{ + // LDP: load page buffer with constant + m_pb = m_c4; +} + + +// TMS1100-specific + +void tms1k_base_device::op_comc() +{ + // COMC: complement chapter buffer + m_cb ^= 1; +} + + +// TMS1400-specific + +void tms1k_base_device::op_tpc() +{ + // TPC: transfer page buffer to chapter buffer + m_cb = m_pb & 3; +} + + +// TMS0980-specific (and possibly child classes) + +void tms1k_base_device::op_xda() +{ + // XDA: exchange DAM and A + // note: setting A to DAM is done with DMTP and AUTA during this instruction + m_ram_address |= (0x10 << (m_x_bits-1)); +} + +void tms1k_base_device::op_off() +{ + // OFF: request auto power-off + m_power_off(1); +} + +void tms1k_base_device::op_seac() +{ + // SEAC: set end around carry + m_eac = 1; +} + +void tms1k_base_device::op_reac() +{ + // REAC: reset end around carry + m_eac = 0; +} + +void tms1k_base_device::op_sal() +{ + // SAL: set add latch (reset is done with RETN) + m_add = 1; +} + +void tms1k_base_device::op_sbl() +{ + // SBL: set branch latch (reset is done with RETN) + m_bl = 1; +} + + + +//------------------------------------------------- +// execute_run +//------------------------------------------------- + +void tms1k_base_device::execute_run() +{ + do + { + m_icount--; + switch (m_subcycle) + { + case 0: + // fetch: rom address 1/2 + + // execute: br/call 2/2 + if (m_fixed & F_BR) op_br(); + if (m_fixed & F_CALL) op_call(); + if (m_fixed & F_RETN) op_retn(); + + // execute: k input valid, read ram, clear alu inputs + dynamic_output(); + set_cki_bus(); + m_ram_in = m_data->read_byte(m_ram_address) & 0xf; + m_dam_in = m_data->read_byte(m_ram_address | (0x10 << (m_x_bits-1))) & 0xf; + m_p = 0; + m_n = 0; + m_carry_in = 0; + + break; + + case 1: + // fetch: rom address 2/2 + m_rom_address = (m_ca << (m_pc_bits+4)) | (m_pa << m_pc_bits) | m_pc; + + // execute: update alu inputs + // N inputs + if (m_micro & M_15TN) m_n |= 0xf; + if (m_micro & M_ATN) m_n |= m_a; + if (m_micro & M_NATN) m_n |= (~m_a & 0xf); + if (m_micro & M_CKN) m_n |= m_cki_bus; + if (m_micro & M_MTN) m_n |= m_ram_in; + + // P inputs + if (m_micro & M_CKP) m_p |= m_cki_bus; + if (m_micro & M_MTP) m_p |= m_ram_in; + if (m_micro & M_YTP) m_p |= m_y; + if (m_micro & M_DMTP) m_p |= m_dam_in; + if (m_micro & M_NDMTP) m_p |= (~m_dam_in & 0xf); + + // carry input + if (m_micro & M_CIN) m_carry_in |= 1; + if (m_micro & M_SSS) m_carry_in |= m_eac; + + break; + + case 2: + { + // fetch: nothing + + // execute: perform alu logic + // note: officially, only 1 alu operation is allowed per opcode + m_adder_out = m_p + m_n + m_carry_in; + int carry_out = m_adder_out >> 4 & 1; + int status = 1; + m_ram_out = -1; + + if (m_micro & M_C8) status &= carry_out; + if (m_micro & M_NE) status &= (m_n != m_p); // COMP + if (m_micro & M_CKM) m_ram_out = m_cki_bus; + + // special status circuit + if (m_micro & M_SSE) + { + m_eac = m_carry_out; + if (m_add) + m_eac |= carry_out; + } + m_carry_out = carry_out; + + if (m_micro & M_STO || (m_micro & M_CME && m_eac == m_add)) + m_ram_out = m_a; + + // handle the other fixed opcodes here + if (m_fixed & F_SBIT) op_sbit(); + if (m_fixed & F_RBIT) op_rbit(); + if (m_fixed & F_SETR) op_setr(); + if (m_fixed & F_RSTR) op_rstr(); + if (m_fixed & F_TDO) op_tdo(); + if (m_fixed & F_CLO) op_clo(); + if (m_fixed & F_LDX) op_ldx(); + if (m_fixed & F_COMX) op_comx(); + if (m_fixed & F_COMX8) op_comx8(); + if (m_fixed & F_LDP) op_ldp(); + if (m_fixed & F_COMC) op_comc(); + if (m_fixed & F_TPC) op_tpc(); + if (m_fixed & F_OFF) op_off(); + if (m_fixed & F_SEAC) op_seac(); + if (m_fixed & F_REAC) op_reac(); + if (m_fixed & F_SAL) op_sal(); + if (m_fixed & F_SBL) op_sbl(); + if (m_fixed & F_XDA) op_xda(); + + // after fixed opcode handling: store status, write ram + m_status = status; + if (m_ram_out != -1) + m_data->write_byte(m_ram_address, m_ram_out); + + break; + } + + case 3: + // fetch: update pc, ram address 1/2 + // execute: register store 1/2 + break; + + case 4: + // execute: register store 2/2 + if (m_micro & M_AUTA) m_a = m_adder_out & 0xf; + if (m_micro & M_AUTY) m_y = m_adder_out & 0xf; + if (m_micro & M_STSL) m_status_latch = m_status; + + // fetch: update pc, ram address 2/2 + read_opcode(); + m_ram_address = m_x << 4 | m_y; + break; + + case 5: + // fetch: instruction decode (handled above, before next_pc) + // execute: br/call 1/2 + break; + } + m_subcycle = (m_subcycle + 1) % 6; + } while (m_icount > 0); +} |