summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/video/maria.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/video/maria.c')
-rw-r--r--src/mame/video/maria.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/mame/video/maria.c b/src/mame/video/maria.c
new file mode 100644
index 00000000000..b2514770e82
--- /dev/null
+++ b/src/mame/video/maria.c
@@ -0,0 +1,484 @@
+// license:BSD-3-Clause
+// copyright-holders:Dan Boris, Fabio Priuli, Mike Saarna, Robert Tuccitto
+/***************************************************************************
+
+ Atari MARIA video emulation
+
+
+ - some history:
+ 2014-12-01 Mike Saarna, Robert Tuccitto Implemented "colorburst kill" bit
+ of the MARIA CTRL register.
+ 2014-10-05 Mike Saarna, Robert Tuccitto Last Line DMA value corrected
+ to 6. GCC and Atari docs both show a difference between
+ Other Line and Last Line as +6 at the lowest part of the
+ range.
+ Blank scanlines are drawn when DMA is off, like real
+ hardware.
+ If MARIA hits the DMA limit, the CPU doesn't run until
+ the next scanline.
+
+ 2014-09-03 Mike Saarna, Robert Tuccitto reorganized DMA penalties to
+ support new rendering timeout code.
+
+ 2014-08-29 Mike Saarna Timeout rendering added.
+
+ 2014-08-26 Fabio Priuli Converted to device
+
+ 2014-05-06 Mike Saarna Added interrupts to DMA cycle eating. Updates to
+ LL, OL, and spin accounting for HALT behavior.
+
+ 2014-03-24 Mike Saarna Fixed DMA regarding startup, shutdown and
+ cycle stealing.
+
+ 2013-05-08 huygens rewrite to emulate line ram buffers (mostly fixes Kung-Fu Master
+ Started DMA cycle stealing implementation
+
+ 2003-06-23 ericball Kangaroo mode & 320 mode & other stuff
+
+ 2002-05-14 kubecj vblank dma stop fix
+
+ 2002-05-13 kubecj fixed 320C mode (displayed 2 pixels instead of one!)
+ noticed that Jinks uses 0x02-320D mode
+ implemented the mode - completely unsure if good!
+ implemented some Maria CTRL variables
+
+ 2002-05-12 kubecj added cases for 0x01-160A, 0x05-160B as stated by docs
+
+***************************************************************************/
+
+#include "emu.h"
+#include "maria.h"
+
+
+#define TRIGGER_HSYNC 64717
+
+#define READ_MEM(x) space.read_byte(x)
+
+const device_type ATARI_MARIA = &device_creator<atari_maria_device>;
+
+
+
+atari_maria_device::atari_maria_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, ATARI_MARIA, "Atari MARIA", tag, owner, clock, "atari_maria", __FILE__)
+{
+}
+
+
+void atari_maria_device::device_start()
+{
+ m_cpu = machine().device<cpu_device>(m_cpu_tag);
+ m_screen = machine().first_screen();
+ m_screen->register_screen_bitmap(m_bitmap);
+
+ save_item(NAME(m_maria_palette));
+ save_item(NAME(m_line_ram));
+ save_item(NAME(m_active_buffer));
+ save_item(NAME(m_write_mode));
+ save_item(NAME(m_dll));
+ save_item(NAME(m_dl));
+ save_item(NAME(m_holey));
+ save_item(NAME(m_offset));
+ save_item(NAME(m_vblank));
+ save_item(NAME(m_dmaon));
+ save_item(NAME(m_dpp));
+ save_item(NAME(m_wsync));
+ save_item(NAME(m_color_kill));
+ save_item(NAME(m_cwidth));
+ save_item(NAME(m_bcntl));
+ save_item(NAME(m_kangaroo));
+ save_item(NAME(m_rm));
+ save_item(NAME(m_nmi));
+ save_item(NAME(m_charbase));
+}
+
+void atari_maria_device::device_reset()
+{
+ for (int i = 0; i < 32; i++)
+ m_maria_palette[i] = 0;
+
+ for (int i = 0; i < 2; i++)
+ for (int j = 0; j < 160; j++)
+ m_line_ram[i][j] = 0;
+
+ m_active_buffer = 0;
+
+ m_write_mode = 0;
+ m_dmaon = 0;
+ m_vblank = 0x80;
+ m_dll = 0;
+ m_wsync = 0;
+
+ m_color_kill = 0;
+ m_cwidth = 0;
+ m_bcntl = 0;
+ m_kangaroo = 0;
+ m_rm = 0;
+
+ m_dl = 0;
+ m_holey = 0;
+ m_offset = 0;
+ m_dpp = 0;
+ m_nmi = 0;
+ m_charbase = 0;
+}
+
+/***************************************************************************
+
+ Stop the video hardware emulation.
+
+***************************************************************************/
+
+int atari_maria_device::is_holey(unsigned int addr)
+{
+ if (((m_holey & 0x02) && ((addr & 0x9000) == 0x9000)) || ( (m_holey & 0x01) && ((addr & 0x8800) == 0x8800)))
+ return 1;
+ else
+ return 0;
+}
+
+int atari_maria_device::write_line_ram(int addr, UINT8 offset, int pal)
+{
+ address_space& space = m_cpu->space(AS_PROGRAM);
+ int c;
+
+ int data = READ_MEM(addr);
+ pal <<= 2;
+
+ if (m_write_mode)
+ {
+ c = (pal & 0x10) | (data & 0x0c) | (data >> 6); // P2 D3 D2 D7 D6
+ if (((c & 3) || m_kangaroo) && (offset < 160))
+ m_line_ram[m_active_buffer][offset] = c;
+ offset++;
+
+ c = (pal & 0x10) | ((data & 0x03) << 2) | ((data & 0x30) >> 4); // P2 D1 D0 D5 D4
+ if (((c & 3) || m_kangaroo) && (offset < 160))
+ m_line_ram[m_active_buffer][offset] = c;
+ }
+ else
+ {
+ for (int i = 0; i < 4; i++, offset++)
+ {
+ c = pal | ((data >> (6 - 2 * i)) & 0x03);
+ if (((c & 3) || m_kangaroo) && (offset < 160))
+ m_line_ram[m_active_buffer][offset] = c;
+ }
+ }
+ return m_write_mode ? 2 : 4;
+}
+
+
+void atari_maria_device::draw_scanline()
+{
+ address_space& space = m_cpu->space(AS_PROGRAM);
+ UINT16 graph_adr, data_addr;
+ int width, pal, ind;
+ UINT8 hpos;
+ UINT16 dl;
+ int d, c, pixel_cell, cells;
+ int maria_cycles;
+
+ cells = 0;
+
+ if(m_dmaon)
+ {
+ // All lines in a zone have the same initial DMA startup time. We'll adjust
+ // cycles for the special last zone line later, as those penalties happen after
+ // MARIA is done rendering, or after its hit the maximum rendering time.
+ maria_cycles = 16;
+
+
+ /* Process this DLL entry */
+ dl = m_dl;
+
+ /* DMA */
+ /* Step through DL's while we're within maximum rendering time. */
+ /* max render time = ( scanline length - DMA start ) */
+ /* 426 = ( 454 - 28 ) */
+
+ while (((READ_MEM(dl + 1) & 0x5f) != 0) && (maria_cycles<426))
+ {
+ /* Extended header */
+ if (!(READ_MEM(dl + 1) & 0x1f))
+ {
+ graph_adr = (READ_MEM(dl + 2) << 8) | READ_MEM(dl);
+ width = ((READ_MEM(dl + 3) ^ 0xff) & 0x1f) + 1;
+ hpos = READ_MEM(dl + 4);
+ pal = READ_MEM(dl + 3) >> 5;
+ m_write_mode = (READ_MEM(dl + 1) & 0x80) >> 5;
+ ind = READ_MEM(dl + 1) & 0x20;
+ dl += 5;
+ maria_cycles += 10;
+ }
+ /* Normal header */
+ else
+ {
+ graph_adr = (READ_MEM(dl + 2) << 8) | READ_MEM(dl);
+ width = ((READ_MEM(dl + 1) ^ 0xff) & 0x1f) + 1;
+ hpos = READ_MEM(dl + 3);
+ pal = READ_MEM(dl + 1) >> 5;
+ ind = 0x00;
+ dl += 4;
+ maria_cycles += 8;
+ }
+
+ /*logerror("%x DL: ADR=%x width=%x hpos=%x pal=%x mode=%x ind=%x\n", m_screen->vpos(), graph_adr, width, hpos, pal, m_write_mode, ind);*/
+
+ for (int x = 0; x < width; x++)
+ {
+ if (maria_cycles >= 426) // ensure we haven't overrun the maximum render time
+ break;
+
+ /* Do indirect mode */
+ if (ind)
+ {
+ c = READ_MEM(graph_adr + x) & 0xff;
+ data_addr = (m_charbase | c) + (m_offset << 8);
+ if (is_holey(data_addr))
+ continue;
+
+ maria_cycles += 3;
+ if (m_cwidth) // two data bytes per map byte
+ {
+ cells = write_line_ram(data_addr, hpos, pal);
+ hpos += cells;
+ cells = write_line_ram(data_addr+1, hpos, pal);
+ hpos += cells;
+ maria_cycles += 6;
+ }
+ else
+ {
+ cells = write_line_ram(data_addr, hpos, pal);
+ hpos += cells;
+ maria_cycles += 3;
+ }
+ }
+ else // direct mode
+ {
+ data_addr = graph_adr + x + (m_offset << 8);
+ if (is_holey(data_addr))
+ continue;
+ cells = write_line_ram(data_addr, hpos, pal);
+ hpos += cells;
+ maria_cycles += 3;
+ }
+ }
+ }
+
+ // Last Line post-render DMA cycle penalties...
+ if (m_offset == 0)
+ {
+ maria_cycles += 6; // extra shutdown time
+ if (READ_MEM(m_dll + 3) & 0x80)
+ maria_cycles += 17; // interrupt overhead
+ }
+
+ // If MARIA used up all of the DMA time then the CPU can't run until next line...
+ if (maria_cycles>=426)
+ {
+ m_cpu->spin_until_trigger(TRIGGER_HSYNC);
+ m_wsync = 1;
+ }
+
+ // Spin the CPU for Maria DMA, if it's not already spinning for WSYNC.
+ // MARIA generates the 6502 clock by dividing its own clock by 4. It needs to HALT and unHALT
+ // the 6502 on ths same clock phase, so MARIA will wait until its clock divides evenly by 4.
+ // To spin until an even divisor, we just round-up any would-be truncations by adding 3.
+ if (!m_wsync)
+ m_cpu->spin_until_time(m_cpu->cycles_to_attotime((maria_cycles+3)/4));
+ }
+
+ // draw line buffer to screen
+ m_active_buffer = !m_active_buffer; // switch buffers
+ UINT16 *scanline;
+ scanline = &m_bitmap.pix16(m_screen->vpos());
+
+
+ for (int i = 0; i < 160; i++)
+ {
+ switch (m_rm)
+ {
+ case 0x00: /* 160A, 160B */
+ case 0x01: /* 160A, 160B */
+ pixel_cell = m_line_ram[m_active_buffer][i];
+ scanline[2 * i] = m_maria_palette[pixel_cell];
+ scanline[2 * i + 1] = m_maria_palette[pixel_cell];
+ break;
+
+ case 0x02: /* 320B, 320D */
+ pixel_cell = m_line_ram[m_active_buffer][i];
+ d = (pixel_cell & 0x10) | (pixel_cell & 0x02) | ((pixel_cell >> 3) & 1); // b4 0 0 b1 b3
+ scanline[2 * i] = m_maria_palette[d];
+ d = (pixel_cell & 0x10) | ((pixel_cell << 1) & 0x02) | ((pixel_cell >> 2) & 1); // b4 0 0 b0 b2
+ scanline[2 * i + 1] = m_maria_palette[d];
+ break;
+
+ case 0x03: /* 320A, 320C */
+ pixel_cell = m_line_ram[m_active_buffer][i];
+ d = (pixel_cell & 0x1c) | (pixel_cell & 0x02); // b4 b3 b2 b1 0
+ scanline[2 * i] = m_maria_palette[d];
+ d = (pixel_cell & 0x1c) | ((pixel_cell << 1) & 0x02); // b4 b3 b2 b0 0
+ scanline[2 * i + 1] = m_maria_palette[d];
+ break;
+ }
+
+ if(m_color_kill) //remove color if there's no colorburst signal
+ {
+ scanline[2 * i] &= 0x0f;
+ scanline[2 * i + 1] &= 0x0f;
+ }
+ }
+
+ for (int i = 0; i < 160; i++) // buffer automaticaly cleared once displayed
+ m_line_ram[m_active_buffer][i] = 0;
+}
+
+
+void atari_maria_device::interrupt(int lines)
+{
+ if (m_wsync)
+ {
+ machine().scheduler().trigger(TRIGGER_HSYNC);
+ m_wsync = 0;
+ }
+
+ int frame_scanline = m_screen->vpos() % (lines + 1);
+ if (frame_scanline == 16)
+ m_vblank = 0x00;
+
+ if (frame_scanline == (lines - 5))
+ m_vblank = 0x80;
+}
+
+
+void atari_maria_device::startdma(int lines)
+{
+ address_space& space = m_cpu->space(AS_PROGRAM);
+ int maria_scanline = m_screen->vpos();
+ int frame_scanline = maria_scanline % (lines + 1);
+
+ if ((frame_scanline == 16) && m_dmaon)
+ {
+ /* end of vblank */
+ m_dll = m_dpp; // currently only handle changes to dll during vblank
+ m_dl = (READ_MEM(m_dll + 1) << 8) | READ_MEM(m_dll+2);
+ m_offset = READ_MEM(m_dll) & 0x0f;
+ m_holey = (READ_MEM(m_dll) & 0x60) >> 5;
+ m_nmi = READ_MEM(m_dll) & 0x80;
+ /* logerror("DLL=%x\n",m_dll); */
+ }
+
+ if ((frame_scanline > 15) && (frame_scanline < (lines - 5)))
+ draw_scanline();
+
+ if ((frame_scanline > 16) && (frame_scanline < (lines - 5)) && m_dmaon)
+ {
+ if (m_offset == 0)
+ {
+ m_dll += 3;
+ m_dl = (READ_MEM(m_dll + 1) << 8) | READ_MEM(m_dll + 2);
+ m_offset = READ_MEM(m_dll) & 0x0f;
+ m_holey = (READ_MEM(m_dll) & 0x60) >> 5;
+ if (READ_MEM(m_dll & 0x10))
+ logerror("dll bit 5 set!\n");
+ m_nmi = READ_MEM(m_dll) & 0x80;
+ }
+ else
+ {
+ m_offset--;
+ }
+ }
+
+ if (m_nmi)
+ {
+ m_cpu->set_input_line(INPUT_LINE_NMI, PULSE_LINE);
+ m_nmi = 0;
+ }
+}
+
+/***************************************************************************
+
+ Refresh the video screen
+
+***************************************************************************/
+
+/* This routine is called at the start of vblank to refresh the screen */
+UINT32 atari_maria_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
+{
+ copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
+ return 0;
+}
+
+
+READ8_MEMBER(atari_maria_device::read)
+{
+ switch (offset)
+ {
+ case 0x08:
+ return m_vblank;
+
+ default:
+ logerror("undefined MARIA read %x\n",offset);
+ return 0x00; // don't know if this should be 0x00 or 0xff
+ }
+}
+
+WRITE8_MEMBER(atari_maria_device::write)
+{
+ if ((offset & 3) != 0)
+ m_maria_palette[offset] = data;
+
+ switch (offset)
+ {
+ case 0x00:
+ // all color ram addresses with 00 for their least significant bits point to the background color
+ for (int i = 0; i < 8; i++)
+ m_maria_palette[4 * i] = data;
+ break;
+ case 0x04:
+ m_cpu->spin_until_trigger(TRIGGER_HSYNC);
+ m_wsync = 1;
+ break;
+ case 0x0C: // DPPH
+ m_dpp = (m_dpp & 0x00ff) | (data << 8);
+ break;
+ case 0x10: // DPPL
+ m_dpp = (m_dpp & 0xff00) | data;
+ break;
+ case 0x14:
+ m_charbase = (data << 8);
+ break;
+ case 0x1C:
+ /*logerror("MARIA CTRL=%x\n",data);*/
+ m_color_kill = data & 0x80;
+ switch ((data >> 5) & 3)
+ {
+ case 0x00:
+ case 01:
+ logerror("dma test mode, do not use.\n");
+ break;
+
+ case 0x02:
+ m_dmaon = 1;
+ break;
+
+ case 0x03:
+ m_dmaon = 0;
+ break;
+ }
+ m_cwidth = data & 0x10;
+ m_bcntl = data & 0x08; // Currently unimplemented as we don't display the border
+ m_kangaroo = data & 0x04;
+ m_rm = data & 0x03;
+
+ /*logerror( "MARIA CTRL: CK:%d DMA:%d CW:%d BC:%d KM:%d RM:%d\n",
+ m_color_kill ? 1 : 0,
+ (data & 0x60) >> 5,
+ m_cwidth ? 1 : 0,
+ m_bcntl ? 1 : 0,
+ m_kangaroo ? 1 : 0,
+ m_rm );*/
+
+ break;
+ }
+}