diff options
Diffstat (limited to 'src/devices/cpu/sm510/sm510.c')
-rw-r--r-- | src/devices/cpu/sm510/sm510.c | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/src/devices/cpu/sm510/sm510.c b/src/devices/cpu/sm510/sm510.c new file mode 100644 index 00000000000..ef4f704590b --- /dev/null +++ b/src/devices/cpu/sm510/sm510.c @@ -0,0 +1,376 @@ +// license:BSD-3-Clause +// copyright-holders:hap +/* + + Sharp SM510 MCU family - known chips: + - SM510: 2.7Kx8 ROM, 128x4 RAM(32x4 for LCD) + - SM511: 4Kx8 ROM, 128x4 RAM(32x4 for LCD), melody controller + - SM512: 4Kx8 ROM, 128x4 RAM(48x4 for LCD), melody controller + + Other chips that may be in the same family, investigate more when one of + them needs to get emulated: SM500, SM530/31, SM4A, SM3903, .. + + References: + - 1990 Sharp Microcomputers Data Book + - 1996 Sharp Microcomputer Databook + + TODO: + - proper support for LFSR program counter in debugger + - callback for lcd screen as MAME bitmap (when needed) + - LCD bs pin blink mode via Y register (0.5s off, 0.5s on) + - LB/SBM is correct? + - SM511 unknown opcodes + +*/ + +#include "sm510.h" +#include "debugger.h" + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +enum +{ + SM510_PC=1, SM510_ACC, SM510_BL, SM510_BM, + SM510_C, SM510_W +}; + +void sm510_base_device::device_start() +{ + m_program = &space(AS_PROGRAM); + m_data = &space(AS_DATA); + m_prgmask = (1 << m_prgwidth) - 1; + m_datamask = (1 << m_datawidth) - 1; + + // resolve callbacks + m_read_k.resolve_safe(0); + m_read_ba.resolve_safe(1); + m_read_b.resolve_safe(1); + m_write_s.resolve_safe(); + m_write_r.resolve_safe(); + + m_write_sega.resolve_safe(); + m_write_segb.resolve_safe(); + m_write_segbs.resolve_safe(); + m_write_segc.resolve_safe(); + + // zerofill + memset(m_stack, 0, sizeof(m_stack)); + m_pc = 0; + m_prev_pc = 0; + m_op = 0; + m_prev_op = 0; + m_param = 0; + m_acc = 0; + m_bl = 0; + m_bm = 0; + m_c = 0; + m_skip = false; + m_w = 0; + m_r = 0; + m_div = 0; + m_1s = false; + m_k_active = false; + m_l = 0; + m_x = 0; + m_y = 0; + m_bp = false; + m_bc = false; + m_halt = false; + m_melody_rd = 0; + m_melody_step_count = 0; + m_melody_duty_count = 0; + m_melody_duty_index = 0; + m_melody_address = 0; + + // register for savestates + save_item(NAME(m_stack)); + save_item(NAME(m_pc)); + save_item(NAME(m_prev_pc)); + save_item(NAME(m_op)); + save_item(NAME(m_prev_op)); + save_item(NAME(m_param)); + save_item(NAME(m_acc)); + save_item(NAME(m_bl)); + save_item(NAME(m_bm)); + save_item(NAME(m_c)); + save_item(NAME(m_skip)); + save_item(NAME(m_w)); + save_item(NAME(m_r)); + save_item(NAME(m_div)); + save_item(NAME(m_1s)); + save_item(NAME(m_k_active)); + save_item(NAME(m_l)); + save_item(NAME(m_x)); + save_item(NAME(m_y)); + save_item(NAME(m_bp)); + save_item(NAME(m_bc)); + save_item(NAME(m_halt)); + save_item(NAME(m_melody_rd)); + save_item(NAME(m_melody_step_count)); + save_item(NAME(m_melody_duty_count)); + save_item(NAME(m_melody_duty_index)); + save_item(NAME(m_melody_address)); + + // register state for debugger + state_add(SM510_PC, "PC", m_pc).formatstr("%04X"); + state_add(SM510_ACC, "ACC", m_acc).formatstr("%01X"); + state_add(SM510_BL, "BL", m_bl).formatstr("%01X"); + state_add(SM510_BM, "BM", m_bm).formatstr("%01X"); + state_add(SM510_C, "C", m_c).formatstr("%01X"); + state_add(SM510_W, "W", m_w).formatstr("%02X"); + + state_add(STATE_GENPC, "curpc", m_pc).formatstr("%04X").noshow(); + state_add(STATE_GENFLAGS, "GENFLAGS", m_c).formatstr("%1s").noshow(); + + m_icountptr = &m_icount; + + // init peripherals + init_divider(); + init_lcd_driver(); + init_melody(); +} + + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void sm510_base_device::device_reset() +{ + m_skip = false; + m_halt = false; + m_op = m_prev_op = 0; + do_branch(3, 7, 0); + m_prev_pc = m_pc; + + // lcd is on (Bp on, BC off, bs(y) off) + m_bp = true; + m_bc = false; + m_y = 0; + + m_r = 0; + m_write_r(0, 0, 0xff); + m_melody_rd &= ~1; +} + + + +//------------------------------------------------- +// lcd driver +//------------------------------------------------- + +inline UINT16 sm510_base_device::get_lcd_row(int column, UINT8* ram) +{ + // output 0 if lcd blackpate/bleeder is off, or in case row doesn't exist + if (ram == NULL || m_bc || !m_bp) + return 0; + + UINT16 rowdata = 0; + for (int i = 0; i < 0x10; i++) + rowdata |= (ram[i] >> column & 1) << i; + + return rowdata; +} + +TIMER_CALLBACK_MEMBER(sm510_base_device::lcd_timer_cb) +{ + // 4 columns + for (int h = 0; h < 4; h++) + { + // 16 segments per row from upper part of RAM + m_write_sega(h | SM510_PORT_SEGA, get_lcd_row(h, m_lcd_ram_a), 0xffff); + m_write_segb(h | SM510_PORT_SEGB, get_lcd_row(h, m_lcd_ram_b), 0xffff); + m_write_segc(h | SM510_PORT_SEGC, get_lcd_row(h, m_lcd_ram_c), 0xffff); + + // bs output from L/X and Y regs + UINT8 bs = (m_l >> h & 1) | ((m_x*2) >> h & 2); + m_write_segbs(h | SM510_PORT_SEGBS, (m_bc || !m_bp) ? 0 : bs, 0xffff); + } + + // schedule next timeout + m_lcd_timer->adjust(attotime::from_ticks(0x200, unscaled_clock())); +} + +void sm510_base_device::init_lcd_driver() +{ + // note: in reality, this timer runs at high frequency off the main divider, strobing one segment at a time + m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sm510_base_device::lcd_timer_cb), this)); + m_lcd_timer->adjust(attotime::from_ticks(0x200, unscaled_clock())); // 64hz default +} + + + +//------------------------------------------------- +// melody controller +//------------------------------------------------- + +void sm510_base_device::clock_melody() +{ + if (!m_melody_rom) + return; + + // tone cycle table (SM511/SM512 datasheet fig.5) + // cmd 0 = cmd, 1 = stop, > 13 = illegal(unknown) + static const UINT8 lut_tone_cycles[4*16] = + { + 0, 0, 7, 8, 8, 9, 9, 10,11,11,12,13,14,14, 7*2, 8*2, + 0, 0, 8, 8, 9, 9, 10,11,11,12,13,13,14,15, 8*2, 8*2, + 0, 0, 8, 8, 9, 9, 10,10,11,12,12,13,14,15, 8*2, 8*2, + 0, 0, 8, 9, 9, 10,10,11,11,12,13,14,14,15, 8*2, 9*2 + }; + + UINT8 cmd = m_melody_rom[m_melody_address] & 0x3f; + UINT8 out = 0; + + // clock duty cycle if tone is active + if ((cmd & 0xf) > 1) + { + out = m_melody_duty_index & m_melody_rd & 1; + m_melody_duty_count++; + int index = m_melody_duty_index << 4 | (cmd & 0xf); + int shift = ~cmd >> 4 & 1; // OCT + + if (m_melody_duty_count >= (lut_tone_cycles[index] << shift)) + { + m_melody_duty_count = 0; + m_melody_duty_index = (m_melody_duty_index + 1) & 3; + } + } + else if ((cmd & 0xf) == 1) + { + // rest tell signal + m_melody_rd |= 2; + } + + // clock time base on F8(d7) + if ((m_div & 0x7f) == 0) + { + UINT8 mask = (cmd & 0x20) ? 0x1f : 0x0f; + m_melody_step_count = (m_melody_step_count + 1) & mask; + + if (m_melody_step_count == 0) + m_melody_address++; + } + + // output to R pin + if (out != m_r) + { + m_write_r(0, out, 0xff); + m_r = out; + } +} + +void sm510_base_device::init_melody() +{ + if (!m_melody_rom) + return; + + // verify melody rom + for (int i = 0; i < 0x100; i++) + { + UINT8 data = m_melody_rom[i]; + if (data & 0xc0 || (data & 0x0f) > 13) + logerror("%s unknown melody ROM data $%02X at $%02X\n", tag(), data, i); + } +} + + + +//------------------------------------------------- +// interrupt/divider +//------------------------------------------------- + +bool sm510_base_device::wake_me_up() +{ + // in halt mode, wake up after 1S signal or K input + if (m_k_active || m_1s) + { + // note: official doc warns that Bl/Bm and the stack are undefined + // after waking up, but we leave it unchanged + m_halt = false; + do_branch(1, 0, 0); + + standard_irq_callback(0); + return true; + } + else + return false; +} + +void sm510_base_device::execute_set_input(int line, int state) +{ + if (line != SM510_INPUT_LINE_K) + return; + + // set K input lines active state + m_k_active = (state != 0); +} + +TIMER_CALLBACK_MEMBER(sm510_base_device::div_timer_cb) +{ + m_div = (m_div + 1) & 0x7fff; + + // 1S signal on overflow(falling edge of f1) + if (m_div == 0) + m_1s = true; + + clock_melody(); +} + +void sm510_base_device::init_divider() +{ + m_div_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sm510_base_device::div_timer_cb), this)); + m_div_timer->adjust(attotime::from_ticks(1, unscaled_clock()), 0, attotime::from_ticks(1, unscaled_clock())); +} + + + +//------------------------------------------------- +// execute +//------------------------------------------------- + +void sm510_base_device::increment_pc() +{ + // PL(program counter low 6 bits) is a simple LFSR: newbit = (bit0==bit1) + // PU,PM(high bits) specify page, PL specifies steps within page + int feed = ((m_pc >> 1 ^ m_pc) & 1) ? 0 : 0x20; + m_pc = feed | (m_pc >> 1 & 0x1f) | (m_pc & ~0x3f); +} + +void sm510_base_device::execute_run() +{ + while (m_icount > 0) + { + m_icount--; + + if (m_halt && !wake_me_up()) + { + // got nothing to do + m_icount = 0; + return; + } + + // remember previous state + m_prev_op = m_op; + m_prev_pc = m_pc; + + // fetch next opcode + debugger_instruction_hook(this, m_pc); + m_op = m_program->read_byte(m_pc); + increment_pc(); + get_opcode_param(); + + // handle opcode if it's not skipped + if (m_skip) + { + m_skip = false; + m_op = 0; // fake nop + } + else + execute_one(); + } +} |