Subversion Repositories Shiroi

Rev

Rev 42 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

#pragma once
/*
    mc6847.h -- Motorola 6847 Video Display Generator

    Do this:
        #define CHIPS_IMPL
    before you include this file in *one* C or C++ file to create the
    implementation.

    Optionally provide the following macros with your own implementation

        CHIPS_ASSERT(c)     -- your own assert macro (default: assert(c))

    EMULATED PINS:

                  +-----------+
            FS <--|           |--> A0
            HS <--|           |...
          (RP) <--|           |--> A12
                  |           |
            AG -->|           |
            AS -->|  MC6847   |
        INTEXT -->|           |<-- D0
           GM0 -->|           |...
           GM1 -->|           |<-- D7
           GM2 -->|           |
           INV -->|           |
           CSS -->|           |
                  +-----------+

    FIXME: documentation

    ## zlib/libpng license

    Copyright (c) 2018 Andre Weissflog
    This software is provided 'as-is', without any express or implied warranty.
    In no event will the authors be held liable for any damages arising from the
    use of this software.
    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:
        1. The origin of this software must not be misrepresented; you must not
        claim that you wrote the original software. If you use this software in a
        product, an acknowledgment in the product documentation would be
        appreciated but is not required.
        2. Altered source versions must be plainly marked as such, and must not
        be misrepresented as being the original software.
        3. This notice may not be removed or altered from any source
        distribution.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
    Pin definitions

    The 13 address bus pins A0 to A12 are on the same location as the
    first 13 CPU address bus pins.

    The data bus pins are on the same location as the CPU's.

    The control pins start at location 40.

    For the synchronization output pins FS (field sync), HS (horizontal sync),
    and RP (Row Preset) not only the current state is important, but
    also the transitions from active to inactive. To capture these
    transitions, two extra 64-bit pin struct members are provided
    which store the raising and falling transition state for each
    pin bit.

    FS (field sync)         --  The inactive-to-active transition coincides with the
                                end of the active display area. The active-to-inactive
                                transition coincides with the trailing edge of the
                                vertical synchronization pulse.

    HS (horizontal sync)    --  The HS pulse coincides with the horizontal synchronization
                                pulse furnished to the television receiver by the VDG.
                                The inactive-to-active transition coincides with the
                                leading edge of the horizontal synchronization pulse
                                and the active-to-inactive transition coincides with the
                                trailing egde.

    RP (row preset)         --  The RP pin will go active every 12 lines to reset
                                a counter for an optional external character rom.

    Mode select pin functions:

    | AG | AS | INT/EXT | INV | GM2 | GM1 | GM0 | # colors | description
    +----+----+---------+-----+-----+-----+-----+----------+------------------------------
    |  0 |  0 |    0    |  0  |  -  |  -  |  -  |     2    | internal alphanum
    |  0 |  0 |    0    |  1  |  -  |  -  |  -  |     2    | internal alphanum, inverted
    |  0 |  0 |    1    |  0  |  -  |  -  |  -  |     2    | external alphanum
    |  0 |  0 |    1    |  1  |  -  |  -  |  -  |     2    | external alphanum, inverted
    +----+----+---------+-----+-----+-----+-----+----------+------------------------------
    |  0 |  1 |    0    |  -  |  -  |  -  |  -  |     8    | semigraphics4 (SG4)
    |  0 |  1 |    1    |  -  |  -  |  -  |  -  |     8    | semigraphics6 (SG6)
    +----+----+---------+-----+-----+-----+-----+----------+------------------------------
    |  1 |  - |    -    |  -  |  0  |  0  |  0  |     4    | 64x64 color graphics 1 (CG1)
    |  1 |  - |    -    |  -  |  0  |  0  |  1  |     2    | 128x64 resolution graphics 1 (RG1)
    |  1 |  - |    -    |  -  |  0  |  1  |  0  |     4    | 128x64 color graphics 2 (CG2)
    |  1 |  - |    -    |  -  |  0  |  1  |  1  |     2    | 128x96 resolution graphics 2 (RG2)
    |  1 |  - |    -    |  -  |  1  |  0  |  0  |     4    | 128x96 color graphics 3 (CG3)
    |  1 |  - |    -    |  -  |  1  |  0  |  1  |     2    | 128x192 resolution graphics 3 (RG3)
    |  1 |  - |    -    |  -  |  1  |  1  |  0  |     4    | 128x192 color graphics 6 (CG6)
    |  1 |  - |    -    |  -  |  1  |  1  |  1  |     2    | 256x192 resolution graphics 6 (RG6)

    The CSS pins select between 2 possible color sets.
*/

// address bus pins
#define MC6847_PIN_A0 (0)
#define MC6847_PIN_A1 (1)
#define MC6847_PIN_A2 (2)
#define MC6847_PIN_A3 (3)
#define MC6847_PIN_A4 (4)
#define MC6847_PIN_A5 (5)
#define MC6847_PIN_A6 (6)
#define MC6847_PIN_A7 (7)
#define MC6847_PIN_A8 (8)
#define MC6847_PIN_A9 (9)
#define MC6847_PIN_A10 (10)
#define MC6847_PIN_A11 (11)
#define MC6847_PIN_A12 (12)

// data bus pins
#define MC6847_PIN_D0 (16)
#define MC6847_PIN_D1 (17)
#define MC6847_PIN_D2 (18)
#define MC6847_PIN_D3 (19)
#define MC6847_PIN_D4 (20)
#define MC6847_PIN_D5 (21)
#define MC6847_PIN_D6 (22)
#define MC6847_PIN_D7 (23)

// synchronization output pins
#define MC6847_PIN_FS (40) // field sync
#define MC6847_PIN_HS (41) // horizontal sync
#define MC6847_PIN_RP (42) // row preset (not emulated)

// mode-select input pins
#define MC6847_PIN_AG (43)     // graphics mode enable
#define MC6847_PIN_AS (44)     // semi-graphics mode enable
#define MC6847_PIN_INTEXT (45) // internal/external select
#define MC6847_PIN_INV (46)    // invert enable
#define MC6847_PIN_GM0 (47)    // graphics mode select 0
#define MC6847_PIN_GM1 (48)    // graphics mode select 1
#define MC6847_PIN_GM2 (49)    // graphics mode select 2
#define MC6847_PIN_CSS (50)    // color select pin

// pin bit masks
#define MC6847_A0 (1ULL << MC6847_PIN_A0)
#define MC6847_A1 (1ULL << MC6847_PIN_A1)
#define MC6847_A2 (1ULL << MC6847_PIN_A2)
#define MC6847_A3 (1ULL << MC6847_PIN_A3)
#define MC6847_A4 (1ULL << MC6847_PIN_A4)
#define MC6847_A5 (1ULL << MC6847_PIN_A5)
#define MC6847_A6 (1ULL << MC6847_PIN_A6)
#define MC6847_A7 (1ULL << MC6847_PIN_A7)
#define MC6847_A8 (1ULL << MC6847_PIN_A8)
#define MC6847_A9 (1ULL << MC6847_PIN_A9)
#define MC6847_A10 (1ULL << MC6847_PIN_A10)
#define MC6847_A11 (1ULL << MC6847_PIN_A11)
#define MC6847_A12 (1ULL << MC6847_PIN_A12)
#define MC6847_D0 (1ULL << MC6847_PIN_D0)
#define MC6847_D1 (1ULL << MC6847_PIN_D1)
#define MC6847_D2 (1ULL << MC6847_PIN_D2)
#define MC6847_D3 (1ULL << MC6847_PIN_D3)
#define MC6847_D4 (1ULL << MC6847_PIN_D4)
#define MC6847_D5 (1ULL << MC6847_PIN_D5)
#define MC6847_D6 (1ULL << MC6847_PIN_D6)
#define MC6847_D7 (1ULL << MC6847_PIN_D7)
#define MC6847_FS (1ULL << MC6847_PIN_FS)
#define MC6847_HS (1ULL << MC6847_PIN_HS)
#define MC6847_RP (1ULL << MC6847_PIN_RP)
#define MC6847_AG (1ULL << MC6847_PIN_AG)
#define MC6847_AS (1ULL << MC6847_PIN_AS)
#define MC6847_INTEXT (1ULL << MC6847_PIN_INTEXT)
#define MC6847_INV (1ULL << MC6847_PIN_INV)
#define MC6847_GM0 (1ULL << MC6847_PIN_GM0)
#define MC6847_GM1 (1ULL << MC6847_PIN_GM1)
#define MC6847_GM2 (1ULL << MC6847_PIN_GM2)
#define MC6847_CSS (1ULL << MC6847_PIN_CSS)
#define MC6847_CTRL_PINS (MC6847_AG | MC6847_AS | MC6847_INTEXT | MC6847_INV | MC6847_GM0 | MC6847_GM1 | MC6847_GM2 | MC6847_CSS)

// helper macros to set and extract address and data to/from pin mask

// extract 13-bit address bus from 64-bit pins
#define MC6847_GET_ADDR(p) ((uint16_t)(p & 0xFFFFULL))
// merge 13-bit address bus value into 64-bit pins
#define MC6847_SET_ADDR(p, a) \
        { p = ((p & ~0xFFFFULL) | ((a) & 0xFFFFULL)); }
// extract 8-bit data bus from 64-bit pins
#define MC6847_GET_DATA(p) ((uint8_t)(((p) & 0xFF0000ULL) >> 16))
// merge 8-bit data bus value into 64-bit pins
#define MC6847_SET_DATA(p, d) \
        { p = (((p) & ~0xFF0000ULL) | (((d) << 16) & 0xFF0000ULL)); }

// public constants
#define MC6847_VBLANK_LINES (13)        // 13 lines vblank at top of screen
#define MC6847_TOP_BORDER_LINES (25)    // 25 lines top border
#define MC6847_DISPLAY_LINES (192)      // 192 lines visible display area
#define MC6847_BOTTOM_BORDER_LINES (26) // 26 lines bottom border
#define MC6847_VRETRACE_LINES (6)       // 6 'lines' for vertical retrace
#define MC6847_ALL_LINES (262)          // all of the above
#define MC6847_DISPLAY_START (MC6847_VBLANK_LINES + MC6847_TOP_BORDER_LINES)
#define MC6847_DISPLAY_END (MC6847_DISPLAY_START + MC6847_DISPLAY_LINES)
#define MC6847_BOTTOM_BORDER_END (MC6847_DISPLAY_END + MC6847_BOTTOM_BORDER_LINES)
#define MC6847_FSYNC_START (MC6847_DISPLAY_END)

// hardware color indices
#define MC6847_HWCOLOR_GFX_GREEN (0)
#define MC6847_HWCOLOR_GFX_YELLOW (1)
#define MC6847_HWCOLOR_GFX_BLUE (2)
#define MC6847_HWCOLOR_GFX_RED (3)
#define MC6847_HWCOLOR_GFX_BUFF (4)
#define MC6847_HWCOLOR_GFX_CYAN (5)
#define MC6847_HWCOLOR_GFX_MAGENTA (6)
#define MC6847_HWCOLOR_GFX_ORANGE (7)
#define MC6847_HWCOLOR_ALNUM_GREEN (8)
#define MC6847_HWCOLOR_ALNUM_DARK_GREEN (9)
#define MC6847_HWCOLOR_ALNUM_ORANGE (10)
#define MC6847_HWCOLOR_ALNUM_DARK_ORANGE (11)
#define MC6847_HWCOLOR_BLACK (12)
#define MC6847_HWCOLOR_NUM (13)

// pixel width and height of entire visible area, including border
#define MC6847_DISPLAY_WIDTH (320)
#define MC6847_DISPLAY_HEIGHT (MC6847_TOP_BORDER_LINES + MC6847_DISPLAY_LINES + MC6847_BOTTOM_BORDER_LINES)

// framebuffer width and height
#define MC6847_FRAMEBUFFER_WIDTH (512)
#define MC6847_FRAMEBUFFER_HEIGHT (MC6847_DISPLAY_HEIGHT)
#define MC6847_FRAMEBUFFER_SIZE_BYTES (MC6847_FRAMEBUFFER_WIDTH * MC6847_FRAMEBUFFER_HEIGHT)

// pixel width and height of only the image area, without border
#define MC6847_IMAGE_WIDTH (256)
#define MC6847_IMAGE_HEIGHT (192)

// horizontal border width
#define MC6847_BORDER_PIXELS ((MC6847_DISPLAY_WIDTH - MC6847_IMAGE_WIDTH) / 2)

// the MC6847 is always clocked at 3.579 MHz
#define MC6847_TICK_HZ (3579545)

// fixed point precision for more precise error accumulation
#define MC6847_FIXEDPOINT_SCALE (16)

// a memory-fetch callback, used to read video memory bytes into the MC6847
typedef uint64_t (*mc6847_fetch_t)(uint64_t pins, void* user_data);

// the mc6847 setup parameters
typedef struct {
        // the CPU tick rate in hz
        int tick_hz;
        // pointer to an uint8_t framebuffer where video image is written to (must be at least 512*244 bytes)
        chips_range_t framebuffer;
        // memory-fetch callback
        mc6847_fetch_t fetch_cb;
        // optional user-data for the fetch callback
        void* user_data;
} mc6847_desc_t;

// the mc6847 state struct
typedef struct {
        // last pin state
        uint64_t pins;

        // internal counters
        int h_count;
        int h_sync_start;
        int h_sync_end;
        int h_period;
        int l_count;

        // true during field-sync
        bool fs;

        // the fetch callback function
        mc6847_fetch_t fetch_cb;
        // optional user-data for the fetch-callback
        void* user_data;
        // pointer to uint8_t buffer where decoded video image is written too
        uint8_t* fb;
        // hardware colors
        uint32_t hwcolors[MC6847_HWCOLOR_NUM];
} mc6847_t;

// initialize a new mc6847_t instance
void mc6847_init(mc6847_t* vdg, const mc6847_desc_t* desc);
// reset a mc6847_t instance
void mc6847_reset(mc6847_t* vdg);
// tick the mc6847_t instance, this will call the fetch_cb and generate the image
uint64_t mc6847_tick(mc6847_t* vdg, uint64_t pins);
// prepare mc6847_t snapshot for saving
void mc6847_snapshot_onsave(mc6847_t* snapshot);
// fixup mc6847_t snapshot after loading
void mc6847_snapshot_onload(mc6847_t* snapshot, mc6847_t* sys);

#ifdef __cplusplus
} // extern "C"
#endif

/*--- IMPLEMENTATION ---------------------------------------------------------*/
#ifdef CHIPS_IMPL
#include <string.h>
#ifndef CHIPS_ASSERT
#include <assert.h>
#define CHIPS_ASSERT(c) assert(c)
#endif

#define _MC6847_CLAMP(x) ((x) > 255 ? 255 : (x))
#define _MC6847_RGBA(r, g, b) (0xFF000000 | _MC6847_CLAMP((r * 4) / 3) | (_MC6847_CLAMP((g * 4) / 3) << 8) | (_MC6847_CLAMP((b * 4) / 3) << 16))

void mc6847_init(mc6847_t* vdg, const mc6847_desc_t* desc) {
        CHIPS_ASSERT(vdg && desc);
        CHIPS_ASSERT(desc->framebuffer.ptr && (desc->framebuffer.size <= MC6847_FRAMEBUFFER_SIZE_BYTES));
        CHIPS_ASSERT(desc->fetch_cb);
        CHIPS_ASSERT((desc->tick_hz > 0) && (desc->tick_hz < MC6847_TICK_HZ));

        memset(vdg, 0, sizeof(*vdg));
        vdg->fb = desc->framebuffer.ptr;
        vdg->fetch_cb = desc->fetch_cb;
        vdg->user_data = desc->user_data;

        /* compute counter periods, the MC6847 is always clocked at 3.579 MHz,
           and the frequency of how the tick function is called must be
           communicated to the init function

           one scanline is 228 3.5 MC6847 ticks
        */
        int64_t tmp = (228LL * desc->tick_hz * MC6847_FIXEDPOINT_SCALE) / MC6847_TICK_HZ;
        vdg->h_period = (int)tmp;
        // hsync starts at tick 10 of a scanline
        tmp = (10LL * desc->tick_hz * MC6847_FIXEDPOINT_SCALE) / MC6847_TICK_HZ;
        vdg->h_sync_start = (int)tmp;
        // hsync is 16 ticks long
        tmp = (26LL * desc->tick_hz * MC6847_FIXEDPOINT_SCALE) / MC6847_TICK_HZ;
        vdg->h_sync_end = (int)tmp;

        /* the default graphics mode color palette

           the MC6847 outputs three color values:
            - Y' - six level analog luminance
            - phiA - three level analog (U)
            - phiB - three level analog (V)

            see discussion here: http://forums.bannister.org/ubbthreads.php?ubb=showflat&Number=64986

            NEW VALUES from here: http://www.stardot.org.uk/forums/viewtopic.php?f=44&t=12503

            green:      19 146  11
            yellow:    155 150  10
            blue:        2  22 175
            red:       155  22   7
            buff:      141 150 154
            cyan:       15 143 155
            magenta:   139  39 155
            orange:    140  31  11

            color intensities are slightly boosted
        */
        vdg->hwcolors[MC6847_HWCOLOR_GFX_GREEN] = _MC6847_RGBA(19, 146, 11);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_YELLOW] = _MC6847_RGBA(155, 150, 10);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_BLUE] = _MC6847_RGBA(2, 22, 175);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_RED] = _MC6847_RGBA(155, 22, 7);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_BUFF] = _MC6847_RGBA(141, 150, 154);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_CYAN] = _MC6847_RGBA(15, 143, 155);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_MAGENTA] = _MC6847_RGBA(139, 39, 155);
        vdg->hwcolors[MC6847_HWCOLOR_GFX_ORANGE] = _MC6847_RGBA(140, 31, 11);
        vdg->hwcolors[MC6847_HWCOLOR_ALNUM_GREEN] = _MC6847_RGBA(19, 146, 11);
        vdg->hwcolors[MC6847_HWCOLOR_ALNUM_DARK_GREEN] = 0xFF002400;
        vdg->hwcolors[MC6847_HWCOLOR_ALNUM_ORANGE] = _MC6847_RGBA(140, 31, 11);
        vdg->hwcolors[MC6847_HWCOLOR_ALNUM_DARK_ORANGE] = 0xFF000E22;
        vdg->hwcolors[MC6847_HWCOLOR_BLACK] = 0xFF111111;
}

void mc6847_reset(mc6847_t* vdg) {
        CHIPS_ASSERT(vdg);
        vdg->h_count = 0;
        vdg->l_count = 0;
}

/*
    internal character ROM dump from MAME
    (ntsc_square_fontdata8x12 in devices/video/mc6847.cpp)
*/
static const uint8_t _mc6847_font[64 * 12] = {
    0x00, 0x00, 0x00, 0x1C, 0x22, 0x02, 0x1A, 0x2A, 0x2A, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x12, 0x12, 0x1C, 0x12, 0x12, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x12, 0x12, 0x12, 0x12, 0x12, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x20, 0x20, 0x38, 0x20, 0x20, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x20, 0x20, 0x38, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x20, 0x20, 0x26, 0x22, 0x22, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x22, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3E,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x36, 0x2A, 0x2A, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x32, 0x2A, 0x26, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x2A, 0x24, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x28, 0x24, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x10, 0x08, 0x04, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x2A, 0x2A, 0x36, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x08,
    0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x3E, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x36, 0x00, 0x36, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1E, 0x20, 0x1C, 0x02, 0x3C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x32, 0x04, 0x08, 0x10, 0x26, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x28, 0x28,
    0x10, 0x2A, 0x24, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x3E, 0x1C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x24, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x02, 0x1C, 0x20, 0x20, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C,
    0x22, 0x02, 0x04, 0x02, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0C, 0x14, 0x3E, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x20, 0x3C, 0x02, 0x02, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x22, 0x1E, 0x02, 0x02, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00,
};

static inline uint8_t _mc6847_border_color(uint64_t pins) {
        if(pins & MC6847_AG) {
                // a graphics mode, either green or buff, depending on CSS pin
                return (pins & MC6847_CSS) ? MC6847_HWCOLOR_GFX_BUFF : MC6847_HWCOLOR_GFX_GREEN;
        } else {
                // alphanumeric or semigraphics mode, always black
                return MC6847_HWCOLOR_BLACK;
        }
}

static void _mc6847_decode_border(mc6847_t* vdg, uint64_t pins, size_t y) {
        uint8_t* dst = &(vdg->fb[y * MC6847_FRAMEBUFFER_WIDTH]);
        uint8_t c = _mc6847_border_color(pins);
        for(size_t x = 0; x < MC6847_DISPLAY_WIDTH; x++) {
                *dst++ = c;
        }
}

static uint64_t _mc6847_decode_scanline(mc6847_t* vdg, uint64_t pins, size_t y) {
        uint8_t* dst = &(vdg->fb[(y + MC6847_TOP_BORDER_LINES) * MC6847_FRAMEBUFFER_WIDTH]);
        uint8_t bc = _mc6847_border_color(pins);
        void* ud = vdg->user_data;

        // left border
        for(size_t i = 0; i < MC6847_BORDER_PIXELS; i++) {
                *dst++ = bc;
        }

        // visible scanline
        if(pins & MC6847_AG) {
                // one of the 8 graphics modes
                size_t sub_mode = (uint8_t)((pins & (MC6847_GM2 | MC6847_GM1)) / MC6847_GM1);
                if(pins & MC6847_GM0) {
                        /*  one of the 'resolution modes' (1 bit == 1 pixel block)
                            GM2|GM1:
                                00:    RG1, 128x64, 16 bytes per row
                                01:    RG2, 128x96, 16 bytes per row
                                10:    RG3, 128x192, 16 bytes per row
                                11:    RG6, 256x192, 32 bytes per row
                        */
                        size_t dots_per_bit = (sub_mode < 3) ? 2 : 1;
                        size_t bytes_per_row = (sub_mode < 3) ? 16 : 32;
                        size_t row_height = (pins & MC6847_GM2) ? 1 : (pins & MC6847_GM1) ? 2 : 3;
                        uint16_t addr = (y / row_height) * bytes_per_row;
                        uint8_t fg_color = (pins & MC6847_CSS) ? MC6847_HWCOLOR_GFX_BUFF : MC6847_HWCOLOR_GFX_GREEN;
                        for(size_t x = 0; x < bytes_per_row; x++) {
                                MC6847_SET_ADDR(pins, addr++);
                                pins = vdg->fetch_cb(pins, ud);
                                uint8_t m = MC6847_GET_DATA(pins);
                                for(int p = 7; p >= 0; p--) {
                                        uint8_t c = ((m >> p) & 1) ? fg_color : MC6847_HWCOLOR_BLACK;
                                        for(size_t d = 0; d < dots_per_bit; d++) {
                                                *dst++ = c;
                                        }
                                }
                        }
                } else {
                        /*  one of the 'color modes' (2 bits per pixel == 4 colors, CSS select
                            lower or upper half of palette)
                            GM2|GM1:
                                00: CG1, 64x64, 16 bytes per row
                                01: CG2, 128x64, 32 bytes per row
                                10: CG3, 128x96, 32 bytes per row
                                11: CG6, 128x192, 32 bytes per row
                        */
                        uint8_t color_offset = (pins & MC6847_CSS) ? 4 : 0;
                        size_t dots_per_2bit = (sub_mode == 0) ? 4 : 2;
                        size_t bytes_per_row = (sub_mode == 0) ? 16 : 32;
                        size_t row_height = (pins & MC6847_GM2) ? ((pins & MC6847_GM1) ? 1 : 2) : 3;
                        uint16_t addr = (y / row_height) * bytes_per_row;
                        for(size_t x = 0; x < bytes_per_row; x++) {
                                MC6847_SET_ADDR(pins, addr++);
                                pins = vdg->fetch_cb(pins, ud);
                                uint8_t m = MC6847_GET_DATA(pins);
                                for(int p = 6; p >= 0; p -= 2) {
                                        const uint8_t c = ((m >> p) & 3) + color_offset;
                                        for(size_t d = 0; d < dots_per_2bit; d++) {
                                                *dst++ = c;
                                        }
                                }
                        }
                }
        } else {
                //  we're in alphanumeric/semigraphics mode, one cell is 8x12 pixels

                // the vidmem src address and offset into the font data
                uint16_t addr = (y / 12) * 32;
                uint8_t m; // the pixel bitmask
                size_t chr_y = y % 12;
                // bit shifters to extract a 2x2 or 2x3 semigraphics 2-bit stack
                size_t shift_2x2 = (1 - (chr_y / 6)) * 2;
                size_t shift_2x3 = (2 - (chr_y / 4)) * 2;
                uint8_t alnum_fg = (pins & MC6847_CSS) ? MC6847_HWCOLOR_ALNUM_ORANGE : MC6847_HWCOLOR_ALNUM_GREEN;
                uint8_t alnum_bg = (pins & MC6847_CSS) ? MC6847_HWCOLOR_ALNUM_DARK_ORANGE : MC6847_HWCOLOR_ALNUM_DARK_GREEN;
                for(size_t x = 0; x < 32; x++) {
                        MC6847_SET_ADDR(pins, addr++);
                        pins = vdg->fetch_cb(pins, ud);
                        uint8_t chr = MC6847_GET_DATA(pins);
                        if(pins & MC6847_AS) {
                                // semigraphics mode
                                uint8_t fg_color;
                                if(pins & MC6847_INTEXT) {
                                        /*  2x3 semigraphics, 2 color sets at 4 colors (selected by CSS pin)
                                            |C1|C0|L5|L4|L3|L2|L1|L0|

                                            +--+--+
                                            |L5|L4|
                                            +--+--+
                                            |L3|L2|
                                            +--+--+
                                            |L1|L0|
                                            +--+--+
                                        */

                                        // extract the 2 horizontal bits from one of the 3 stacks
                                        m = (chr >> shift_2x3) & 3;
                                        // 2 bits of color, CSS bit selects upper or lower half of color palette
                                        fg_color = ((chr >> 6) & 3) + ((pins & MC6847_CSS) ? 4 : 0);
                                } else {
                                        /*  2x2 semigraphics, 8 colors + black
                                            |xx|C2|C1|C0|L3|L2|L1|L0|

                                            +--+--+
                                            |L3|L2|
                                            +--+--+
                                            |L1|L0|
                                            +--+--+
                                        */

                                        // extract the 2 horizontal bits from the upper or lower stack
                                        m = (chr >> shift_2x2) & 3;
                                        // 3 color bits directly point into the color palette
                                        fg_color = (chr >> 4) & 7;
                                }
                                // write the horizontal pixel blocks (2 blocks @ 4 pixel each)
                                for(int p = 1; p >= 0; p--) {
                                        uint8_t c = (m & (1 << p)) ? fg_color : MC6847_HWCOLOR_BLACK;
                                        *dst++ = c;
                                        *dst++ = c;
                                        *dst++ = c;
                                        *dst++ = c;
                                }
                        } else {
                                /*  alphanumeric mode
                                    FIXME: INT_EXT (switch between internal and external font
                                */
                                uint8_t m = _mc6847_font[(chr & 0x3F) * 12 + chr_y];
                                if(pins & MC6847_INV) {
                                        m = ~m;
                                }
                                for(int p = 7; p >= 0; p--) {
                                        *dst++ = m & (1 << p) ? alnum_fg : alnum_bg;
                                }
                        }
                }
        }

        // right border
        for(size_t i = 0; i < MC6847_BORDER_PIXELS; i++) {
                *dst++ = bc;
        }

        return pins;
}

uint64_t mc6847_tick(mc6847_t* vdg, uint64_t pins) {

        // output pins will be set each tick
        pins &= ~(MC6847_HS | MC6847_FS);

        // horizontal and vertical field sync
        vdg->h_count += MC6847_FIXEDPOINT_SCALE;
        if((vdg->h_count >= vdg->h_sync_start) && (vdg->h_count < vdg->h_sync_end)) {
                // horizontal sync on
                pins |= MC6847_HS;
                if(vdg->l_count == MC6847_FSYNC_START) {
                        // switch field sync on
                        vdg->fs = true;
                }
        }
        if(vdg->fs) {
                pins |= MC6847_FS;
        }

        // rewind horizontal counter?
        if(vdg->h_count >= vdg->h_period) {
                vdg->h_count -= vdg->h_period;
                vdg->l_count++;
                if(vdg->l_count >= MC6847_ALL_LINES) {
                        // rewind line counter, field sync off
                        vdg->l_count = 0;
                        vdg->fs = false;
                }
                if(vdg->l_count < MC6847_VBLANK_LINES) {
                        // inside vblank area, nothing to do
                } else if(vdg->l_count < MC6847_DISPLAY_START) {
                        // top border
                        size_t y = (size_t)(vdg->l_count - MC6847_VBLANK_LINES);
                        _mc6847_decode_border(vdg, pins, y);
                } else if(vdg->l_count < MC6847_DISPLAY_END) {
                        // visible area
                        size_t y = (size_t)(vdg->l_count - MC6847_DISPLAY_START);
                        pins = _mc6847_decode_scanline(vdg, pins, y);
                } else if(vdg->l_count < MC6847_BOTTOM_BORDER_END) {
                        // bottom border
                        size_t y = (size_t)(vdg->l_count - MC6847_VBLANK_LINES);
                        _mc6847_decode_border(vdg, pins, y);
                }
        }
        vdg->pins = pins;
        return pins;
}

void mc6847_snapshot_onsave(mc6847_t* snapshot) {
        CHIPS_ASSERT(snapshot);
        snapshot->fetch_cb = 0;
        snapshot->user_data = 0;
        snapshot->fb = 0;
}

void mc6847_snapshot_onload(mc6847_t* snapshot, mc6847_t* sys) {
        CHIPS_ASSERT(snapshot && sys);
        snapshot->fetch_cb = sys->fetch_cb;
        snapshot->user_data = sys->user_data;
        snapshot->fb = sys->fb;
}

#endif // CHIPS_IMPL