// license:BSD-3-Clause
// copyright-holders:Wilbert Pol
/**********************************************************************

    NEC HuC6270 Video Display Controller

    The HuC6270 basically outputs a 9-bit stream of pixel data which
    holds a color index, a palette index, and an indication whether
    the pixel contains background data or from sprite data.

    This data can be used by a colour encoder to output graphics.

    A regular screen is displayed as follows:

        |<- HDS ->|<--       HDW       -->|<- HDE ->|<- HSW ->|
        |---------|-----------------------|---------|---------|
    VSW |                                                     |
        |---------|-----------------------|---------|---------|
    VDS |                                                     |
        |                  overscan                           |
        |---------|-----------------------|---------|---------|
        |         |                       |                   |
        |         |                       |                   |
        |         |                       |                   |
        |         |                       |                   |
    VDW | overscan|    active display     |      overscan     |
        |         |                       |                   |
        |         |                       |                   |
        |         |                       |                   |
        |         |                       |                   |
        |---------|-----------------------|---------|---------|
    VCR |                  overscan                           |
        |                                                     |
        |---------|-----------------------|---------|---------|
        ^end hsync
         ^start vsync (30 cycles after hsync)


KNOWN ISSUES
  - Violent Soldier (probably connected):
    - In the intro some artefacts appear at the top of the
      screen every now and then.
  - In ccovell's splitres test not all sections seem to be aligned properly.
  - Side Arms: Seems to be totally broken.


TODO
  - Fix timing of VRAM-SATB DMA
  - Implement VRAM-VRAM DMA
  - DMA speeds differ depending on the dot clock selected in the huc6270
  - Convert VRAM bus to actual space address (optimization)

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

#include "emu.h"
#include "huc6270.h"

//#define VERBOSE 1
#include "logmacro.h"


enum {
	MAWR = 0x00,
	MARR = 0x01,
	VxR = 0x02,
	CR = 0x05,
	RCR = 0x06,
	BXR = 0x07,
	BYR = 0x08,
	MWR = 0x09,
	HSR = 0x0a,
	HDR = 0x0b,
	VPR = 0x0c,
	VDW = 0x0d,
	VCR = 0x0e,
	DCR = 0x0f,
	SOUR = 0x10,
	DESR = 0x11,
	LENR = 0x12,
	DVSSR = 0x13
};

ALLOW_SAVE_TYPE(huc6270_device::v_state);
ALLOW_SAVE_TYPE(huc6270_device::h_state);


/* Bits in the VDC status register */
#define HUC6270_BSY         0x40    /* Set when the VDC accesses VRAM */
#define HUC6270_VD          0x20    /* Set when in the vertical blanking period */
#define HUC6270_DV          0x10    /* Set when a VRAM > VRAM DMA transfer is done */
#define HUC6270_DS          0x08    /* Set when a VRAM > SATB DMA transfer is done */
#define HUC6270_RR          0x04    /* Set when the current scanline equals the RCR register */
#define HUC6270_OR          0x02    /* Set when there are more than 16 sprites on a line */
#define HUC6270_CR          0x01    /* Set when sprite #0 overlaps with another sprite */


DEFINE_DEVICE_TYPE(HUC6270, huc6270_device, "huc6270", "Hudson HuC6270 VDC")


constexpr uint8_t huc6270_device::vram_increments[4];

huc6270_device::huc6270_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, HUC6270, tag, owner, clock)
	, m_vram_size(0)
	, m_irq_changed_cb(*this)
{
}


/*
  Read one row of tile data from video ram
*/
inline void huc6270_device::fetch_bat_tile_row()
{
	const uint16_t bat_data = m_vram[m_bat_address & m_vram_mask];
	const uint16_t tile_palette = (bat_data >> 8) & 0xf0;
	uint16_t data1 = m_vram[(((bat_data & 0x0fff) << 4) + m_bat_row + 0) & m_vram_mask];
	uint16_t data2 = (data1 >> 7) & 0x1fe;
	uint16_t data3 = m_vram[(((bat_data & 0x0fff) << 4) + m_bat_row + 8) & m_vram_mask];
	uint16_t data4 = (data3 >> 5) & 0x7f8;
	data3 <<= 2;

	for (int i = 7; i >= 0; i--)
	{
		uint16_t c = (data1 & 0x01) | (data2 & 0x02) | (data3 & 0x04) | (data4 & 0x08);

		/* Colour 0 for background tiles is always taken from palette 0 */
		if (c)
			c |= tile_palette;

		m_bat_tile_row[i] = c;

		data1 >>= 1;
		data2 >>= 1;
		data3 >>= 1;
		data4 >>= 1;
	}
}


void huc6270_device::add_sprite(int index, int x, int pattern, int line, int flip_x, int palette, int priority, int sat_lsb)
{
	const int i = m_sprites_this_line;

	if (i < 16)
	{
		uint32_t b0, b1, b2, b3;

		if (flip_x)
			flip_x = 0x0f;

		pattern += ((line >> 4) << 1);

		if ((m_mwr & 0x0c) == 0x04)
		{
			if (!sat_lsb)
			{
				b0 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x00) & m_vram_mask];
				b1 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x10) & m_vram_mask] << 1;
			}
			else
			{
				b0 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x20) & m_vram_mask];
				b1 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x30) & m_vram_mask] << 1;
			}
			b2 = 0;
			b3 = 0;
		}
		else
		{
			b0 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x00) & m_vram_mask];
			b1 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x10) & m_vram_mask] << 1;
			b2 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x20) & m_vram_mask] << 2;
			b3 = m_vram[((pattern * 0x40) + (line & 0x0f) + 0x30) & m_vram_mask] << 3;
		}

		for (int j = 15; j >= 0; j--)
		{
			uint8_t data = (b3 & 0x08) | (b2 & 0x04) | (b1 & 0x02) | (b0 & 0x01);

			if (data)
			{
				data |= palette << 4;

				if (x + (j ^ flip_x) < 1024)
				{
					if (!m_sprite_row[x + (j ^ flip_x)])
					{
						m_sprite_row[x + (j ^ flip_x)] = (priority ? 0x4000 : 0x0000) | (index << 8) | data;
					}
					else
					{
						if (!(m_sprite_row[x + (j ^ flip_x)] & 0xff00))
						{
							/* Sprite 0 collission */
							m_sprite_row[x + (j ^ flip_x)] |= 0x8000;
						}
					}
				}
			}

			b0 >>= 1;
			b1 >>= 1;
			b2 >>= 1;
			b3 >>= 1;
		}

		m_sprites_this_line += 1;
	}
}


void huc6270_device::select_sprites()
{
	m_sprites_this_line = 0;
	memset(m_sprite_row, 0, sizeof(m_sprite_row));
	m_sprite_row_index = 0x20;

	for (int i = 0; i < 4 * 64; i += 4)
	{
		static const int cgy_table[4] = { 16, 32, 64, 64 };
		const int cgy = (m_sat[i + 3] >> 12) & 0x03;
		const int height = cgy_table[cgy];
		// TODO: we are one line off in alignment, is following compensation right?
		// cfr. rennybla & draculax (at least), they are otherwise offset by 1
		// compared to background.
		int sprite_line = m_raster_count - 1 - m_sat[i];

		if (sprite_line >= 0 && sprite_line < height)
		{
			int pattern = m_sat[i + 2] >> 1;
			const bool sat_lsb = BIT(m_sat[i + 2], 0);
			const int palette = m_sat[i + 3] & 0x0f;
			const bool priority = BIT(m_sat[i + 3], 7);
			const bool cgx = BIT(m_sat[i + 3], 8);

			/* If CGY is set to 1, bit 1 of the sprite pattern index is forced to 0 */
			if (BIT(cgy, 0))
				pattern &= ~0x0002;

			/* If CGY is set to 2 or 3, bits 1 and 2 of the sprite pattern index are forced to 0 */
			if (BIT(cgy, 1))
				pattern &= ~0x0006;

			/* Recalculate line index when sprite is flipped vertically */
			if (BIT(m_sat[i + 3], 15))
				sprite_line = (height - 1) - sprite_line;

			/* Is the sprite 32 pixels wide */
			if (cgx)
			{
				/* If CGX is set, bit 0 of the sprite pattern index is forced to 0 */
				pattern &= ~0x0001;

				/* Check for horizontal flip */
				if (BIT(m_sat[i + 3], 11))
				{
					/* Add to our list of sprites for this line */
					add_sprite(i / 4, m_sat[i + 1], pattern + 1, sprite_line, 1, palette, priority, sat_lsb);
					add_sprite(i / 4, m_sat[i + 1] + 16, pattern, sprite_line, 1, palette, priority, sat_lsb);
				}
				else
				{
					/* Add to our list of sprites for this line */
					add_sprite(i / 4, m_sat[i + 1], pattern, sprite_line, 0, palette, priority, sat_lsb);
					add_sprite(i / 4, m_sat[i + 1] + 16, pattern + 1, sprite_line, 0, palette, priority, sat_lsb);
				}
			}
			else
			{
				/* Add to our list of sprites for this line */
				add_sprite(i / 4, m_sat[i + 1], pattern, sprite_line, BIT(m_sat[i + 3], 11), palette, priority, sat_lsb);
			}
		}
	}

	/* Check for sprite overflow */
	if (m_sprites_this_line >= 16)
	{
		/* note: flag is set only if irq is taken, Mizubaku Daibouken relies on this behaviour */
		if (BIT(m_cr, 1))
		{
			m_status |= HUC6270_OR;
			m_irq_changed_cb(ASSERT_LINE);
		}
	}
}


inline void huc6270_device::handle_vblank()
{
	if (!m_vd_triggered)
	{
		if (BIT(m_cr, 3))
		{
			m_status |= HUC6270_VD;
			m_irq_changed_cb(ASSERT_LINE);
		}

		/* Should we initiate a VRAM->SATB DMA transfer.
		   The timing for this is incorrect.
		 */
		if (m_dvssr_written || BIT(m_dcr, 4))
		{
			LOG("SATB transfer from %05x\n", m_dvssr << 1);
			for (int i = 0; i < 4 * 64; i += 4)
			{
				m_sat[i + 0] = m_vram[(m_dvssr + i + 0) & m_vram_mask] & 0x03ff;
				m_sat[i + 1] = m_vram[(m_dvssr + i + 1) & m_vram_mask] & 0x03ff;
				m_sat[i + 2] = m_vram[(m_dvssr + i + 2) & m_vram_mask] & 0x07ff;
				m_sat[i + 3] = m_vram[(m_dvssr + i + 3) & m_vram_mask];
			}
			m_dvssr_written = false;

			/* Generate SATB interrupt if requested */
			if (BIT(m_dcr, 0))
			{
				m_satb_countdown = 4;
//              m_status |= HUC6270_DS;
//              m_irq_changed_cb(ASSERT_LINE);
			}
		}

		m_vd_triggered = true;
	}
}


inline void huc6270_device::next_vert_state()
{
	switch (m_vert_state)
	{
	case v_state::VSW:
		m_vert_state = v_state::VDS;
		m_vert_to_go = ((m_vpr >> 8) & 0xff) + 2;
		break;

	case v_state::VDS:
		m_vert_state = v_state::VDW;
		m_vert_to_go = (m_vdw & 0x1ff) + 1;
		m_byr_latched = m_byr;
		m_vd_triggered = false;
		break;

	case v_state::VDW:
		m_vert_state = v_state::VCR;
		m_vert_to_go = (m_vcr & 0xff);
		handle_vblank();
		break;

	case v_state::VCR:
		m_vert_state = v_state::VSW;
		m_vert_to_go = (m_vpr & 0x1f) + 1;
		break;
	}
}


inline void huc6270_device::next_horz_state()
{
	switch (m_horz_state)
	{
	case h_state::HDS:
		m_bxr_latched = m_bxr;
		m_horz_state = h_state::HDW;
		m_horz_to_go = (m_hdr & 0x7f) + 1;
		{
			static const int width_shift[4] = { 5, 6, 7, 7 };

			const uint16_t v = (m_byr_latched) & (BIT(m_mwr, 6) ? 0x1ff : 0xff);
			m_bat_row = v & 7;
			m_bat_address_mask = (1 << width_shift[(m_mwr >> 4) & 0x03]) - 1;
			m_bat_address = ((v >> 3) << (width_shift[(m_mwr >> 4) & 0x03]))
				| ((m_bxr_latched >> 3) & m_bat_address_mask);
			m_bat_column = m_bxr & 7;
			fetch_bat_tile_row();
		}
		break;

	case h_state::HDW:
		m_horz_state = h_state::HDE;
		m_horz_to_go = ((m_hdr >> 8) & 0x7f) + 1;
		break;

	case h_state::HDE:
		m_horz_state = h_state::HSW;
		m_horz_to_go = (m_hsr & 0x1f) + 1;
		break;

	case h_state::HSW:
		m_horz_state = h_state::HDS;
		m_horz_to_go = std::max(((m_hsr >> 8) & 0x7f), 2) + 1;

		/* If section has ended, advance to next vertical state */
		while (m_vert_to_go == 0)
			next_vert_state();

		/* Select sprites for the coming line */
		select_sprites();
		break;
	}
	m_horz_steps = 0;
}


u16 huc6270_device::next_pixel()
{
	uint16_t data = HUC6270_SPRITE;

	/* Check if we're on an active display line */
	if (m_vert_state == v_state::VDW)
	{
		/* Check if we're in active display area */
		if (m_horz_state == h_state::HDW)
		{
			const uint8_t sprite_data = m_sprite_row[m_sprite_row_index] & 0x00ff;
			const bool collission = BIT(m_sprite_row[m_sprite_row_index], 15);

			if (BIT(m_cr, 7))
			{
				data = HUC6270_BACKGROUND | m_bat_tile_row[m_bat_column];
				if (sprite_data && BIT(m_cr, 6))
				{
					if (BIT(m_sprite_row[m_sprite_row_index], 14))
					{
						data = HUC6270_SPRITE | sprite_data;
					}
					else
					{
						if (data == HUC6270_BACKGROUND)
						{
							data = HUC6270_SPRITE | sprite_data;
						}
					}
				}
			}
			else
			{
				if (BIT(m_cr, 6))
				{
					data = HUC6270_SPRITE | sprite_data;
				}
			}

			m_sprite_row_index = m_sprite_row_index + 1;
			m_bat_column += 1;
			if (m_bat_column >= 8)
			{
				m_bat_address = (m_bat_address & ~m_bat_address_mask)
					| ((m_bat_address + 1) & m_bat_address_mask);
				m_bat_column = 0;
				fetch_bat_tile_row();
			}

			if (collission && BIT(m_cr, 0))
			{
				m_status |= HUC6270_CR;
				m_irq_changed_cb(ASSERT_LINE);
			}
		}
	}

	m_horz_steps++;
	if (m_horz_steps == 8)
	{
		m_horz_to_go -= 1;
		m_horz_steps = 0;
		while (m_horz_to_go == 0)
			next_horz_state();
	}
	return data;
}


//inline u16 huc6270_device::time_until_next_event()
//{
//  return m_horz_to_go * 8 + m_horz_steps;
//}


void huc6270_device::vsync_changed(int state)
{
	state &= 0x01;
	if (m_vsync != state)
	{
		/* Check for high->low VSYNC transition */
		if (!state)
		{
			m_vert_state = v_state::VCR;
			m_vert_to_go = 0;

			while (m_vert_to_go == 0)
				next_vert_state();
		}
		else
		{
			/* Check for low->high VSYNC transition */
			// VBlank IRQ happens at the beginning of HDW period after VDW ends
			handle_vblank();
		}
	}

	m_vsync = state;
}


void huc6270_device::hsync_changed(int state)
{
	state &= 0x01;

	if(m_hsync != state)
	{
		/* Check for low->high HSYNC transition */
		if(state)
		{
			if (m_satb_countdown)
			{
				m_satb_countdown--;

				if (m_satb_countdown == 0)
				{
					m_status |= HUC6270_DS;
					m_irq_changed_cb(ASSERT_LINE);
				}
			}

			m_horz_state = h_state::HSW;
			m_horz_to_go = 0;
			m_horz_steps = 0;
			m_byr_latched += 1;
			m_raster_count += 1;
			// raster count VSW latch happens one line earlier (cfr. +2 on assignment)
			// This has been confirmed on real HW, where the last possible RCR with
			// 240 VDW is 0x130 (i.e. 64 + 240). m_vert_to_go == 1 will also
			// cause several side effects, namely:
			// - draculax Stage 4' "all blue" Richter;
			// - faussete Stage 2 excessive slowdown;
			// - xwiber Stage 2 boss never spawning (MT#07384)
			if (m_vert_to_go == 2 && m_vert_state == v_state::VDS)
			{
				m_raster_count = 0x40;
			}

			m_vert_to_go -= 1;

			while (m_horz_to_go == 0)
				next_horz_state();

			handle_dma();
		}
		else
		{
			/* Check for high->low HSYNC transition */
			// RCR IRQ happens near the end of the HDW period
			if (m_raster_count == m_rcr && BIT(m_cr, 2))
			{
				m_status |= HUC6270_RR;
				m_irq_changed_cb(ASSERT_LINE);
			}
		}
	}

	m_hsync = state;
}

inline void huc6270_device::handle_dma()
{
	/* Should we perform VRAM-VRAM dma.
	   The timing for this is incorrect.
	 */
	if (m_dma_enabled)
	{
		const int desr_inc = (m_dcr & 0x0008) ? -1 : +1;
		const int sour_inc = (m_dcr & 0x0004) ? -1 : +1;

		LOG("doing dma sour = %04x, desr = %04x, lenr = %04x\n", m_sour, m_desr, m_lenr);

		do {
			// area 0x8000-0xffff cannot be r/w (open bus)
			const uint16_t data = (m_sour <= m_vram_mask) ? m_vram[m_sour] : 0;

			if (m_desr <= m_vram_mask)
				m_vram[m_desr] = data;
			m_sour += sour_inc;
			m_desr += desr_inc;
			m_lenr -= 1;
		} while (m_lenr != 0xffff);

		if (BIT(m_dcr, 1))
		{
			m_status |= HUC6270_DV;
			m_irq_changed_cb(ASSERT_LINE);
		}
		m_dma_enabled = false;
	}
}

u8 huc6270_device::read(offs_t offset)
{
	uint8_t data = 0x00;

	switch (offset & 3)
	{
		case 0x00:  /* status */
			data = m_status;
			if (!machine().side_effects_disabled())
			{
				m_status &= ~(HUC6270_VD | HUC6270_DV | HUC6270_RR | HUC6270_CR | HUC6270_OR | HUC6270_DS);
				m_irq_changed_cb(CLEAR_LINE);
			}
			break;

		case 0x02:
			data = m_vrr & 0xff;
			break;

		case 0x03:
			data = m_vrr >> 8;
			if (!machine().side_effects_disabled())
			{
				if (m_register_index == VxR)
				{
					m_marr += vram_increments[(m_cr >> 11) & 3];

					if (m_marr <= m_vram_mask)
					{
						m_vrr = m_vram[m_marr];
					}
					else
					{
						// TODO: test with real HW
						m_vrr = 0;
						logerror("%s Open Bus VRAM read (register read) %04x\n",this->tag(),m_marr);
					}
				}
			}
			break;
	}
	return data;
}


void huc6270_device::write(offs_t offset, u8 data)
{
	LOG("%s: huc6270 write %02x <- %02x ", machine().describe_context(), offset, data);

	switch (offset & 3)
	{
		case 0x00:  /* VDC register select */
			m_register_index = data & 0x1F;
			break;

		case 0x02:  /* VDC data LSB */
			switch (m_register_index)
			{
				case MAWR:      /* memory address write register LSB */
					m_mawr = (m_mawr & 0xff00) | data;
					break;

				case MARR:      /* memory address read register LSB */
					m_marr = (m_marr & 0xff00) | data;
					if(m_marr <= m_vram_mask)
						m_vrr = m_vram[m_marr];
					else
					{
						// TODO: test with real HW
						m_vrr = 0;
						logerror("%s Open Bus VRAM read (memory address) %04x\n",this->tag(),m_marr);
					}
					break;

				case VxR:       /* vram write data LSB */
					m_vwr = (m_vwr & 0xff00) | data;
					break;

				case CR:        /* control register LSB */
					m_cr = (m_cr & 0xff00) | data;
					break;

				case RCR:       /* raster compare register LSB */
					m_rcr = (m_rcr & 0x0300) | data;
//                  if (m_raster_count == m_rcr && m_cr & 0x04)
//                  {
//                      m_status |= HUC6270_RR;
//                      m_irq_changed_cb(ASSERT_LINE);
//                  }
					break;

				case BXR:       /* background x-scroll register LSB */
					m_bxr = (m_bxr & 0x0300) | data;
					break;

				case BYR:       /* background y-scroll register LSB */
					m_byr = (m_byr & 0x0100) | data;
					m_byr_latched = m_byr;
					break;

				case MWR:       /* memory width register LSB */
					m_mwr = (m_mwr & 0xff00) | data;
					break;

				case HSR:       /* horizontal sync register LSB */
					m_hsr = (m_hsr & 0xff00) | data;
					break;

				case HDR:       /* horizontal display register LSB */
					m_hdr = (m_hdr & 0xff00) | data;
					break;

				case VPR:       /* vertical sync register LSB */
					m_vpr = (m_vpr & 0xff00) | data;
					break;

				case VDW:       /* vertical display register LSB */
					m_vdw = (m_vdw & 0xff00) | data;
					break;

				case VCR:       /* vertical display end position register LSB */
					m_vcr = (m_vcr & 0xff00) | data;
					break;

				case DCR:       /* DMA control register LSB */
					m_dcr = (m_dcr & 0xff00) | data;
					break;

				case SOUR:      /* DMA source address register LSB */
					m_sour = (m_sour & 0xff00) | data;
					break;

				case DESR:      /* DMA destination address register LSB */
					m_desr = (m_desr & 0xff00) | data;
					break;

				case LENR:      /* DMA length register LSB */
					m_lenr = (m_lenr & 0xff00) | data;
					break;

				case DVSSR:     /* Sprite attribute table LSB */
					m_dvssr = (m_dvssr & 0xff00) | data;
					m_dvssr_written = true;
					break;
			}
			break;

		case 0x03:  /* VDC data MSB */
			switch (m_register_index)
			{
				case MAWR:      /* memory address write register MSB */
					m_mawr = (m_mawr & 0x00ff) | (data << 8);
					break;

				case MARR:      /* memory address read register MSB */
					m_marr = (m_marr & 0x00ff) | (data << 8);
					if (m_marr <= m_vram_mask)
						m_vrr = m_vram[m_marr];
					else
						m_vrr = 0;
					break;

				case VxR:       /* vram write data MSB */
					m_vwr = (m_vwr & 0x00ff) | (data << 8);
					// area 0x8000-0xffff is NOP and cannot be written to.
					if (m_mawr <= m_vram_mask)
						m_vram[m_mawr] = m_vwr;
					m_mawr += vram_increments[(m_cr >> 11) & 3];
					break;

				case CR:        /* control register MSB */
					m_cr = (m_cr & 0x00ff) | (data << 8);
					break;

				case RCR:       /* raster compare register MSB */
					m_rcr = (m_rcr & 0x00ff) | ((data & 0x03) << 8);
//printf("%s: RCR set to %03x\n", machine().describe_context().c_str(), m_rcr);
//                  if (m_raster_count == m_rcr && m_cr & 0x04)
//                  {
//                      m_status |= HUC6270_RR;
//                      m_irq_changed_cb(ASSERT_LINE);
//                  }
					break;

				case BXR:       /* background x-scroll register MSB */
					m_bxr = (m_bxr & 0x00ff) | ((data & 0x03) << 8);
					break;

				case BYR:       /* background y-scroll register MSB */
					m_byr = (m_byr & 0x00ff) | ((data & 0x01) << 8);
					m_byr_latched = m_byr;
					break;

				case MWR:       /* memory width register MSB */
					m_mwr = (m_mwr & 0x00ff) | (data << 8);
					break;

				case HSR:       /* horizontal sync register MSB */
					m_hsr = (m_hsr & 0x00ff) | (data << 8);
					break;

				case HDR:       /* horizontal display register MSB */
					m_hdr = (m_hdr & 0x00ff) | (data << 8);
					break;

				case VPR:       /* vertical sync register MSB */
					m_vpr = (m_vpr & 0x00ff) | (data << 8);
					break;

				case VDW:       /* vertical display register MSB */
					m_vdw = (m_vdw & 0x00ff) | (data << 8);
					break;

				case VCR:       /* vertical display end position register MSB */
					m_vcr = (m_vcr & 0x00ff) | (data << 8);
					break;

				case DCR:       /* DMA control register MSB */
					m_dcr = (m_dcr & 0x00ff) | (data << 8);
					break;

				case SOUR:      /* DMA source address register MSB */
					m_sour = (m_sour & 0x00ff) | (data << 8);
					break;

				case DESR:      /* DMA destination address register MSB */
					m_desr = (m_desr & 0x00ff) | (data << 8);
					break;

				case LENR:      /* DMA length register MSB */
					m_lenr = (m_lenr & 0x00ff) | (data << 8);
					m_dma_enabled = true;
//logerror("DMA is not supported yet.\n");
					break;

				case DVSSR:     /* Sprite attribute table MSB */
					m_dvssr = (m_dvssr & 0x00ff) | (data << 8);
					m_dvssr_written = true;
					break;
			}
			break;
	}
	LOG("\n");
}


void huc6270_device::device_start()
{
	assert(!(m_vram_size & (m_vram_size - 1)));
	m_vram = make_unique_clear<uint16_t []>(m_vram_size / sizeof(uint16_t));
	m_vram_mask = (m_vram_size >> 1) - 1;

	save_pointer(NAME(m_vram), m_vram_size / sizeof(uint16_t));

	save_item(NAME(m_register_index));
	save_item(NAME(m_mawr));
	save_item(NAME(m_marr));
	save_item(NAME(m_vrr));
	save_item(NAME(m_vwr));
	save_item(NAME(m_cr));
	save_item(NAME(m_rcr));
	save_item(NAME(m_bxr));
	save_item(NAME(m_byr));
	save_item(NAME(m_mwr));
	save_item(NAME(m_hsr));
	save_item(NAME(m_hdr));
	save_item(NAME(m_vpr));
	save_item(NAME(m_vdw));
	save_item(NAME(m_vcr));
	save_item(NAME(m_dcr));
	save_item(NAME(m_sour));
	save_item(NAME(m_desr));
	save_item(NAME(m_lenr));
	save_item(NAME(m_dvssr));
	save_item(NAME(m_status));
	save_item(NAME(m_hsync));
	save_item(NAME(m_vsync));
	save_item(NAME(m_vert_state));
	save_item(NAME(m_horz_state));
	save_item(NAME(m_vd_triggered));
	save_item(NAME(m_vert_to_go));
	save_item(NAME(m_horz_to_go));
	save_item(NAME(m_horz_steps));
	save_item(NAME(m_raster_count));
	save_item(NAME(m_dvssr_written));
	save_item(NAME(m_satb_countdown));
	save_item(NAME(m_dma_enabled));
	save_item(NAME(m_byr_latched));
	save_item(NAME(m_bxr_latched));
	save_item(NAME(m_bat_address));
	save_item(NAME(m_bat_address_mask));
	save_item(NAME(m_bat_row));
	save_item(NAME(m_bat_column));
	save_item(NAME(m_bat_tile_row));
	save_item(NAME(m_sat));
	save_item(NAME(m_sprites_this_line));
	save_item(NAME(m_sprite_row_index));
	save_item(NAME(m_sprite_row));
}


void huc6270_device::device_reset()
{
	m_mawr = 0;
	m_marr = 0;
	m_vrr = 0;
	m_vwr = 0;
	m_cr = 0;
	m_rcr = 0;
	m_bxr = 0;
	m_byr = 0;
	m_mwr = 0;
	m_hsr = 0x0202;     /* Take some defaults for horizontal timing */
	m_hdr = 0x041f;
	m_vpr = 0x0f02;     /* Take some defaults for vertical timing */
	m_vdw = 0x00ef;
	m_vcr = 0x0004;
	m_dcr = 0;
	m_sour = 0;
	m_lenr = 0;
	m_dvssr = 0;
	m_status = 0;
	m_vd_triggered = false;
	m_dvssr_written = false;
	m_satb_countdown = 0;
	m_raster_count = 0x4000;
	m_vert_to_go = 0;
	m_vert_state = v_state::VSW;
	m_horz_steps = 0;
	m_horz_to_go = 0;
	m_horz_state = h_state::HDS;
	m_hsync = false;
	m_vsync = false;
	m_dma_enabled = false;
	m_byr_latched = 0;

	memset(m_sat, 0, sizeof(m_sat));
}
