mame/src/devices/cpu/tms1000/smc1102.cpp
hap 0b93402dcf smc1102: add lcd and interrupt handling
Systems promoted to working
---------------------------
K28: Talking Learning Computer (model 7-232) [hap, Sean Riddle]
2023-04-14 21:57:39 +02:00

306 lines
7.4 KiB
C++

// license:BSD-3-Clause
// copyright-holders:hap
/*
Suwa Seikosha (now Seiko Epson) SMC1102, SMC1112
SMC1102 is a CMOS MCU based on TMS1100, keeping the same ALU and opcode mnemonics.
They added a timer, interrupts, and a built-in LCD controller.
In the USA, it was marketed by S-MOS Systems, an affiliate of Seiko Group.
SMC1112 die notes (SMC1102 is assumed to be the same):
- 128x4 RAM array at top-left
- 256*64 8-bit ROM array at the bottom
- 30-term MPLA with 14 microinstructions, and 16 fixed opcodes next to it
(assumed neither of them is supposed to be customized)
- 32x4 LCD RAM at the left
- no output PLA
TODO:
- each opcode is 4 cycles instead of 6
- LCD refresh timing is unknown
- add timer
- add halt opcode
*/
#include "emu.h"
#include "smc1102.h"
#include "tms1k_dasm.h"
// device definitions
DEFINE_DEVICE_TYPE(SMC1102, smc1102_cpu_device, "smc1102", "Suwa Seikosha SMC1102") // 60-pin QFP or 42-pin DIP
DEFINE_DEVICE_TYPE(SMC1112, smc1112_cpu_device, "smc1112", "Suwa Seikosha SMC1112") // low power version
smc1102_cpu_device::smc1102_cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, u8 o_pins, u8 r_pins, u8 pc_bits, u8 byte_bits, u8 x_bits, u8 stack_levels, int rom_width, address_map_constructor rom_map, int ram_width, address_map_constructor ram_map) :
tms1100_cpu_device(mconfig, type, tag, owner, clock, o_pins, r_pins, pc_bits, byte_bits, x_bits, stack_levels, rom_width, rom_map, ram_width, ram_map),
m_write_segs(*this)
{ }
smc1102_cpu_device::smc1102_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
smc1102_cpu_device(mconfig, SMC1102, tag, owner, clock, 0 /* o pins */, 8 /* r pins */, 6 /* pc bits */, 8 /* byte width */, 3 /* x width */, 4 /* stack levels */, 11 /* rom width */, address_map_constructor(FUNC(smc1102_cpu_device::rom_11bit), this), 7 /* ram width */, address_map_constructor(FUNC(smc1102_cpu_device::ram_7bit), this))
{ }
smc1112_cpu_device::smc1112_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
smc1102_cpu_device(mconfig, SMC1112, tag, owner, clock, 0, 8, 6, 8, 3, 4, 11, address_map_constructor(FUNC(smc1112_cpu_device::rom_11bit), this), 7, address_map_constructor(FUNC(smc1112_cpu_device::ram_7bit), this))
{ }
// disasm
std::unique_ptr<util::disasm_interface> smc1102_cpu_device::create_disassembler()
{
return std::make_unique<smc1102_disassembler>();
}
// device_start/reset
void smc1102_cpu_device::device_start()
{
tms1100_cpu_device::device_start();
m_write_segs.resolve_safe();
// zerofill
memset(m_lcd_ram, 0, sizeof(m_lcd_ram));
m_lcd_sr = 0;
m_inten = false;
m_selin = 0;
m_k_line = false;
memset(m_stack, 0, sizeof(m_stack));
m_sp = 0;
m_pb_stack = 0;
m_cb_stack = 0;
m_x_stack = 0;
m_y_stack = 0;
m_s_stack = 0;
// register for savestates
save_item(NAME(m_lcd_ram));
save_item(NAME(m_lcd_sr));
save_item(NAME(m_inten));
save_item(NAME(m_selin));
save_item(NAME(m_k_line));
save_item(NAME(m_stack));
save_item(NAME(m_sp));
save_item(NAME(m_pb_stack));
save_item(NAME(m_cb_stack));
save_item(NAME(m_x_stack));
save_item(NAME(m_y_stack));
save_item(NAME(m_s_stack));
}
void smc1102_cpu_device::device_reset()
{
tms1100_cpu_device::device_reset();
m_inten = false;
m_selin = 0;
// changed/added fixed instructions (mostly handled in op_extra)
m_fixed_decode[0x0a] = F_EXTRA;
m_fixed_decode[0x71] = F_EXTRA;
m_fixed_decode[0x74] = F_EXTRA;
m_fixed_decode[0x75] = F_EXTRA;
m_fixed_decode[0x76] = F_RETN;
m_fixed_decode[0x78] = F_EXTRA;
m_fixed_decode[0x7b] = F_EXTRA;
m_fixed_decode[0x72] = m_fixed_decode[0x73] = F_EXTRA;
m_fixed_decode[0x7c] = m_fixed_decode[0x7d] = F_EXTRA;
}
u32 smc1102_cpu_device::decode_micro(offs_t offset)
{
// TCY, YNEC, TCMIY
static const u16 micro1[3] = { 0x0402, 0x1204, 0x1032 };
// 0x20, 0x30, 0x00, 0x70
static const u16 micro2[0x40] =
{
0x0102, 0x0801, 0x0802, 0x1001, 0x306a, 0x303a, 0x2021, 0x2020,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0e04, 0x0e04, 0x0e04, 0x0e04, 0x0899, 0x0099, 0x0819, 0x0804,
0x0904, 0x0898, 0x1104, 0x2821, 0x104a, 0x101a, 0x0909, 0x0849,
0x0401, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0404, 0x0000,
0x0519, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0519,
0x0000, 0x0519, 0x0519, 0x0000, 0x0000, 0x0000, 0x0519, 0x0419,
};
u16 mask = 0;
if (offset >= 0x40 && offset < 0x70)
mask = micro1[offset >> 4 & 3];
else if (offset < 0x80 && (offset & 0xf0) != 0x10)
mask = micro2[((offset ^ 0x20) | (offset >> 1 & 0x20)) & 0x3f];
// does not have M_MTN or M_STSL
const u32 md[14] = { M_AUTA, M_AUTY, M_NE, M_C8, M_CIN, M_CKM, M_15TN, M_NATN, M_ATN, M_CKN, M_CKP, M_MTP, M_YTP, M_STO };
u32 decode = 0;
for (int bit = 0; bit < 14; bit++)
if (mask & (1 << bit))
decode |= md[bit];
return decode;
}
// interrupt/timer
void smc1102_cpu_device::execute_set_input(int line, int state)
{
switch (line)
{
case SMC1102_INPUT_LINE_K:
m_k_line = bool(state);
break;
default:
break;
}
}
void smc1102_cpu_device::read_opcode()
{
// return from interrupt
if (m_opcode == 0x76)
{
// restore registers
m_pb = m_pb_stack;
m_cb = m_cb_stack;
m_x = m_x_stack;
m_y = m_y_stack;
m_status = m_s_stack;
}
// check interrupts (blocked after INTEN)
if (m_inten && m_opcode != 0x74)
{
bool taken = (m_selin & 2) ? false : m_k_line;
if (taken)
{
interrupt();
return;
}
}
tms1100_cpu_device::read_opcode();
}
void smc1102_cpu_device::interrupt()
{
standard_irq_callback(0, m_rom_address);
// save registers
m_pb_stack = m_pb;
m_cb_stack = m_cb;
m_x_stack = m_x;
m_y_stack = m_y;
m_s_stack = m_status;
// insert CALL to 0 on page 14
m_opcode = 0xc0;
m_c4 = 0;
m_fixed = m_fixed_decode[m_opcode];
m_micro = m_micro_decode[m_opcode];
m_pb = 0xe;
m_cb = 0;
m_status = 1;
m_inten = false;
}
// opcode deviations
void smc1102_cpu_device::op_call()
{
// CALL: call subroutine
if (!m_status)
return;
m_stack[m_sp] = m_ca << 10 | m_pa << 6 | m_pc;
m_sp = (m_sp + 1) % m_stack_levels;
m_pc = m_opcode & m_pc_mask;
m_pa = m_pb;
m_ca = m_cb;
}
void smc1102_cpu_device::op_retn()
{
// RETN: return from subroutine
m_sp = (m_stack_levels + m_sp - 1) % m_stack_levels;
m_pc = m_stack[m_sp] & m_pc_mask;
m_pa = m_pb = m_stack[m_sp] >> 6 & 0xf;
m_ca = m_stack[m_sp] >> 10 & 1; // not CB
}
void smc1102_cpu_device::op_tasr()
{
// TASR: transfer A to LCD S/R
m_lcd_sr = m_lcd_sr << 4 | m_a;
}
void smc1102_cpu_device::op_tsg()
{
// TSG: transfer LCD S/R to RAM
m_lcd_ram[m_opcode & 3] = m_lcd_sr;
m_write_segs(m_opcode & 3, m_lcd_sr);
}
void smc1102_cpu_device::op_intdis()
{
// INTDIS: disable interrupt
m_inten = false;
}
void smc1102_cpu_device::op_inten()
{
// INTEN: enable interrupt after next instruction
m_inten = true;
}
void smc1102_cpu_device::op_selin()
{
// SELIN: select interrupt
m_selin = m_a & 3;
}
void smc1102_cpu_device::op_tmset()
{
// TMSET: transfer A to timer latch
}
void smc1102_cpu_device::op_halt()
{
// HALT: stop CPU
}
void smc1102_cpu_device::op_extra()
{
switch (m_opcode)
{
case 0x0a: op_tasr(); break;
case 0x71: op_halt(); break;
case 0x74: op_inten(); break;
case 0x75: op_intdis(); break;
case 0x78: op_selin(); break;
case 0x7b: op_tmset(); break;
case 0x72: case 0x73: case 0x7c: case 0x7d:
op_tsg(); break;
default: break;
}
}