Entry 762

STIC video chip emulation

   

Submitted by JZbiciak on May 10, 2008 at 1:15 a.m.
Language: C. Code size: 99.2 KB.

/* ======================================================================== */
/*  STIC.C -- New, complete, hopefully fast STIC implementation.            */
/* ======================================================================== */

#include "config.h"
#include "periph/periph.h"
#include "mem/mem.h"
#include "cp1600/cp1600.h"
#include "demo/demo.h"
#include "gfx/gfx.h"
#include "stic.h"
#include "speed/speed.h"
#include "debug/debug_.h"


static const char rcs_id[] UNUSED = "$Id$";

LOCAL void stic_draw_fgbg(stic_t *stic);
LOCAL void stic_draw_cstk(stic_t *stic);


#ifdef __GNUC__
#define ALIGN __attribute__((aligned(128)))
#else
#define ALIGN 
#endif

/* ======================================================================== */
/*  STIC Register Masks                                                     */
/*  Only certain bits in each STIC register are writeable.  The bits that   */
/*  are not implemented return a fixed pattern of 0s and 1s.  The table     */
/*  below encodes this information in the form of an "AND / OR" mask pair.  */
/*  The data is first ANDed with the AND mask.  This mask effectively       */
/*  indicates the implemented bits.  The data is then ORed with the OR      */
/*  mask.  This second mask effectively indicates which of the bits always  */
/*  read as 1.                                                              */
/* ======================================================================== */
struct stic_reg_mask_t
{
    uint_32 and_mask;
    uint_32 or_mask;
};

LOCAL const struct stic_reg_mask_t stic_reg_mask[0x40] ALIGN =
{
    /* MOB X Registers                                  0x00 - 0x07 */
    {0x07FF,0x3800}, {0x07FF,0x3800}, {0x07FF,0x3800}, {0x07FF,0x3800},
    {0x07FF,0x3800}, {0x07FF,0x3800}, {0x07FF,0x3800}, {0x07FF,0x3800},

    /* MOB Y Registers                                  0x08 - 0x0F */
    {0x0FFF,0x3000}, {0x0FFF,0x3000}, {0x0FFF,0x3000}, {0x0FFF,0x3000},
    {0x0FFF,0x3000}, {0x0FFF,0x3000}, {0x0FFF,0x3000}, {0x0FFF,0x3000},

    /* MOB A Registers                                  0x10 - 0x17 */
    {0x3FFF,0x0000}, {0x3FFF,0x0000}, {0x3FFF,0x0000}, {0x3FFF,0x0000},
    {0x3FFF,0x0000}, {0x3FFF,0x0000}, {0x3FFF,0x0000}, {0x3FFF,0x0000},

    /* MOB C Registers                                  0x18 - 0x1F */
    {0x03FE,0x3C00}, {0x03FD,0x3C00}, {0x03FB,0x3C00}, {0x03F7,0x3C00},
    {0x03EF,0x3C00}, {0x03DF,0x3C00}, {0x03BF,0x3C00}, {0x037F,0x3C00},

    /* Display enable, Mode select                      0x20 - 0x21 */
    {0x0000,0x3FFF}, {0x0000,0x3FFF}, 

    /* Unimplemented registers                          0x22 - 0x27 */
    {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF},
    {0x0000,0x3FFF}, {0x0000,0x3FFF},

    /* Color stack, border color                        0x28 - 0x2C */
    {0x000F,0x3FF0}, {0x000F,0x3FF0}, {0x000F,0x3FF0}, {0x000F,0x3FF0}, 
    {0x000F,0x3FF0}, 

    /* Unimplemented registers                          0x2D - 0x2F */
    {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF},

    /* Horiz delay, vertical delay, border extension    0x30 - 0x32 */
    {0x0007,0x3FF8}, {0x0007,0x3FF8}, {0x0003,0x3FFC},

    /* Unimplemented registers                          0x33 - 0x3F */
    {0x0000,0x3FFF},
    {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF},
    {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF},
    {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF}, {0x0000,0x3FFF}
};

/* ======================================================================== */
/*  MOB height secret decoder ring.                                         */
/* ======================================================================== */
LOCAL const int stic_mob_hgt[8] = { 8, 16, 16, 32, 32, 64, 64, 128 };

/* ======================================================================== */
/*  STIC Color Nibble Masks -- For generating packed-nibble pixels.         */
/* ======================================================================== */
LOCAL const uint_32 stic_color_mask[16] ALIGN =
{
    0x00000000, 0x11111111, 0x22222222, 0x33333333,
    0x44444444, 0x55555555, 0x66666666, 0x77777777,
    0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB,
    0xCCCCCCCC, 0xDDDDDDDD, 0xEEEEEEEE, 0xFFFFFFFF
};

/* ======================================================================== */
/*  STIC Color Mask and Bit Manipulation Lookup Tables                      */
/*   -- b2n expands 8 bits to 8 nibbles.                                    */
/*   -- b2n_d expands 8 bits to 4 nibbles, pixel-doubling as it goes.       */
/*   -- b2n_r expands 8 bits to 4 nibbles, reversing bit order as it goes.  */
/*   -- b2n_rd expands 8 bits to 4 nibbles, reversing and pixel-doubleling. */
/*   -- n2b expands 2 nibbles to 2 bytes.                                   */
/*   -- bit_r reverses the bit order in an 8-bit byte.                      */
/*   -- bit_d doubles the bits in an 8-bit byte to a 16-bit int.            */
/*   -- bit_rd doubles the bits and reverses them.                          */
/*  These are computed at runtime for now.                                  */
/* ======================================================================== */
LOCAL uint_16 stic_n2b  [256] ALIGN;
LOCAL uint_32 stic_b2n  [256] ALIGN, stic_b2n_r [256] ALIGN; 
LOCAL uint_32 stic_b2n_d[16]  ALIGN, stic_b2n_rd[16]  ALIGN; 
LOCAL uint_16 stic_bit  [256] ALIGN, stic_bit_r [256] ALIGN;
LOCAL uint_16 stic_bit_d[256] ALIGN, stic_bit_rd[256] ALIGN;


/* ======================================================================== */
/*  STIC_CTRL_RD -- Read from a STIC control register (addr <= 0x3F)        */
/* ======================================================================== */
uint_32 stic_ctrl_rd(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t *)per->parent;
    uint_64 access_time = req && req->req ? req->req->now + 4 : 0;

    (void)data;

#if 1
    /* -------------------------------------------------------------------- */
    /*  Is this access after the Bus-Copy -> Bus-Isolation transition       */
    /*  or before the Bus-Isolation -> Bus-Copy transition?                 */
    /* -------------------------------------------------------------------- */
    if (access_time > stic->stic_accessible ||
        access_time < stic->req_bus->intak)
    {
        /* ---------------------------------------------------------------- */
        /*  Yes:  Return garbage.                                           */
        /* ---------------------------------------------------------------- */
        return addr < 0x80 ? 0x000E & addr : 0xFFFF;
    }
#endif

    /* -------------------------------------------------------------------- */
    /*  If reading location 0x21, put the display into Color-Stack mode.    */
    /* -------------------------------------------------------------------- */
    if ((addr & 0x7F) == 0x0021)
    {
        if (stic->mode != 0) stic->bt_dirty = 1;
        stic->mode = 0;
//jzp_printf("COLORSTACK\n");
        stic->upd  = stic_draw_cstk;
    }

    /* -------------------------------------------------------------------- */
    /*  If we're accessing a STIC CR alias, just return 0xFFFF.             */
    /* -------------------------------------------------------------------- */
    if (addr >= 0x4000)
        return 0xFFFF;

    /* -------------------------------------------------------------------- */
    /*  If we're reading 0x40-0x7F, just sample GRAM and return.            */
    /* -------------------------------------------------------------------- */
    if (addr >= 0x0040)
        return stic->gmem[addr + 0x800] & 0xFF;

    /* -------------------------------------------------------------------- */
    /*  Now just return the raw value from our internal register file,      */
    /*  appropriately conditioned by the read/write masks.                  */
    /* -------------------------------------------------------------------- */
    return (stic->raw[addr] & stic_reg_mask[addr].and_mask) |
            stic_reg_mask[addr].or_mask;
}

/* ======================================================================== */
/*  STIC_CTRL_PEEK -- Like read, except w/out side effects or restrictions  */
/* ======================================================================== */
uint_32 stic_ctrl_peek(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t *)per->parent;

    (void)req;
    (void)data;

    if (addr > 0x40)
        return 0xFFFF;

    /* -------------------------------------------------------------------- */
    /*  Just return the raw value from our internal register file,          */
    /*  appropriately conditioned by the read/write masks.                  */
    /* -------------------------------------------------------------------- */
    return (stic->raw[addr] & stic_reg_mask[addr].and_mask) |
            stic_reg_mask[addr].or_mask;
}

/* ======================================================================== */
/*  STIC_CTRL_WR -- Write to a STIC control register (addr <= 0x3F)         */
/* ======================================================================== */
void stic_ctrl_wr(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t *)per->parent;
    uint_64 access_time = req && req->req ? req->req->now + 4 : 0;
    uint_32 old = 0;

    addr &= 0x7F;

    /* -------------------------------------------------------------------- */
    /*  Ignore writes to the strange GROM visibility window at $40 and up.  */
    /* -------------------------------------------------------------------- */
    if (addr >= 0x40)
        return;

#if 1
    /* -------------------------------------------------------------------- */
    /*  Is this access after the Bus-Copy -> Bus-Isolation transition       */
    /*  or before the Bus-Isolation -> Bus-Copy transition?                 */
    /* -------------------------------------------------------------------- */
    if (access_time > stic->stic_accessible || 
        access_time < stic->req_bus->intak)
    {
        /* ---------------------------------------------------------------- */
        /*  Yes:  Drop the write.                                           */
        /* ---------------------------------------------------------------- */
//jzp_printf("access_time = %llu  accessible = %llu  intak = %llu\n", access_time, stic->stic_accessible, stic->req_bus->intak);
        return;
    }
#endif

    /* -------------------------------------------------------------------- */
    /*  If writing location 0x20, enable the display.                       */
    /* -------------------------------------------------------------------- */
    if (addr == 0x0020)
    {
//jzp_printf("got ve post, stic->phase = %d\n", stic->phase);
        stic->ve_post = 1;
    }

    /* -------------------------------------------------------------------- */
    /*  If writing location 0x21, put the display into FGBG mode.           */
    /* -------------------------------------------------------------------- */
    if (addr == 0x0021)
    {
        if (stic->mode != 1) stic->bt_dirty = 1;
        stic->mode = 1;
//jzp_printf("FOREGROUND/BACKGROUND\n");
        stic->upd  = stic_draw_fgbg;
    }

    /* -------------------------------------------------------------------- */
    /*  Now capture the write and store it in its raw, encoded form (after  */
    /*  adjusting for the and/or masks).  If the old != new, mark the frame */
    /*  as 'dirty'.                                                         */
    /* -------------------------------------------------------------------- */
    old  = stic->raw[addr];
    data &= stic_reg_mask[addr].and_mask;
    data |= stic_reg_mask[addr].or_mask;
    stic->raw[addr] = data;

    if (old != data)
    {
        stic->bt_dirty = 1;
        if (addr == 0x2C)
        {
            gfx_set_bord(stic->gfx, data & 0xF);
        }
    }
}

/* ======================================================================== */
/*  STIC_RESET   -- Reset state internal to the STIC                        */
/* ======================================================================== */
void stic_reset(periph_t *per)
{
    stic_t *stic = (stic_t*)per->parent;
    int a;

    /* -------------------------------------------------------------------- */
    /*  Fill all the STIC registers with 1.                                 */
    /* -------------------------------------------------------------------- */
    memset(stic->raw, 0, sizeof(stic->raw));  /* first, zero it */
    /* then fill it with 1s via ctrl writes */
    for (a = 0x00; a < 0x40; a++)
    {
        if (a != 0x20)
            stic_ctrl_wr(per, per->parent, a, 0xFFFF);
    }

    /* -------------------------------------------------------------------- */
    /*  Resync the internal state machine.                                  */
    /* -------------------------------------------------------------------- */
    stic->fifo_ptr        = 0;
    stic->stic_accessible = 0;
    stic->gmem_accessible = 0;
    stic->req_bus->intak  = ~0ULL;
    stic->req_bus->next_busrq = ~0ULL;
    stic->req_bus->next_intrq = ~0ULL;
    /*
    stic->stic_cr.min_tick = 1;
    stic->stic_cr.max_tick = phase_len;
    stic->next_phase      += phase_len;
    stic->phase            = new_phase;
    */
}


/* ======================================================================== */
/*  STIC_BTAB_WR -- Capture writes to the background cards.                 */
/* ======================================================================== */
void stic_btab_wr(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t*)per->parent;

    /* -------------------------------------------------------------------- */
    /*  Note -- this architecture has problems if I want to accurately      */
    /*  model the incremental sampling of BACKTAB that the STIC performs    */
    /*  throughout display time.  To do it correctly w/ this setup, I need  */
    /*  to double-buffer here, which shouldn't be too bad.  What's 240      */
    /*  words, really?                                                      */
    /* -------------------------------------------------------------------- */
    (void)req;  /* this will become un-ignored later. */

    data &= 0x3FFF;  /* only lower 14 bits seen by STIC. */
    
    if (addr < 0xF0)
    {
        if (data != stic->btab_sr[addr])
            stic->bt_dirty = 1;

        stic->btab_sr[addr] = data;
    }
}


/* ======================================================================== */
/*  STIC_GMEM_WR -- Capture writes to the Graphics RAM.                     */
/* ======================================================================== */
void stic_gmem_wr(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t*)per->parent;
    uint_64 access_time = req && req->req ? req->req->now + 4 : 0;

#if 1
    /* -------------------------------------------------------------------- */
    /*  Drop the write if in Bus Isolation mode.                            */
    /* -------------------------------------------------------------------- */
    if (access_time > stic->gmem_accessible || 
        access_time < stic->req_bus->intak)
    {
        return;
    }
#endif

    /* -------------------------------------------------------------------- */
    /*  We're mapped into the entire 4K address space for GRAM/GROM.  Drop  */
    /*  all writes for GROM addresses.                                      */
    /* -------------------------------------------------------------------- */
    if ((addr & 0x0FFF) < 0x0800)
        return;

    /* -------------------------------------------------------------------- */
    /*  Mask according to what the GRAM will actually see address and data  */
    /*  wise.  As a result, this should even correctly work for the many    */
    /*  GRAM write-aliases.                                                 */
    /* -------------------------------------------------------------------- */
    addr  = (addr & 0x01FF) + 0x0800;
    data &= 0x00FF;  /* Only the lower 8 bits of a GRAM write matter. */

    if (data != stic->gmem[addr]) 
        stic->gr_dirty = 1;

    stic->gmem[addr] = data;
}


/* ======================================================================== */
/*  STIC_GMEM_POKE -- Same as GMEM_WR, except ignores bus isolation.        */
/* ======================================================================== */
void stic_gmem_poke(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t*)per->parent;

    (void)req;

    /* -------------------------------------------------------------------- */
    /*  Don't allow pokes to GROM.                                          */
    /* -------------------------------------------------------------------- */
    if ((addr & 0x0FFF) < 0x0800)
        return;

    /* -------------------------------------------------------------------- */
    /*  Mask according to what the GRAM will actually see address and data  */
    /*  wise.  As a result, this should even correctly work for the many    */
    /*  GRAM write-aliases.                                                 */
    /* -------------------------------------------------------------------- */
    addr  = (addr & 0x01FF) + 0x0800;
    data &= 0x00FF;  /* Only the lower 8 bits of a GRAM write matter. */

    if (data != stic->gmem[addr]) 
        stic->gr_dirty = 1;

    stic->gmem[addr] = data;
}


/* ======================================================================== */
/*  STIC_GMEM_RD -- Read values out of GRAM, GROM, taking into account      */
/*                  when GRAM/GROM are visible.                             */
/* ======================================================================== */
uint_32 stic_gmem_rd(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t*)per->parent;
    uint_64 access_time = req && req->req ? req->req->now + 4 : 0;

    (void)data;
    
#if 1
    /* -------------------------------------------------------------------- */
    /*  Disallow access to graphics memory if in Bus Isolation.  System     */
    /*  Memory will return $FFFF for these reads.                           */
    /* -------------------------------------------------------------------- */
    if (access_time > stic->gmem_accessible ||
        access_time < stic->req_bus->intak)
    {
//jzp_printf("access_time = %llu  gmem_accessible = %llu  intak = %llu\n", access_time, stic->gmem_accessible, stic->req_bus->intak);
        return 0x3FFF & addr;
    }
#endif

    /* -------------------------------------------------------------------- */
    /*  If this is a GRAM address, adjust it for aliases.                   */
    /* -------------------------------------------------------------------- */
    if (addr & 0x0800)
        addr = (addr & 0x09FF);


    /* -------------------------------------------------------------------- */
    /*  Return the data.                                                    */
    /* -------------------------------------------------------------------- */
    return stic->gmem[addr] & 0xFF;
}

/* ======================================================================== */
/*  STIC_GMEM_PEEK -- Like gmem_rd, except always works.                    */
/* ======================================================================== */
uint_32 stic_gmem_peek(periph_t *per, periph_t *req, uint_32 addr, uint_32 data)
{
    stic_t *stic = (stic_t*)per->parent;
    (void)req;
    (void)data;

    /* -------------------------------------------------------------------- */
    /*  If this is a GRAM address, adjust it for aliases.                   */
    /* -------------------------------------------------------------------- */
    if (addr & 0x0800)
        addr = (addr & 0x09FF);

    /* -------------------------------------------------------------------- */
    /*  Return the data.                                                    */
    /* -------------------------------------------------------------------- */
    return stic->gmem[addr] & 0xFF;
}



/* ======================================================================== */
/*  STIC_INIT    -- Initialize this ugly ass peripheral.  Booyah!           */
/* ======================================================================== */
int stic_init
(
    stic_t      *RESTRICT stic,
    uint_16     *RESTRICT grom_img,
    req_bus_t   *RESTRICT req_bus,   
    gfx_t       *RESTRICT gfx,
    demo_t      *RESTRICT demo
)
{
    int i, j;

    /* -------------------------------------------------------------------- */
    /*  First, zero out the STIC structure to get rid of anything that      */
    /*  might be dangling.                                                  */
    /* -------------------------------------------------------------------- */
    memset((void*)stic, 0, sizeof(stic_t));

    /* -------------------------------------------------------------------- */
    /*  Set our graphics subsystem pointers.                                */
    /* -------------------------------------------------------------------- */
    stic->gfx  = gfx;
    stic->disp = gfx->vid;

    /* -------------------------------------------------------------------- */
    /*  Register the demo recorder, if there is one.                        */
    /* -------------------------------------------------------------------- */
    stic->demo = demo;

    /* -------------------------------------------------------------------- */
    /*  Initialize the bit/nibble expansion tables.                         */
    /* -------------------------------------------------------------------- */

    /*  Calculate bit-to-nibble masks b2n, b2n_r */
    for (i = 0; i < 256; i++)
    {
        uint_32 b2n, b2n_r;
        b2n = b2n_r = 0;

        for (j = 0; j < 8; j++)
            if ((i >> j) & 1)
            {
                b2n   |= 0xF << (j * 4);
                b2n_r |= 0xF << (28 - j*4);
            }

        stic_b2n  [i] = b2n;
        stic_b2n_r[i] = b2n_r;
    }

    /* Calculate bit-to-byte masks b2n_d, b2n_rd */
    for (i = 0; i < 16; i++)
    {
        uint_32 b2n_d, b2n_rd;
        b2n_d = b2n_rd = 0;

        for (j = 0; j < 4; j++)
            if ((i >> j) & 1)
            {
                b2n_d  |= 0xFF << (j * 8);
                b2n_rd |= 0xFF << (24 - j*8);
            }

        stic_b2n_d [i] = b2n_d;
        stic_b2n_rd[i] = b2n_rd;
    }

    /* Calculate n2b */
#ifdef BYTE_LE
    for (i = 0; i < 16; i++)
        for (j = 0; j < 16; j++)
            stic_n2b[16*j + i] = 256*i + j;
#else
    for (i = 0; i < 16; i++)
        for (j = 0; j < 16; j++)
            stic_n2b[16*j + i] = i + 256*j;
#endif

    /* Calculate bit_r 8-bit bit-reverse table */
    for (i = 0; i < 256; i++)
    {
        uint_32 bit_r = i;

        bit_r = ((bit_r & 0xAA) >> 1) | ((bit_r & 0x55) << 1);
        bit_r = ((bit_r & 0xCC) >> 2) | ((bit_r & 0x33) << 2);
        bit_r = ((bit_r & 0xF0) >> 4) | ((bit_r & 0x0F) << 4);

        stic_bit  [i] = i     << 8;
        stic_bit_r[i] = bit_r << 8;
    }

    /* Calculate bit-doubling tables bit_d, bit_rd */
    for (i = 0; i < 256; i++)
    {
        uint_32 bit_d = i, bit_rd = stic_bit_r[i] >> 8;

        for (j = 7; j > 0; j--)
        {
            bit_d  += bit_d  & (~0U << j);
            bit_rd += bit_rd & (~0U << j);
        }

        stic_bit_d [i] = 3 * bit_d;
        stic_bit_rd[i] = 3 * bit_rd;
    }

    /* -------------------------------------------------------------------- */
    /*  Initialize graphics memory.                                         */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < 2048; i++)
        stic->gmem[i] = grom_img[i];

    /* -------------------------------------------------------------------- */
    /*  Set up our internal flags.                                          */
    /* -------------------------------------------------------------------- */
    stic->phase = 0;
    stic->mode  = 0;
    stic->upd   = stic_draw_cstk;
    
    /* -------------------------------------------------------------------- */
    /*  Record our INTRQ/BUSRQ request bus pointer.  Usually points us to   */
    /*  cp1600->req_bus.                                                    */
    /* -------------------------------------------------------------------- */
    stic->req_bus = req_bus;

    /* -------------------------------------------------------------------- */
    /*  Now, set up our peripheral functions for the main STIC peripheral.  */
    /* -------------------------------------------------------------------- */
    stic->stic_cr.read      = stic_ctrl_rd;
    stic->stic_cr.write     = stic_ctrl_wr;
    stic->stic_cr.peek      = stic_ctrl_peek;
    stic->stic_cr.poke      = stic_ctrl_wr;
    stic->stic_cr.tick      = stic_tick;
    stic->stic_cr.reset     = stic_reset;
    stic->stic_cr.min_tick  = 57; /* to get started.  stic_tick will reset. */
    stic->stic_cr.max_tick  = 57;
    stic->stic_cr.addr_base = 0x00000000;
    stic->stic_cr.addr_mask = 0x0000FFFF;
    stic->stic_cr.parent    = (void*) stic;
    stic->phase             = 0;
    stic->next_phase        = 57;
    
    /* -------------------------------------------------------------------- */
    /*  Lastly, set up the 'snooping' STIC peripherals.                     */
    /* -------------------------------------------------------------------- */
    stic->snoop_btab.read       = NULL;
    stic->snoop_btab.write      = stic_btab_wr;
    stic->snoop_btab.peek       = NULL;
    stic->snoop_btab.poke       = stic_btab_wr;
    stic->snoop_btab.tick       = NULL;
    stic->snoop_btab.min_tick   = ~0U;
    stic->snoop_btab.max_tick   = ~0U;
    stic->snoop_btab.addr_base  = 0x00000200;
    stic->snoop_btab.addr_mask  = 0x000000FF;
    stic->snoop_btab.parent     = (void*) stic;

    stic->snoop_gram.read       = stic_gmem_rd;
    stic->snoop_gram.write      = stic_gmem_wr;
    stic->snoop_gram.peek       = stic_gmem_peek;
    stic->snoop_gram.poke       = stic_gmem_poke;
    stic->snoop_gram.tick       = NULL;
    stic->snoop_gram.min_tick   = ~0U;
    stic->snoop_gram.max_tick   = ~0U;
    stic->snoop_gram.addr_base  = 0x00003000;
    stic->snoop_gram.addr_mask  = 0x0000FFFF;
    stic->snoop_gram.parent     = (void*) stic;

    return 0;
}


/* ======================================================================== */
/*  STIC BACKTAB display list architecture:                                 */
/*                                                                          */
/*  There are two main BACKTAB renderers for the STIC:  DRAW_CSTK and       */
/*  DRAW_FGBG.  These correspond to the two primary STIC modes.  Each of    */
/*  these renderers produce a pair of display lists that feed into the      */
/*  rest of the STIC display computation.                                   */
/*                                                                          */
/*  The two lists correspond to the colors of the displayed pixels for      */
/*  each card, and the bitmap of "foreground vs. background" pixels for     */
/*  each card.  What's important to note about these lists is that they     */
/*  are in card order, and are not rasterized onto the 160x96 background    */
/*  yet.                                                                    */
/*                                                                          */
/*  The display color list stores a list of 32-bit ints, each containing    */
/*  8 4-bit pixels packed as nibbles within each word.  By packing pixels   */
/*  in this manner, pixel color computation becomes exceedingly efficient.  */
/*  Indeed, it's just a couple ANDs and an OR to merge foreground and       */
/*  background colors for an entire 8-pixel row of a card.                  */
/*                                                                          */
/*  The foreground bitmap list stores a list of bytes, each containing      */
/*  bits indicating which pixels are foreground and which pixels are        */
/*  background.  A '1' bit in this bitmap indicates a foreground pixel.     */
/*  This secondary bitmap will be used to compute MOB collisions later,     */
/*  using nice simple bitwise ANDs to detect coincidence.                   */
/*                                                                          */
/*  The two lists are stored as lists of cards to limit the amount of       */
/*  addressing work that the display computation loops must do.  By         */
/*  tightly limiting the focus of these loops and by constructing a nice    */
/*  linear output pattern, this code should be fairly efficient.            */
/*                                                                          */
/*  In addition to the two display lists, the BACKTAB renderers produce     */
/*  a third, short list containing the "last background color" associated   */
/*  with each row of cards.  This information will be used to render the    */
/*  pixels to the left and above the BACKTAB image later in the engine.     */
/*                                                                          */
/*  These lists will feed into a unified render engine which will merge     */
/*  the MOB images and the BACKTAB image into the final frame buffer.       */
/* ======================================================================== */


/* ======================================================================== */
/*  STIC_DO_MOB -- Render a given MOB.                                      */
/* ======================================================================== */
LOCAL void stic_do_mob(stic_t *stic, int mob)
{
    int y, yy, y_flip, gr_idx, y_res = 8;
    uint_32 x_reg, y_reg, a_reg;
    uint_32 fg_clr, fg_msk;
    uint_16 *      RESTRICT bit_remap;
    uint_32 *const RESTRICT mob_img = stic->mob_img;
    uint_16 *const RESTRICT mob_bmp = stic->mob_bmp[mob];

    /* -------------------------------------------------------------------- */
    /*  Grab the MOB's information.                                         */
    /* -------------------------------------------------------------------- */
    x_reg = stic->raw[mob + 0x00];
    y_reg = stic->raw[mob + 0x08];
    a_reg = stic->raw[mob + 0x10];


    /* -------------------------------------------------------------------- */
    /*  Decode the various control bits from the MOB's registers.           */
    /* -------------------------------------------------------------------- */
    fg_clr = ((a_reg >> 9) & 0x08) | (a_reg & 0x07);
    fg_msk = stic_color_mask[fg_clr];

    if (x_reg & 0x0400)                /* --- double width --- */
    {                                  /* x-flip */  /* normal */
        bit_remap = (y_reg & 0x0400) ? stic_bit_rd : stic_bit_d;
    } else                             /* --- single width --- */
    {                                  /* x-flip */  /* normal */
        bit_remap = (y_reg & 0x0400) ? stic_bit_r  : stic_bit;
    }

    if (y_reg & 0x80)
        y_res = 16;

    y_flip = y_reg & 0x0800 ? y_res - 1 : 0;   /* y-flip vs. normal */

    /* -------------------------------------------------------------------- */
    /*  Decode the GROM/GRAM index.  Bits 9 and 10 are ignored if the card  */
    /*  is from GRAM, or if the display is in Foreground/Background mode.   */
    /* -------------------------------------------------------------------- */
    gr_idx = a_reg & 0xFF8;
    if (stic->mode == 1 || (gr_idx & 0x800))
        gr_idx &= 0x9F8;

    if (y_res == 16)
        gr_idx &= 0xFF0;

    /* -------------------------------------------------------------------- */
    /*  Generate the MOB's bitmap from its color and GRAM/GROM image.       */
    /*  Each MOB is generated to a 16x16 bitmap, regardless of its actual   */
    /*  size.  We handle x-flip, y-flip and x-size here.  We handle y-size  */
    /*  later when compositing the MOBs into a single bitmap.               */
    /* -------------------------------------------------------------------- */
    for (y = 0; y < y_res; y++)
    {
        uint_32 row = stic->gmem[gr_idx + y];
        uint_16 bit = bit_remap[row];
        uint_32 lpix = stic_b2n[bit >> 8  ] & fg_msk;
        uint_32 rpix = stic_b2n[bit & 0xFF] & fg_msk;

        yy = y ^ y_flip;

        mob_img[2*yy + 0] = lpix;
        mob_img[2*yy + 1] = rpix;
        mob_bmp[yy]       = bit;
    }

    for (y = y_res; y < 16; y++)
    {
        mob_img[2*y + 0] = 0;
        mob_img[2*y + 1] = 0;
        mob_bmp[y]       = 0;
    }
}

/* ======================================================================== */
/*  STIC_DRAW_MOBS -- Draw all 8 MOBs onto the 256x96 bitplane.             */
/* ======================================================================== */
LOCAL void stic_draw_mobs(stic_t *stic)
{
    int i, j;
    uint_32 *const RESTRICT mpl_img = stic->mpl_img;
    uint_32 *const RESTRICT mpl_pri = stic->mpl_pri;
    uint_32 *const RESTRICT mpl_vsb = stic->mpl_vsb;
    uint_32 *const RESTRICT mob_img = stic->mob_img;

    /* -------------------------------------------------------------------- */
    /*  First, clear the MOB plane.  We only need to clear the visibility   */
    /*  and priority bits, not the color plane.  This is because we ignore  */
    /*  the contents of the color plane wherever the visibility bit is 0.   */
    /* -------------------------------------------------------------------- */
    memset(mpl_pri, 0, 192 * 224 / 8);
    memset(mpl_vsb, 0, 192 * 224 / 8);

    /* -------------------------------------------------------------------- */
    /*  Generate the bitmaps for the 8 MOBs if they're active, and put      */
    /*  together the MOB plane.                                             */
    /* -------------------------------------------------------------------- */
    for (i = 7; i >= 0; i--)
    {
        uint_32 x_reg = stic->raw[i + 0x00];
        uint_32 y_reg = stic->raw[i + 0x08];
        uint_32 x_pos =  x_reg & 0xFF;
        uint_32 y_pos = (y_reg & 0x7F) * 2;
        uint_32 prio  = (stic->raw[i + 0x10] & 0x2000) ? ~0U : 0;
        uint_32 visb  = x_reg & 0x200;
        uint_32 y_shf = (y_reg >> 8) & 3;
        int     y_stp = 1 << y_shf;
        int     y_hgt = stic_mob_hgt[(y_reg >> 7) & 7];
        uint_32 x_rad = (x_pos & 7) * 4;
        uint_32 x_lad = 32 - x_rad;
        uint_32 x_ofs = (x_pos >> 3);
        uint_32 x_ofb = (x_pos & 31);
        uint_16 *const RESTRICT mob_bmp = stic->mob_bmp[i];
        int     y, y_res;

        /* ---------------------------------------------------------------- */
        /*  Compute bounding box for MOB and tell gfx about it.  We can     */
        /*  use this information to draw debug boxes around MOBs and other  */
        /*  nice things.  Bounding box is inclusive on all four edges.      */
        /* ---------------------------------------------------------------- */
        stic->gfx->bbox[i][0] = x_pos;
        stic->gfx->bbox[i][1] = y_pos;
        stic->gfx->bbox[i][2] = x_pos + (x_reg & 0x400 ? 15 : 7);
        stic->gfx->bbox[i][3] = y_pos + y_hgt - 1;

        /* ---------------------------------------------------------------- */
        /*  Skip this MOB if it is off-screen or is both not-visible and    */
        /*  non-interacting.                                                */
        /* ---------------------------------------------------------------- */
        if (x_pos == 0 || x_pos >= 167 || (x_reg & 0x300) == 0 ||
            y_pos >= 208)
            continue;

        /* ---------------------------------------------------------------- */
        /*  Generate the bitmap information for this MOB.                   */
        /* ---------------------------------------------------------------- */
        stic_do_mob(stic, i);

        /* ---------------------------------------------------------------- */
        /*  If this MOB is visible, put it into the color display image.    */
        /* ---------------------------------------------------------------- */
        if (!visb || stic->drop_frame > 0)
            continue;

        y_res = y_reg & 0x80 ? 16 : 8;

        if (y_pos + (y_res << y_shf) > 208)
            y_res = (207 - y_pos + (1 << y_shf)) >> y_shf;

        for (y = 0; y < y_res; y++)
        {
            uint_32 l_pix, m_pix, r_pix;    /* colors for 16-pixel MOB      */
            uint_32 l_msk, m_msk, r_msk;    /* visibility masks             */
            uint_32 l_old, m_old, r_old;    /* previous colors in disp      */
            uint_32 l_new, m_new, r_new;    /* merged old and MOB           */
            uint_32 l_pri, r_pri;           /* priority bit masks.          */
            int     b_idx, v_idx;           /* index into BMP and VISB      */
            uint_32 l_bmp, r_bmp;           /* 1-bpp bitmaps of MOB         */
                                                                          
            /* ------------------------------------------------------------ */
            /*  Get the 4-bpp images of the MOB into l_pix, m_pix.          */
            /* ------------------------------------------------------------ */
            l_pix = mob_img[2*y + 0];                                    
            m_pix = mob_img[2*y + 1];                                    
            l_msk = stic_b2n[mob_bmp[y] >> 8  ];                         
            m_msk = stic_b2n[mob_bmp[y] & 0xFF];                         
                                                                          
            /* ------------------------------------------------------------ */
            /*  Shift these right according to the X position of MOB.       */
            /*  A 16-pixel wide MOB will straddle up to 3 32-bit words      */
            /*  at 4-bpp.  (x_rad == "x position right adjust")             */
            /* ------------------------------------------------------------ */
            if (x_rad)
            {
                r_pix = (m_pix << x_lad);
                m_pix = (l_pix << x_lad) | (m_pix >> x_rad);
                l_pix =                    (l_pix >> x_rad);
                r_msk = (m_msk << x_lad);
                m_msk = (l_msk << x_lad) | (m_msk >> x_rad);
                l_msk =                    (l_msk >> x_rad);
            } else
            {
                r_pix = 0;
                r_msk = 0;
            }

            /* ------------------------------------------------------------ */
            /*  Similarly, shift the 1-bpp masks right according to the X   */
            /*  position of the MOB.                                        */
            /* ------------------------------------------------------------ */
            if (x_ofb <= 16)
            {
                l_bmp = mob_bmp[y] << (16 - x_ofb);
                r_bmp = 0;
            } else if (x_ofb <= 32)
            {
                l_bmp = mob_bmp[y] >> (x_ofb - 16);
                r_bmp = mob_bmp[y] << (48 - x_ofb);
            } else
            {
                l_bmp = 0;
                r_bmp = mob_bmp[y] << (48 - x_ofb);
            }
                                                                          
            /* ------------------------------------------------------------ */
            /*  Take the computed values and merge them into the image.     */
            /*  This is where we replicate pixels to account for y-size.    */
            /* ------------------------------------------------------------ */
            b_idx = (y_pos + (y << y_shf)) * 24 + x_ofs;
            v_idx = b_idx >> 2;
            for (j = 0; j < y_stp; j++, b_idx += 24, v_idx += 6)
            {
                /* -------------------------------------------------------- */
                /*  Now, merge the colors into the MOB color plane.         */
                /* -------------------------------------------------------- */
                l_old = mpl_img[b_idx + 0];                                
                m_old = mpl_img[b_idx + 1];                                
                r_old = mpl_img[b_idx + 2];                                
                                                                           
                l_new = (l_old &