/***************************************************************************** * pce * *****************************************************************************/ /***************************************************************************** * File name: src/arch/spectrum/cmd.c * * Created: 2022-02-02 by Hampa Hug * * Copyright: (C) 2022-2025 Hampa Hug * *****************************************************************************/ /***************************************************************************** * This program is free software. You can redistribute it and / or modify it * * under the terms of the GNU General Public License version 2 as published * * by the Free Software Foundation. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY, without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * * Public License for more details. * *****************************************************************************/ #include "main.h" #include "snapshot.h" #include "spectrum.h" #include "video.h" #include #include #include #include #include #include #include #include static mon_cmd_t par_cmd[] = { { "c", "[cnt]", "clock" }, { "gb", "[addr...]", "run with breakpoints" }, { "g", "", "run" }, { "hm", "", "print help on messages" }, { "i", "port", "input a byte from a port" }, { "last", "on|off", "start/stop recording pc values" }, { "last", "[cnt [idx]]", "print last pc values [80 0]" }, { "n", "[0|1]", "execute to next instruction" }, { "o", "port val", "output a byte to a port" }, { "p", "[cnt]", "execute cnt instructions, skip calls [1]" }, { "r", "reg [val]", "set a register" }, { "s", "[what]", "print status (cpu|mem)" }, { "t", "[cnt]", "execute cnt instructions [1]" }, { "u", "[addr [cnt]]", "disassemble" }, { "xl", "fname", "load a snapshot" }, { "xs", "fname", "save a snapshot" } }; void spec_last_set (spectrum_t *sim, unsigned pc); static void spec_disasm_str (char *dst, e8080_disasm_t *op, int with_pc, int with_comment) { unsigned i, k, n; char comment[256]; comment[0] = 0; if (with_pc) { n = sprintf (dst, "%04X ", (unsigned) op->pc); } else { n = 0; } for (i = 0; i < op->data_cnt; i++) { n += sprintf (dst + n, "%02X ", (unsigned) op->data[i]); } for (i = op->data_cnt; i < 4; i++) { dst[n++] = ' '; dst[n++] = ' '; dst[n++] = ' '; } k = n + 8; n += sprintf (dst + n, "%s", op->op); if (op->arg_cnt > 0) { while (n < k) { dst[n++] = ' '; } n += sprintf (dst + n, "%s", op->arg[0]); for (i = 1; i < op->arg_cnt; i++) { n += sprintf (dst + n, ", %s", op->arg[i]); } } if (with_comment && (comment[0] != 0)) { while (n < 40) { dst[n++] = ' '; } dst[n++] = ';'; } dst[n] = 0; } static void spec_disasm_cur (e8080_t *c, e8080_disasm_t *op) { if (e8080_get_flags (c) & E8080_FLAG_Z80) { z80_disasm_cur (c, op); } else { e8080_disasm_cur (c, op); } } static void spec_disasm_mem (e8080_t *c, e8080_disasm_t *op, unsigned short addr) { if (e8080_get_flags (c) & E8080_FLAG_Z80) { z80_disasm_mem (c, op, addr); } else { e8080_disasm_mem (c, op, addr); } } static void spec_print_cpu_z80_1 (e8080_t *c) { e8080_disasm_t op; char str[256]; spec_disasm_cur (c, &op); spec_disasm_str (str, &op, 0, 0); if (c->halt) { pce_printf ("HALT=1 "); } pce_printf ( "A=%02X BC=%04X DE=%04X HL=%04X IX=%04X IY=%04X SP=%04X PC=%04X PSW=%02X[%c%c%c%c%c] %s\n", (unsigned) e8080_get_a (c), (unsigned) e8080_get_bc (c), (unsigned) e8080_get_de (c), (unsigned) e8080_get_hl (c), (unsigned) e8080_get_ix (c), (unsigned) e8080_get_iy (c), (unsigned) e8080_get_sp (c), (unsigned) e8080_get_pc (c), (unsigned) e8080_get_psw (c), (e8080_get_sf (c)) ? 'S' : '-', (e8080_get_zf (c)) ? 'Z' : '-', (e8080_get_af (c)) ? 'A' : '-', (e8080_get_pf (c)) ? 'P' : '-', (e8080_get_cf (c)) ? 'C' : '-', str ); } static void spec_print_cpu_8080_1 (e8080_t *c) { e8080_disasm_t op; char str[256]; spec_disasm_cur (c, &op); spec_disasm_str (str, &op, 0, 0); if (c->halt) { pce_printf ("HALT=1 "); } pce_printf ( "A=%02X BC=%04X DE=%04X HL=%04X SP=%04X PC=%04X PSW=%02X[%c%c%c%c%c] %s\n", (unsigned) e8080_get_a (c), (unsigned) e8080_get_bc (c), (unsigned) e8080_get_de (c), (unsigned) e8080_get_hl (c), (unsigned) e8080_get_sp (c), (unsigned) e8080_get_pc (c), (unsigned) e8080_get_psw (c), (e8080_get_sf (c)) ? 'S' : '-', (e8080_get_zf (c)) ? 'Z' : '-', (e8080_get_af (c)) ? 'A' : '-', (e8080_get_pf (c)) ? 'P' : '-', (e8080_get_cf (c)) ? 'C' : '-', str ); } static void spec_print_cpu_z80_2 (e8080_t *c) { e8080_disasm_t op; char str[256]; spec_disasm_cur (c, &op); spec_disasm_str (str, &op, 1, 1); pce_printf ( "A=%02X BC=%04X DE=%04X HL=%04X IX=%04X IY=%04X" " SP=%04X PC=%04X F=%02X[%c%c%c%c%c] [%c%c]\n" "R=%02X I=%02X IM=%u IFF=%u/%u IPC=%04X ICNT=%u HALT=%u\n" "%s\n", e8080_get_a (c), e8080_get_bc (c), e8080_get_de (c), e8080_get_hl (c), e8080_get_ix (c), e8080_get_iy (c), e8080_get_sp (c), e8080_get_pc (c), e8080_get_psw (c), (e8080_get_sf (c)) ? 'S' : '-', (e8080_get_zf (c)) ? 'Z' : '-', (e8080_get_af (c)) ? 'A' : '-', (e8080_get_pf (c)) ? 'P' : '-', (e8080_get_cf (c)) ? 'C' : '-', c->int_val ? 'I' : '-', c->nmi_val ? 'N' : '-', e8080_get_r (c), e8080_get_i (c), e8080_get_im (c), e8080_get_iff1 (c), e8080_get_iff2 (c), e8080_get_int_pc (c), e8080_get_int_cnt (c), e8080_get_halt (c), str ); } static void spec_print_cpu_8080_2 (e8080_t *c) { e8080_disasm_t op; char str[256]; spec_disasm_cur (c, &op); spec_disasm_str (str, &op, 1, 1); pce_printf ( "A=%02X BC=%04X DE=%04X HL=%04X SP=%04X PC=%04X PSW=%02X[%c%c%c%c%c]\n" "%s\n", (unsigned) e8080_get_a (c), (unsigned) e8080_get_bc (c), (unsigned) e8080_get_de (c), (unsigned) e8080_get_hl (c), (unsigned) e8080_get_sp (c), (unsigned) e8080_get_pc (c), (unsigned) e8080_get_psw (c), (e8080_get_sf (c)) ? 'S' : '-', (e8080_get_zf (c)) ? 'Z' : '-', (e8080_get_af (c)) ? 'A' : '-', (e8080_get_pf (c)) ? 'P' : '-', (e8080_get_cf (c)) ? 'C' : '-', str ); if (c->halt) { pce_printf ("HALT=1\n"); } } void spec_print_cpu (e8080_t *c, int oneline) { if (oneline) { if (e8080_get_flags (c) & E8080_FLAG_Z80) { spec_print_cpu_z80_1 (c); } else { spec_print_cpu_8080_1 (c); } } else { if (e8080_get_flags (c) & E8080_FLAG_Z80) { spec_print_cpu_z80_2 (c); } else { spec_print_cpu_8080_2 (c); } } } void print_state_cpu (e8080_t *c) { if (e8080_get_flags (c) & E8080_FLAG_Z80) { pce_prt_sep ("Z80"); } else { pce_prt_sep ("8080"); } spec_print_cpu (c, 0); } static void print_state_mem (spectrum_t *sim) { pce_prt_sep ("MEMORY"); mem_prt_state (sim->mem, stdout); } static void print_state_system (spectrum_t *sim) { pce_prt_sep ("SPECTRUM"); pce_printf ("IM=%u\n", e8080_get_im (sim->cpu)); pce_printf ("FE = %02X\n", sim->port_fe); pce_printf ("KEY=[%02X %02X %02X %02X %02X %02X %02X %02X]\n", sim->keys[0], sim->keys[1], sim->keys[2], sim->keys[3], sim->keys[4], sim->keys[5], sim->keys[6], sim->keys[7] ); } static void print_state_video (spectrum_t *sim) { spec_video_t *vid; vid = &sim->video; pce_prt_sep ("VIDEO"); pce_printf ("FRAME=%lu+%lu ROW=%u+%u COL=%u DE=%d\n", vid->frame_counter, vid->frame_clock, vid->row, vid->row_clock, 2 * vid->row_clock, vid->de ); } static void print_state (spectrum_t *sim, const char *str) { cmd_t cmd; cmd_set_str (&cmd, str); if (cmd_match_eol (&cmd)) { return; } while (!cmd_match_eol (&cmd)) { if (cmd_match (&cmd, "cpu")) { print_state_cpu (sim->cpu); } else if (cmd_match (&cmd, "mem")) { print_state_mem (sim); } else if (cmd_match (&cmd, "sys")) { print_state_system (sim); } else if (cmd_match (&cmd, "video")) { print_state_video (sim); } else { printf ("unknown component (%s)\n", cmd_get_str (&cmd)); return; } } } static int spec_check_break (spectrum_t *sim) { if (bps_check (&sim->bps, 0, e8080_get_pc (sim->cpu), stdout)) { return (1); } if (sim->brk) { return (1); } return (0); } static int spec_exec (spectrum_t *sim) { unsigned n1, n2; n1 = e8080_get_opcnt (sim->cpu); do { spec_clock (sim); if (sim->brk) { return (1); } n2 = e8080_get_opcnt (sim->cpu); } while (n1 == n2); return (0); } static int spec_exec_to (spectrum_t *sim, unsigned short addr) { while (e8080_get_pc (sim->cpu) != addr) { spec_exec (sim); if (spec_check_break (sim)) { return (1); } } return (0); } static int spec_exec_off (spectrum_t *sim, unsigned short addr) { unsigned icnt; icnt = e8080_get_int_cnt (sim->cpu); while (e8080_get_pc (sim->cpu) == addr) { spec_exec (sim); if (spec_check_break (sim)) { return (1); } if (e8080_get_int_cnt (sim->cpu) != icnt) { if (spec_exec_to (sim, e8080_get_int_pc (sim->cpu))) { return (1); } icnt = e8080_get_int_cnt (sim->cpu); } } return (0); } void spec_run (spectrum_t *sim) { pce_start (&sim->brk); spec_clock_discontinuity (sim); while (1) { spec_clock (sim); if (sim->brk) { break; } } pce_stop(); } static int spec_hook_exec (void *ext) { spectrum_t *sim = ext; spec_last_set (sim, e8080_get_pc (sim->cpu)); return (0); } static int spec_hook_undef (void *ext, unsigned op) { spectrum_t *sim = ext; pce_log (MSG_DEB, "%04X: undefined operation [%02X %02X %02X %02X]\n", (unsigned) e8080_get_pc (sim->cpu), (unsigned) e8080_get_mem8 (sim->cpu, e8080_get_pc (sim->cpu)), (unsigned) e8080_get_mem8 (sim->cpu, e8080_get_pc (sim->cpu) + 1), (unsigned) e8080_get_mem8 (sim->cpu, e8080_get_pc (sim->cpu) + 2), (unsigned) e8080_get_mem8 (sim->cpu, e8080_get_pc (sim->cpu) + 3) ); /* pce_usleep (5000); */ return (0); } static int spec_hook_rst (void *ext, unsigned n) { return (0); } void spec_last_enable (spectrum_t *sim, int enable) { unsigned i; enable = (enable != 0); if (sim->last_enabled == enable) { return; } sim->last_enabled = enable; sim->last_idx = 0; for (i = 0; i < SPEC_LAST_CNT; i++) { sim->last[i] = 0; } if (sim->last_enabled) { e8080_set_hook_exec_fct (sim->cpu, sim, spec_hook_exec); } else { e8080_set_hook_exec_fct (sim->cpu, NULL, NULL); } } unsigned spec_last_get (spectrum_t *sim, unsigned idx) { if (sim->last_enabled == 0) { return (0); } if (idx == 0) { return (e8080_get_pc (sim->cpu)); } if (idx > SPEC_LAST_CNT) { return (0); } idx = (sim->last_idx + SPEC_LAST_CNT - (idx - 1)) % SPEC_LAST_CNT; return (sim->last[idx]); } void spec_last_set (spectrum_t *sim, unsigned pc) { if (sim->last[sim->last_idx] == pc) { return; } sim->last_idx = (sim->last_idx + 1) % SPEC_LAST_CNT; sim->last[sim->last_idx] = pc; } static void spec_cmd_c (cmd_t *cmd, spectrum_t *sim) { unsigned short cnt; cnt = 1; cmd_match_uint16 (cmd, &cnt); if (!cmd_match_end (cmd)) { return; } while (cnt > 0) { spec_clock (sim); cnt -= 1; } print_state_cpu (sim->cpu); } static void spec_cmd_g_b (cmd_t *cmd, spectrum_t *sim) { unsigned short addr; breakpoint_t *bp; while (cmd_match_uint16 (cmd, &addr)) { bp = bp_addr_new (addr); bps_bp_add (&sim->bps, bp); } if (!cmd_match_end (cmd)) { return; } pce_start (&sim->brk); spec_clock_discontinuity (sim); while (1) { spec_exec (sim); if (spec_check_break (sim)) { break; } } pce_stop(); } static void spec_cmd_g (cmd_t *cmd, spectrum_t *sim) { if (cmd_match (cmd, "b")) { spec_cmd_g_b (cmd, sim); return; } if (!cmd_match_end (cmd)) { return; } spec_run (sim); } static void spec_cmd_hm (cmd_t *cmd) { pce_puts ( "emu.exit\n" "emu.reset\n" "emu.stop\n" "\n" "emu.cas.commit\n" "emu.cas.create \n" "emu.cas.play\n" "emu.cas.load []\n" "emu.cas.read \n" "emu.cas.record\n" "emu.cas.space\n" "emu.cas.state\n" "emu.cas.stop\n" "emu.cas.truncate\n" "emu.cas.write \n" "\n" "emu.term.fullscreen \"0\" | \"1\"\n" "emu.term.fullscreen.toggle\n" "emu.term.grab\n" "emu.term.release\n" "emu.term.screenshot []\n" "emu.term.title \n" ); } static void spec_cmd_i (cmd_t *cmd, spectrum_t *sim) { unsigned short port; while (cmd_match_uint16 (cmd, &port)) { pce_printf ("%04X: %02X\n", port, e8080_get_port8 (sim->cpu, port) ); } cmd_match_end (cmd); } static void spec_cmd_last (cmd_t *cmd, spectrum_t *sim) { unsigned i, j, k; unsigned col, pc; unsigned short idx, cnt; if (cmd_match (cmd, "on")) { spec_last_enable (sim, 1); cmd_match_end (cmd); return; } if (cmd_match (cmd, "off")) { spec_last_enable (sim, 0); cmd_match_end (cmd); return; } if (sim->last_enabled == 0) { return; } col = 8; cnt = 128; idx = 0; cmd_match_uint16 (cmd, &cnt); cmd_match_uint16 (cmd, &idx); if (!cmd_match_end (cmd)) { return; } cnt = (cnt + col - 1) / col; for (i = cnt; i > 0; i--) { for (j = col; j > 0; j--) { k = idx + (j - 1) * cnt + i - 1; pc = spec_last_get (sim, k); pce_printf (" %04X", pc); } pce_putc ('\n'); } } static void spec_cmd_n (cmd_t *cmd, spectrum_t *sim) { int s; unsigned pc; unsigned short i, n; e8080_disasm_t dis; n = 1; s = 0; if (cmd_match_uint16 (cmd, &n)) { s = 1; } if (!cmd_match_end (cmd)) { return; } pce_start (&sim->brk); spec_clock_discontinuity (sim); for (i = 0; i < n; i++) { spec_disasm_cur (sim->cpu, &dis); if (s) { spec_print_cpu (sim->cpu, 1); } pc = (e8080_get_pc (sim->cpu) + dis.data_cnt) & 0xffff; if (spec_exec_to (sim, pc)) { pce_printf ("T %04X\n", pc); break; } } if (s) { spec_print_cpu (sim->cpu, 1); } pce_stop(); print_state_cpu (sim->cpu); } static void spec_cmd_o (cmd_t *cmd, spectrum_t *sim) { unsigned short port, val; if (!cmd_match_uint16 (cmd, &port)) { cmd_error (cmd, "need a port address"); return; } if (!cmd_match_uint16 (cmd, &val)) { cmd_error (cmd, "need a value"); return; } if (!cmd_match_end (cmd)) { return; } e8080_set_port8 (sim->cpu, port, val); } static void spec_cmd_p (cmd_t *cmd, spectrum_t *sim) { int s; unsigned short i, n; unsigned pc, to, op; if (cmd_match (cmd, "o")) { spec_cmd_n (cmd, sim); return; } n = 1; s = 0; if (cmd_match_uint16 (cmd, &n)) { s = 1; } if (!cmd_match_end (cmd)) { return; } pce_start (&sim->brk); spec_clock_discontinuity (sim); for (i = 0; i < n; i++) { if (s) { spec_print_cpu (sim->cpu, 1); } pc = e8080_get_pc (sim->cpu); op = e8080_get_mem8 (sim->cpu, pc); to = pc; if ((op == 0xcd) || ((op & 0xc7) == 0xc4)) { /* call */ to = (pc + 3) & 0xffff; } else if ((op & 0xc7) == 0xc7) { /* rst */ to = (pc + 1) & 0xffff; } else if (op == 0x10) { /* djnz */ to = (pc + 2) & 0xffff; } if (to != pc) { if (spec_exec_to (sim, to)) { pce_printf ("T %04X\n", pc); break; } } else { if (spec_exec_off (sim, pc)) { pce_printf ("F %04X\n", pc); break; } } } if (s) { spec_print_cpu (sim->cpu, 1); } pce_stop(); print_state_cpu (sim->cpu); } static void spec_cmd_r (cmd_t *cmd, spectrum_t *sim) { unsigned long val; char sym[256]; if (cmd_match_eol (cmd)) { print_state_cpu (sim->cpu); return; } if (!cmd_match_ident (cmd, sym, 256)) { cmd_error (cmd, "missing register"); return; } if (e8080_get_reg (sim->cpu, sym, &val)) { cmd_error (cmd, "bad register\n"); return; } if (cmd_match_eol (cmd)) { pce_printf ("%02lX\n", val); return; } if (!cmd_match_uint32 (cmd, &val)) { cmd_error (cmd, "missing value\n"); return; } if (!cmd_match_end (cmd)) { return; } e8080_set_reg (sim->cpu, sym, val); print_state_cpu (sim->cpu); } static void spec_cmd_s (cmd_t *cmd, spectrum_t *sim) { if (cmd_match_eol (cmd)) { print_state_cpu (sim->cpu); return; } print_state (sim, cmd_get_str (cmd)); } static void spec_cmd_t (cmd_t *cmd, spectrum_t *sim) { int s; unsigned short i, n; n = 1; s = 0; if (cmd_match_uint16 (cmd, &n)) { s = 1; } if (!cmd_match_end (cmd)) { return; } pce_start (&sim->brk); spec_clock_discontinuity (sim); for (i = 0; i < n; i++) { if (s) { spec_print_cpu (sim->cpu, 1); } if (spec_exec (sim)) { break; } } if (s) { spec_print_cpu (sim->cpu, 1); } pce_stop(); print_state_cpu (sim->cpu); } static void spec_cmd_u (cmd_t *cmd, spectrum_t *sim) { int to; unsigned i; unsigned short addr, cnt, taddr; static unsigned int first = 1; static unsigned short saddr = 0; e8080_disasm_t op; char str[256]; if (first) { first = 0; saddr = e8080_get_pc (sim->cpu); } to = 0; addr = saddr; cnt = 16; if (cmd_match (cmd, "t")) { to = 1; } if (cmd_match_uint16 (cmd, &addr)) { cmd_match_uint16 (cmd, &cnt); } if (!cmd_match_end (cmd)) { return; } if (to) { if (addr < (2 * cnt)) { taddr = 0; } else { taddr = addr - 2 * cnt; } while (taddr <= addr) { spec_disasm_mem (sim->cpu, &op, taddr); spec_disasm_str (str, &op, 1, 1); pce_printf ("%s\n", str); taddr += op.data_cnt; } } else { for (i = 0; i < cnt; i++) { spec_disasm_mem (sim->cpu, &op, addr); spec_disasm_str (str, &op, 1, 1); pce_printf ("%s\n", str); addr += op.data_cnt; } } saddr = addr; } static void spec_cmd_xl (cmd_t *cmd, spectrum_t *sim) { char name[256]; if (!cmd_match_str (cmd, name, 256)) { return; } if (!cmd_match_end (cmd)) { return; } if (spec_snap_load (sim, name)) { pce_printf ("load error (%s)\n", name); } } static void spec_cmd_xs (cmd_t *cmd, spectrum_t *sim) { char name[256]; if (!cmd_match_str (cmd, name, 256)) { return; } if (!cmd_match_end (cmd)) { return; } if (spec_snap_save (sim, name)) { pce_printf ("load error (%s)\n", name); } } static void spec_cmd_x (cmd_t *cmd, spectrum_t *sim) { if (cmd_match (cmd, "l")) { spec_cmd_xl (cmd, sim); } else if (cmd_match (cmd, "s")) { spec_cmd_xs (cmd, sim); } else { cmd_error (cmd, "\n"); } } int spec_cmd (spectrum_t *sim, cmd_t *cmd) { if (sim->term != NULL) { trm_check (sim->term); } if (cmd_match (cmd, "b")) { cmd_do_b (cmd, &sim->bps); } else if (cmd_match (cmd, "c")) { spec_cmd_c (cmd, sim); } else if (cmd_match (cmd, "g")) { spec_cmd_g (cmd, sim); } else if (cmd_match (cmd, "hm")) { spec_cmd_hm (cmd); } else if (cmd_match (cmd, "i")) { spec_cmd_i (cmd, sim); } else if (cmd_match (cmd, "last")) { spec_cmd_last (cmd, sim); } else if (cmd_match (cmd, "n")) { spec_cmd_n (cmd, sim); } else if (cmd_match (cmd, "o")) { spec_cmd_o (cmd, sim); } else if (cmd_match (cmd, "p")) { spec_cmd_p (cmd, sim); } else if (cmd_match (cmd, "r")) { spec_cmd_r (cmd, sim); } else if (cmd_match (cmd, "s")) { spec_cmd_s (cmd, sim); } else if (cmd_match (cmd, "t")) { spec_cmd_t (cmd, sim); } else if (cmd_match (cmd, "u")) { spec_cmd_u (cmd, sim); } else if (cmd_match (cmd, "x")) { spec_cmd_x (cmd, sim); } else { return (1); } return (0); } void spec_cmd_init (spectrum_t *sim, monitor_t *mon) { mon_cmd_add (mon, par_cmd, sizeof (par_cmd) / sizeof (par_cmd[0])); mon_cmd_add_bp (mon); e8080_set_hook_exec_fct (sim->cpu, NULL, NULL); e8080_set_hook_undef_fct (sim->cpu, sim, spec_hook_undef); e8080_set_hook_rst_fct (sim->cpu, sim, spec_hook_rst); }