// license:BSD-3-Clause // copyright-holders:Alex Pasadyn, Zsolt Vasvari, Ernesto Corvi, Aaron Giles // thanks-to:Kurt Mahan /************************************************************************* Williams/Midway Y/Z-unit system **************************************************************************/ #include "emu.h" #include "includes/midyunit.h" #include "screen.h" /* compile-time options */ #define LOG_DMA 0 /* DMAs are logged if the 'L' key is pressed */ /* constants for the DMA chip */ enum { DMA_COMMAND = 0, DMA_ROWBYTES, DMA_OFFSETLO, DMA_OFFSETHI, DMA_XSTART, DMA_YSTART, DMA_WIDTH, DMA_HEIGHT, DMA_PALETTE, DMA_COLOR }; /************************************* * * Video startup * *************************************/ VIDEO_START_MEMBER(midyunit_state,common) { /* allocate memory */ m_cmos_ram = std::make_unique((0x2000 * 4)/2); m_local_videoram = make_unique_clear(0x80000/2); m_pen_map = std::make_unique(65536); m_nvram->set_base(m_cmos_ram.get(), 0x2000 * 4); m_dma_timer = timer_alloc(TIMER_DMA); m_autoerase_line_timer = timer_alloc(TIMER_AUTOERASE_LINE); /* reset all the globals */ m_cmos_page = 0; m_autoerase_enable = 0; m_yawdim_dma = 0; /* reset DMA state */ memset(m_dma_register, 0, sizeof(m_dma_register)); memset(&m_dma_state, 0, sizeof(m_dma_state)); /* register for state saving */ save_item(NAME(m_autoerase_enable)); save_pointer(NAME(m_local_videoram), 0x80000/2); save_pointer(NAME(m_cmos_ram), (0x2000 * 4)/2); save_item(NAME(m_videobank_select)); save_item(NAME(m_dma_register)); } VIDEO_START_MEMBER(midyunit_state,midyunit_4bit) { int i; VIDEO_START_CALL_MEMBER(common); /* init for 4-bit */ for (i = 0; i < 65536; i++) m_pen_map[i] = ((i & 0xf000) >> 8) | (i & 0x000f); m_palette_mask = 0x00ff; } VIDEO_START_MEMBER(midyunit_state,midyunit_6bit) { int i; VIDEO_START_CALL_MEMBER(common); /* init for 6-bit */ for (i = 0; i < 65536; i++) m_pen_map[i] = ((i & 0xc000) >> 8) | (i & 0x0f3f); m_palette_mask = 0x0fff; } VIDEO_START_MEMBER(midyunit_state,mkyawdim) { VIDEO_START_CALL_MEMBER(midyunit_6bit); m_yawdim_dma = 1; } VIDEO_START_MEMBER(midyunit_state,midzunit) { int i; VIDEO_START_CALL_MEMBER(common); /* init for 8-bit */ for (i = 0; i < 65536; i++) m_pen_map[i] = i & 0x1fff; m_palette_mask = 0x1fff; } /************************************* * * Banked graphics ROM access * *************************************/ READ16_MEMBER(midyunit_state::midyunit_gfxrom_r) { offset *= 2; if (m_palette_mask == 0x00ff) return m_gfx_rom[offset] | (m_gfx_rom[offset] << 4) | (m_gfx_rom[offset + 1] << 8) | (m_gfx_rom[offset + 1] << 12); else return m_gfx_rom[offset] | (m_gfx_rom[offset + 1] << 8); } /************************************* * * Video/color RAM read/write * *************************************/ WRITE16_MEMBER(midyunit_state::midyunit_vram_w) { offset *= 2; if (m_videobank_select) { if (ACCESSING_BITS_0_7) m_local_videoram[offset] = (data & 0x00ff) | (m_dma_register[DMA_PALETTE] << 8); if (ACCESSING_BITS_8_15) m_local_videoram[offset + 1] = (data >> 8) | (m_dma_register[DMA_PALETTE] & 0xff00); } else { if (ACCESSING_BITS_0_7) m_local_videoram[offset] = (m_local_videoram[offset] & 0x00ff) | (data << 8); if (ACCESSING_BITS_8_15) m_local_videoram[offset + 1] = (m_local_videoram[offset + 1] & 0x00ff) | (data & 0xff00); } } READ16_MEMBER(midyunit_state::midyunit_vram_r) { offset *= 2; if (m_videobank_select) return (m_local_videoram[offset] & 0x00ff) | (m_local_videoram[offset + 1] << 8); else return (m_local_videoram[offset] >> 8) | (m_local_videoram[offset + 1] & 0xff00); } /************************************* * * Shift register read/write * *************************************/ TMS340X0_TO_SHIFTREG_CB_MEMBER(midyunit_state::to_shiftreg) { memcpy(shiftreg, &m_local_videoram[address >> 3], 2 * 512 * sizeof(uint16_t)); } TMS340X0_FROM_SHIFTREG_CB_MEMBER(midyunit_state::from_shiftreg) { memcpy(&m_local_videoram[address >> 3], shiftreg, 2 * 512 * sizeof(uint16_t)); } /************************************* * * Y/Z-unit control register * *************************************/ WRITE16_MEMBER(midyunit_state::midyunit_control_w) { /* * Narc system register * ------------------ * * | Bit | Use * --+-FEDCBA9876543210-+------------ * | xxxxxxxx-------- | 7 segment led on CPU board * | --------xx------ | CMOS page * | ----------x----- | - OBJ PAL RAM select * | -----------x---- | - autoerase enable * | ---------------- | - watchdog * */ if (ACCESSING_BITS_0_7) { /* CMOS page is bits 6-7 */ m_cmos_page = ((data >> 6) & 3) * 0x1000; /* video bank select is bit 5 */ m_videobank_select = (data >> 5) & 1; /* handle autoerase disable (bit 4) */ m_autoerase_enable = ((data & 0x10) == 0); } } /************************************* * * Palette handlers * *************************************/ WRITE16_MEMBER(midyunit_state::midyunit_paletteram_w) { int newword; COMBINE_DATA(&m_generic_paletteram_16[offset]); newword = m_generic_paletteram_16[offset]; m_palette->set_pen_color(offset & m_palette_mask, pal5bit(newword >> 10), pal5bit(newword >> 5), pal5bit(newword >> 0)); } /************************************* * * DMA drawing routines * *************************************/ void midyunit_state::dma_draw(uint16_t command) { struct dma_state_t &dma_state = m_dma_state; int dx = (command & 0x10) ? -1 : 1; int height = dma_state.height; int width = dma_state.width; uint8_t *base = m_gfx_rom; uint32_t offset = dma_state.offset >> 3; uint16_t pal = dma_state.palette; uint16_t color = pal | dma_state.color; int x, y; /* we only need the low 4 bits of the command */ command &= 0x0f; /* loop over the height */ for (y = 0; y < height; y++) { int tx = dma_state.xpos; int ty = dma_state.ypos; uint32_t o = offset; uint16_t *dest; /* determine Y position */ ty = (ty + y) & 0x1ff; offset += dma_state.rowbytes; /* determine destination pointer */ dest = &m_local_videoram[ty * 512]; /* check for overruns if they are relevant */ if (o >= 0x06000000 && command < 0x0c) continue; /* switch off the zero/non-zero options */ switch (command) { case 0x00: /* draw nothing */ break; case 0x01: /* draw only 0 pixels */ for (x = 0; x < width; x++, tx += dx) if (base[o++] == 0) dest[tx] = pal; break; case 0x02: /* draw only non-0 pixels */ for (x = 0; x < width; x++, tx += dx) { int pixel = base[o++]; if (pixel != 0) dest[tx] = pal | pixel; } break; case 0x03: /* draw all pixels */ for (x = 0; x < width; x++, tx += dx) dest[tx] = pal | base[o++]; break; case 0x04: /* color only 0 pixels */ case 0x05: /* color only 0 pixels */ for (x = 0; x < width; x++, tx += dx) if (base[o++] == 0) dest[tx] = color; break; case 0x06: /* color only 0 pixels, copy the rest */ case 0x07: /* color only 0 pixels, copy the rest */ for (x = 0; x < width; x++, tx += dx) { int pixel = base[o++]; dest[tx] = (pixel == 0) ? color : (pal | pixel); } break; case 0x08: /* color only non-0 pixels */ case 0x0a: /* color only non-0 pixels */ for (x = 0; x < width; x++, tx += dx) if (base[o++] != 0) dest[tx] = color; break; case 0x09: /* color only non-0 pixels, copy the rest */ case 0x0b: /* color only non-0 pixels, copy the rest */ for (x = 0; x < width; x++, tx += dx) { int pixel = base[o++]; dest[tx] = (pixel != 0) ? color : (pal | pixel); } break; case 0x0c: /* color all pixels */ case 0x0d: /* color all pixels */ case 0x0e: /* color all pixels */ case 0x0f: /* color all pixels */ for (x = 0; x < width; x++, tx += dx) dest[tx] = color; break; } } } /************************************* * * Timer callbacks * *************************************/ void midyunit_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_DMA: dma_callback(ptr, param); break; case TIMER_AUTOERASE_LINE: autoerase_line(ptr, param); break; default: throw emu_fatalerror("Unknown id in midyunit_state::device_timer"); } } TIMER_CALLBACK_MEMBER(midyunit_state::dma_callback) { m_dma_register[DMA_COMMAND] &= ~0x8000; /* tell the cpu we're done */ m_maincpu->set_input_line(0, ASSERT_LINE); } /************************************* * * DMA reader * *************************************/ READ16_MEMBER(midyunit_state::midyunit_dma_r) { return m_dma_register[offset]; } /************************************* * * DMA write handler * *************************************/ /* * DMA registers * ------------------ * * Register | Bit | Use * ----------+-FEDCBA9876543210-+------------ * 0 | x--------------- | trigger write (or clear if zero) * | ---184-1-------- | unknown * | ----------x----- | flip y * | -----------x---- | flip x * | ------------x--- | blit nonzero pixels as color * | -------------x-- | blit zero pixels as color * | --------------x- | blit nonzero pixels * | ---------------x | blit zero pixels * 1 | xxxxxxxxxxxxxxxx | width offset * 2 | xxxxxxxxxxxxxxxx | source address low word * 3 | xxxxxxxxxxxxxxxx | source address high word * 4 | xxxxxxxxxxxxxxxx | detination x * 5 | xxxxxxxxxxxxxxxx | destination y * 6 | xxxxxxxxxxxxxxxx | image columns * 7 | xxxxxxxxxxxxxxxx | image rows * 8 | xxxxxxxxxxxxxxxx | palette * 9 | xxxxxxxxxxxxxxxx | color */ WRITE16_MEMBER(midyunit_state::midyunit_dma_w) { struct dma_state_t &dma_state = m_dma_state; uint32_t gfxoffset; int command; /* blend with the current register contents */ COMBINE_DATA(&m_dma_register[offset]); /* only writes to DMA_COMMAND actually cause actions */ if (offset != DMA_COMMAND) return; /* high bit triggers action */ command = m_dma_register[DMA_COMMAND]; m_maincpu->set_input_line(0, CLEAR_LINE); if (!(command & 0x8000)) return; if (LOG_DMA) { if (machine().input().code_pressed(KEYCODE_L)) { logerror("----\n"); logerror("DMA command %04X: (xflip=%d yflip=%d)\n", command, (command >> 4) & 1, (command >> 5) & 1); logerror(" offset=%08X pos=(%d,%d) w=%d h=%d rb=%d\n", m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16), (int16_t)m_dma_register[DMA_XSTART], (int16_t)m_dma_register[DMA_YSTART], m_dma_register[DMA_WIDTH], m_dma_register[DMA_HEIGHT], (int16_t)m_dma_register[DMA_ROWBYTES]); logerror(" palette=%04X color=%04X\n", m_dma_register[DMA_PALETTE], m_dma_register[DMA_COLOR]); } } g_profiler.start(PROFILER_USER1); /* fill in the basic data */ dma_state.rowbytes = (int16_t)m_dma_register[DMA_ROWBYTES]; dma_state.xpos = (int16_t)m_dma_register[DMA_XSTART]; dma_state.ypos = (int16_t)m_dma_register[DMA_YSTART]; dma_state.width = m_dma_register[DMA_WIDTH]; dma_state.height = m_dma_register[DMA_HEIGHT]; dma_state.palette = m_dma_register[DMA_PALETTE] << 8; dma_state.color = m_dma_register[DMA_COLOR] & 0xff; /* determine the offset and adjust the rowbytes */ gfxoffset = m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16); if (command & 0x10) { if (!m_yawdim_dma) { gfxoffset -= (dma_state.width - 1) * 8; dma_state.rowbytes = (dma_state.rowbytes - dma_state.width + 3) & ~3; } else dma_state.rowbytes = (dma_state.rowbytes + dma_state.width + 3) & ~3; dma_state.xpos += dma_state.width - 1; } else dma_state.rowbytes = (dma_state.rowbytes + dma_state.width + 3) & ~3; /* apply Y clipping */ if (dma_state.ypos < 0) { dma_state.height -= -dma_state.ypos; dma_state.offset += (-dma_state.ypos * dma_state.rowbytes) << 3; dma_state.ypos = 0; } if (dma_state.ypos + dma_state.height > 512) dma_state.height = 512 - dma_state.ypos; /* apply X clipping */ if (!(command & 0x10)) { if (dma_state.xpos < 0) { dma_state.width -= -dma_state.xpos; dma_state.offset += -dma_state.xpos << 3; dma_state.xpos = 0; } if (dma_state.xpos + dma_state.width > 512) dma_state.width = 512 - dma_state.xpos; } else { if (dma_state.xpos >= 512) { dma_state.width -= dma_state.xpos - 511; dma_state.offset += (dma_state.xpos - 511) << 3; dma_state.xpos = 511; } if (dma_state.xpos - dma_state.width < 0) dma_state.width = dma_state.xpos; } /* determine the location and draw */ if (gfxoffset < 0x02000000) gfxoffset += 0x02000000; { dma_state.offset = gfxoffset - 0x02000000; dma_draw(command); } /* signal we're done */ m_dma_timer->adjust(attotime::from_nsec(41 * dma_state.width * dma_state.height)); g_profiler.stop(); } /************************************* * * Core refresh routine * *************************************/ TIMER_CALLBACK_MEMBER(midyunit_state::autoerase_line) { int scanline = param; if (m_autoerase_enable && scanline >= 0 && scanline < 510) memcpy(&m_local_videoram[512 * scanline], &m_local_videoram[512 * (510 + (scanline & 1))], 512 * sizeof(uint16_t)); } TMS340X0_SCANLINE_IND16_CB_MEMBER(midyunit_state::scanline_update) { uint16_t *src = &m_local_videoram[(params->rowaddr << 9) & 0x3fe00]; uint16_t *dest = &bitmap.pix16(scanline); int coladdr = params->coladdr << 1; int x; /* adjust the display address to account for ignored bits */ for (x = params->heblnk; x < params->hsblnk; x++) dest[x] = m_pen_map[src[coladdr++ & 0x1ff]]; /* handle autoerase on the previous line */ autoerase_line(nullptr, params->rowaddr - 1); /* if this is the last update of the screen, set a timer to clear out the final line */ /* (since we update one behind) */ if (scanline == screen.visible_area().max_y) m_autoerase_line_timer->adjust(screen.time_until_pos(scanline + 1), params->rowaddr); }