/*
 * ============================================================================
 *  Title:    Debugger
 *  Author:   J. Zbiciak
 * ============================================================================
 *  This module will implement the debugger as a combination of interactive
 *  interface and a peripheral which maps into the entire address space
 *  which allows capturing memory events.
 * ============================================================================
 */

#define STK_TRC 1

#include "config.h"
#include "periph/periph.h"
#include "cp1600/cp1600.h"
#include "cp1600/op_decode.h"
#include "lzoe/lzoe.h"
#include "file/file.h"
#include "gfx/gfx.h"
#include "demo/demo.h"
#include "stic/stic.h"
#include "speed/speed.h"
#include "util/symtab.h"
#include "debug_.h"
#include "debug_if.h"
#include "debug_tag.h"
#include "debug_dasm1600.h"
#include "debug/source.h"

#define HISTSIZE (0x10000)
#define HISTMASK (HISTSIZE-1)
#define RH_RECSIZE (16)

LOCAL void debug_write_memattr(const char *fname);
LOCAL uint_32 debug_peek(periph_t *p, periph_t *r, uint_32 a, uint_32 d);
LOCAL void debug_poke(periph_t *p, periph_t *r, uint_32 a, uint_32 d);
LOCAL void debug_disasm_cache_inval(void);
LOCAL void debug_print_reghist(int count, int offset);

LOCAL int dc_hits = 0, dc_miss = 0, dc_nocache = 0;
LOCAL int dc_unhook_ok = 0, dc_unhook_odd = 0;

LOCAL uint_16 *debug_reghist = NULL;
LOCAL uint_32 *debug_profile = NULL;
LOCAL uint_8  *debug_memattr = NULL;
LOCAL uint_16 *debug_mempc   = NULL;
LOCAL int      debug_rh_ptr = -1;
LOCAL int      debug_histinit = 0;
LOCAL void debug_write_reghist(const char *, periph_p, cp1600_t *);
LOCAL symtab_t *debug_symtab;
LOCAL int disasm_mode = 0;  /* -1 is disasm only, 0 is mixed, 1 is src only */

LOCAL uint_32 debug_watch_w[0x10000 >> 5];
LOCAL uint_32 debug_watch_r[0x10000 >> 5];
#define WATCHING(x,y) ((int)((debug_watch_##y[(x) >> 5] >> ((x) & 31)) & 1))
#define WATCHTOG(x,y) ((debug_watch_##y[(x) >> 5] ^= 1u << ((x) & 31)))

/* JSR table:  The first address is the address of the JSR and the second is
 * the return address.  JSR_RET_WINDOW is the lookahead window for watching
 * reads past the address of a JSR instruction to detect data-after-JSR.
 */
#define MAX_JSRS (128)
#define JSR_RET_WINDOW (3)
LOCAL uint_16 debug_jsrs[MAX_JSRS][2];
LOCAL int debug_num_jsrs = 0;

int debug_fault_detected = 0;
const char *debug_halt_reason = NULL;

LOCAL char str_null[] = "(null)";

#ifdef STK_TRC

#define MAX_STK (256)

LOCAL uint_16 stk_pc[MAX_STK];
LOCAL uint_32 stk_deep = 0;
LOCAL uint_32 stk_base = 0;
LOCAL uint_32 stk_wr   = 0;

LOCAL FILE *stk_trc = NULL;


#endif

/* ======================================================================== */
/*  DEBUG_PRINT_USAGE                                                       */
/* ======================================================================== */
LOCAL void debug_print_usage(void)
{
    jzp_printf(
"jzIntv Debugger Commands\n"
"\n"
"Running the game:\n"
"  r <#>        'R'un <#> cycles.  <#> defaults to \"infinity\"\n"
"  s <#>        'S'tep <#> cycles showing disassembly as it runs.  \n"
"               <#> defaults to 1 cycle. -1 means \"forever.\"\n"
"  t <#>        'T'race-over a JSR.  Similar to \"step\" except it attempts\n"
"               to step over functions called by JSR.\n"
"  t?           List active tracepoints.\n"
"  [enter]      [enter] with no command is the same as \"s1\" or \"t1\",\n"
"               depending on whether \"s\" or \"t\" was used most recently\n"
"  f <#>        'F'ast forward to <#>.  When running in fast-forward mode,\n"
"               the debugger skips all breakpoints and will only stop at the\n"
"               target address or when the user pushes the BREAK key.  <#>\n"
"               defaults to the current program counter\n"
"  z            Toggle showing timestamps during 'step'\n"
"  x            Toggle showing CPU reads and writes during 'step'\n"
"  b <#>        Set a 'B'reakpoint at <#>.  <#> defaults to the current PC.\n"
"  b?           List active breakpoints.\n"
"  n <#>        u'N'set a breakpoint at <#>.  <#> defaults to the current PC \n"
"\n"
    );
    jzp_printf(
">> Note:  Pressing the BREAK key while running or stepping will drop jzIntv\n"
">>        back to the debugger prompt.  BREAK is usually bound to F4.\n"
"\n"
"Viewing / changing state:\n"
"  u <#1> <#2>  'U'nassemble <#2> instructions starting at <#1>.  <#2>\n"
"               defaults to 10.  <#1> defaults to the current PC after 'r'un\n"
"               or 's'tep, or to the next address after 'u'nassemble.\n"
"  m <#1> <#2>  Show <#2> 'm'emory locations starting at <#1>.  <#2> defaults\n"
"               to 40 (hex).  <#1> defaults to the first undisplayed location\n"
"               after the last 'm' command.\n"
"  g <#1> <#2>  Write the value <#2> to re'G'ister <#1>.\n"
"  e <#1> <#2>  'E'nter the value <#2> to memory location <#1>.\n"
"  p <#1> <#2>  'P'oke the value <#2> to memory location <#1>.  'P'oke can\n"
"               always write to GRAM and STIC registers whereas 'E'nter\n"
"               has the same restrictions as the CPU.\n"
"  w <#1> <#2>  Toggle write 'w'atching on range from <#1> through <#2>,\n"
"               inclusive.  If <#2> is omitted, it toggles the watch flag for\n"
"               a single location\n"
"  w?           List active write watches.\n"
"  @ <#1> <#2>  Toggle read watching on range from <#1> through <#2>,\n"
"               inclusive.  If <#2> is omitted, it toggles the watch flag for\n"
"               a single location\n"
"  @?           List active read watches.\n"
"\n"
    );
    jzp_printf(
"STIC Specific\n"
"  ^            Toggle displaying dropped writes to STIC registers or GRAM\n"
"  %%            Toggle displaying dropped reads of STIC registers or GRAM\n"
"  $            Toggle displaying STIC video FIFO loads\n"
"  #            Toggle halting when display blanks\n"
"  gs           Write a GRAM snapshot to gram_XXXX.gif\n"
"  gt <#1> <#2> Display a textual representation of GRAM starting at <#1> and\n"
"               showing <#2> tiles.  Defaults to 0 64 (entire GRAM).\n"
"  ni <#1>      Set the Non-Interruptible Instruction threshold to <#1>\n"
"  bb           Halt on dropped BUSRQ\n"
"  bi           Halt on dropped INTRM\n"
/*
"   $<path>     Enable STIC video FIFO checks with bitmap in <path>\n"
"   $           Disable STIC video FIFO checks\n"
*/
"\n"
    );
    jzp_printf(
"Statistics / History tracking:\n"
"  d            Dump CPU and memory state to a series of files.  Requires\n"
"               history or attribute logging to be enabled.\n"
"  h            Toggle history logging.  Use \"d\" to dump to \"dump.hst\"\n"
"               and \"dump.cpu\"\n"
"  a            Toggle memory attribute logging.  Use \"d\" to dump to \n"
"               \"dump.atr\"\n"
"  ! <#1> <#2>  Print the last <#1> instructions that ran, ending <#2>\n"
"               cycles back. <#1> defaults to 40, <#2> defaults to 0.\n"
"\n"
    );
    jzp_printf(
"Miscellaneous commands:\n"
"  l <path>     Load symbol table from <path>.  Format must be same as that\n"
"               output by as1600's -s flag\n"
"  / <string>   Look for symbols containing <string>.  If <string> is also a\n"
"               valid hex number, look for symbols at that address.  To only\n"
"               look for symbols at an address use /$<addr>\n"
"  o <#>        Set label/address 'o'utput format.  # is one of:\n"
"                   0 => LABEL ($1234)       <--- default\n"
"                   1 => LABEL\n"
"                   2 => $1234 (LABEL)\n"
"                   3 => $1234\n"
"  < <path>     Execute a script from <path>. Scripts are just collections of\n"
"               debugger commands, one per line.\n"
#ifdef WIN32
"  > <#>        Change the command window width to <#>\n"
#endif
"  q            Quit jzIntv\n"
"  ?            Print this usage information\n"
"  ??           Print debugger status information\n"
    );
    jzp_printf(
"\n"
">> Most commands that take an address can also take a symbol defined by a\n"
">> symbol table file.  These are output by as1600's -s flag.  You can load a\n"
">> symbol table into jzIntv with the --sym-file=<path> command line flag or\n"
">> with the 'L'oad command shown above\n"
"\n"
    );
}

/* ======================================================================== */
/*  DEBUG_TAG_RANGE                                                         */
/* ======================================================================== */
void debug_tag_range(uint_32 lo, uint_32 hi, uint_32 flags)
{
    if (!debug_memattr)
    {
        debug_memattr = CALLOC(uint_8 ,  0x10000);
        debug_mempc   = CALLOC(uint_16,  0x10000);
        if (!debug_memattr || !debug_mempc)
        {
            CONDFREE(debug_memattr);
            CONDFREE(debug_mempc  );
            return;
        }
    }
    if (debug_memattr)
    {
        while (lo <= hi)
            debug_memattr[lo++] |= flags;
    }
}

/* ======================================================================== */
/*  DEBUG_READ_SYMTBL                                                       */
/* ======================================================================== */
LOCAL void debug_read_symtbl(const char *fname)
{
    LZFILE *f;
    char buf[512], symb[512];

    if ((f = lzoe_fopen(fname, "r")) == NULL)
    {
        jzp_printf("debug: Could not open symbol table file '%s' for reading\n",
                    fname);
        return;
    }

    if (!debug_symtab)
        debug_symtab = symtab_create();

    jzp_printf("debug: Reading symbols from %s\n", fname);

    /* -------------------------------------------------------------------- */
    /*  Read in lines from .sym file from as1600.  Format is as follows:    */
    /*                                                                      */
    /*      ???????? DOIDLEHST                                              */
    /*      ???????? IDLEHST                                                */
    /*      0000a000 _GFX                                                   */
    /*      0000a000 TOPMOUNT                                               */
    /*                                                                      */
    /*  Ignore undefined symbols and symbols outside the range 0000 - FFFF. */
    /* -------------------------------------------------------------------- */
    while (lzoe_fgets(buf, 512, f) != NULL)
    {
        uint_32 addr = 0, old_addr;

        if (buf[0] == '?')
            continue;

        addr = 0xFFFFFFFF;
        symb[0] = 0;
        if (sscanf(buf, "%x %s", &addr, symb) != 2)
            continue;

        if (addr > 0xFFFF || symb[0] == 0)
            continue;

        if (symtab_getaddr(debug_symtab, symb, &old_addr) == 0)
        {
            if (old_addr != addr)
            {
                jzp_printf("debug: Warning, '%s' already has value $%.4X.\n"
                           "       Ignoring new value $%.4X\n",
                           symb, old_addr, addr);
            }
        } else
        {
            symtab_defsym(debug_symtab, symb, addr);
        }
    }

    lzoe_fclose(f);
}

/* ======================================================================== */
/*  DEBUG_DECODE_VAL -- Decide if a string is a label or a hex constant     */
/*                      and return the value.                               */
/* ======================================================================== */
LOCAL int debug_decode_val(const char *str, uint_32 maxval)
{
    uint_32 val;
    const char *x;
    const char *s = str;
    int must_hex = 0;

    if (s[0] == '$') { s++; must_hex = 1; }
    else if (debug_symtab && (symtab_getaddr(debug_symtab, s, &val) == 0))
        goto got_it;

    for (x = s; *x; x++)
        if (!(isdigit(*x) || (toupper(*x) >= 'A' && toupper(*x) <= 'F')))
        {
            jzp_printf(must_hex ? "Error parsing: %s\n"
                                : "Could not find symbol named '%s'\n", str);
            return -1;
        }

    if (sscanf(s, "%x", &val) != 1)
    {
        jzp_printf("Error parsing: %s\n", s);
        return -1;
    }

got_it:

    if (val > maxval)
    {
        jzp_printf("Value %.8X is out of range.\n", val);
        return -1;
    }

    return (int)val;
}

/* ======================================================================== */
/*  DEBUG_SYMB_FOR_ADDR  -- Returns symbol associated with and address, or  */
/*                          NULL if there is none.  Performs no formatting. */
/*                                                                          */
/*  Prefers symbols that start w/out a '.' if available.                    */
/* ======================================================================== */
const char *debug_symb_for_addr
(
    const uint_32 addr
)
{
    if (!debug_symtab)
        return NULL;

    const char *best_symb = NULL;
    const char *curr_symb = NULL;
    int which = 0;

    do {
        curr_symb = symtab_getsym(debug_symtab, addr, 0, which);
        if (curr_symb)
        {
            if (!best_symb || curr_symb[0] != '.')
                best_symb = curr_symb;
        }
        which++;
    } while (curr_symb && best_symb[0] == '.');

    return best_symb;
}

/* ======================================================================== */
/*  DEBUG_FORMAT_ADDR    -- Format an address with label information if     */
/*                          available, according to the current address     */
/*                          format, in the provided buffer.                 */
/*                                                                          */
/*  Note:  Buffer must be at least 10 characters long.                      */
/* ======================================================================== */
LOCAL char *debug_format_addr
(
    const debug_t *const debug, 
    const uint_32 addr, 
    char *const s4a_buf,
    const int s4a_len
)
{
    const char *const symb = debug_symb_for_addr(addr);
    int fmt = debug->symb_addr_format;
    int len;

    if (!symb)
    {
        fmt = 3;
        len = 0;
    } else
    {
        len = strlen(symb);
    }
     
    switch (fmt)
    {
        case 0:
            if (len > s4a_len - 9)
                len = s4a_len - 9;

            sprintf(s4a_buf, "%*s ($%.4X)", len, symb, addr);
            break;

        case 1:
            if (len > s4a_len - 1)
                len = s4a_len - 1;

            sprintf(s4a_buf, "%*s", len, symb);
            break;

        case 2:
            if (len > s4a_len - 9)
                len = s4a_len - 9;

            sprintf(s4a_buf, "$%.4X (%*s)", addr, len, symb);
            break;

        case 3:
            sprintf(s4a_buf, "$%.4X", addr);
            break;
    }

    return s4a_buf;
}

/* ======================================================================== */
/*  DEBUG_SEARCH_SYMBOL  -- Search for symbols matching a string.           */
/* ======================================================================== */
LOCAL void print_symbol(const char *symbol, uint_32 addr, int which)
{
    UNUSED(which);
    jzp_printf(" $%.4X %s\n", addr, symbol);
}

LOCAL void debug_search_symbol(const char *search_string)
{
    char *copy;
    char *s, *start;
    uint_32 addr;

    if (!debug_symtab)
        return;

    s = copy = strdup(search_string);

    while (*s && isspace(*s))
        s++;

    start = s;

    if (*start == '$')
    {
        start++;
        goto must_hex;
    }

    while (*s && !isspace(*s) && *s != '\r' && *s != '\n')
        s++;

    *s = 0;

    if (*start)
        symtab_grep_for_symbol(debug_symtab, print_symbol, start);

    for (s = start; *s; s++)
        if (!(isdigit(*s) || (toupper(*s) >= 'A' && toupper(*s) <= 'F')))
            goto not_hex;

must_hex:
    if (sscanf(start, "%x", &addr) == 1)
    {
        int i = 0;
        const char *symb;

        while ((symb = symtab_getsym(debug_symtab, addr, 0, i)) != NULL)
        {
            print_symbol(symb, addr, i);
            i++;
        }
    }

not_hex:
    free(copy);
}

/* ======================================================================== */
/*  DEBUG_HIT_JSR_RET -- Get JSR address for apparent "step over" PC addr,  */
/*                       and remove address from list of active tracepoints */
/* ======================================================================== */
LOCAL uint_32 debug_get_jsr_addr(uint_32 ret_addr)
{
    int i, j;
    uint_32 jsr_addr = 0;

    for (i = 0; i < debug_num_jsrs; i++)
    {
        if (debug_jsrs[i][1] == ret_addr)
        {
            jsr_addr = debug_jsrs[i][0];
            for (j = i + 1; j < debug_num_jsrs; i++, j++)
            {
                debug_jsrs[i][0] = debug_jsrs[j][0];
                debug_jsrs[i][1] = debug_jsrs[j][1];
            }
            debug_num_jsrs--;
        }
    }

    return jsr_addr;
}

/* ======================================================================== */
/*  DEBUG_SET_JSR_RET -- Set JSR address and return address when stepping   */
/*                       over a JSR instruction.                            */
/* ======================================================================== */
LOCAL void debug_set_jsr_ret(uint_32 jsr_addr, uint_32 ret_addr)
{
    int i;

    /* Replace redundant tracepoints */
    for (i = 0; i < debug_num_jsrs; i++)
        if (debug_jsrs[i][0] == jsr_addr)
        {
            debug_jsrs[i][0] = jsr_addr;
            debug_jsrs[i][1] = ret_addr;
            return;
        }

    /* Don't track more than MAX_JSRS outstanding JSRs. */
    if (debug_num_jsrs >= MAX_JSRS)
        return;

    debug_jsrs[debug_num_jsrs][0] = jsr_addr;
    debug_jsrs[debug_num_jsrs][1] = ret_addr;
    debug_num_jsrs++;
}

/* ======================================================================== */
/*  DEBUG_CHK_JSR_RET -- Watch for reads to JSR return locations, and move  */
/*                       them forward if they're seen.  This is intended to */
/*                       catch functions that have arguments after the JSR  */
/* ======================================================================== */
LOCAL void debug_chk_jsr_ret(cp1600_t *cp, uint_32 read_addr)
{
    int i;

    /* -------------------------------------------------------------------- */
    /*  Ignore reads if we have no tracepoints set or the read is an        */
    /*  instruction fetch (PC == read address).                             */
    /* -------------------------------------------------------------------- */
    if (!debug_num_jsrs)
        return;

    if (cp->r[7] == read_addr)
        return;

    /* -------------------------------------------------------------------- */
    /*  Adjust the return address if we see a read to an address in the     */
    /*  range "ret_addr" to "ret_addr + JSR_RET_WINDOW", inclusive.  Set    */
    /*  the return address to 1 after the address of the read.              */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < debug_num_jsrs; i++)
    {
        if (read_addr >=           debug_jsrs[i][1] &&
            read_addr <= (uint_16)(debug_jsrs[i][1] + JSR_RET_WINDOW))
        {
            cp1600_clr_breakpt(cp, debug_jsrs[i][1], CP1600_BKPT_ONCE);
            cp1600_set_breakpt(cp, read_addr + 1,    CP1600_BKPT_ONCE);
            debug_jsrs[i][1] = read_addr + 1;
            return;
        }
    }

    return;
}

/* ======================================================================== */
/*  DEBUG_FIND_JSRS  -- If we're stepping over JSRs, handle any JSR at the  */
/*                      current PC.                                         */
/* ======================================================================== */
LOCAL int debug_find_jsrs(periph_t *p, cp1600_t *cp, uint_32 pc)
{
    uint_16 n1, n2;

    /* Is this a JUMP family instruction? */
    n1 = periph_peek((periph_t*)p->bus, p, (pc + 0) & 0xFFFF, ~0) & 0x3FF;
    n2 = periph_peek((periph_t*)p->bus, p, (pc + 1) & 0xFFFF, ~0) & 0x3FF;
    if (n1 == 0x0004 && (n2 & 0x300) != 0x300)
    {
        cp1600_set_breakpt(cp, pc + 3, CP1600_BKPT_ONCE);
        debug_set_jsr_ret(pc, pc + 3);
        return 1;
    }

    return 0;
}

/* ======================================================================== */
/*  DEBUG_OPEN_SCRIPT   Open up a new debugger script file                  */
/* ======================================================================== */
LOCAL void debug_open_script(debug_t *debug, const char *script)
{
    if (debug->filestk_depth < DEBUG_MAX_FILESTK - 1)
    {
        LZFILE *f = lzoe_fopen(script, "r");
        if (!f)
            jzp_printf("debug: Could not open script file '%s'\n", script);
        else
            debug->filestk[++debug->filestk_depth] = f;
    } else
        jzp_printf("debug: Script files nested too deeply\n");
}

/* ======================================================================== */
/*  DEBUG_SHOW_VICINITY  -- Show the source code in the vicinity of an      */
/*                          address, if available.                          */
/* ======================================================================== */
LOCAL void debug_show_vicinity(uint_32 addr, int range)
{
    int file, line;
    int i;

    file = file_line_for_addr(addr, &line);

    if (file < 0)
    {
        jzp_printf("Unable to find source for address $%.4X\n", addr);
        return;
    }

    for (i = -range; i <= range; i++)
    {
        const char *src = source_for_file_line(file, line + i);

        if (src)
            jzp_printf("%c%s\n", i ? ' ' : '>', src);
    }
}

#ifdef STK_TRC
/* ======================================================================== */
/*  DEBUG_INFER_STACK    -- Try to figure out a reasonable base for the     */
/*                          stack.                                          */
/* ======================================================================== */
LOCAL uint_16 debug_infer_stack(cp1600_t *const cp)
{
    uint_32 val;

    /* First look for the symbol _m.stk */
    if (debug_symtab && (symtab_getaddr(debug_symtab, "_m.stk", &val) == 0))
        return val;

    /* Next look for the symbol STACK */
    if (debug_symtab && (symtab_getaddr(debug_symtab, "STACK", &val) == 0))
        return val;

    /* Finally, assume R6 is at bottom of stack. */
    return cp->r[6];
}
#endif

/* ======================================================================== */
/*  DEBUG_PRINT_BREAKPT_CB   -- Callback for printing out a breakpoint.     */
/* ======================================================================== */
LOCAL void debug_print_breakpt_cb(void *opaque, uint_32 addr)
{
    debug_t *const debug = (debug_t *)opaque;
    char lblbuf[1024];
    jzp_printf(">> %s\n", debug_format_addr(debug, addr, lblbuf, sizeof(lblbuf)));
}

/* ======================================================================== */
/*  DEBUG_LIST_BREAKPOINTS   -- List active breakpoints                     */
/*  DEBUG_LIST_TRACEPOINTS   -- List active tracepoints                     */
/* ======================================================================== */
LOCAL void debug_list_breakpoints(debug_t *const debug, cp1600_t *const cp)
{
    jzp_printf("Breakpoints:\n");
    cp1600_list_breakpts(cp, CP1600_BKPT, debug_print_breakpt_cb, debug);
}

LOCAL void debug_list_tracepoints(debug_t *const debug, cp1600_t *const cp)
{
    jzp_printf("Tracepoints:\n");
    cp1600_list_breakpts(cp, CP1600_BKPT_ONCE, debug_print_breakpt_cb, debug);
}

/* ======================================================================== */
/*  DEBUG_LIST_WATCHES       -- List read/write watch addresses             */
/* ======================================================================== */
LOCAL void debug_list_watches(const debug_t *const debug, 
                              const uint_32 *const bmap)
{
    const int fmt = debug->symb_addr_format;
    const char *symb = NULL;
    uint_32 addr_lo = 0, addr_hi = 0;
    int in_range = 0;
    uint_32 addr;

    for (addr = 0; addr <= 0x10000; addr++)
    {
        const uint_32 addr_idx = addr >> 5;
        const uint_32 addr_bit = (1u) << (addr & 0x1F);
        const int watching = addr < 0x10000 && (bmap[addr_idx] & addr_bit) != 0;

        symb = fmt == 3 || addr >= 0x10000 ? NULL : debug_symb_for_addr(addr);

        /* ---------------------------------------------------------------- */
        /*  If we encounter a symbol, flush current non-symboled range.     */
        /*  Also flush range if no longer watching.                         */
        /* ---------------------------------------------------------------- */
        if (in_range && (symb || !watching))
        {
            jzp_printf(
                addr_lo == addr_hi ? "Watching $%04X\n" 
                                   : "Watching $%04X - $%04X\n",
                addr_lo, addr_hi);
            in_range = 0;
            addr_lo = addr_hi = -1;
        }

        /* ---------------------------------------------------------------- */
        /*  If we're watching this location, and don't have a range open,   */
        /*  start a new range.                                              */
        /* ---------------------------------------------------------------- */
        if (!in_range && watching)
        {
            /* ------------------------------------------------------------ */
            /*  If this was a symboled location, just print its name and    */
            /*  don't open a range.  Otherwise, open a range.               */
            /* ------------------------------------------------------------ */
            if (symb)
            {
                char buf[1024];
                jzp_printf("Watching %s\n", 
                           debug_format_addr(debug, addr, buf, sizeof(buf)));
                in_range = 0;
                addr_lo = addr_hi = -1;
            } else
            {
                in_range = 1;
                addr_lo = addr_hi = addr;
            }
        }

        /* ---------------------------------------------------------------- */
        /*  If we're in a range, try to extend it.  Otherwise, print it.    */
        /* ---------------------------------------------------------------- */
        if (in_range && watching)
            addr_hi = addr;
    }
}

/*
 * ============================================================================
 *  DEBUG_RD         -- Capture/print a read event.
 *  DEBUG_WR         -- Capture/print a write event.
 *  DEBUG_TK         -- Debugger 'tick' function.  This where the user command
 *                      line called from.
 * ============================================================================
 */
uint_32 debug_rd(periph_t *p, periph_t *r, uint_32 a, uint_32 d)
{
    debug_t *debug = (debug_t*)p;
    cp1600_t *cp = debug->cp1600;
    char addrbuf[36], pcbuf[36];

    if (p == r->req)
        return ~0;

    debug_chk_jsr_ret(cp, a);

    if (debug->show_rd || WATCHING(a,r))
    {
        jzp_printf(" RD a=%s d=%.4X %-16s (PC = %s) t=%" LL_FMT "u\n",
                debug_format_addr(debug, a, addrbuf, sizeof(addrbuf)),
                d, r==(periph_p)p->bus ? r->req->name:r->name,
                debug_format_addr(debug, cp->oldpc, pcbuf, sizeof(pcbuf)),
                cp->periph.now);
    }

    if (debug_memattr)
    {
        if (cp->r[7] == a)
            debug_memattr[a] |= DEBUG_MA_CODE;
        else if (cp->D)
            debug_memattr[a] |= DEBUG_MA_SDBD | DEBUG_MA_DATA;
        else
            debug_memattr[a] |= DEBUG_MA_DATA;

        debug_mempc[a]  = cp->r[7];
    }

#ifdef STK_TRC
    if (stk_trc && a <= stk_wr && a >= stk_base && (a == cp->r[6] - 1u))
    {
        if (stk_wr - stk_base >= stk_deep)
        {
            int i;

            stk_deep = stk_wr - stk_base + 1;

            fprintf(stk_trc, "Deep: %d\n", stk_deep);
            for (i = 0; i < (int)stk_deep; i++)
                fprintf(stk_trc, " %.4X", stk_pc[i]);

            fprintf(stk_trc, "\n");
            fflush(stk_trc);
        }
    }
#endif

    return ~0U;
}

void    debug_wr(periph_t *p, periph_t *r, uint_32 a, uint_32 d)
{
    debug_t *debug = (debug_t*)p;
    cp1600_t *cp = debug->cp1600;
    char addrbuf[36], pcbuf[36];

    if (p == r->req)
        return;

    if (debug->show_wr || WATCHING(a,w))
    {
        jzp_printf(" WR a=%s d=%.4X %-16s (PC = %s) t=%" LL_FMT "u\n",
                debug_format_addr(debug, a, addrbuf, sizeof(addrbuf)),
                d, r==(periph_p)p->bus ? r->req->name:r->name,
                debug_format_addr(debug, cp->oldpc, pcbuf, sizeof(pcbuf)),
                cp->periph.now);
    }

    if (debug_memattr)
    {
        debug_memattr[a] |= DEBUG_MA_DATA | DEBUG_MA_WRITE;
        debug_mempc  [a]  = cp->r[7];
    }

#ifdef STK_TRC
    if (stk_trc && a >= stk_base && a < stk_base + MAX_STK && a == cp->r[6])
    {
        stk_wr = a;
        stk_pc[a - stk_base] = cp->oldpc;
    }
#endif
}

LOCAL uint_32 debug_peek(periph_t *p, periph_t *r, uint_32 a, uint_32 d)
{
    UNUSED(p);
    UNUSED(r);
    UNUSED(a);
    UNUSED(d);
    return ~0U;
}

LOCAL void debug_poke(periph_t *p, periph_t *r, uint_32 a, uint_32 d)
{
    UNUSED(p);
    UNUSED(r);
    UNUSED(a);
    UNUSED(d);
}

LOCAL const char debug_req_ack_char[16] = 
{
            /*    BUSAK   BUSRQ   INTAK   INTRQ     Meaning             */
    '-',    /*      0       0       0       0       Normal operation    */
    'q',    /*      0       0       0       1       Interrupt pending   */
    '2',    /*      0       0       1       0       Invalid             */
    'Q',    /*      0       0       1       1       Interrupt taken     */
    'b',    /*      0       1       0       0       Bus req pending     */
    '5',    /*      0       1       0       1       Invalid             */
    '6',    /*      0       1       1       0       Invalid             */
    '7',    /*      0       1       1       1       Invalid             */
    '8',    /*      1       0       0       0       Invalid             */
    '9',    /*      1       0       0       1       Invalid             */
    'a',    /*      1       0       1       0       Invalid             */
    'x',    /*      1       0       1       1       Invalid             */
    'B',    /*      1       1       0       0       Bus req ack'd.      */
    'd',    /*      1       1       0       1       Invalid             */
    'e',    /*      1       1       1       0       Invalid             */
    'f',    /*      1       1       1       1       Invalid             */
};

static const char *symb_format_desc[4] =
{
    "LABEL ($1234)",
    "LABEL",
    "$1234 (LABEL)",
    "$1234"
};


static int non_int_threshold = 54;


uint_32 debug_tk(periph_t *p, uint_32 len)
{
    debug_t *debug = (debug_t*)p;
    cp1600_t *cp = debug->cp1600;
    uint_32 pc = cp->r[7];
    char    buf[1024], *s;
    static  uint_16 next_hex_dump = 0x0000;
    static  int     prev_pc = -1;
    static  int     non_int = 0;
    static  int     show_time = 1;
    static  int     show_rdwr = 0;
    static  int     last_over = 0;
    static  uint_32 fast_fwd  = 0, ff_bkpt = 0;
    static  uint_64 prev_rh_now     = 0;
    static  int     prev_rh_req_ack = 0;
    sint_32 slen = (sint_32)len;
    uint_64 instrs = cp->tot_instr - debug->tot_instr;
    uint_64 now = cp->periph.now;
    uint_16 next_disassem = pc;
    int     req_ack, wind = 0, show_once = 0;

    debug->tot_instr = cp->tot_instr;
    req_ack = cp->req_ack_state;

    /* -------------------------------------------------------------------- */
    /*  If we're keeping a trace history, update it now.                    */
    /* -------------------------------------------------------------------- */
    if (debug_rh_ptr >= 0 && (now != prev_rh_now || req_ack != prev_rh_req_ack))
    {
        int i;
        static int ni_start = -1, ni_end = -1;
        static uint_64 ni_cycle = 0;

        prev_rh_now     = now;
        prev_rh_req_ack = req_ack;

        if (slen > 0 && prev_pc > 0 && cp->intr == 0)
        {
            non_int += len;
            if (ni_start < 0) 
            {
                ni_cycle = now - len;
                ni_start = prev_pc;
            }
            ni_end = pc;
        } else
        {
            non_int += len;
            if (*debug->vid_enable && non_int >= non_int_threshold &&
                ni_start > 0 && ni_end > 0)
            {
                char pcbuf0[36], pcbuf1[36];
                jzp_printf(
                    "NON_INT = %d at PC = %s .. %s, starting at cycle %"
                    LL_FMT "u\n", non_int,
                    debug_format_addr(debug, ni_start, pcbuf0, sizeof(pcbuf0)),
                    debug_format_addr(debug, ni_end, pcbuf1, sizeof(pcbuf1)),
                    ni_cycle);
            }

            non_int = 0;
            ni_cycle = 0;
            ni_start = ni_end = -1;
        }

        memcpy(debug_reghist + debug_rh_ptr * RH_RECSIZE, cp->r, 16);

        debug_reghist[debug_rh_ptr * RH_RECSIZE + 8] = 1 +
                                              ((!!cp->S    ) << 1) +
                                              ((!!cp->C    ) << 2) +
                                              ((!!cp->O    ) << 3) +
                                              ((!!cp->Z    ) << 4) +
                                              ((!!cp->I    ) << 5) +
                                              ((!!cp->D    ) << 6) +
                                              ((!!cp->intr ) << 7) +
                                              ((req_ack    ) << 8);

        for (i = 0; i < 3; i++)
            debug_reghist[debug_rh_ptr * RH_RECSIZE + 9 + i] =
                periph_peek((periph_t*)p->bus, p, pc + i, ~0);

        debug_reghist[debug_rh_ptr * RH_RECSIZE + 12] = (now    ) & 0xFFFF;
        debug_reghist[debug_rh_ptr * RH_RECSIZE + 13] = (now>>16) & 0xFFFF;
        debug_reghist[debug_rh_ptr * RH_RECSIZE + 14] = (now>>32) & 0xFFFF;
        debug_reghist[debug_rh_ptr * RH_RECSIZE + 15] = (now>>48) & 0xFFFF;

        debug_rh_ptr = (debug_rh_ptr + 1) & HISTMASK;

        if (slen > 0 && prev_pc > 0)
        {
            debug_profile[prev_pc] += len * 2;
            debug_profile[pc]      |= !!cp->D;
        }
    }
    prev_pc = pc;

    /* -------------------------------------------------------------------- */
    /*  If slen == -CYC_MAX, we're crashing.                                */
    /* -------------------------------------------------------------------- */
    if (slen == -CYC_MAX || debug_fault_detected)
    {
        if ( debug_fault_detected == DEBUG_CRASHING )
        {
            if (debug_rh_ptr >= 0)  debug_write_reghist("dump.hst", p, cp);
            if (debug_memattr)      debug_write_memattr("dump.atr");
            jzp_printf("CPU crashed!\n");
        } else if ( debug_fault_detected == DEBUG_ASYNC_HALT )
        {
            jzp_printf("Asynchronous halt requested.\n");
        } else if ( debug_fault_detected == DEBUG_HLT_INSTR )
        {
            jzp_printf("HLT instruction reached\n");
        }
        if (debug_halt_reason)
        {
            jzp_printf("Reason: '%s'\n", debug_halt_reason);
        }
        jzp_flush();
        debug_fault_detected = 0;
        debug_halt_reason    = NULL;
        slen = -CYC_MAX;
    }

    /* -------------------------------------------------------------------- */
    /*  Short-circuit the debugger if we're not doing anything interesting  */
    /* -------------------------------------------------------------------- */
    if (debug->step_count < 0 && slen > 0 && !debug->show_ins)
        return len;

    /* -------------------------------------------------------------------- */
    /*  If we're "fast-forwarding", only stop if our PC == the ffwd addr    */
    /*  or someone stops us with the "break" key.  This ignores all other   */
    /*  breakpoints, tracepoints, etc.  We barrel through everything.  We   */
    /*  cancel the fast forward on "break" key, or we reach our target.     */
    /* -------------------------------------------------------------------- */
    if (fast_fwd && debug->step_count != 0 && pc != fast_fwd)
    {
        /* ---------------------------------------------------------------- */
        /*  Pop off "return from JSRs" that we pass while fast-forwarding.  */
        /*  We shouldn't keep them since we've popped the return stack.     */
        /* ---------------------------------------------------------------- */
        if (cp->hit_breakpoint == BK_TRACEPOINT)
            debug_get_jsr_addr(pc);

        return len;
    }


    /* -------------------------------------------------------------------- */
    /*  Decrement our CPU step count.                                       */
    /* -------------------------------------------------------------------- */
    if (debug->step_count > 0)
    {
        debug->step_count -= instrs;
        if (debug->step_count < 0)
            debug->step_count = 0;

    }

    /* -------------------------------------------------------------------- */
    /*  If this was a breakpoint or a "tracepoint" (ie. from stepping over  */
    /*  a JSR instruction), the cycle count will be negative.               */
    /* -------------------------------------------------------------------- */
    if (slen < 0)
    {
        uint_32 jsr_addr = 0;

        /* ---------------------------------------------------------------- */
        /*  If we hit a tracepoint (ie. the return from a JSR), then don't  */
        /*  drop into the debugger.  Rather, go back into "step" mode with  */
        /*  our remaining step count.                                       */
        /* ---------------------------------------------------------------- */
        if (cp->hit_breakpoint == BK_TRACEPOINT &&
                (jsr_addr = debug_get_jsr_addr(pc)) != 0)
        {
            jzp_printf("Returned from JSR at $%.4X.\n", jsr_addr);
            debug->step_count = debug->step_over >> 1;
            debug->step_over &= 1;
            debug->show_ins   = 1;
            debug->show_rd    = show_rdwr;
            debug->show_wr    = show_rdwr;

            if (debug->step_count > 0)
                debug->step_count--;

            if (debug_rh_ptr < 0)
                cp->step_count = 1;
        }

        /* ---------------------------------------------------------------- */
        /*  If we hit a breakpoint or anything other than a tracepoint,     */
        /*  drop into the debugger.  In the case of a breakpoint, let the   */
        /*  user know that we hit a breakpoint.                             */
        /* ---------------------------------------------------------------- */
        if (cp->hit_breakpoint == BK_BREAKPOINT)
            jzp_printf(fast_fwd ? "Fast forwarded to $%.4X\n" :
                                  "Hit breakpoint at $%.4X\n", pc);
        if (cp->hit_breakpoint != BK_TRACEPOINT)
            debug->step_count = 0;
    }

    /* -------------------------------------------------------------------- */
    /*  Clear fast-fwd here so we can modify breakpoint message.            */
    /* -------------------------------------------------------------------- */
    if (fast_fwd)
    {
        if (!ff_bkpt)
            cp1600_clr_breakpt(cp, fast_fwd, CP1600_BKPT);
        fast_fwd = 0;
    }

    /* -------------------------------------------------------------------- */
    /*  If we're stepping over JSRs, go into "run" mode if the next         */
    /*  instruction is a JSR.   We'll return to step mode once we hit the   */
    /*  return statement from the JSR.                                      */
    /* -------------------------------------------------------------------- */
    if (debug->step_count > 0 && debug->step_over)
    {
        if (debug_find_jsrs(p, cp, pc))
        {
            debug->step_over  = 1 | (debug->step_count << 1);
            debug->step_count = debug->step_count ? -1 : 0;
            debug->show_ins   = 0;
            debug->show_rd    = 0;
            debug->show_wr    = 0;
            show_once         = 1;
        }
    }

    /* -------------------------------------------------------------------- */
    /*  If we're in sync with the CPU, then grab the current PC and words   */
    /*  at that location, disassemble and display.                          */
    /* -------------------------------------------------------------------- */
    if (debug->show_ins || !debug->step_count || show_once /*|| !len*/)
    {
        const char *dis, *symb;
        int disasm_width;

        show_once = 0;

show_disassem:
        disasm_width = get_disp_width() - (show_time ? 60 : 51);
        if (disasm_width < 20)
            disasm_width = -1;

        pc = cp->r[7];

        /* ---------------------------------------------------------------- */
        /*  Where possible, cache disassembled instructions for speed.      */
        /* ---------------------------------------------------------------- */
        dis = debug_disasm_src(p, cp, pc, NULL, cp->D, disasm_width);
        dis = dis ? dis + 22 : str_null;

        /* ---------------------------------------------------------------- */
        /*  Print the state of the machine, along w/ disassembly.           */
        /* ---------------------------------------------------------------- */
        if ((symb = debug_symb_for_addr(pc)) != NULL)
            jzp_printf("%s:\n", symb);

        jzp_printf(
            show_time ?
               " %.4X %.4X %.4X %.4X %.4X %.4X %.4X %.4X %c%c%c%c%c%c%c%c"
               "%-*.*s %8" LL_FMT "u\n"
            :
               " %.4X %.4X %.4X %.4X %.4X %.4X %.4X %.4X %c%c%c%c%c%c%c%c"
               "%-*.*s\n"
            ,
               cp->r[0], cp->r[1], cp->r[2], cp->r[3],
               cp->r[4], cp->r[5], cp->r[6], cp->r[7],
               cp->S ? 'S' : '-',
               cp->C ? 'C' : '-',
               cp->O ? 'O' : '-',
               cp->Z ? 'Z' : '-',
               cp->I ? 'I' : '-',
               cp->D ? 'D' : '-',
               cp->intr ? 'i' : '-',
               debug_req_ack_char[req_ack],
               disasm_width, disasm_width, dis,
               now);

        if (debug->speed) speed_resync(debug->speed);
    }

    /* -------------------------------------------------------------------- */
    /*  If we've exhausted our CPU step count, drop into command prompt.    */
    /* -------------------------------------------------------------------- */
    if (debug->step_count == 0 /*|| !len*/)
    {
        int cmd = 0, c, c2 = -1, c3 = -1, arg = -1, arg2 = -1;
        char argstr[101], argstr2[101];
        static int over = 0;

        wind = gfx_force_windowed(debug->gfx, 1);

next_cmd:
        do
        {
            jzp_printf("> ");
            jzp_flush();

            while (!lzoe_fgets(buf,1023,debug->filestk[debug->filestk_depth]))
            {
                if (debug->filestk_depth == 0)
                {
                    strcpy(buf, "q");
                    break;
                } else
                    lzoe_fclose(debug->filestk[debug->filestk_depth--]);
            }

            if (debug->filestk_depth > 0)
                fputs(buf, stdout);

            /* ignore anything after a semicolon */
            if ((s = strchr(buf, ';')) != NULL)
                *s = 0;

            s = buf;

            cmd = -1;

            /* ignore leading whitespace */
            while (*s && isspace(*s)) s++;

            /* chop trailing newline, whitespace */
            {
                char *ss, *nws;
                for (ss = nws = s; *ss; ss++)
                    if (!isspace(*ss))
                        nws = ss;
                nws[1] = 0;
                if ((ss = strchr(s, '\n')) != NULL) *ss = 0;
                if ((ss = strchr(s, '\r')) != NULL) *ss = 0;
            }

            /* step over / trace over if empty line, but only if interactive */
            if (!*s)
            {
                if (debug->filestk_depth == 0)
                {
                    strcpy(buf, last_over ? "t1" : "s1");
                    s = buf;
                } else
                    goto next_cmd;
            }

            c = toupper(*s++);

            /* Two letter commands:
                'GS' for GRAMSHOT
                'GT' for GRAM text
                'GQ' for debuG reQs
             */
            c2 = toupper(s[0]);
            c3 = isalpha(c2) && s[1] ? toupper(s[1]) : ' ';
            if (c == 'G')
            {
                if (c2 == 'S' || c2 == 'T' || c2 == 'Q') s++;
                else c2 = -1;
            }

            if (c == 'N')
            {
                c2 = toupper(*s);
                if (c2 == 'I' && isspace(c3)) s++;
                else c2 = -1;
            }

            if (c == 'B')
            {
                c2 = toupper(*s);
                if ((c2 == 'B' || c2 == 'I') && isspace(c3)) s++;
                else if (c2 != '?') c2 = -1;
            }

            /* Allow second character for T?, W?, Q?, ?? */
            if (c == 'T' || c == 'W' || c == '@' || c == '?')
            {
                if (c2 == '?') s++;
                else c2 = -1;
            }

            /* skip whitespace after command character but before first arg */
            while (isspace(*s))
                s++;

            /* decode the command */
            if (c == 'S') { cmd = 1; over = 0; }                /* step into */
            if (c == 'T' && c2 == -1 ) { cmd = 1; over = 1; }  /* trace over */
            if (c == 'T' && c2 == '?') cmd = 39;         /* list tracepoints */
            if (c == 'R') { cmd = 2; over = 0; }                     /* run  */
            if (c == 'F') cmd = 23;                       /* fast forward to */
            if (c == 'D') cmd = 3;                             /* dump/abort */
            if (c == 'Q') cmd = 4;                                   /* quit */
            if (c == 'B' && c2 == -1 ) cmd = 5;                /* breakpoint */
            if (c == 'B' && c2 == 'B') cmd = 37;   /* Break on missing BUSRQ */
            if (c == 'B' && c2 == 'I') cmd = 38;   /* Break on missing INTRM */
            if (c == 'B' && c2 == '?') cmd = 40;         /* List breakpoints */
            if (c == 'M') cmd = 6;                               /* mem dump */
            if (c == 'U') cmd = 7;                          /* 'Un'assemble. */
            if (c == 'C') cmd = 8;               /* Disassembly Cache stats. */
            if (c == 'G' && c2 == -1 ) cmd = 9;  /* Change re'G'ister value. */
            if (c == 'G' && c2 == 'S') cmd = 33;                 /* GRAMSHOT */
            if (c == 'G' && c2 == 'T') cmd = 34;           /* GRAM text dump */
            if (c == 'G' && c2 == 'Q') cmd = 35;               /* debuG reQs */
            if (c == 'N' && c2 == -1 ) cmd = 10;         /* uNset breakpoint */
            if (c == 'N' && c2 == 'I') cmd = 36;  /* Non-Interrupt threshold */
            if (c == 'H') cmd = 11;                        /* toggle History */
            if (c == 'W' && c2 == -1 ) cmd = 12;       /* toggle watch write */
            if (c == 'W' && c2 == '?') cmd = 41;       /* List write-watches */
            if (c == '@' && c2 == -1 ) cmd = 17;        /* toggle watch read */
            if (c == '@' && c2 == '?') cmd = 42;        /* List read-watches */
            if (c == 'A') cmd = 13;     /* enable memory Attribute discovery */
            if (c == 'P') cmd = 14;    /* poke into memory. (no side effect) */
            if (c == 'E') cmd = 15;       /* write to memory. (side effects) */
#ifdef STK_TRC
            if (c == 'K') cmd = 16;                       /* stack tracing.  */
#endif
            if (c == 'Z') cmd = 18;             /* toggle showing timestamps */
            if (c == 'X') cmd = 19; /* toggle showing read/write during step */
            if (c == '?' && c2 == -1 ) cmd = 20;  /* print usage information */
            if (c == '?' && c2 == '?') cmd = 43;    /* print debugger status */
            if (c == 'L') cmd = 21;             /* load a symbol table file. */
            if (c == '/') cmd = 22;           /* search for symbol in symtab */
            if (c == '$') cmd = 24;       /* toggle STIC video FIFO messages */
            if (c == '%') cmd = 25;             /* toggle STIC dropped reads */
            if (c == '^') cmd = 26;             /* toggle STIC droped writes */
            if (c == 'O') cmd = 27;       /* change label/addr output format */
            if (c == '<') cmd = 28;                 /* read script from file */
            if (c == '!') cmd = 29;   /* print last N instructions from hist */
            if (c == '>') cmd = 30;                      /* Set window width */
            if (c == 'V') cmd = 31;             /* Print source for vicinity */
            if (c == '#') cmd = 32;            /* Breakpoint on screen blank */

            if (cmd == -1)
            {
                jzp_printf("Did not understand command '%s'.  "
                            "Type '?' for help\n", buf);
                goto next_cmd;
            }

            if (cmd == 1)
            {
                if (sscanf(s, "%d", &arg) != 1)
                    arg = 1;
            } else if (cmd == 2)
            {
                if (sscanf(s, "%d", &arg) != 1)
                    arg = -1;
            } else if (cmd == 5 || cmd == 10 || cmd == 23) {
                if (sscanf(s, "%100s", argstr) != 1)
                    arg = pc;
                else
                {
                    arg = debug_decode_val(argstr, 0xFFFF);
                    if (arg == -1)
                        goto next_cmd;
                }
#ifdef STK_TRC
            } else if (cmd == 16) {
                if (sscanf(s, "%100s", argstr) != 1)
                    arg = debug_infer_stack(cp);
                else
                {
                    arg = debug_decode_val(argstr, 0xFFFF);
                    if (arg == -1)
                        goto next_cmd;
                }
#endif
            } else if (cmd == 6 || cmd == 7 || cmd == 31) {
                int args = sscanf(s, "%100s %100s", argstr, argstr2);

                if (args >= 1) arg  = debug_decode_val(argstr,  0xFFFF);
                if (args >= 2) arg2 = debug_decode_val(argstr2, 0xFFFF);

                if ((args >= 1 && arg  == -1) ||
                    (args >= 2 && arg2 == -1))
                    goto next_cmd;

                if (args < 1) arg  = cmd == 6 ? next_hex_dump : next_disassem;
                if (args < 2) arg2 = cmd == 6 ? 0x40          : 0x10;

                if (cmd == 7)
                    next_disassem = arg;

                if (cmd == 6)
                    next_hex_dump = arg + arg2;
            } else if (cmd == 9) {
                int args = sscanf(s, "%100s %100s", argstr, argstr2);

                if (args >= 1) arg  = debug_decode_val(argstr, 7);
                if (args >= 2) arg2 = debug_decode_val(argstr2, 0xFFFF);

                if ((args >= 1 && arg  == -1) ||
                    (args >= 2 && arg2 == -1))
                    goto next_cmd;

                if (args != 2 || arg < 0 || arg > 7) arg = -2;
            } else if (cmd == 12 || cmd == 17) {
                int args = sscanf(s, "%100s %100s", argstr, argstr2);

                if (args >= 1) arg  = debug_decode_val(argstr,  0xFFFF);
                if (args >= 2) arg2 = debug_decode_val(argstr2, 0xFFFF);

                if ((args >= 1 && arg  == -1) ||
                    (args >= 2 && arg2 == -1))
                    goto next_cmd;

                if (args == 1) arg2 = arg;
                if (arg2 < arg) { int tmp = arg2; arg2 = arg; arg = tmp; }
            } else if (cmd == 14 || cmd == 15) {
                int args = sscanf(s, "%100s %100s", argstr, argstr2);

                if (args >= 1) arg  = debug_decode_val(argstr,  0xFFFF);
                if (args >= 2) arg2 = debug_decode_val(argstr2, 0xFFFF);

                if ((args >= 1 && arg  == -1) ||
                    (args >= 2 && arg2 == -1))
                    arg = -2;

                if (args != 2 || arg < 0x0000 || arg > 0xFFFF) arg = -2;
            } else if (cmd == 27 || cmd == 30)
            {
                if (sscanf(s, "%d", &arg) != 1)
                    arg = 0;
            } else if (cmd == 29)
            {
                int args = sscanf(s, "%d %d", &arg, &arg2);
                if (args != 2)              arg2 = 0;
                if (args != 1 && args != 2) arg = 40;
            } else if (cmd == 34)
            {
                int args = sscanf(s, "%d %d", &arg, &arg2);
                if (args == 1) { arg2 = 1; }
                if (args <  1) { arg = 0; arg2 = 64; }
            } else if (cmd == 36)
            {
                if (sscanf(s, "%d", &arg) != 1 || arg < 1)
                    arg = 54;
            } else
                arg=0;
        } while (!cmd || arg < -1);

        switch (cmd)
        {
            case 1: /* step/trace */
            case 2: /* run */
            {
                if (cmd == 1)
                    last_over = over;
                else
                    over = 0;

                if (over)
                {
                    debug->step_over = 1 | (arg << 1);
                    if (debug_find_jsrs(p, cp, pc))
                    {
                        cmd = 2;
                        arg = -1;
                    }
                } else
                    debug->step_over = 0;

                debug->step_count = arg;
                debug->show_ins   = cmd == 1;
                debug->show_rd = debug->show_wr = cmd == 1 ? show_rdwr : 0;
                if (debug_rh_ptr < 0)
                    cp->step_count = cmd == 1 ? 1 : arg > 0 ? arg : 0;

                break;
            }
            case 23:    /* fast forward */
            {
                if (arg > 0 && arg <= 0xFFFF)
                {
                    fast_fwd           = arg;
                    debug->step_over   = 0;
                    debug->step_count  = -1;
                    debug->show_ins    = 0;
                    debug->show_rd     = 0;
                    debug->show_wr     = 0;
                    if (debug_rh_ptr < 0)
                        cp->step_count = 0;
                    ff_bkpt = cp1600_set_breakpt(cp, arg, CP1600_BKPT);
                }
                break;
            }
            case 3:
                dump_state();
                if (debug_rh_ptr >= 0)
                    debug_write_reghist("dump.hst", p, cp);
                if (debug_memattr)
                    debug_write_memattr("dump.atr");
                goto next_cmd;
            case 4:
                exit(0);
            case 5:
            case 10:
            {
                int set = cmd == 5;
                if (set)
                    cp1600_set_breakpt(cp, arg, CP1600_BKPT);
                else
                    cp1600_clr_breakpt(cp, arg, CP1600_BKPT);
                jzp_printf("%s breakpoint at $%.4X\n", set ? "Set" : "Unset", arg);
                goto next_cmd;
            }
            case 6:
                debug_dispmem(p, arg, arg2);
                goto next_cmd;
            case 7:
                debug_disasm_mem(p, cp, &next_disassem, arg2);
                goto next_cmd;
            case 8:
                jzp_printf("dc hits: %6d  misses: %6d  nocache: %6d  "
                       "unhook: %6d vs %6d\n", dc_hits, dc_miss, dc_nocache,
                       dc_unhook_ok, dc_unhook_odd);
                goto next_cmd;
            case 9:
                cp->r[arg] = arg2;
                goto show_disassem;
            case 11:
                if (!debug_histinit)
                {
                    debug_reghist = CALLOC(uint_16, (HISTSIZE+1)* RH_RECSIZE);
                    debug_profile = CALLOC(uint_32, 0x10000);
                    if (!debug_reghist || !debug_profile)
                    {
                        CONDFREE(debug_reghist);
                        CONDFREE(debug_profile);
                        jzp_printf("Couldn't allocate register history\n");
                    } else
                    {
                        debug_histinit = 1;
                    }
                }
                if (debug_histinit)
                {
                    if (debug_rh_ptr < 0) debug_rh_ptr = 0;
                    else                  debug_rh_ptr = -1;
                    jzp_printf("Register History is %s\n",
                            debug_rh_ptr ? "Off" : "On");
                    if (!debug_rh_ptr)
                    {
                        memset(debug_reghist, 0,
                               RH_RECSIZE*sizeof(uint_16)*HISTSIZE);
                        memset(debug_profile, 0, 0x10000 * sizeof(uint_32));
                    }
                    cp->step_count = debug_rh_ptr ? 0 : 1;
                }
                jzp_flush();
                goto next_cmd;
            case 12:
                {
                    int i, watch;

                    for (i = arg; i <= arg2; i++)
                    {
                        WATCHTOG(i,w);
                    }

                    for (i = arg, watch=-1; i <= arg2; i++)
                    {
                        if (WATCHING(i,w) != watch)
                        {
                            if (watch != -1)
                                jzp_printf(" through $%.4X\n", i - 1);
                            watch = WATCHING(i,w);
                            jzp_printf("%s watching writes to $%.4X",
                                   watch ? "Now" : "No longer", i);
                        }
                    }
                    if (arg2 >= arg)
                    {
                        jzp_printf(arg == arg2?"\n":" through $%.4X\n", arg2);
                    }
                }
                goto next_cmd;
            case 17:
                {
                    int i, watch;

                    for (i = arg; i <= arg2; i++)
                    {
                        WATCHTOG(i,r);
                    }

                    for (i = arg, watch=-1; i <= arg2; i++)
                    {
                        if (WATCHING(i,r) != watch)
                        {
                            if (watch != -1)
                                jzp_printf(" through $%.4X\n", i - 1);
                            watch = WATCHING(i,r);
                            jzp_printf("%s watching reads of $%.4X",
                                   watch ? "Now" : "No longer", i);
                        }
                    }
                    if (arg2 >= arg)
                    {
                        jzp_printf(arg == arg2?"\n":" through $%.4X\n", arg2);
                    }
                }
                goto next_cmd;
            case 13:
                if (!debug_memattr)
                {
                    debug_memattr = CALLOC(uint_8 ,  0x10000);
                    debug_mempc   = CALLOC(uint_16,  0x10000);
                    if (!debug_memattr || !debug_mempc)
                    {
                        CONDFREE(debug_memattr);
                        CONDFREE(debug_mempc  );
                        jzp_printf("Couldn't allocate memory attribute map\n");
                    } else
                    {
                        jzp_printf("Memory attribute map enabled.\n");
                    }
                } else
                {
                    memset(debug_memattr, 0, 0x10000 * sizeof(uint_8));
                    memset(debug_mempc  , 0, 0x10000 * sizeof(uint_16));
                    jzp_printf("Reset memory attribute map.\n");
                }
                goto next_cmd;
            case 14: /* poke */
            {
                if (arg >= 0)
                {
                    periph_poke((periph_p)cp->periph.bus,
                                (periph_p)cp, arg, arg2);
                    cp1600_invalidate(cp, arg, arg);
                }
                else
                    jzp_printf("invalid args.  P addr data\n");

                goto next_cmd;
            }
            case 15: /* enter */
            {
                if (arg >= 0)
                {
                    periph_write((periph_p)cp->periph.bus,
                                 (periph_p)cp, arg, arg2);
                    cp1600_invalidate(cp, arg, arg);
                }
                else
                    jzp_printf("invalid args.  E addr data\n");

                goto next_cmd;
            }
#ifdef STK_TRC
            case 16:
            {
                if (!stk_trc)
                {
                    jzp_printf("Opening stack.trc\n");
                    jzp_printf("Stack base set to $%.4X\n", arg);
                    stk_base = stk_wr = arg;
                    stk_trc  = fopen("stack.trc", "a");
                    if (stk_trc)
                    {
                        fprintf(stk_trc, "--- start of log\n");
                    }
                } else
                {
                    jzp_printf("Closing stack.trc\n");
                    fprintf(stk_trc, "--- end of log\n");
                    fclose(stk_trc);
                    stk_trc = NULL;
                }
                goto next_cmd;
            }
#endif
            case 18:
            {
                show_time = !show_time;
                jzp_printf(show_time
                        ? "Now showing timestamps during step\n"
                        : "No longer showing timestamps during step\n");
                goto next_cmd;
            }
            case 19:
            {
                show_rdwr = !show_rdwr;
                jzp_printf(show_rdwr
                        ? "Now showing CPU reads/writes during step\n"
                        : "No longer showing CPU reads/writes during step\n");
                goto next_cmd;
            }
            case 20:
            {
                debug_print_usage(); goto next_cmd;
            }
            case 21:
            {
                debug_read_symtbl(s);
                goto next_cmd;
            }
            case 22:
            {
                debug_search_symbol(s);
                goto next_cmd;
            }

            case 24:
            {
                debug->stic->debug_flags ^= STIC_SHOW_FIFO_LOAD;
                jzp_printf(debug->stic->debug_flags & STIC_SHOW_FIFO_LOAD
                        ? "Now showing STIC video FIFO loads\n"
                        : "No longer showing STIC video FIFO loads\n");
                goto next_cmd;
            }
            case 25:
            {
                debug->stic->debug_flags ^= STIC_SHOW_RD_DROP;
                jzp_printf(debug->stic->debug_flags & STIC_SHOW_RD_DROP
                        ? "Now showing dropped STIC CTRL/GMEM reads\n"
                        : "No longer showing dropped STIC CTRL/GMEM reads\n");
                goto next_cmd;
            }
            case 26:
            {
                debug->stic->debug_flags ^= STIC_SHOW_WR_DROP;
                jzp_printf(debug->stic->debug_flags & STIC_SHOW_WR_DROP
                        ? "Now showing dropped STIC CTRL/GMEM writes\n"
                        : "No longer showing dropped STIC CTRL/GMEM writes\n");
                goto next_cmd;
            }
            case 32:
            {
                debug->stic->debug_flags ^= STIC_HALT_ON_BLANK;
                jzp_printf(debug->stic->debug_flags & STIC_HALT_ON_BLANK
                        ? "Now halting when display blanks\n"
                        : "No longer halting when display blanks\n");
                goto next_cmd;
            }
            case 27:
            {
                if (arg > 3) arg = 3;
                if (set_symb_addr_format(arg))
                {
                    jzp_printf("debug: Changed address format to \"%s\"\n",
                                symb_format_desc[arg]);

                    debug->symb_addr_format = arg;
                    debug_disasm_cache_inval();
                }
                goto next_cmd;
            }
            case 28:
            {
                debug_open_script(debug, s);
                goto next_cmd;
            }
            case 29:
            {
                if (debug_reghist)
                    debug_print_reghist(arg, arg2);
                goto next_cmd;
            }
            case 30:
            {
                if (arg >= 70)
                    set_disp_width(arg);
                goto next_cmd;
            }
            case 31:
            {
                debug_show_vicinity(arg, arg2);
                goto next_cmd;
            }
            case 33:
            {
                stic_gram_to_gif(debug->stic);
                goto next_cmd;
            }
            case 34:
            {
                stic_gram_to_text(debug->stic, arg, arg2, get_disp_width());
                goto next_cmd;
            } 
            case 35:
            {
                debug->stic->debug_flags ^= STIC_DBG_REQS;
                jzp_printf(debug->stic->debug_flags & STIC_DBG_REQS
                        ? "Now showing STIC BUSRQ/INTRM generation details\n"
                        : "No longer showing STIC BUSRQ/INTRM generation "
                          "details\n");
                goto next_cmd;
            } 
            case 36:
            {
                jzp_printf("Setting NON_INT threshold to %d\n", arg);
                non_int_threshold = arg;
                goto next_cmd;
            }
            case 37:
            {
                debug->stic->debug_flags ^= STIC_HALT_ON_BUSRQ_DROP;
                jzp_printf(debug->stic->debug_flags & STIC_HALT_ON_BUSRQ_DROP
                        ? "Now halting when CPU blocks BUSRQ STIC fetch.\n"
                        : "No longer halting when CPU blocks BUSRQ STIC "
                          "fetch.\n");
                goto next_cmd;
            } 
            case 38:
            {
                debug->stic->debug_flags ^= STIC_HALT_ON_INTRM_DROP;
                jzp_printf(debug->stic->debug_flags & STIC_HALT_ON_INTRM_DROP
                        ? "Now halting when CPU blocks INTRM dispatch.\n"
                        : "No longer halting when CPU blocks INTRM "
                          "dispatch.\n");
                goto next_cmd;
            } 
            case 39:
            {
                /* List tracepoints */
                debug_list_tracepoints(debug, cp);
                goto next_cmd;
            }
            case 40:
            {
                /* List breakpoints */
                debug_list_breakpoints(debug, cp);
                goto next_cmd;
            }
            case 41:
            {
                /* List write-watches */
                jzp_printf("Watched writes:\n");
                debug_list_watches(debug, debug_watch_w);
                goto next_cmd;
            }
            case 42:
            {
                /* List read-watches */
                jzp_printf("Watched reads:\n");
                debug_list_watches(debug, debug_watch_r);
                goto next_cmd;
            }
            case 43:
            {
                const int af = debug->symb_addr_format > 3 ? 3 
                             : debug->symb_addr_format;
                const uint_32 sf = debug->stic->debug_flags;
                /* Print debugger status (command "??") */
                jzp_printf("Debugger misc status:\n");
                jzp_printf("  Register history:         %s\n",
                            debug_rh_ptr < 0 ? "Off" : "On");
                jzp_printf("  Memory attribute map:     %s\n",
                            debug_memattr ? "On" : "Off");
                jzp_printf("  Show cycles:              %s\n",
                            show_time ? "Yes" : "No");
                jzp_printf("  Show read/write:          %s\n",
                            show_rdwr ? "Yes" : "No");
                jzp_printf("  Non-int threshold:        %d\n",
                            non_int_threshold);
                jzp_printf("  Symbol output format:     %s\n",
                            symb_format_desc[af]);

                jzp_printf("\nSTIC-specific debug flags:\n");
                jzp_printf("  Show write drops:         %s\n",
                            (sf & STIC_SHOW_WR_DROP) ? "On" : "Off");
                jzp_printf("  Show read drops:          %s\n",
                            (sf & STIC_SHOW_RD_DROP) ? "On" : "Off");
                jzp_printf("  Show FIFO load:           %s\n",
                            (sf & STIC_SHOW_FIFO_LOAD) ? "On" : "Off");
                jzp_printf("  Halt on display blank:    %s\n",
                            (sf & STIC_HALT_ON_BLANK)  ? "On" : "Off");
                jzp_printf("  Halt on missed BUSRQ:     %s\n",
                            (sf & STIC_HALT_ON_BUSRQ_DROP) ? "On" : "Off");
                jzp_printf("  Halt on missed INTRM:     %s\n",
                            (sf & STIC_HALT_ON_INTRM_DROP) ? "On" : "Off");
                            
                goto next_cmd;
            }
        }

        if (debug->speed) speed_resync(debug->speed);
        if (wind)         gfx_toggle_windowed(debug->gfx, 1);
    }

    return len;
}



/*
 * ============================================================================
 *  Instruction Disassembly Cache
 *
 *  This holds a cache of disassembled instructions.  The cache is maintained
 *  as an LRU list of entries.  We place an upper bound on the number of
 *  entries just so we don't totally thrash through memory.
 * ============================================================================
 */
#define DISASM_CACHE (64)      /* Cache size. */

typedef struct disasm_cache_t
{
    char                    disasm[64];
    uint_32                 len;
    struct disasm_cache_t   *next,*prev;
    char                    **hook;
} disasm_cache_t;

disasm_cache_t *disasm_cache = NULL;

/*
 * ============================================================================
 *  DEBUG_DISASM_CACHE_INVAL -- invalidate the entire disassembly cache
 * ============================================================================
 */
LOCAL void debug_disasm_cache_inval(void)
{
    disasm_cache_t *dc = disasm_cache;

    /* -------------------------------------------------------------------- */
    /*  Go through the entire disassembly cache and unhook all cached       */
    /*  disassemblies.                                                      */
    /* -------------------------------------------------------------------- */
    do
    {
        if (dc->hook && *dc->hook == (char*)dc) /* Unhook it if we're hooked */
            *dc->hook = NULL;

        dc = dc->next;
    } while (dc && dc != disasm_cache);
}

/*
 * ============================================================================
 *  DEBUG_DISASM      -- Disassembles one instruction, returning a pointer
 *                       to the disassembled text.  Uses the disassembly
 *                       cache if possible.
 * ============================================================================
 */
char * debug_disasm(periph_t *p, cp1600_t *cp, uint_16 addr,
                    uint_32 *len, int dbd)
{
    static char buf[1024];
    uint_16 w1, w2, w3, pc = addr;
    disasm_cache_t *disasm = NULL;
    int instr_len;

    /* -------------------------------------------------------------------- */
    /*  If memory attribute tracking is enabled and this looks like data,   */
    /*  just return a DECLE directive.  We never cache these.               */
    /* -------------------------------------------------------------------- */
    if (debug_memattr &&
        (debug_memattr[addr] & (DEBUG_MA_CODE|DEBUG_MA_DATA)) == DEBUG_MA_DATA)
    {
        char c, d;
        w1 = periph_read((periph_t*)p->bus, p, pc    , ~0);

        c = w1 >> 8;
        d = w1;

        if ( !isalnum(c) ) c = '.';
        if ( !isalnum(d) ) d = '.';

        sprintf(buf, " %.4X                   DECLE $%.4X ; '%c%c'",
                     w1, w1, c, d);

        if (len)
            *len = 1;

        return buf;
    }

#if 1
    /* -------------------------------------------------------------------- */
    /*  If we can't cache this disassembly, or if it's not disassembled     */
    /*  yet, go do the disassembly.  Also force disassembly if DBD is set   */
    /*  since we might have seen this instr before w/out DBD set.           */
    /* -------------------------------------------------------------------- */
    if (!cp->disasm[pc] || dbd)
    {
        w1 = periph_read((periph_t*)p->bus, p, pc    , ~0);
        w2 = periph_read((periph_t*)p->bus, p, pc + 1, ~0);
        w3 = periph_read((periph_t*)p->bus, p, pc + 2, ~0);

        instr_len = dasm1600(buf, pc, dbd, w1, w2, w3);

        dc_miss++;
        dc_hits--;            /* compensate for dc_hits++ in LRU update */

        /* ------------------------------------------------------------ */
        /*  If DBD is set, or if this instruction hasn't been decoded   */
        /*  yet, don't cache this instruction.                          */
        /* ------------------------------------------------------------ */
        if (dbd || !cp->instr[pc])
        {
            if (len) *len = instr_len;
            return buf + 17;
        }

        /* ------------------------------------------------------------ */
        /*  Allocate a new disassembly record by evicting the LRU.      */
        /* ------------------------------------------------------------ */
        disasm = disasm_cache->prev;        /* Tail of LRU list.        */

        if (disasm->hook &&                 /* Is this already hooked?  */
            *disasm->hook == (char*)disasm) /* Unhook it.               */
        {
            dc_unhook_ok++;
            *disasm->hook = NULL;
        } else
            dc_unhook_odd++;

        /* ------------------------------------------------------------ */
        /*  Hook this disassembly to the instruction record.            */
        /* ------------------------------------------------------------ */
        disasm->hook   = &cp->disasm[pc];
        cp->disasm[pc] = (char*) disasm;

        strncpy(disasm->disasm, buf + 17, sizeof(disasm->disasm));
        disasm->disasm[sizeof(disasm->disasm) - 1] = 0;
        disasm->len = instr_len;

    }

    /* -------------------------------------------------------------------- */
    /*  Grab the cached disassembly and update the LRU.                     */
    /* -------------------------------------------------------------------- */
    dc_hits++;
    disasm = (disasm_cache_t *)(void *)cp->disasm[pc];

    /* -------------------------------------------------------------------- */
    /*  Move this disasm record to head of the LRU.  Do this by first       */
    /*  unhooking it from the doubly-linked list, and then reinserting      */
    /*  it at the head of the doubly-linked list.                           */
    /* -------------------------------------------------------------------- */
    disasm->next->prev = disasm->prev;      /* Unhook 'next'.           */
    disasm->prev->next = disasm->next;      /* Unhook 'prev'.           */

    disasm->next = disasm_cache->next;      /* Hook us to new 'next'.   */
    disasm->prev = disasm_cache;            /* Hook us to new 'prev'.   */

    disasm->next->prev = disasm;            /* Hook new 'next' to us.   */
    disasm_cache->next = disasm;            /* Hook new 'prev' to us.   */


    /* -------------------------------------------------------------------- */
    /*  Returned cached disassembly.                                        */
    /* -------------------------------------------------------------------- */
    if (len) *len = disasm ? disasm->len : 0;
    return disasm ? disasm->disasm : NULL;
#else
    w1 = periph_read((periph_t*)p->bus, p, pc    , ~0);
    w2 = periph_read((periph_t*)p->bus, p, pc + 1, ~0);
    w3 = periph_read((periph_t*)p->bus, p, pc + 2, ~0);

    instr_len = dasm1600(buf, pc, cp->D, w1, w2, w3);

    if (len) *len = instr_len;

    return buf + 17;
#endif
}

/*
 * ============================================================================
 *  DEBUG_DISASM_SRC  -- Attempt to append source to the disassembly
 * ============================================================================
 */
const char * debug_disasm_src(periph_t *p, cp1600_t *cp, uint_16 addr,
                              uint_32 *len, int dbd, int disasm_width)
{
    static char buf[1024];
    const char *dis, *src;

    if (disasm_mode <  0)   // disasm only
    {
        return debug_disasm(p, cp, addr, len, dbd);
    }

    if (disasm_mode == 0)   // mixed disasm / source (default)
    {
        dis = debug_disasm(p, cp, addr, len, dbd);

        if (disasm_width < 40)
            return dis;

        src = source_for_addr(addr);

        if (src)
        {
            snprintf(buf, 1007, "%-61.61s | %s", dis, src);
            return buf;
        }

        return dis;
    }

    // source-only when source available
    src = source_for_addr(addr);

    if (!src)
        return debug_disasm(p, cp, addr, len, dbd);

    return src;
}

/*
 * ============================================================================
 *  DEBUG_DISASM_MEM  -- Disassembles a range of memory locations.
 * ============================================================================
 */
void debug_disasm_mem(periph_t *p, cp1600_t *cp, uint_16 *paddr, uint_32 cnt)
{
    const char *dis;
    const char *symb;
    uint_32 tot = 0, len = ~0, w0 = 0;
    uint_16 addr = *paddr;

    while (tot <= cnt && len > 0)
    {
        int disp_width   = get_disp_width();
        int disasm_width = disp_width - 42;
        if (len == 1)
            w0 = periph_read((periph_t*)p->bus, p, addr-1, ~0);

        dis = debug_disasm_src(p, cp, addr, &len, len == 1 && w0 == 0x0001,
                               disasm_width);

        if ((symb = debug_symb_for_addr(addr)) != NULL)
            jzp_printf("                   %s:\n", symb);
        jzp_printf(" $%.4X:   %-*.*s\n", addr,
                   disp_width - 12, disp_width - 12, dis);

        addr += len;
        tot++;
    }

    *paddr = addr;
}

/*
 * ============================================================================
 *  DEBUG_DISPMEM     -- Displays ten lines of "hex dump" memory information.
 *                       The first arg is the address to start dumping at.
 *                       The second arg is the number of addresses to dump.
 * ============================================================================
 */
void debug_dispmem(periph_t *p, uint_16 addr, uint_16 len)
{
    int         i, j, k, l;
    uint_32     w[8];

    /* -------------------------------------------------------------------- */
    /*  Round our address down to the next lower multiple of 8.  Add this   */
    /*  difference to the length, and then round the length up to the       */
    /*  next multiple of 8.                                                 */
    /* -------------------------------------------------------------------- */
    k = addr & ~7;

    len = (len + 7 + addr - k) & ~7;

    /* -------------------------------------------------------------------- */
    /*  Iterate until we've dumped the entire block of memory.              */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < len; i += 8)
    {
        l = k;

        /* ---------------------------------------------------------------- */
        /*  Read one row of 8 memory locations.                             */
        /* ---------------------------------------------------------------- */
        for (j = 0; j < 8; j++)
        {
            w[j] = periph_peek((periph_t*)p->bus, p, k++, ~0);
            k &= 0xFFFF;
        }

        /* ---------------------------------------------------------------- */
        /*  Display them.                                                   */
        /* ---------------------------------------------------------------- */
        jzp_printf( "%.4X:  %.4X%c %.4X%c %.4X%c %.4X%c "
                    " %.4X%c %.4X%c %.4X%c %.4X%c   # ",
                    l, w[0], l + 0 == addr ? '*' : ' ',
                       w[1], l + 1 == addr ? '*' : ' ',
                       w[2], l + 2 == addr ? '*' : ' ',
                       w[3], l + 3 == addr ? '*' : ' ',
                       w[4], l + 4 == addr ? '*' : ' ',
                       w[5], l + 5 == addr ? '*' : ' ',
                       w[6], l + 6 == addr ? '*' : ' ',
                       w[7], l + 7 == addr ? '*' : ' ');

        /* ---------------------------------------------------------------- */
        /*  Display ASCII equivalents.                                      */
        /* ---------------------------------------------------------------- */
        for (j = 0; j < 8; j++)
        {
            char c, d;

            c = w[j] >> 8;
            d = w[j];

            if ( !isalnum(c) )
                c = '.';
            if ( !isalnum(d) )
                d = '.';

            jzp_printf( "%c%c", c, d );
        }

        jzp_printf( "\n" );
    }
}

/*
 * ============================================================================
 *  DEBUG_RENDER_REGHIST  -- stringify a record in the register history
 * ============================================================================
 */
LOCAL const char *debug_render_reghist(int ofs, int indent, int disp_width)
{
    int idx, S, C, O, Z, I, D, intr, req_ack, nreq_ack, disasm_width;
    uint_16 pc, flags, r0, r1, r2, r3, r4, r5, r6, w1, w2, w3;
    char *dis, *rslt, *out;
    const char *symb, *src = NULL;
    static char buf[1024];
    uint_64 now, n0, n1, n2, n3;

    disasm_width = disp_width - 60;
    if (disasm_width > 500) disasm_width = 500;

    idx   = ((ofs + debug_rh_ptr) & HISTMASK) * RH_RECSIZE;
    n3    = debug_reghist[idx + 15];
    n2    = debug_reghist[idx + 14];
    n1    = debug_reghist[idx + 13];
    n0    = debug_reghist[idx + 12];
    now   = (n3 << 48) | (n2 << 32) | (n1 << 16) | n0;
    w3    = debug_reghist[idx + 11];
    w2    = debug_reghist[idx + 10];
    w1    = debug_reghist[idx +  9];
    flags = debug_reghist[idx +  8];
    pc    = debug_reghist[idx +  7];
    r6    = debug_reghist[idx +  6];
    r5    = debug_reghist[idx +  5];
    r4    = debug_reghist[idx +  4];
    r3    = debug_reghist[idx +  3];
    r2    = debug_reghist[idx +  2];
    r1    = debug_reghist[idx +  1];
    r0    = debug_reghist[idx +  0];

    if ((flags & 1) == 0)
    {
        buf[0] = 0;
        return buf;
    }

    S        = (flags >> 1) & 1;
    C        = (flags >> 2) & 1;
    O        = (flags >> 3) & 1;
    Z        = (flags >> 4) & 1;
    I        = (flags >> 5) & 1;
    D        = (flags >> 6) & 1;
    intr     = (flags >> 7) & 1;
    req_ack  = (flags >> 8) & 15;

    nreq_ack = (debug_reghist[idx + 8 + RH_RECSIZE] >> 8) & 15;

    /* -------------------------------------------------------------------- */
    /*  Directly decode exactly what is in memory now, rather than rely     */
    /*  on the disassembly cache.                                           */
    /* -------------------------------------------------------------------- */
    dasm1600(buf, pc, D, w1, w2, w3);
    dis = buf + 39;

    /* Did this instruction get stomped by a BUSAK/INTAK? */
    if ((nreq_ack & (CP1600_INTAK|CP1600_BUSAK)) != 0)
    {
        dis = buf + 37;
        dis[0] = '>';
        dis[1] = '>';
    }

    /* -------------------------------------------------------------------- */
    /*  See if we can get a source line for this address.                   */
    /* -------------------------------------------------------------------- */
    if (disasm_width > 40)
        src = source_for_addr(pc);

    if (src)
    {
        dis[40] = 0;
        strcat(dis, "                                        ");
        dis[39] = ' ';
        dis[40] = '|';
        dis[41] = ' ';
        strncpy(dis + 42, src, disasm_width - 42);
    }

    /* -------------------------------------------------------------------- */
    /*  Move the interesting portion of the disassembly to the beginning    */
    /*  of the buffer.                                                      */
    /* -------------------------------------------------------------------- */
    memmove(buf, dis, disasm_width);
    dis = buf;
    dis[disasm_width] = 0;

    /* -------------------------------------------------------------------- */
    /*  Print the state of the machine, along w/ disassembly.               */
    /* -------------------------------------------------------------------- */
    rslt = out = buf + disasm_width + 1;

    if ((symb = debug_symb_for_addr(pc)) != NULL)
    {
        int len = strlen(symb);
        if (len > disp_width - 2)
            len = disp_width - 2;

        strncpy(out, symb, disp_width - 1);
        out[len    ] = ':';
        out[len + 1] = '\n';

        out += len + 2;
    }

    sprintf(out, "%*s%.4X %.4X %.4X %.4X %.4X %.4X %.4X %.4X %c%c%c%c%c%c%c%c"
           "%-*.*s %8" LL_FMT "u\n",
           indent, "",
           r0, r1, r2, r3, r4, r5, r6, pc,
           S ? 'S' : '-', C ? 'C' : '-', O ? 'O' : '-', Z ? 'Z' : '-',
           I ? 'I' : '-', D ? 'D' : '-',
           intr ? 'i' : '-',
           debug_req_ack_char[req_ack],
           disasm_width, disasm_width, dis, now);

    return rslt;
}

/*
 * ============================================================================
 *  DEBUG_PRINT_REGHIST -- Print recent register history
 * ============================================================================
 */
LOCAL void debug_print_reghist(int count, int offset)
{
    int i, w = get_disp_width();

    for (i = -offset - count; i < -offset; i++)
    {
        const char *hist =  debug_render_reghist(i, 1, w);
        if (hist[0])
            jzp_printf("%s", hist);
    }
}

/*
 * ============================================================================
 *  DEBUG_WRITE_REGHIST  -- Write CPU register history trace to file.
 * ============================================================================
 */
LOCAL void debug_write_reghist(const char *fname, periph_p p, cp1600_t *cp)
{
    FILE *f;
    int i;
    double total = 0.0;
    char *dis;
    uint_16 pc;

    f = fopen(fname, "w");
    if (!f)
    {
        fprintf(stderr, "Could not open register history file '%s'\n", fname);
        return;
    }

    jzp_printf("Dumping register history to '%s'...  ", fname);
    jzp_flush();

    for (i = 0; i < HISTSIZE; i++)
    {
        fputs(debug_render_reghist(i, 0, 80), f);
    }
    jzp_printf("Done.\n");

    jzp_printf("Dumping profile data to '%s'...  ", fname);
    jzp_flush();

    fprintf(f,"\n\nProfile\n");

    /* -------------------------------------------------------------------- */
    /*  First total up the cycles profiled.                                 */
    /* -------------------------------------------------------------------- */
    for (i = total = 0; i <= 0xFFFF; i++)
        total += (debug_profile[i] >> 1);

    /* -------------------------------------------------------------------- */
    /*  Then output the blow-by-blow.                                       */
    /* -------------------------------------------------------------------- */
    for (i = pc = 0; i <= 0xFFFF; i++, pc++)
    {
        double cycles;

        /* ---------------------------------------------------------------- */
        /*  Skip addresses that were never executed.                        */
        /* ---------------------------------------------------------------- */
        if (!debug_profile[pc]) continue;

        /* ---------------------------------------------------------------- */
        /*  Disassemble those that were.                                    */
        /* ---------------------------------------------------------------- */
        dis = debug_disasm(p, cp, pc, NULL, debug_profile[pc] & 1);
        dis = dis ? dis : str_null;

        /* ---------------------------------------------------------------- */
        /*  Write the disassembly to the file.                              */
        /* ---------------------------------------------------------------- */
        cycles = debug_profile[pc] >> 1;
        fprintf(f, "%10d (%7.3f%%) | $%.4X:%s\n",
                (uint_32)cycles, 100.0 * cycles / total, pc, dis);
    }
    jzp_printf("Done.\n");

    jzp_flush();
    fclose(f);
}


/*
 * ============================================================================
 *  DEBUG_WRITE_MEMATTR  -- Write memory attribute map for disassembler
 * ============================================================================
 */
LOCAL void debug_write_memattr(const char *fname)
{
    FILE *f;
    int i, span_s, span_e;
    const char *dir = NULL;

    f = fopen(fname, "w");
    if (!f)
    {
        fprintf(stderr, "Could not open memory attribute file '%s'\n", fname);
        return;
    }

    jzp_printf("Dumping memory attribute map to '%s'...  ", fname);
    jzp_flush();

    for (i = 0; i < 0x10000; i = span_e + 1)
    {
        span_s = i;
        for (span_e = span_s; span_e < 0x10000 &&
             debug_memattr[span_e] == debug_memattr[span_s]/* &&
             debug_mempc  [span_e] == debug_mempc  [span_s]*/;
             span_e++)
             ;
        span_e--;

        if (debug_memattr[span_s] == 0)
            continue;

        dir = NULL;
        if        (debug_memattr[span_s] & DEBUG_MA_CODE)
        {
            dir = "code";
        } else if (debug_memattr[span_s] & DEBUG_MA_SDBD)
        {
            dir = "dbdata";
        } else if (debug_memattr[span_s] & DEBUG_MA_DATA)
        {
            dir = "data";
        }

        if (dir)
        {
            /*
            fprintf(f, "%-6s %.4X %.4X ; %.4X\n", dir, span_s, span_e,
                    debug_mempc[span_s]);
            */
            fprintf(f, "%-6s %.4X %.4X\n", dir, span_s, span_e);
        }
    }

    jzp_printf("Done.\n");
    jzp_flush();

    fclose(f);
}


/*
 * ============================================================================
 *  DEBUG_DTOR       -- Tears down all the debugger state.
 * ============================================================================
 */
LOCAL void debug_dtor(periph_p p)
{
    debug_t *debug = (debug_t*)p;

    UNUSED(debug);

    dc_hits = dc_miss = dc_nocache = dc_unhook_ok = dc_unhook_odd = 0;

    CONDFREE(disasm_cache );
    CONDFREE(debug_memattr);
    CONDFREE(debug_mempc  );
    CONDFREE(debug_reghist);
    CONDFREE(debug_profile);

    if (debug_symtab)
        symtab_destroy(debug_symtab);

    debug_symtab         = NULL;
    debug_histinit       = 0;
    debug_rh_ptr         = -1;
    debug_fault_detected = 0;
    debug_halt_reason    = NULL;
    debug_num_jsrs       = 0;

#ifdef STK_TRC
    if (stk_trc)
        fclose(stk_trc);

    stk_trc  = NULL;
    stk_deep = 0;
    stk_base = 0;
    stk_wr   = 0;
#endif
}


/*
 * ============================================================================
 *  DEBUG_INIT       -- Initializes a debugger object and registers a CPU
 *                      pointer.
 * ============================================================================
 */
int debug_init(debug_t *debug, cp1600_t *cp1600, speed_t *speed, gfx_t *gfx,
               stic_t *stic, const char *symtbl, uint_8 *vid_enable,
               const char *script)
{
    static uint_8   dummy  = 0;

    /* -------------------------------------------------------------------- */
    /*  Set up the debugger's state.                                        */
    /* -------------------------------------------------------------------- */
    debug->periph.read     = debug_rd;
    debug->periph.write    = debug_wr;
    debug->periph.peek     = debug_peek;
    debug->periph.poke     = debug_poke;
    debug->periph.tick     = NULL;
    debug->periph.min_tick = ~0U;
    debug->periph.max_tick = ~0U;
    debug->periph.dtor     = debug_dtor;

    debug->periph.addr_base = 0;
    debug->periph.addr_mask = ~0U;

    debug->show_rd = 1;
    debug->show_wr = 1;
    debug->cp1600  = cp1600;
    debug->speed   = speed;
    debug->gfx     = gfx;

    debug->vid_enable     = vid_enable     ? vid_enable     : &dummy;
    debug->stic           = stic;

    debug->filestk[0] = lzoe_filep( stdin );

    /* -------------------------------------------------------------------- */
    /*  Register the debugger's tick function with the CPU.  We're ticked   */
    /*  by the CPU, not by the peripheral bus.                              */
    /* -------------------------------------------------------------------- */
    cp1600_instr_tick(cp1600, debug_tk, (periph_p)debug);

    /* -------------------------------------------------------------------- */
    /*  Set up the instruction disassembly cache.                           */
    /* -------------------------------------------------------------------- */
    if (!disasm_cache)
    {
        int i;

        disasm_cache = CALLOC(disasm_cache_t, DISASM_CACHE);

        for (i = 0; i < DISASM_CACHE-1; i++)
        {
            disasm_cache[i  ].next = &disasm_cache[i+1];
            disasm_cache[i+1].prev = &disasm_cache[i  ];
        }
        disasm_cache[0].prev = &disasm_cache[DISASM_CACHE-1];
        disasm_cache[DISASM_CACHE-1].next = &disasm_cache[0];
    }

    /* -------------------------------------------------------------------- */
    /*  Clear watch array                                                   */
    /* -------------------------------------------------------------------- */
    memset(debug_watch_r, 0, sizeof(debug_watch_r));
    memset(debug_watch_w, 0, sizeof(debug_watch_w));

    /* -------------------------------------------------------------------- */
    /*  Read symbol table if requested to do so.                            */
    /* -------------------------------------------------------------------- */
    if (symtbl)
        debug_read_symtbl(symtbl);

    /* -------------------------------------------------------------------- */
    /*  Set up the debugger script if requested.                            */
    /* -------------------------------------------------------------------- */
    if (script)
        debug_open_script(debug, script);

    return 0;
}

/* ======================================================================== */
/*  This program is free software; you can redistribute it and/or modify    */
/*  it under the terms of the GNU General Public License as published by    */
/*  the Free Software Foundation; either version 2 of the License, or       */
/*  (at your option) any later version.                                     */
/*                                                                          */
/*  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.                                */
/*                                                                          */
/*  You should have received a copy of the GNU General Public License       */
/*  along with this program; if not, write to the Free Software             */
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               */
/* ======================================================================== */
/*                 Copyright (c) 1998-1999, Joseph Zbiciak                  */
/* ======================================================================== */
