
/***************************************************************************

    v9938 / v9958 emulation

***************************************************************************/

/*
 todo:

 - sprite collision
 - vdp engine -- make run at correct speed
 - vr/hr/fh flags: double-check all of that
 - make vdp engine work in exp. ram
*/

#include "driver.h"
#include "v9938.h"

#define VERBOSE 0
#define LOG(x)	do { if (VERBOSE) logerror x; } while (0)

typedef struct {
	/* general */
	int model;
	int offset_x, offset_y, visible_y, mode;
	/* palette */
	int	pal_write_first, cmd_write_first;
	UINT8 pal_write, cmd_write;
	UINT8 palReg[32], statReg[10], contReg[48], read_ahead;
	/* memory */
	UINT16 address_latch;
	UINT8 *vram, *vram_exp;
	int vram_size;
    /* interrupt */
    UINT8 INT;
    void (*INTCallback)(running_machine *, int);
	int scanline;
    /* blinking */
    int blink, blink_count;
    /* sprites */
    int sprite_limit;
	/* size */
	int size, size_old, size_auto, size_now;
	/* mouse */
	UINT8 mx_delta, my_delta;
	/* mouse & lightpen */
	UINT8 button_state;
	/* palette */
	UINT16 pal_ind16[16];
	UINT16 pal_ind256[256];
	/* render screen */
	const device_config *screen;
	/* render bitmap */
	bitmap_t *bitmap;
	/* Command unit */
	struct {
		int SX,SY;
		int DX,DY;
		int TX,TY;
		int NX,NY;
		int MX;
		int ASX,ADX,ANX;
		UINT8 CL;
		UINT8 LO;
		UINT8 CM;
		UINT8 MXS, MXD;
		} MMC;
	int  VdpOpsCnt;
	void (*VdpEngine)(void);
} V9938;

static UINT16 *pal_indYJK;

static V9938 vdps[2];
static V9938 *vdp = NULL;

#define V9938_MODE_TEXT1	(0)
#define V9938_MODE_MULTI	(1)
#define V9938_MODE_GRAPHIC1	(2)
#define V9938_MODE_GRAPHIC2	(3)
#define V9938_MODE_GRAPHIC3	(4)
#define V9938_MODE_GRAPHIC4	(5)
#define V9938_MODE_GRAPHIC5	(6)
#define V9938_MODE_GRAPHIC6	(7)
#define V9938_MODE_GRAPHIC7	(8)
#define V9938_MODE_TEXT2	(9)
#define V9938_MODE_UNKNOWN	(10)


static const char *const v9938_modes[] = {
	"TEXT 1", "MULTICOLOR", "GRAPHIC 1", "GRAPHIC 2", "GRAPHIC 3",
	"GRAPHIC 4", "GRAPHIC 5", "GRAPHIC 6", "GRAPHIC 7", "TEXT 2",
	"UNKNOWN" };

static void v9938_register_write (running_machine *machine, int reg, int data);
static void v9938_update_command (void);
static void v9938_cpu_to_vdp (UINT8 V);
static UINT8 v9938_command_unit_w (UINT8 Op);
static UINT8 v9938_vdp_to_cpu (void);
static void v9938_set_mode (void);
static void v9938_refresh_line (running_machine *machine, bitmap_t *bmp, int line);

/***************************************************************************

    Palette functions

***************************************************************************/

/*
About the colour burst registers:

The color burst registers will only have effect on the composite video outputfrom
the V9938. but the output is only NTSC (Never The Same Color ,so the
effects are already present) . this system is not used in europe
the european machines use a separate PAL  (Phase Alternating Line) encoder
or no encoder at all , only RGB output.

Erik de Boer.

--
Right now they're not emulated. For completeness sake they should -- with
a dip-switch to turn them off. I really don't know how they work though. :(
*/

/*
 In screen 8, the colors are encoded as:

 7  6  5  4  3  2  1  0
+--+--+--+--+--+--+--+--+
|g2|g1|g0|r2|r1|r0|b2|b1|
+--+--+--+--+--+--+--+--+

b0 is set if b2 and b1 are set (remember, color bus is 3 bits)

*/

PALETTE_INIT( v9938 )
{
	int	i;

	/* create the full 512 colour palette */
	for (i=0;i<512;i++)
		palette_set_color_rgb(machine, i, pal3bit(i >> 6), pal3bit(i >> 3), pal3bit(i >> 0));
}

/*

The v9958 can display up to 19286 colours. For this we need a larger palette.

The colours are encoded in 17 bits; however there are just 19268 different colours.
Here we calculate the palette and a 2^17 reference table to the palette,
which is: pal_indYJK. It's 256K in size, but I can't think of a faster way
to emulate this. Also it keeps the palette a reasonable size. :)

*/

PALETTE_INIT( v9958 )
{
	int r,g,b,y,j,k,i,k0,j0,n;
	UINT8 pal[19268*3];

	/* init v9938 512-color palette */
	PALETTE_INIT_CALL(v9938);

	/* set up YJK table */
		pal_indYJK = (UINT16 *)auto_malloc(0x20000 * sizeof(UINT16));

	LOG(("Building YJK table for V9958 screens, may take a while ... \n"));
	i = 0;
	for (y=0;y<32;y++) for (k=0;k<64;k++) for (j=0;j<64;j++)
	{
		/* calculate the color */
		if (k >= 32) k0 = (k - 64); else k0 = k;
		if (j >= 32) j0 = (j - 64); else j0 = j;
		r = y + j0;
		b = (y * 5 - 2 * j0 - k0) / 4;
		g = y + k0;
		if (r < 0) r = 0; else if (r > 31) r = 31;
		if (g < 0) g = 0; else if (g > 31) g = 31;
		if (b < 0) b = 0; else if (b > 31) b = 31;

		r = (r << 3) | (r >> 2);
		b = (b << 3) | (b >> 2);
		g = (g << 3) | (g >> 2);
		/* have we seen this one before? */
		n = 0;
		while (n < i)
		{
			if (pal[n*3+0] == r && pal[n*3+1] == g && pal[n*3+2] == b)
			{
				pal_indYJK[y | j << 5 | k << (5 + 6)] = n + 512;
				break;
			}
			n++;
		}

		if (i == n)
		{
			/* so we haven't; add it */
			pal[i*3+0] = r;
			pal[i*3+1] = g;
			pal[i*3+2] = b;
			palette_set_color(machine, i+512, MAKE_RGB(r, g, b));
			pal_indYJK[y | j << 5 | k << (5 + 6)] = i + 512;
			i++;
		}
	}

	if (i != 19268)
		LOG( ("Table creation failed - %d colours out of 19286 created\n", i));
}

/*

 so lookups for screen 12 will look like:

 int ind;

 ind = (*data & 7) << 11 | (*(data + 1) & 7) << 14 |
       (*(data + 2) & 7) << 5 | (*(data + 3) & 7) << 8;

 pixel0 = pal_indYJK[ind | (*data >> 3) & 31];
 pixel1 = pal_indYJK[ind | (*(data + 1) >> 3) & 31];
 pixel2 = pal_indYJK[ind | (*(data + 2) >> 3) & 31];
 pixel3 = pal_indYJK[ind | (*(data + 3) >> 3) & 31];

and for screen 11:

pixel0 = (*data) & 8 ? pal_ind16[(*data) >> 4] : pal_indYJK[ind | (*data >> 3) & 30];
pixel1 = *(data+1) & 8 ? pal_ind16[*(data+1) >> 4] : pal_indYJK[ind | *(data+1) >> 3) & 30];
pixel2 = *(data+2) & 8 ? pal_ind16[*(data+2) >> 4] : pal_indYJK[ind | *(data+2) >> 3) & 30];
pixel3 = *(data+3) & 8 ? pal_ind16[*(data+3) >> 4] : pal_indYJK[ind | *(data+3) >> 3) & 30];

*/

static void v9938_palette_w(UINT8 data)
	{
	int indexp;

	if (vdp->pal_write_first)
		{
		/* store in register */
		indexp = vdp->contReg[0x10] & 15;
		vdp->palReg[indexp*2] = vdp->pal_write & 0x77;
		vdp->palReg[indexp*2+1] = data & 0x07;
		/* update palette */
		vdp->pal_ind16[indexp] = (((int)vdp->pal_write << 2) & 0x01c0)  |
						 	   (((int)data << 3) & 0x0038)  |
						 		((int)vdp->pal_write & 0x0007);

		vdp->contReg[0x10] = (vdp->contReg[0x10] + 1) & 15;
		vdp->pal_write_first = 0;
		}
	else
		{
		vdp->pal_write = data;
		vdp->pal_write_first = 1;
		}
	}

WRITE8_HANDLER( v9938_0_palette_w )
{
	vdp = &vdps[0];
	v9938_palette_w(data);
};

WRITE8_HANDLER( v9938_1_palette_w )
{
	vdp = &vdps[1];
	v9938_palette_w(data);
}

static void v9938_reset_palette (void)
	{
	/* taken from V9938 Technical Data book, page 148. it's in G-R-B format */
	static const UINT8 pal16[16*3] = {
		0, 0, 0, /* 0: black/transparent */
		0, 0, 0, /* 1: black */
		6, 1, 1, /* 2: medium green */
		7, 3, 3, /* 3: light green */
		1, 1, 7, /* 4: dark blue */
		3, 2, 7, /* 5: light blue */
		1, 5, 1, /* 6: dark red */
		6, 2, 7, /* 7: cyan */
		1, 7, 1, /* 8: medium red */
		3, 7, 3, /* 9: light red */
		6, 6, 1, /* 10: dark yellow */
		6, 6, 4, /* 11: light yellow */
		4, 1, 1, /* 12: dark green */
		2, 6, 5, /* 13: magenta */
		5, 5, 5, /* 14: gray */
		7, 7, 7  /* 15: white */
        };
	int i, red, ind;

	for (i=0;i<16;i++)
		{
		/* set the palette registers */
		vdp->palReg[i*2+0] = pal16[i*3+1] << 4 | pal16[i*3+2];
		vdp->palReg[i*2+1] = pal16[i*3];
		/* set the reference table */
		vdp->pal_ind16[i] = pal16[i*3+1] << 6 | pal16[i*3] << 3 | pal16[i*3+2];
		}

	/* set internal palette GRAPHIC 7 */
	for (i=0;i<256;i++)
		{
		ind = (i << 4) & 0x01c0;
		ind |= (i >> 2) & 0x0038;
		red = (i << 1) & 6; if (red == 6) red++;
		ind |= red;

		vdp->pal_ind256[i] = ind;
		}
	}

/***************************************************************************

    Memory functions

***************************************************************************/

static void v9938_vram_write (int offset, int data)
	{
	int newoffset;

	if ( (vdp->mode == V9938_MODE_GRAPHIC6) || (vdp->mode == V9938_MODE_GRAPHIC7) )
		{
        newoffset = ((offset & 1) << 16) | (offset >> 1);
   		if (newoffset < vdp->vram_size)
        	vdp->vram[newoffset] = data;
		}
	else
		{
		if (offset < vdp->vram_size)
			vdp->vram[offset] = data;
        }
	}

static int v9938_vram_read (int offset)
	{
	if ( (vdp->mode == V9938_MODE_GRAPHIC6) || (vdp->mode == V9938_MODE_GRAPHIC7) )
		return vdp->vram[((offset & 1) << 16) | (offset >> 1)];
	else
		return vdp->vram[offset];
	}

static void v9938_vram_w( UINT8 data )
	{
	int address;

	/*v9938_update_command ();*/

	vdp->cmd_write_first = 0;

    address = ((int)vdp->contReg[14] << 14) | vdp->address_latch;

    if (vdp->contReg[45] & 0x40)
        {
		if ( (vdp->mode == V9938_MODE_GRAPHIC6) || (vdp->mode == V9938_MODE_GRAPHIC7) )
			address >>= 1;	/* correct? */
        if (vdp->vram_exp && address < 0x10000)
            vdp->vram_exp[address] = data;
        }
    else
        {
		v9938_vram_write (address, data);
        }

	vdp->address_latch = (vdp->address_latch + 1) & 0x3fff;
	if ((!vdp->address_latch) && (vdp->contReg[0] & 0x0c) ) /* correct ??? */
		{
		vdp->contReg[14] = (vdp->contReg[14] + 1) & 7;
		}
	}

 WRITE8_HANDLER (v9938_0_vram_w)
 {
	 vdp = &vdps[0];
	 v9938_vram_w( data );
 }

 WRITE8_HANDLER (v9938_1_vram_w)
 {
	 vdp = &vdps[1];
	 v9938_vram_w( data );
 }

 static UINT8 v9938_vram_r(void)
	{
	UINT8 ret;
	int address;

	address = ((int)vdp->contReg[14] << 14) | vdp->address_latch;

	vdp->cmd_write_first = 0;

	ret = vdp->read_ahead;

	if (vdp->contReg[45] & 0x40)
		{
		if ( (vdp->mode == V9938_MODE_GRAPHIC6) || (vdp->mode == V9938_MODE_GRAPHIC7) )
			address >>= 1;	/* correct? */
		/* correct? */
		if (vdp->vram_exp && address < 0x10000)
			vdp->read_ahead = vdp->vram_exp[address];
		else
			vdp->read_ahead = 0xff;
		}
	else
		{
		vdp->read_ahead = v9938_vram_read (address);
		}

	vdp->address_latch = (vdp->address_latch + 1) & 0x3fff;
	if ((!vdp->address_latch) && (vdp->contReg[0] & 0x0c) ) /* correct ??? */
		{
		vdp->contReg[14] = (vdp->contReg[14] + 1) & 7;
		}

	return ret;
	}

READ8_HANDLER (v9938_0_vram_r)
{
	vdp = &vdps[0];
	return v9938_vram_r();
}

READ8_HANDLER (v9938_1_vram_r)
{
	vdp = &vdps[1];
	return v9938_vram_r();
}

static void v9938_command_w(running_machine *machine, UINT8 data)
	{
	if (vdp->cmd_write_first)
		{
		if (data & 0x80)
		{
			if (!(data & 0x40))
			v9938_register_write (machine, data & 0x3f, vdp->cmd_write);
		}
		else
			{
			vdp->address_latch =
				(((UINT16)data << 8) | vdp->cmd_write) & 0x3fff;
			if ( !(data & 0x40) ) v9938_vram_r (); /* read ahead! */
			}

		vdp->cmd_write_first = 0;
		}
	else
		{
		vdp->cmd_write = data;
		vdp->cmd_write_first = 1;
		}
	}

WRITE8_HANDLER (v9938_0_command_w)
{
	vdp = &vdps[0];
	v9938_command_w(space->machine, data);
}

WRITE8_HANDLER (v9938_1_command_w)
{
	vdp = &vdps[1];
	v9938_command_w(space->machine, data);
}

/***************************************************************************

    Init/stop/reset/Interrupt functions

***************************************************************************/

void v9938_init (running_machine *machine, int which, const device_config *screen, bitmap_t *bitmap, int model, int vram_size, void (*callback)(running_machine *, int) )
{
	vdp = &vdps[which];

	memset (vdp, 0, sizeof (*vdp) );

	vdp->VdpOpsCnt = 1;
	vdp->VdpEngine = NULL;

	vdp->screen = screen;
	vdp->bitmap = bitmap;
	vdp->model = model;
	vdp->vram_size = vram_size;
	vdp->INTCallback = callback;
	vdp->size_old = -1;

	/* allocate VRAM */
	vdp->vram = (UINT8 *)auto_malloc (0x20000);
	memset (vdp->vram, 0, 0x20000);
	if (vdp->vram_size < 0x20000)
	{
		/* set unavailable RAM to 0xff */
		memset (vdp->vram + vdp->vram_size, 0xff, (0x20000 - vdp->vram_size) );
	}

	/* do we have expanded memory? */
	if (vdp->vram_size > 0x20000)
	{
		vdp->vram_exp = (UINT8 *)auto_malloc (0x10000);
		memset (vdp->vram_exp, 0, 0x10000);
	}
	else
		vdp->vram_exp = NULL;

	state_save_register_item(machine, "v9938", NULL, which, vdp->offset_x);
	state_save_register_item(machine, "v9938", NULL, which, vdp->offset_y);
	state_save_register_item(machine, "v9938", NULL, which, vdp->visible_y);
	state_save_register_item(machine, "v9938", NULL, which, vdp->mode);
	state_save_register_item(machine, "v9938", NULL, which, vdp->pal_write_first);
	state_save_register_item(machine, "v9938", NULL, which, vdp->cmd_write_first);
	state_save_register_item(machine, "v9938", NULL, which, vdp->pal_write);
	state_save_register_item(machine, "v9938", NULL, which, vdp->cmd_write);
	state_save_register_item_array(machine, "v9938", NULL, which, vdp->palReg);
	state_save_register_item_array(machine, "v9938", NULL, which, vdp->statReg);
	state_save_register_item_array(machine, "v9938", NULL, which, vdp->contReg);
	state_save_register_item(machine, "v9938", NULL, which, vdp->read_ahead);
	state_save_register_item_pointer(machine, "v9938", NULL, which, vdp->vram, 0x20000);
	if ( vdp->vram_exp != NULL )
		state_save_register_item_pointer(machine, "v9938", NULL, which, vdp->vram_exp, 0x10000);
	state_save_register_item(machine, "v9938", NULL, which, vdp->INT);
	state_save_register_item(machine, "v9938", NULL, which, vdp->scanline);
	state_save_register_item(machine, "v9938", NULL, which, vdp->blink);
	state_save_register_item(machine, "v9938", NULL, which, vdp->blink_count);
	state_save_register_item(machine, "v9938", NULL, which, vdp->size);
	state_save_register_item(machine, "v9938", NULL, which, vdp->size_old);
	state_save_register_item(machine, "v9938", NULL, which, vdp->size_auto);
	state_save_register_item(machine, "v9938", NULL, which, vdp->size_now);
	state_save_register_item(machine, "v9938", NULL, which, vdp->mx_delta);
	state_save_register_item(machine, "v9938", NULL, which, vdp->my_delta);
	state_save_register_item(machine, "v9938", NULL, which, vdp->button_state);
	state_save_register_item_array(machine, "v9938", NULL, which, vdp->pal_ind16);
	state_save_register_item_array(machine, "v9938", NULL, which, vdp->pal_ind256);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.SX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.SY);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.DX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.DY);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.TX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.TY);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.NX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.NY);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.MX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.ASX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.ADX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.ANX);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.CL);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.LO);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.CM);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.MXS);
	state_save_register_item(machine, "v9938", NULL, which, vdp->MMC.MXD);
	state_save_register_item(machine, "v9938", NULL, which, vdp->VdpOpsCnt);
}

void v9938_reset (int which)
{
	int i;

	vdp = &vdps[which];

	/* offset reset */
	vdp->offset_x = 8;
	vdp->offset_y = 8 + 16;
	vdp->visible_y = 192;
	/* register reset */
	v9938_reset_palette (); /* palette registers */
	for (i=0;i<10;i++) vdp->statReg[i] = 0;
	vdp->statReg[2] = 0x0c;
	if (vdp->model == MODEL_V9958) vdp->statReg[1] |= 4;
	for (i=0;i<48;i++) vdp->contReg[i] = 0;
	vdp->cmd_write_first = vdp->pal_write_first = 0;
	vdp->INT = 0;
	vdp->read_ahead = 0; vdp->address_latch = 0; /* ??? */
	vdp->scanline = 0;
}

static void v9938_check_int (running_machine *machine)
	{
	UINT8 n;

	n = ( (vdp->contReg[1] & 0x20) && (vdp->statReg[0] & 0x80) /*&& vdp->vblank_int*/) ||
		( (vdp->statReg[1] & 0x01) && (vdp->contReg[0] & 0x10) );

	/*if(n && vdp->vblank_int)
    {
        vdp->vblank_int = 0;
    }*/

	if (n != vdp->INT)
		{
		vdp->INT = n;
		LOG(("V9938: IRQ line %s\n", n ? "up" : "down"));
		}

	/*
    ** Somehow the IRQ request is going down without cpu_irq_line () being
    ** called; because of this Mr. Ghost, Xevious and SD Snatcher don't
    ** run. As a patch it's called every scanline
    */
	vdp->INTCallback (machine, n);
	}

void v9938_set_sprite_limit (int which, int i)
	{
	vdp = &vdps[which];
	vdp->sprite_limit = i;
	}

void v9938_set_resolution (int which, int i)
	{
	vdp = &vdps[which];
	if (i == RENDER_AUTO)
		{
		vdp->size_auto = 1;
		}
	else
		{
		vdp->size = i;
		vdp->size_auto = 0;
		}
	}

/***************************************************************************

    Register functions

***************************************************************************/

static void v9938_register_w(running_machine *machine, UINT8 data)
{
	int reg;

	reg = vdp->contReg[17] & 0x3f;
	if (reg != 17)
		v9938_register_write (machine, reg, data); /* true ? */

	if (!(vdp->contReg[17] & 0x80))
		vdp->contReg[17] = (vdp->contReg[17] + 1) & 0x3f;
}

WRITE8_HANDLER (v9938_0_register_w)
{
	vdp = &vdps[0];
	v9938_register_w(space->machine, data);
}

WRITE8_HANDLER (v9938_1_register_w)
{
	vdp = &vdps[1];
	v9938_register_w(space->machine, data);
}

static void v9938_register_write (running_machine *machine, int reg, int data)
{
	static UINT8 const reg_mask[] =
	{
		0x7e, 0x7b, 0x7f, 0xff, 0x3f, 0xff, 0x3f, 0xff,
		0xfb, 0xbf, 0x07, 0x03, 0xff, 0xff, 0x07, 0x0f,
		0x0f, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0x00, 0x7f, 0x3f, 0x07
	};

	if (reg <= 27)
	{
		data &= reg_mask[reg];
		if (vdp->contReg[reg] == data)
			return;
	}

	if (reg > 46)
	{
		LOG(("V9938: Attempted to write to non-existant R#%d\n", reg));
		return;
	}

	/*v9938_update_command (); */

	switch (reg) {
	/* registers that affect interrupt and display mode */
	case 0:
	case 1:
		vdp->contReg[reg] = data;
		v9938_set_mode ();
		v9938_check_int (machine);
		LOG(("V9938: mode = %s\n", v9938_modes[vdp->mode]));
		break;

	case 18:
	case 9:
		vdp->contReg[reg] = data;
		/* recalc offset */
		vdp->offset_x = (( (~vdp->contReg[18] - 8) & 0x0f) + 1);
		vdp->offset_y = ((~(vdp->contReg[18]>>4) - 8) & 0x0f) + 7;
		if (vdp->contReg[9] & 0x80)
		{
			vdp->visible_y = 212;
		}
		else
		{
			vdp->visible_y = 192;
			vdp->offset_y += 10;
		}
		break;

	case 15:
		vdp->pal_write_first = 0;
		break;

	/* color burst registers aren't emulated */
	case 20:
	case 21:
	case 22:
		LOG(("V9938: Write %02xh to R#%d; color burst not emulated\n",
			data, reg));
		break;
	case 25:
	case 26:
	case 27:
		if (vdp->model != MODEL_V9958)
		{
			LOG(("V9938: Attempting to write %02xh to V9958 R#%d\n", data, reg));
			data = 0;
		}
		break;

	case 44:
		v9938_cpu_to_vdp (data);
		break;

	case 46:
		v9938_command_unit_w (data);
		break;
	}

	if (reg != 15)
		LOG(("V9938: Write %02x to R#%d\n", data, reg));

	vdp->contReg[reg] = data;
}

 static UINT8 v9938_status_r(running_machine *machine)
	{
	int reg;
	UINT8 ret;

	vdp->cmd_write_first = 0;

	reg = vdp->contReg[15] & 0x0f;
	if (reg > 9)
		return 0xff;

	switch (reg)
		{
		case 0:
			ret = vdp->statReg[0];
			vdp->statReg[0] &= 0x1f;
			break;
		case 1:
			ret = vdp->statReg[1];
			vdp->statReg[1] &= 0xfe;
			if ((vdp->contReg[8] & 0xc0) == 0x80)
				/* mouse mode: add button state */
				ret |= vdp->button_state & 0xc0;
			break;
		case 2:
			/*v9938_update_command ();*/
/*
    WTF is this? Whatever this was intended to do, it is nonsensical.
    Might as well pick a random number....
    This was an attempt to emulate H-Blank flag ;)
            n = cycles_currently_ran ();
            if ( (n < 28) || (n > 199) ) vdp.statReg[2] |= 0x20;
            else vdp.statReg[2] &= ~0x20;
*/
			if (mame_rand(machine) & 1) vdp->statReg[2] |= 0x20;
			else vdp->statReg[2] &= ~0x20;
			ret = vdp->statReg[2];
			break;
		case 3:
			if ((vdp->contReg[8] & 0xc0) == 0x80)
			{	/* mouse mode: return x mouse delta */
				ret = vdp->mx_delta;
				vdp->mx_delta = 0;
			}
			else
				ret = vdp->statReg[3];
			break;
		case 5:
			if ((vdp->contReg[8] & 0xc0) == 0x80)
			{	/* mouse mode: return y mouse delta */
				ret = vdp->my_delta;
				vdp->my_delta = 0;
			}
			else
				ret = vdp->statReg[5];
			break;
		case 7:
			ret = vdp->statReg[7];
			vdp->statReg[7] = vdp->contReg[44] = v9938_vdp_to_cpu () ;
			break;
		default:
			ret = vdp->statReg[reg];
			break;
		}

	LOG(("V9938: Read %02x from S#%d\n", ret, reg));
	v9938_check_int (machine);

	return ret;
	}

READ8_HANDLER( v9938_0_status_r )
{
	vdp = &vdps[0];
	return v9938_status_r(space->machine);
}

READ8_HANDLER( v9938_1_status_r )
{
	vdp = &vdps[1];
	return v9938_status_r(space->machine);
}

/***************************************************************************

    Refresh / render function

***************************************************************************/

#define V9938_SECOND_FIELD ( !(((vdp->contReg[9] & 0x04) && !(vdp->statReg[2] & 2)) || vdp->blink))

#define V9938_BPP	(16)
#define V9938_WIDTH	(512 + 32)
#include "v9938mod.c"
#undef 	V9938_WIDTH
#define V9938_WIDTH	(256 + 16)
#include "v9938mod.c"
#undef 	V9938_WIDTH
#undef	V9938_BPP

static void v9938_sprite_mode1 (int line, UINT8 *col)
	{
	UINT8	*attrtbl, *patterntbl, *patternptr;
	int x, y, p, height, c, p2, i, n, pattern;

	memset (col, 0, 256);

	/* are sprites disabled? */
	if (vdp->contReg[8] & 0x02) return;

	attrtbl = vdp->vram + (vdp->contReg[5] << 7) + (vdp->contReg[11] << 15);
	patterntbl = vdp->vram + (vdp->contReg[6] << 11);

	/* 16x16 or 8x8 sprites */
	height = (vdp->contReg[1] & 2) ? 16 : 8;
	/* magnified sprites (zoomed) */
	if (vdp->contReg[1] & 1) height *= 2;

	p2 = p = 0;
	while (1)
		{
		y = attrtbl[0];
		if (y == 208) break;
		y = (y - vdp->contReg[23]) & 255;
		if (y > 208)
			y = -(~y&255);
		else
			y++;

		/* if sprite in range, has to be drawn */
		if ( (line >= y) && (line  < (y + height) ) )
			{
			if (p2 == 4)
				{
				/* max maximum sprites per line! */
				if ( !(vdp->statReg[0] & 0x40) )
					vdp->statReg[0] = (vdp->statReg[0] & 0xa0) | 0x40 | p;

				if (vdp->sprite_limit) break;
				}
			/* get x */
			x = attrtbl[1];
			if (attrtbl[3] & 0x80) x -= 32;

			/* get pattern */
			pattern = attrtbl[2];
			if (vdp->contReg[1] & 2)
				pattern &= 0xfc;
			n = line - y;
			patternptr = patterntbl + pattern * 8 +
				((vdp->contReg[1] & 1) ? n/2  : n);
			pattern = patternptr[0] << 8 | patternptr[16];

			/* get colour */
			c = attrtbl[3] & 0x0f;

			/* draw left part */
			n = 0;
			while (1)
				{
				if (n == 0) pattern = patternptr[0];
				else if ( (n == 1) && (vdp->contReg[1] & 2) ) pattern = patternptr[16];
				else break;

				n++;

				for (i=0;i<8;i++)
					{
					if (pattern & 0x80)
						{
						if ( (x >= 0) && (x < 256) )
							{
							if (col[x] & 0x40)
								{
								/* we have a collision! */
								if (p2 < 4)
									vdp->statReg[0] |= 0x20;
								}
							if ( !(col[x] & 0x80) )
								{
								if (c || (vdp->contReg[8] & 0x20) )
									col[x] |= 0xc0 | c;
								else
									col[x] |= 0x40;
								}

							/* if zoomed, draw another pixel */
							if (vdp->contReg[1] & 1)
								{
								if (col[x+1] & 0x40)
    	                        	{
       		                    	/* we have a collision! */
									if (p2 < 4)
										vdp->statReg[0] |= 0x20;
                            		}
                        		if ( !(col[x+1] & 0x80) )
	                            	{
   		                         	if (c || (vdp->contReg[8] & 0x20) )
										col[x+1] |= 0xc0 | c;
									else
										col[x+1] |= 0x80;
                            		}
								}
							}
						}
					if (vdp->contReg[1] & 1) x += 2; else x++;
					pattern <<= 1;
					}
				}

			p2++;
			}

		if (p >= 31) break;
		p++;
		attrtbl += 4;
		}

	if ( !(vdp->statReg[0] & 0x40) )
		vdp->statReg[0] = (vdp->statReg[0] & 0xa0) | p;
	}

static void v9938_sprite_mode2 (int line, UINT8 *col)
	{
	int attrtbl, patterntbl, patternptr, colourtbl;
	int x, i, y, p, height, c, p2, n, pattern, colourmask, first_cc_seen;

	memset (col, 0, 256);

	/* are sprites disabled? */
	if (vdp->contReg[8] & 0x02) return;

	attrtbl = ( (vdp->contReg[5] & 0xfc) << 7) + (vdp->contReg[11] << 15);
	colourtbl =  ( (vdp->contReg[5] & 0xf8) << 7) + (vdp->contReg[11] << 15);
	patterntbl = (vdp->contReg[6] << 11);
	colourmask = ( (vdp->contReg[5] & 3) << 3) | 0x7; /* check this! */

	/* 16x16 or 8x8 sprites */
	height = (vdp->contReg[1] & 2) ? 16 : 8;
	/* magnified sprites (zoomed) */
	if (vdp->contReg[1] & 1) height *= 2;

	p2 = p = first_cc_seen = 0;
	while (1)
		{
		y = v9938_vram_read (attrtbl);
		if (y == 216) break;
		y = (y - vdp->contReg[23]) & 255;
		if (y > 216)
			y = -(~y&255);
		else
			y++;

		/* if sprite in range, has to be drawn */
		if ( (line >= y) && (line  < (y + height) ) )
			{
			if (p2 == 8)
				{
				/* max maximum sprites per line! */
				if ( !(vdp->statReg[0] & 0x40) )
					vdp->statReg[0] = (vdp->statReg[0] & 0xa0) | 0x40 | p;

				if (vdp->sprite_limit) break;
				}

			n = line - y; if (vdp->contReg[1] & 1) n /= 2;
			/* get colour */
			c = v9938_vram_read (colourtbl + (((p&colourmask)*16) + n));

			/* don't draw all sprite with CC set before any sprites
               with CC = 0 are seen on this line */
			if (c & 0x40)
				{
				if (!first_cc_seen)
					goto skip_first_cc_set;
				}
			else
				first_cc_seen = 1;

			/* get pattern */
			pattern = v9938_vram_read (attrtbl + 2);
			if (vdp->contReg[1] & 2)
				pattern &= 0xfc;
			patternptr = patterntbl + pattern * 8 + n;
			pattern = (v9938_vram_read (patternptr) << 8) |
				v9938_vram_read (patternptr + 16);

			/* get x */
			x = v9938_vram_read (attrtbl + 1);
			if (c & 0x80) x -= 32;

			n = (vdp->contReg[1] & 2) ? 16 : 8;
			while (n--)
				{
				for (i=0;i<=(vdp->contReg[1] & 1);i++)
					{
					if ( (x >= 0) && (x < 256) )
						{
						if ( (pattern & 0x8000) && !(col[x] & 0x10) )
							{
							if ( (c & 15) || (vdp->contReg[8] & 0x20) )
								{
								if ( !(c & 0x40) )
									{
									if (col[x] & 0x20) col[x] |= 0x10;
									else
										col[x] |= 0x20 | (c & 15);
									}
								else
									col[x] |= c & 15;

								col[x] |= 0x80;
								}
							}
						else
							{
							if ( !(c & 0x40) && (col[x] & 0x20) )
								col[x] |= 0x10;
							}

						if ( !(c & 0x60) && (pattern & 0x8000) )
							{
							if (col[x] & 0x40)
								{
								/* sprite collision! */
								if (p2 < 8)
									vdp->statReg[0] |= 0x20;
								}
							else
								col[x] |= 0x40;
							}

						x++;
						}
					}

				pattern <<= 1;
				}

skip_first_cc_set:
			p2++;
			}

		if (p >= 31) break;
		p++;
		attrtbl += 4;
		}

	if ( !(vdp->statReg[0] & 0x40) )
		vdp->statReg[0] = (vdp->statReg[0] & 0xa0) | p;
	}

typedef struct {
	UINT8 m;
	void (*visible_16)(const pen_t *, UINT16*, int);
	void (*visible_16s)(const pen_t *, UINT16*, int);
	void (*border_16)(const pen_t *, UINT16*);
	void (*border_16s)(const pen_t *, UINT16*);
	void (*sprites)(int, UINT8*);
	void (*draw_sprite_16)(const pen_t *, UINT16*, UINT8*);
	void (*draw_sprite_16s)(const pen_t *, UINT16*, UINT8*);
} V9938_MODE;

static const V9938_MODE modes[] = {
	{ 0x02,
		v9938_mode_text1_16,
		v9938_mode_text1_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		NULL,
		NULL,
		NULL },
	{ 0x01,
		v9938_mode_multi_16,
		v9938_mode_multi_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		v9938_sprite_mode1,
		v9938_default_draw_sprite_16,
		v9938_default_draw_sprite_16s },
	{ 0x00,
		v9938_mode_graphic1_16,
		v9938_mode_graphic1_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		v9938_sprite_mode1,
		v9938_default_draw_sprite_16,
		v9938_default_draw_sprite_16s },
	{ 0x04,
		v9938_mode_graphic23_16,
		v9938_mode_graphic23_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		v9938_sprite_mode1,
		v9938_default_draw_sprite_16,
		v9938_default_draw_sprite_16s },
	{ 0x08,
		v9938_mode_graphic23_16,
		v9938_mode_graphic23_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		v9938_sprite_mode2,
		v9938_default_draw_sprite_16,
		v9938_default_draw_sprite_16s },
	{ 0x0c,
		v9938_mode_graphic4_16,
		v9938_mode_graphic4_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		v9938_sprite_mode2,
		v9938_default_draw_sprite_16,
		v9938_default_draw_sprite_16s },
	{ 0x10,
		v9938_mode_graphic5_16,
		v9938_mode_graphic5_16s,
		v9938_graphic5_border_16,
		v9938_graphic5_border_16s,
		v9938_sprite_mode2,
		v9938_graphic5_draw_sprite_16,
		v9938_graphic5_draw_sprite_16s },
	{ 0x14,
		v9938_mode_graphic6_16,
		v9938_mode_graphic6_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		v9938_sprite_mode2,
		v9938_default_draw_sprite_16,
		v9938_default_draw_sprite_16s },
	{ 0x1c,
		v9938_mode_graphic7_16,
		v9938_mode_graphic7_16s,
		v9938_graphic7_border_16,
		v9938_graphic7_border_16s,
		v9938_sprite_mode2,
		v9938_graphic7_draw_sprite_16,
		v9938_graphic7_draw_sprite_16s },
	{ 0x0a,
		v9938_mode_text2_16,
		v9938_mode_text2_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		NULL,
		NULL,
		NULL },
	{ 0xff,
		v9938_mode_unknown_16,
		v9938_mode_unknown_16s,
		v9938_default_border_16,
		v9938_default_border_16s,
		NULL,
		NULL,
		NULL },
};

static void v9938_set_mode (void)
	{
	int n,i;

	n = (((vdp->contReg[0] & 0x0e) << 1) | ((vdp->contReg[1] & 0x18) >> 3));
	for (i=0;;i++)
		{
		if ( (modes[i].m == n) || (modes[i].m == 0xff) ) break;
		}
	vdp->mode = i;
	}

static void v9938_refresh_16 (running_machine *machine, bitmap_t *bmp, int line)
	{
	const pen_t *pens = machine->pens;
	int i, double_lines;
	UINT8 col[256];
	UINT16 *ln, *ln2 = NULL;

	double_lines = 0;

	if (vdp->size == RENDER_HIGH)
		{
		if (vdp->contReg[9] & 0x08)
			{
			vdp->size_now = RENDER_HIGH;
			ln = BITMAP_ADDR16(bmp, line*2+((vdp->statReg[2]>>1)&1), 0);
			}
		else
			{
			ln = BITMAP_ADDR16(bmp, line*2, 0);
			ln2 = BITMAP_ADDR16(bmp, line*2+1, 0);
			double_lines = 1;
			}
		}
	else
		ln = BITMAP_ADDR16(bmp, line, 0);

	if ( !(vdp->contReg[1] & 0x40) || (vdp->statReg[2] & 0x40) )
		{
		if (vdp->size == RENDER_HIGH)
			modes[vdp->mode].border_16 (pens, ln);
		else
			modes[vdp->mode].border_16s (pens, ln);
		}
	else
		{
		i = (line - vdp->offset_y) & 255;
		if (vdp->size == RENDER_HIGH)
			{
			modes[vdp->mode].visible_16 (pens, ln, i);
			if (modes[vdp->mode].sprites)
				{
				modes[vdp->mode].sprites (i, col);
				modes[vdp->mode].draw_sprite_16 (pens, ln, col);
				}
			}
		else
			{
			modes[vdp->mode].visible_16s (pens, ln, i);
			if (modes[vdp->mode].sprites)
				{
				modes[vdp->mode].sprites (i, col);
				modes[vdp->mode].draw_sprite_16s (pens, ln, col);
				}
			}
		}

	if (double_lines)
		memcpy (ln2, ln, (512 + 32) * 2);
	}

static void v9938_refresh_line (running_machine *machine, bitmap_t *bmp, int line)
	{
	int ind16, ind256;

	ind16 = vdp->pal_ind16[0];
	ind256 = vdp->pal_ind256[0];

	if ( !(vdp->contReg[8] & 0x20) && (vdp->mode != V9938_MODE_GRAPHIC5) )
		{
		vdp->pal_ind16[0] = vdp->pal_ind16[(vdp->contReg[7] & 0x0f)];
		vdp->pal_ind256[0] = vdp->pal_ind256[vdp->contReg[7]];
		}

	v9938_refresh_16 (machine, bmp, line);

	if ( !(vdp->contReg[8] & 0x20) && (vdp->mode != V9938_MODE_GRAPHIC5) )
		{
		vdp->pal_ind16[0] = ind16;
		vdp->pal_ind256[0] = ind256;
		}
	}

/*

From: awulms@inter.nl.net (Alex Wulms)
*** About the HR/VR topic: this is how it works according to me:

*** HR:
HR is very straightforward:
-HR=1 during 'display time'
-HR=0 during 'horizontal border, horizontal retrace'
I have put 'display time' and 'horizontal border, horizontal retrace' between
quotes because HR does not only flip between 0 and 1 during the display of
the 192/212 display lines, but also during the vertical border and during the
vertical retrace.

*** VR:
VR is a little bit tricky
-VR always gets set to 0 when the VDP starts with display line 0
-VR gets set to 1 when the VDP reaches display line (192 if LN=0) or (212 if
LN=1)
-The VDP displays contents of VRAM as long as VR=0

As a consequence of this behaviour, it is possible to program the famous
overscan trick, where VRAM contents is shown in the borders:
Generate an interrupt at line 230 (or so) and on this interrupt: set LN=1
Generate an interrupt at line 200 (or so) and on this interrupt: set LN=0
Repeat the above two steps

*** The top/bottom border contents during overscan:
On screen 0:
1) The VDP keeps increasing the name table address pointer during bottom
border, vertical retrace and top border
2) The VDP resets the name table address pointer when the first display line
is reached

On the other screens:
1) The VDP keeps increasing the name table address pointer during the bottom
border
2) The VDP resets the name table address pointer such that the top border
contents connects up with the first display line. E.g., when the top border
is 26 lines high, the VDP will take:
'logical'      vram line
TOPB000  256-26
...
TOPB025  256-01
DISPL000 000
...
DISPL211 211
BOTB000  212
...
BOTB024  236



*** About the horizontal interrupt

All relevant definitions on a row:
-FH: Bit 0 of status register 1
-IE1: Bit 4 of mode register 0
-IL: Line number in mode register 19
-DL: The line that the VDP is going to display (corrected for vertical scroll)
-IRQ: Interrupt request line of VDP to Z80

At the *start* of every new line (display, bottom border, part of vertical
display), the VDP does:
-FH = (FH && IE1) || (IL==DL)

After reading of status register 1 by the CPU, the VDP does:
-FH = 0

Furthermore, the following is true all the time:
-IRQ = FH && IE1

The resulting behaviour:
When IE1=0:
-FH will be set as soon as display of line IL starts
-FH will be reset as soon as status register 1 is read
-FH will be reset as soon as the next display line is reached

When IE=1:
-FH and IRQ will be set as soon as display line IL is reached
-FH and IRQ will be reset as soon as status register 1 is read

Another subtile result:
If, while FH and IRQ are set, IE1 gets reset, the next happens:
-IRQ is reset immediately (since IRQ is always FH && IE1)
-FH will be reset as soon as display of the next line starts (unless the next
line is line IL)


*** About the vertical interrupt:
Another relevant definition:
-FV: Bit 7 of status register 0
-IE0: Bit 5 of mode register 1

I only know for sure the behaviour when IE0=1:
-FV and IRQ will be set as soon as VR changes from 0 to 1
-FV and IRQ will be reset as soon as status register 0 is read

A consequence is that NO vertical interrupts will be generated during the
overscan trick, described in the VR section above.

I do not know the behaviour of FV when IE0=0. That is the part that I still
have to test.
*/

static void v9938_interrupt_start_vblank (void)
	{
#if 0
	if (input_code_pressed (KEYCODE_D) )
		{
		FILE *fp;
		int i;

		fp = fopen ("vram.dmp", "wb");
		if (fp)
			{
			fwrite (vdp->vram, 0x10000, 1, fp);
			fclose (fp);
			popmessage("saved");
			}

		for (i=0;i<24;i++) mame_printf_debug ("R#%d = %02x\n", i, vdp->contReg[i]);
		}
#endif

	/* at every frame, vdp switches fields */
	vdp->statReg[2] = (vdp->statReg[2] & 0xfd) | (~vdp->statReg[2] & 2);

	/* color blinking */
	if (!(vdp->contReg[13] & 0xf0))
		vdp->blink = 0;
	else if (!(vdp->contReg[13] & 0x0f))
		vdp->blink = 1;
	else
		{
		/* both on and off counter are non-zero: timed blinking */
		if (vdp->blink_count)
			vdp->blink_count--;
		if (!vdp->blink_count)
			{
			vdp->blink = !vdp->blink;
			if (vdp->blink)
				vdp->blink_count = (vdp->contReg[13] >> 4) * 10;
			else
				vdp->blink_count = (vdp->contReg[13] & 0x0f) * 10;
			}
		}

	/* check screen rendering size */
	if (vdp->size_auto && (vdp->size_now >= 0) && (vdp->size != vdp->size_now) )
		vdp->size = vdp->size_now;

	if (vdp->size != vdp->size_old)
		{
		if (vdp->size == RENDER_HIGH)
			video_screen_set_visarea (vdp->screen, 0, 512 + 32 - 1, 0, 424 + 56 - 1);
		else
			video_screen_set_visarea (vdp->screen, 0, 256 + 16 - 1, 0, 212 + 28 - 1);

		vdp->size_old = vdp->size;
		}

	vdp->size_now = -1;
	}

int v9938_interrupt (running_machine *machine, int which)
{
	int scanline, max, pal, scanline_start;

	vdp = &vdps[which];

	v9938_update_command ();

	pal = vdp->contReg[9] & 2;
	if (pal) scanline_start = 53; else scanline_start = 22;

	/* set flags */
	if (vdp->scanline == (vdp->offset_y + scanline_start) )
	{
		vdp->statReg[2] &= ~0x40;
	}
	else if (vdp->scanline == (vdp->offset_y + vdp->visible_y + scanline_start) )
	{
		vdp->statReg[2] |= 0x40;
		vdp->statReg[0] |= 0x80;
	}

	max = (pal) ? 255 : (vdp->contReg[9] & 0x80) ? 234 : 244;
	scanline = (vdp->scanline - scanline_start - vdp->offset_y);
	if ( (scanline >= 0) && (scanline <= max) &&
	   ( ( (scanline + vdp->contReg[23]) & 255) == vdp->contReg[19]) )
	{
		vdp->statReg[1] |= 1;
		LOG(("V9938: scanline interrupt (%d)\n", scanline));
	}
	else
		if ( !(vdp->contReg[0] & 0x10) ) vdp->statReg[1] &= 0xfe;

	v9938_check_int (machine);

	/* check for start of vblank */
	if ((pal && (vdp->scanline == 310)) ||
		(!pal && (vdp->scanline == 259)))
		v9938_interrupt_start_vblank ();

	/* render the current line */
	if ((vdp->scanline >= scanline_start) && (vdp->scanline < (212 + 28 + scanline_start)))
	{
		scanline = (vdp->scanline - scanline_start) & 255;

		v9938_refresh_line (machine, vdp->bitmap, scanline);
	}

	max = (vdp->contReg[9] & 2) ? 313 : 262;
	if (++vdp->scanline == max)
		vdp->scanline = 0;

	return vdp->INT;
}

/*
    Not really right... won't work with sprites in graphics 7
    and with palette updated mid-screen
*/
int v9938_get_transpen(int which)
{
	vdp = &vdps[which];
	if (vdp->mode == V9938_MODE_GRAPHIC7)
	{
		return vdp->pal_ind256[0];
	}
	else
	{
		return vdp->pal_ind16[0];
	}
}

/*
    Driver-specific function: update the vdp mouse state
*/
void v9938_update_mouse_state(int which, int mx_delta, int my_delta, int button_state)
{
	vdp = &vdps[which];

	/* save button state */
	vdp->button_state = (button_state << 6) & 0xc0;

	if ((vdp->contReg[8] & 0xc0) == 0x80)
	{	/* vdp will process mouse deltas only if it is in mouse mode */
		vdp->mx_delta += mx_delta;
		vdp->my_delta += my_delta;
	}
}

/***************************************************************************

    Command unit

***************************************************************************/

#define VDP vdp->contReg
#define VDPStatus vdp->statReg
#define VRAM vdp->vram
#define VRAM_EXP vdp->vram_exp
#define ScrMode vdp->mode

/*************************************************************/
/** Completely rewritten by Alex Wulms:                     **/
/**  - VDP Command execution 'in parallel' with CPU         **/
/**  - Corrected behaviour of VDP commands                  **/
/**  - Made it easier to implement correct S7/8 mapping     **/
/**    by concentrating VRAM access in one single place     **/
/**  - Made use of the 'in parallel' VDP command exec       **/
/**    and correct timing. You must call the function       **/
/**    LoopVDP() from LoopZ80 in MSX.c. You must call it    **/
/**    exactly 256 times per screen refresh.                **/
/** Started on       : 11-11-1999                           **/
/** Beta release 1 on:  9-12-1999                           **/
/** Beta release 2 on: 20-01-2000                           **/
/**  - Corrected behaviour of VRM <-> Z80 transfer          **/
/**  - Improved performance of the code                     **/
/** Public release 1.0: 20-04-2000                          **/
/*************************************************************/

#define VDP_VRMP5(MX, X, Y) ((!MX) ? (VRAM + ((Y&1023)<<7) + ((X&255)>>1)) : (VRAM_EXP + ((Y&511)<<7) + ((X&255)>>1)))
#define VDP_VRMP6(MX, X, Y) ((!MX) ? (VRAM + ((Y&1023)<<7) + ((X&511)>>2)) : (VRAM_EXP + ((Y&511)<<7) + ((X&511)>>2)))
//#define VDP_VRMP7(MX, X, Y) ((!MX) ? (VRAM + ((Y&511)<<8) + ((X&511)>>1)) : (VRAM_EXP + ((Y&255)<<8) + ((X&511)>>1)))
#define VDP_VRMP7(MX, X, Y) ((!MX) ? (VRAM + ((X&2)<<15) + ((Y&511)<<7) + ((X&511)>>2)) : (VRAM_EXP + ((Y&511)<<7) + ((X&511)>>2))/*(VRAM_EXP + ((Y&255)<<8) + ((X&511)>>1))*/)
//#define VDP_VRMP8(MX, X, Y) ((!MX) ? (VRAM + ((Y&511)<<8) + (X&255)) : (VRAM_EXP + ((Y&255)<<8) + (X&255)))
#define VDP_VRMP8(MX, X, Y) ((!MX) ? (VRAM + ((X&1)<<16) + ((Y&511)<<7) + ((X>>1)&127)) : (VRAM_EXP + ((Y&511)<<7) + ((X>>1)&127))/*(VRAM_EXP + ((Y&255)<<8) + (X&255))*/)

#define VDP_VRMP(M, MX, X, Y) VDPVRMP(M, MX, X, Y)
#define VDP_POINT(M, MX, X, Y) VDPpoint(M, MX, X, Y)
#define VDP_PSET(M, MX, X, Y, C, O) VDPpset(M, MX, X, Y, C, O)

#define CM_ABRT  0x0
#define CM_POINT 0x4
#define CM_PSET  0x5
#define CM_SRCH  0x6
#define CM_LINE  0x7
#define CM_LMMV  0x8
#define CM_LMMM  0x9
#define CM_LMCM  0xA
#define CM_LMMC  0xB
#define CM_HMMV  0xC
#define CM_HMMM  0xD
#define CM_YMMM  0xE
#define CM_HMMC  0xF

/*************************************************************/
/* Many VDP commands are executed in some kind of loop but   */
/* essentially, there are only a few basic loop structures   */
/* that are re-used. We define the loop structures that are  */
/* re-used here so that they have to be entered only once    */
/*************************************************************/
#define pre_loop \
    while ((cnt-=delta) > 0) {


/* Loop over DX, DY */
#define post__x_y(MX) \
    if (!--ANX || ((ADX+=TX)&MX)) { \
      if (!(--NY&1023) || (DY+=TY)==-1) \
        break; \
      else { \
        ADX=DX; \
        ANX=NX; \
      } \
    } \
  }

/* Loop over DX, SY, DY */
#define post__xyy(MX) \
    if ((ADX+=TX)&MX) { \
      if (!(--NY&1023) || (SY+=TY)==-1 || (DY+=TY)==-1) \
        break; \
      else \
        ADX=DX; \
    } \
  }

/* Loop over SX, DX, SY, DY */
#define post_xxyy(MX) \
    if (!--ANX || ((ASX+=TX)&MX) || ((ADX+=TX)&MX)) { \
      if (!(--NY&1023) || (SY+=TY)==-1 || (DY+=TY)==-1) \
        break; \
      else { \
        ASX=SX; \
        ADX=DX; \
        ANX=NX; \
      } \
    } \
  }

/*************************************************************/
/** Function prototypes                                     **/
/*************************************************************/
static UINT8 *VDPVRMP(register UINT8 M, register int MX, register int X, register int Y);

static UINT8 VDPpoint5(register int MXS, register int SX, register int SY);
static UINT8 VDPpoint6(register int MXS, register int SX, register int SY);
static UINT8 VDPpoint7(register int MXS, register int SX, register int SY);
static UINT8 VDPpoint8(register int MXS, register int SX, register int SY);

static UINT8 VDPpoint(register UINT8 SM, register int MXS,
                     register int SX, register int SY);

static void VDPpsetlowlevel(register UINT8 *P, register UINT8 CL,
                            register UINT8 M, register UINT8 OP);

static void VDPpset5(register int MXD, register int DX, register int DY,
                     register UINT8 CL, register UINT8 OP);
static void VDPpset6(register int MXD, register int DX, register int DY,
                     register UINT8 CL, register UINT8 OP);
static void VDPpset7(register int MXD, register int DX, register int DY,
                     register UINT8 CL, register UINT8 OP);
static void VDPpset8(register int MXD, register int DX, register int DY,
                     register UINT8 CL, register UINT8 OP);

static void VDPpset(register UINT8 SM, register int MXD,
                    register int DX, register int DY,
                    register UINT8 CL, register UINT8 OP);

static int GetVdpTimingValue(register const int *);

static void SrchEngine(void);
static void LineEngine(void);
static void LmmvEngine(void);
static void LmmmEngine(void);
static void LmcmEngine(void);
static void LmmcEngine(void);
static void HmmvEngine(void);
static void HmmmEngine(void);
static void YmmmEngine(void);
static void HmmcEngine(void);

static void ReportVdpCommand(register UINT8 Op);

/*************************************************************/
/** Variables visible only in this module                   **/
/*************************************************************/
static const UINT8 Mask[4] = { 0x0F,0x03,0x0F,0xFF };
static const int  PPB[4]  = { 2,4,2,1 };
static const int  PPL[4]  = { 256,512,512,256 };

                      /*  SprOn SprOn SprOf SprOf */
                      /*  ScrOf ScrOn ScrOf ScrOn */
static const int srch_timing[8]={ 818, 1025,  818,  830,   /* ntsc */
                            696,  854,  696,  684 }; /* pal  */
static const int line_timing[8]={ 1063, 1259, 1063, 1161,
                            904,  1026, 904,  953 };
static const int hmmv_timing[8]={ 439,  549,  439,  531,
                            366,  439,  366,  427 };
static const int lmmv_timing[8]={ 873,  1135, 873, 1056,
                            732,  909,  732,  854 };
static const int ymmm_timing[8]={ 586,  952,  586,  610,
                            488,  720,  488,  500 };
static const int hmmm_timing[8]={ 818,  1111, 818,  854,
                            684,  879,  684,  708 };
static const int lmmm_timing[8]={ 1160, 1599, 1160, 1172,
                            964,  1257, 964,  977 };

/** VDPVRMP() **********************************************/
/** Calculate addr of a pixel in vram                       **/
/*************************************************************/
INLINE UINT8 *VDPVRMP(UINT8 M,int MX,int X,int Y)
{
  switch(M)
  {
    case 0: return VDP_VRMP5(MX,X,Y);
    case 1: return VDP_VRMP6(MX,X,Y);
    case 2: return VDP_VRMP7(MX,X,Y);
    case 3: return VDP_VRMP8(MX,X,Y);
  }

  return(VRAM);
}

/** VDPpoint5() ***********************************************/
/** Get a pixel on screen 5                                 **/
/*************************************************************/
INLINE UINT8 VDPpoint5(int MXS, int SX, int SY)
{
  return (*VDP_VRMP5(MXS, SX, SY) >>
          (((~SX)&1)<<2)
         )&15;
}

/** VDPpoint6() ***********************************************/
/** Get a pixel on screen 6                                 **/
/*************************************************************/
INLINE UINT8 VDPpoint6(int MXS, int SX, int SY)
{
  return (*VDP_VRMP6(MXS, SX, SY) >>
          (((~SX)&3)<<1)
         )&3;
}

/** VDPpoint7() ***********************************************/
/** Get a pixel on screen 7                                 **/
/*************************************************************/
INLINE UINT8 VDPpoint7(int MXS, int SX, int SY)
{
  return (*VDP_VRMP7(MXS, SX, SY) >>
          (((~SX)&1)<<2)
         )&15;
}

/** VDPpoint8() ***********************************************/
/** Get a pixel on screen 8                                 **/
/*************************************************************/
INLINE UINT8 VDPpoint8(int MXS, int SX, int SY)
{
  return *VDP_VRMP8(MXS, SX, SY);
}

/** VDPpoint() ************************************************/
/** Get a pixel on a screen                                 **/
/*************************************************************/
INLINE UINT8 VDPpoint(UINT8 SM, int MXS, int SX, int SY)
{
  switch(SM)
  {
    case 0: return VDPpoint5(MXS,SX,SY);
    case 1: return VDPpoint6(MXS,SX,SY);
    case 2: return VDPpoint7(MXS,SX,SY);
    case 3: return VDPpoint8(MXS,SX,SY);
  }

  return(0);
}

/** VDPpsetlowlevel() ****************************************/
/** Low level function to set a pixel on a screen           **/
/** Make it inline to make it fast                          **/
/*************************************************************/
INLINE void VDPpsetlowlevel(UINT8 *P, UINT8 CL, UINT8 M, UINT8 OP)
{
  switch (OP)
  {
    case 0: *P = (*P & M) | CL; break;
    case 1: *P = *P & (CL | M); break;
    case 2: *P |= CL; break;
    case 3: *P ^= CL; break;
    case 4: *P = (*P & M) | ~(CL | M); break;
    case 8: if (CL) *P = (*P & M) | CL; break;
    case 9: if (CL) *P = *P & (CL | M); break;
    case 10: if (CL) *P |= CL; break;
    case 11:  if (CL) *P ^= CL; break;
    case 12:  if (CL) *P = (*P & M) | ~(CL|M); break;
  }
}

/** VDPpset5() ***********************************************/
/** Set a pixel on screen 5                                 **/
/*************************************************************/
INLINE void VDPpset5(int MXD, int DX, int DY, UINT8 CL, UINT8 OP)
{
  register UINT8 SH = ((~DX)&1)<<2;

  VDPpsetlowlevel(VDP_VRMP5(MXD, DX, DY),
                  CL << SH, ~(15<<SH), OP);
}

/** VDPpset6() ***********************************************/
/** Set a pixel on screen 6                                 **/
/*************************************************************/
INLINE void VDPpset6(int MXD, int DX, int DY, UINT8 CL, UINT8 OP)
{
  register UINT8 SH = ((~DX)&3)<<1;

  VDPpsetlowlevel(VDP_VRMP6(MXD, DX, DY),
                  CL << SH, ~(3<<SH), OP);
}

/** VDPpset7() ***********************************************/
/** Set a pixel on screen 7                                 **/
/*************************************************************/
INLINE void VDPpset7(int MXD, int DX, int DY, UINT8 CL, UINT8 OP)
{
  register UINT8 SH = ((~DX)&1)<<2;

  VDPpsetlowlevel(VDP_VRMP7(MXD, DX, DY),
                  CL << SH, ~(15<<SH), OP);
}

/** VDPpset8() ***********************************************/
/** Set a pixel on screen 8                                 **/
/*************************************************************/
INLINE void VDPpset8(int MXD, int DX, int DY, UINT8 CL, UINT8 OP)
{
  VDPpsetlowlevel(VDP_VRMP8(MXD, DX, DY),
                  CL, 0, OP);
}

/** VDPpset() ************************************************/
/** Set a pixel on a screen                                 **/
/*************************************************************/
INLINE void VDPpset(UINT8 SM, int MXD, int DX, int DY, UINT8 CL, UINT8 OP)
{
  switch (SM) {
    case 0: VDPpset5(MXD, DX, DY, CL, OP); break;
    case 1: VDPpset6(MXD, DX, DY, CL, OP); break;
    case 2: VDPpset7(MXD, DX, DY, CL, OP); break;
    case 3: VDPpset8(MXD, DX, DY, CL, OP); break;
  }
}

/** GetVdpTimingValue() **************************************/
/** Get timing value for a certain VDP command              **/
/*************************************************************/
static int GetVdpTimingValue(register const int *timing_values)
{
  return(timing_values[((VDP[1]>>6)&1)|(VDP[8]&2)|((VDP[9]<<1)&4)]);
}

/** SrchEgine()** ********************************************/
/** Search a dot                                            **/
/*************************************************************/
void SrchEngine(void)
{
  register int SX=vdp->MMC.SX;
  register int SY=vdp->MMC.SY;
  register int TX=vdp->MMC.TX;
  register int ANX=vdp->MMC.ANX;
  register UINT8 CL=vdp->MMC.CL;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(srch_timing);
  cnt = vdp->VdpOpsCnt;

#define pre_srch \
    pre_loop \
      if ((
#define post_srch(MX) \
           ==CL) ^ANX) { \
      VDPStatus[2]|=0x10; /* Border detected */ \
      break; \
    } \
    if ((SX+=TX) & MX) { \
      VDPStatus[2]&=0xEF; /* Border not detected */ \
      break; \
    } \
  }

  switch (ScrMode) {
    default:
    case 5: pre_srch VDPpoint5(MXD, SX, SY) post_srch(256)
            break;
    case 6: pre_srch VDPpoint6(MXD, SX, SY) post_srch(512)
            break;
    case 7: pre_srch VDPpoint7(MXD, SX, SY) post_srch(512)
            break;
    case 8: pre_srch VDPpoint8(MXD, SX, SY) post_srch(256)
            break;
  }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    /* Update SX in VDP registers */
    VDPStatus[8]=SX&0xFF;
    VDPStatus[9]=(SX>>8)|0xFE;
  }
  else {
    vdp->MMC.SX=SX;
  }
}

/** LineEgine()** ********************************************/
/** Draw a line                                             **/
/*************************************************************/
void LineEngine(void)
{
  register int DX=vdp->MMC.DX;
  register int DY=vdp->MMC.DY;
  register int TX=vdp->MMC.TX;
  register int TY=vdp->MMC.TY;
  register int NX=vdp->MMC.NX;
  register int NY=vdp->MMC.NY;
  register int ASX=vdp->MMC.ASX;
  register int ADX=vdp->MMC.ADX;
  register UINT8 CL=vdp->MMC.CL;
  register UINT8 LO=vdp->MMC.LO;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(line_timing);
  cnt = vdp->VdpOpsCnt;

#define post_linexmaj(MX) \
      DX+=TX; \
      if ((ASX-=NY)<0) { \
        ASX+=NX; \
        DY+=TY; \
      } \
      ASX&=1023; /* Mask to 10 bits range */ \
      if (ADX++==NX || (DX&MX)) \
        break; \
    }
#define post_lineymaj(MX) \
      DY+=TY; \
      if ((ASX-=NY)<0) { \
        ASX+=NX; \
        DX+=TX; \
      } \
      ASX&=1023; /* Mask to 10 bits range */ \
      if (ADX++==NX || (DX&MX)) \
        break; \
    }

  if ((VDP[45]&0x01)==0)
    /* X-Axis is major direction */
    switch (ScrMode) {
      default:
      case 5: pre_loop VDPpset5(MXD, DX, DY, CL, LO); post_linexmaj(256)
              break;
      case 6: pre_loop VDPpset6(MXD, DX, DY, CL, LO); post_linexmaj(512)
              break;
      case 7: pre_loop VDPpset7(MXD, DX, DY, CL, LO); post_linexmaj(512)
              break;
      case 8: pre_loop VDPpset8(MXD, DX, DY, CL, LO); post_linexmaj(256)
              break;
    }
  else
    /* Y-Axis is major direction */
    switch (ScrMode) {
      default:
      case 5: pre_loop VDPpset5(MXD, DX, DY, CL, LO); post_lineymaj(256)
              break;
      case 6: pre_loop VDPpset6(MXD, DX, DY, CL, LO); post_lineymaj(512)
              break;
      case 7: pre_loop VDPpset7(MXD, DX, DY, CL, LO); post_lineymaj(512)
              break;
      case 8: pre_loop VDPpset8(MXD, DX, DY, CL, LO); post_lineymaj(256)
              break;
    }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    VDP[38]=DY & 0xFF;
    VDP[39]=(DY>>8) & 0x03;
  }
  else {
    vdp->MMC.DX=DX;
    vdp->MMC.DY=DY;
    vdp->MMC.ASX=ASX;
    vdp->MMC.ADX=ADX;
  }
}

/** LmmvEngine() *********************************************/
/** VDP -> Vram                                             **/
/*************************************************************/
void LmmvEngine(void)
{
  register int DX=vdp->MMC.DX;
  register int DY=vdp->MMC.DY;
  register int TX=vdp->MMC.TX;
  register int TY=vdp->MMC.TY;
  register int NX=vdp->MMC.NX;
  register int NY=vdp->MMC.NY;
  register int ADX=vdp->MMC.ADX;
  register int ANX=vdp->MMC.ANX;
  register UINT8 CL=vdp->MMC.CL;
  register UINT8 LO=vdp->MMC.LO;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(lmmv_timing);
  cnt = vdp->VdpOpsCnt;

  switch (ScrMode) {
    default:
    case 5: pre_loop VDPpset5(MXD, ADX, DY, CL, LO); post__x_y(256)
            break;
    case 6: pre_loop VDPpset6(MXD, ADX, DY, CL, LO); post__x_y(512)
            break;
    case 7: pre_loop VDPpset7(MXD, ADX, DY, CL, LO); post__x_y(512)
            break;
    case 8: pre_loop VDPpset8(MXD, ADX, DY, CL, LO); post__x_y(256)
            break;
  }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    if (!NY)
      DY+=TY;
    VDP[38]=DY & 0xFF;
    VDP[39]=(DY>>8) & 0x03;
    VDP[42]=NY & 0xFF;
    VDP[43]=(NY>>8) & 0x03;
  }
  else {
    vdp->MMC.DY=DY;
    vdp->MMC.NY=NY;
    vdp->MMC.ANX=ANX;
    vdp->MMC.ADX=ADX;
  }
}

/** LmmmEngine() *********************************************/
/** Vram -> Vram                                            **/
/*************************************************************/
void LmmmEngine(void)
{
  register int SX=vdp->MMC.SX;
  register int SY=vdp->MMC.SY;
  register int DX=vdp->MMC.DX;
  register int DY=vdp->MMC.DY;
  register int TX=vdp->MMC.TX;
  register int TY=vdp->MMC.TY;
  register int NX=vdp->MMC.NX;
  register int NY=vdp->MMC.NY;
  register int ASX=vdp->MMC.ASX;
  register int ADX=vdp->MMC.ADX;
  register int ANX=vdp->MMC.ANX;
  register UINT8 LO=vdp->MMC.LO;
  register int MXS = vdp->MMC.MXS;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(lmmm_timing);
  cnt = vdp->VdpOpsCnt;

  switch (ScrMode) {
    default:
    case 5: pre_loop VDPpset5(MXD, ADX, DY, VDPpoint5(MXS, ASX, SY), LO); post_xxyy(256)
            break;
    case 6: pre_loop VDPpset6(MXD, ADX, DY, VDPpoint6(MXS, ASX, SY), LO); post_xxyy(512)
            break;
    case 7: pre_loop VDPpset7(MXD, ADX, DY, VDPpoint7(MXS, ASX, SY), LO); post_xxyy(512)
            break;
    case 8: pre_loop VDPpset8(MXD, ADX, DY, VDPpoint8(MXS, ASX, SY), LO); post_xxyy(256)
            break;
  }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    if (!NY) {
      SY+=TY;
      DY+=TY;
    }
    else
      if (SY==-1)
        DY+=TY;
    VDP[42]=NY & 0xFF;
    VDP[43]=(NY>>8) & 0x03;
    VDP[34]=SY & 0xFF;
    VDP[35]=(SY>>8) & 0x03;
    VDP[38]=DY & 0xFF;
    VDP[39]=(DY>>8) & 0x03;
  }
  else {
    vdp->MMC.SY=SY;
    vdp->MMC.DY=DY;
    vdp->MMC.NY=NY;
    vdp->MMC.ANX=ANX;
    vdp->MMC.ASX=ASX;
    vdp->MMC.ADX=ADX;
  }
}

/** LmcmEngine() *********************************************/
/** Vram -> CPU                                             **/
/*************************************************************/
void LmcmEngine(void)
{
  if ((VDPStatus[2]&0x80)!=0x80) {

    VDPStatus[7]=VDP[44]=VDP_POINT(((ScrMode >= 5) && (ScrMode <= 8)) ? (ScrMode-5) : 0, vdp->MMC.MXS, vdp->MMC.ASX, vdp->MMC.SY);
    vdp->VdpOpsCnt-=GetVdpTimingValue(lmmv_timing);
    VDPStatus[2]|=0x80;

    if (!--vdp->MMC.ANX || ((vdp->MMC.ASX+=vdp->MMC.TX)&vdp->MMC.MX)) {
      if (!(--vdp->MMC.NY & 1023) || (vdp->MMC.SY+=vdp->MMC.TY)==-1) {
        VDPStatus[2]&=0xFE;
        vdp->VdpEngine=0;
        if (!vdp->MMC.NY)
          vdp->MMC.DY+=vdp->MMC.TY;
        VDP[42]=vdp->MMC.NY & 0xFF;
        VDP[43]=(vdp->MMC.NY>>8) & 0x03;
        VDP[34]=vdp->MMC.SY & 0xFF;
        VDP[35]=(vdp->MMC.SY>>8) & 0x03;
      }
      else {
        vdp->MMC.ASX=vdp->MMC.SX;
        vdp->MMC.ANX=vdp->MMC.NX;
      }
    }
  }
}

/** LmmcEngine() *********************************************/
/** CPU -> Vram                                             **/
/*************************************************************/
void LmmcEngine(void)
{
  if ((VDPStatus[2]&0x80)!=0x80) {
    register UINT8 SM=((ScrMode >= 5) && (ScrMode <= 8)) ? (ScrMode-5) : 0;

    VDPStatus[7]=VDP[44]&=Mask[SM];
    VDP_PSET(SM, vdp->MMC.MXD, vdp->MMC.ADX, vdp->MMC.DY, VDP[44], vdp->MMC.LO);
    vdp->VdpOpsCnt-=GetVdpTimingValue(lmmv_timing);
    VDPStatus[2]|=0x80;

    if (!--vdp->MMC.ANX || ((vdp->MMC.ADX+=vdp->MMC.TX)&vdp->MMC.MX)) {
      if (!(--vdp->MMC.NY&1023) || (vdp->MMC.DY+=vdp->MMC.TY)==-1) {
        VDPStatus[2]&=0xFE;
        vdp->VdpEngine=0;
        if (!vdp->MMC.NY)
          vdp->MMC.DY+=vdp->MMC.TY;
        VDP[42]=vdp->MMC.NY & 0xFF;
        VDP[43]=(vdp->MMC.NY>>8) & 0x03;
        VDP[38]=vdp->MMC.DY & 0xFF;
        VDP[39]=(vdp->MMC.DY>>8) & 0x03;
      }
      else {
        vdp->MMC.ADX=vdp->MMC.DX;
        vdp->MMC.ANX=vdp->MMC.NX;
      }
    }
  }
}

/** HmmvEngine() *********************************************/
/** VDP --> Vram                                            **/
/*************************************************************/
void HmmvEngine(void)
{
  register int DX=vdp->MMC.DX;
  register int DY=vdp->MMC.DY;
  register int TX=vdp->MMC.TX;
  register int TY=vdp->MMC.TY;
  register int NX=vdp->MMC.NX;
  register int NY=vdp->MMC.NY;
  register int ADX=vdp->MMC.ADX;
  register int ANX=vdp->MMC.ANX;
  register UINT8 CL=vdp->MMC.CL;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(hmmv_timing);
  cnt = vdp->VdpOpsCnt;

  switch (ScrMode) {
    default:
    case 5: pre_loop *VDP_VRMP5(MXD, ADX, DY) = CL; post__x_y(256)
            break;
    case 6: pre_loop *VDP_VRMP6(MXD, ADX, DY) = CL; post__x_y(512)
            break;
    case 7: pre_loop *VDP_VRMP7(MXD, ADX, DY) = CL; post__x_y(512)
            break;
    case 8: pre_loop *VDP_VRMP8(MXD, ADX, DY) = CL; post__x_y(256)
            break;
  }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    if (!NY)
      DY+=TY;
    VDP[42]=NY & 0xFF;
    VDP[43]=(NY>>8) & 0x03;
    VDP[38]=DY & 0xFF;
    VDP[39]=(DY>>8) & 0x03;
  }
  else {
    vdp->MMC.DY=DY;
    vdp->MMC.NY=NY;
    vdp->MMC.ANX=ANX;
    vdp->MMC.ADX=ADX;
  }
}

/** HmmmEngine() *********************************************/
/** Vram -> Vram                                            **/
/*************************************************************/
void HmmmEngine(void)
{
  register int SX=vdp->MMC.SX;
  register int SY=vdp->MMC.SY;
  register int DX=vdp->MMC.DX;
  register int DY=vdp->MMC.DY;
  register int TX=vdp->MMC.TX;
  register int TY=vdp->MMC.TY;
  register int NX=vdp->MMC.NX;
  register int NY=vdp->MMC.NY;
  register int ASX=vdp->MMC.ASX;
  register int ADX=vdp->MMC.ADX;
  register int ANX=vdp->MMC.ANX;
  register int MXS = vdp->MMC.MXS;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(hmmm_timing);
  cnt = vdp->VdpOpsCnt;

  switch (ScrMode) {
    default:
    case 5: pre_loop *VDP_VRMP5(MXD, ADX, DY) = *VDP_VRMP5(MXS, ASX, SY); post_xxyy(256)
            break;
    case 6: pre_loop *VDP_VRMP6(MXD, ADX, DY) = *VDP_VRMP6(MXS, ASX, SY); post_xxyy(512)
            break;
    case 7: pre_loop *VDP_VRMP7(MXD, ADX, DY) = *VDP_VRMP7(MXS, ASX, SY); post_xxyy(512)
            break;
    case 8: pre_loop *VDP_VRMP8(MXD, ADX, DY) = *VDP_VRMP8(MXS, ASX, SY); post_xxyy(256)
            break;
  }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    if (!NY) {
      SY+=TY;
      DY+=TY;
    }
    else
      if (SY==-1)
        DY+=TY;
    VDP[42]=NY & 0xFF;
    VDP[43]=(NY>>8) & 0x03;
    VDP[34]=SY & 0xFF;
    VDP[35]=(SY>>8) & 0x03;
    VDP[38]=DY & 0xFF;
    VDP[39]=(DY>>8) & 0x03;
  }
  else {
    vdp->MMC.SY=SY;
    vdp->MMC.DY=DY;
    vdp->MMC.NY=NY;
    vdp->MMC.ANX=ANX;
    vdp->MMC.ASX=ASX;
    vdp->MMC.ADX=ADX;
  }
}

/** YmmmEngine() *********************************************/
/** Vram -> Vram                                            **/
/*************************************************************/
void YmmmEngine(void)
{
  register int SY=vdp->MMC.SY;
  register int DX=vdp->MMC.DX;
  register int DY=vdp->MMC.DY;
  register int TX=vdp->MMC.TX;
  register int TY=vdp->MMC.TY;
  register int NY=vdp->MMC.NY;
  register int ADX=vdp->MMC.ADX;
  register int MXD = vdp->MMC.MXD;
  register int cnt;
  register int delta;

  delta = GetVdpTimingValue(ymmm_timing);
  cnt = vdp->VdpOpsCnt;

  switch (ScrMode) {
    default:
    case 5: pre_loop *VDP_VRMP5(MXD, ADX, DY) = *VDP_VRMP5(MXD, ADX, SY); post__xyy(256)
            break;
    case 6: pre_loop *VDP_VRMP6(MXD, ADX, DY) = *VDP_VRMP6(MXD, ADX, SY); post__xyy(512)
            break;
    case 7: pre_loop *VDP_VRMP7(MXD, ADX, DY) = *VDP_VRMP7(MXD, ADX, SY); post__xyy(512)
            break;
    case 8: pre_loop *VDP_VRMP8(MXD, ADX, DY) = *VDP_VRMP8(MXD, ADX, SY); post__xyy(256)
            break;
  }

  if ((vdp->VdpOpsCnt=cnt)>0) {
    /* Command execution done */
    VDPStatus[2]&=0xFE;
    vdp->VdpEngine=0;
    if (!NY) {
      SY+=TY;
      DY+=TY;
    }
    else
      if (SY==-1)
        DY+=TY;
    VDP[42]=NY & 0xFF;
    VDP[43]=(NY>>8) & 0x03;
    VDP[34]=SY & 0xFF;
    VDP[35]=(SY>>8) & 0x03;
    VDP[38]=DY & 0xFF;
    VDP[39]=(DY>>8) & 0x03;
  }
  else {
    vdp->MMC.SY=SY;
    vdp->MMC.DY=DY;
    vdp->MMC.NY=NY;
    vdp->MMC.ADX=ADX;
  }
}

/** HmmcEngine() *********************************************/
/** CPU -> Vram                                             **/
/*************************************************************/
void HmmcEngine(void)
{
  if ((VDPStatus[2]&0x80)!=0x80) {

    *VDP_VRMP(((ScrMode >= 5) && (ScrMode <= 8)) ? (ScrMode-5) : 0, vdp->MMC.MXD, vdp->MMC.ADX, vdp->MMC.DY)=VDP[44];
    vdp->VdpOpsCnt-=GetVdpTimingValue(hmmv_timing);
    VDPStatus[2]|=0x80;

    if (!--vdp->MMC.ANX || ((vdp->MMC.ADX+=vdp->MMC.TX)&vdp->MMC.MX)) {
      if (!(--vdp->MMC.NY&1023) || (vdp->MMC.DY+=vdp->MMC.TY)==-1) {
        VDPStatus[2]&=0xFE;
        vdp->VdpEngine=0;
        if (!vdp->MMC.NY)
          vdp->MMC.DY+=vdp->MMC.TY;
        VDP[42]=vdp->MMC.NY & 0xFF;
        VDP[43]=(vdp->MMC.NY>>8) & 0x03;
        VDP[38]=vdp->MMC.DY & 0xFF;
        VDP[39]=(vdp->MMC.DY>>8) & 0x03;
      }
      else {
        vdp->MMC.ADX=vdp->MMC.DX;
        vdp->MMC.ANX=vdp->MMC.NX;
      }
    }
  }
}

/** VDPWrite() ***********************************************/
/** Use this function to transfer pixel(s) from CPU to vdp-> **/
/*************************************************************/
static void v9938_cpu_to_vdp (UINT8 V)
{
  VDPStatus[2]&=0x7F;
  VDPStatus[7]=VDP[44]=V;
  if(vdp->VdpEngine&&(vdp->VdpOpsCnt>0)) vdp->VdpEngine();
}

/** VDPRead() ************************************************/
/** Use this function to transfer pixel(s) from VDP to CPU. **/
/*************************************************************/
static UINT8 v9938_vdp_to_cpu (void)
{
  VDPStatus[2]&=0x7F;
  if(vdp->VdpEngine&&(vdp->VdpOpsCnt>0)) vdp->VdpEngine();
  return(VDP[44]);
}

/** ReportVdpCommand() ***************************************/
/** Report VDP Command to be executed                       **/
/*************************************************************/
static void ReportVdpCommand(register UINT8 Op)
{
	static const char *const Ops[16] =
	{
		"SET ","AND ","OR  ","XOR ","NOT ","NOP ","NOP ","NOP ",
		"TSET","TAND","TOR ","TXOR","TNOT","NOP ","NOP ","NOP "
	};
	static const char *const Commands[16] =
	{
		" ABRT"," ????"," ????"," ????","POINT"," PSET"," SRCH"," LINE",
		" LMMV"," LMMM"," LMCM"," LMMC"," HMMV"," HMMM"," YMMM"," HMMC"
	};

	register UINT8 CL, CM, LO;
	register int SX,SY, DX,DY, NX,NY;

	/* Fetch arguments */
	CL = VDP[44];
	SX = (VDP[32]+((int)VDP[33]<<8)) & 511;
	SY = (VDP[34]+((int)VDP[35]<<8)) & 1023;
	DX = (VDP[36]+((int)VDP[37]<<8)) & 511;
	DY = (VDP[38]+((int)VDP[39]<<8)) & 1023;
	NX = (VDP[40]+((int)VDP[41]<<8)) & 1023;
	NY = (VDP[42]+((int)VDP[43]<<8)) & 1023;
	CM = Op>>4;
	LO = Op&0x0F;

	LOG(("V9938: Opcode %02Xh %s-%s (%d,%d)->(%d,%d),%d [%d,%d]%s\n",
			Op, Commands[CM], Ops[LO],
			SX,SY, DX,DY, CL, VDP[45]&0x04? -NX:NX,
			VDP[45]&0x08? -NY:NY,
			VDP[45]&0x70? " on ExtVRAM":""
		));
}

/** VDPDraw() ************************************************/
/** Perform a given V9938 operation Op.                     **/
/*************************************************************/
static UINT8 v9938_command_unit_w (UINT8 Op)
{
  register int SM;

  /* V9938 ops only work in SCREENs 5-8 */
  if (ScrMode<5)
    return(0);

  SM = ScrMode-5;         /* Screen mode index 0..3  */

  vdp->MMC.CM = Op>>4;
  if ((vdp->MMC.CM & 0x0C) != 0x0C && vdp->MMC.CM != 0)
    /* Dot operation: use only relevant bits of color */
    VDPStatus[7]=(VDP[44]&=Mask[SM]);

/*  if(Verbose&0x02) */
    ReportVdpCommand(Op);

  switch(Op>>4) {
    case CM_ABRT:
      VDPStatus[2]&=0xFE;
      vdp->VdpEngine=0;
      return 1;
    case CM_POINT:
      VDPStatus[2]&=0xFE;
      vdp->VdpEngine=0;
      VDPStatus[7]=VDP[44]=
                   VDP_POINT(SM, (VDP[45] & 0x10) != 0,
                                 VDP[32]+((int)VDP[33]<<8),
                                 VDP[34]+((int)VDP[35]<<8));
      return 1;
    case CM_PSET:
      VDPStatus[2]&=0xFE;
      vdp->VdpEngine=0;
      VDP_PSET(SM, (VDP[45] & 0x20) != 0,
               VDP[36]+((int)VDP[37]<<8),
               VDP[38]+((int)VDP[39]<<8),
               VDP[44],
               Op&0x0F);
      return 1;
    case CM_SRCH:
      vdp->VdpEngine=SrchEngine;
      break;
    case CM_LINE:
      vdp->VdpEngine=LineEngine;
      break;
    case CM_LMMV:
      vdp->VdpEngine=LmmvEngine;
      break;
    case CM_LMMM:
      vdp->VdpEngine=LmmmEngine;
      break;
    case CM_LMCM:
      vdp->VdpEngine=LmcmEngine;
      break;
    case CM_LMMC:
      vdp->VdpEngine=LmmcEngine;
      break;
    case CM_HMMV:
      vdp->VdpEngine=HmmvEngine;
      break;
    case CM_HMMM:
      vdp->VdpEngine=HmmmEngine;
      break;
    case CM_YMMM:
      vdp->VdpEngine=YmmmEngine;
      break;
    case CM_HMMC:
      vdp->VdpEngine=HmmcEngine;
      break;
    default:
      LOG(("V9938: Unrecognized opcode %02Xh\n",Op));
        return(0);
  }

  /* Fetch unconditional arguments */
  vdp->MMC.SX = (VDP[32]+((int)VDP[33]<<8)) & 511;
  vdp->MMC.SY = (VDP[34]+((int)VDP[35]<<8)) & 1023;
  vdp->MMC.DX = (VDP[36]+((int)VDP[37]<<8)) & 511;
  vdp->MMC.DY = (VDP[38]+((int)VDP[39]<<8)) & 1023;
  vdp->MMC.NY = (VDP[42]+((int)VDP[43]<<8)) & 1023;
  vdp->MMC.TY = VDP[45]&0x08? -1:1;
  vdp->MMC.MX = PPL[SM];
  vdp->MMC.CL = VDP[44];
  vdp->MMC.LO = Op&0x0F;
  vdp->MMC.MXS = (VDP[45] & 0x10) != 0;
  vdp->MMC.MXD = (VDP[45] & 0x20) != 0;

  /* Argument depends on UINT8 or dot operation */
  if ((vdp->MMC.CM & 0x0C) == 0x0C) {
    vdp->MMC.TX = VDP[45]&0x04? -PPB[SM]:PPB[SM];
    vdp->MMC.NX = ((VDP[40]+((int)VDP[41]<<8)) & 1023)/PPB[SM];
  }
  else {
    vdp->MMC.TX = VDP[45]&0x04? -1:1;
    vdp->MMC.NX = (VDP[40]+((int)VDP[41]<<8)) & 1023;
  }

  /* X loop variables are treated specially for LINE command */
  if (vdp->MMC.CM == CM_LINE) {
    vdp->MMC.ASX=((vdp->MMC.NX-1)>>1);
    vdp->MMC.ADX=0;
  }
  else {
    vdp->MMC.ASX = vdp->MMC.SX;
    vdp->MMC.ADX = vdp->MMC.DX;
  }

  /* NX loop variable is treated specially for SRCH command */
  if (vdp->MMC.CM == CM_SRCH)
    vdp->MMC.ANX=(VDP[45]&0x02)!=0; /* Do we look for "==" or "!="? */
  else
    vdp->MMC.ANX = vdp->MMC.NX;

  /* Command execution started */
  VDPStatus[2]|=0x01;

  /* Start execution if we still have time slices */
  if(vdp->VdpEngine&&(vdp->VdpOpsCnt>0)) vdp->VdpEngine();

  /* Operation successfull initiated */
  return(1);
}

/** LoopVDP() ************************************************/
/** Run X steps of active VDP command                       **/
/*************************************************************/
static void v9938_update_command (void)
{
  if(vdp->VdpOpsCnt<=0)
  {
    vdp->VdpOpsCnt+=13662;
    if(vdp->VdpEngine&&(vdp->VdpOpsCnt>0)) vdp->VdpEngine();
  }
  else
  {
    vdp->VdpOpsCnt=13662;
    if(vdp->VdpEngine) vdp->VdpEngine();
  }
}

