diff options
Diffstat (limited to 'docs/release/src/mame/video/avgdvg.cpp')
-rw-r--r-- | docs/release/src/mame/video/avgdvg.cpp | 1503 |
1 files changed, 1503 insertions, 0 deletions
diff --git a/docs/release/src/mame/video/avgdvg.cpp b/docs/release/src/mame/video/avgdvg.cpp new file mode 100644 index 00000000000..00746047f97 --- /dev/null +++ b/docs/release/src/mame/video/avgdvg.cpp @@ -0,0 +1,1503 @@ +// license:BSD-3-Clause +// copyright-holders:Mathis Rosenhauer +// thanks-to:Eric Smith, Brad Oliver, Bernd Wiebelt, Aaron Giles, Andrew Caldwell +/************************************************************************* + + avgdvg.c: Atari DVG and AVG + + Some parts of this code are based on the original version by Eric + Smith, Brad Oliver, Bernd Wiebelt, Aaron Giles, Andrew Caldwell + + The schematics and Jed Margolin's article on Vector Generators were + very helpful in understanding the hardware. + + +**************************************************************************/ + +#include "emu.h" +#include "avgdvg.h" + +#include "screen.h" + + +/************************************* + * + * Macros and defines + * + *************************************/ + +#define MASTER_CLOCK (12096000) +#define VGSLICE (10000) +#define VGVECTOR 0 +#define VGCLIP 1 + + +/************************************* + * + * Flipping + * + *************************************/ + +void avgdvg_device_base::apply_flipping(int &x, int &y) const +{ + if (m_flip_x) + x += (m_xcenter - x) << 1; + if (m_flip_y) + y += (m_ycenter - y) << 1; +} + + +/************************************* + * + * Vector buffering + * + *************************************/ + +void avgdvg_device_base::vg_flush() +{ + int cx0 = 0, cy0 = 0, cx1 = 0x5000000, cy1 = 0x5000000; + int i = 0; + + while (m_vectbuf[i].status == VGCLIP) + i++; + int xs = m_vectbuf[i].x; + int ys = m_vectbuf[i].y; + + for (i = 0; i < m_nvect; i++) + { + if (m_vectbuf[i].status == VGVECTOR) + { + int xe = m_vectbuf[i].x; + int ye = m_vectbuf[i].y; + int x0 = xs, y0 = ys, x1 = xe, y1 = ye; + + xs = xe; + ys = ye; + + if ((x0 < cx0 && x1 < cx0) || (x0 > cx1 && x1 > cx1)) + continue; + + if (x0 < cx0) + { + y0 += s64(cx0 - x0) * s64(y1 - y0) / (x1 - x0); + x0 = cx0; + } + else if (x0 > cx1) + { + y0 += s64(cx1 - x0) * s64(y1 - y0) / (x1 - x0); + x0 = cx1; + } + if (x1 < cx0) + { + y1 += s64(cx0 - x1) * s64(y1 - y0) / (x1 - x0); + x1 = cx0; + } + else if (x1 > cx1) + { + y1 += s64(cx1 - x1) * s64(y1 - y0) / (x1 - x0); + x1 = cx1; + } + + if ((y0 < cy0 && y1 < cy0) || (y0 > cy1 && y1 > cy1)) + continue; + + if (y0 < cy0) + { + x0 += s64(cy0 - y0) * s64(x1 - x0) / (y1 - y0); + y0 = cy0; + } + else if (y0 > cy1) + { + x0 += s64(cy1 - y0) * s64(x1 - x0) / (y1 - y0); + y0 = cy1; + } + if (y1 < cy0) + { + x1 += s64(cy0 - y1) * s64(x1 - x0) / (y1 - y0); + y1 = cy0; + } + else if (y1 > cy1) + { + x1 += s64(cy1 - y1) * s64(x1 - x0) / (y1 - y0); + y1 = cy1; + } + + m_vector->add_point(x0, y0, m_vectbuf[i].color, 0); + m_vector->add_point(x1, y1, m_vectbuf[i].color, m_vectbuf[i].intensity); + } + + if (m_vectbuf[i].status == VGCLIP) + { + cx0 = m_vectbuf[i].x; + cy0 = m_vectbuf[i].y; + cx1 = m_vectbuf[i].arg1; + cy1 = m_vectbuf[i].arg2; + using std::swap; + if (cx0 > cx1) + swap(cx0, cx1); + if (cy0 > cy1) + swap(cy0, cy1); + } + } + + m_nvect = 0; +} + +void avgdvg_device_base::vg_add_point_buf(int x, int y, rgb_t color, int intensity) +{ + if (m_nvect < MAXVECT) + { + m_vectbuf[m_nvect].status = VGVECTOR; + m_vectbuf[m_nvect].x = x; + m_vectbuf[m_nvect].y = y; + m_vectbuf[m_nvect].color = color; + m_vectbuf[m_nvect].intensity = intensity; + m_nvect++; + } +} + +void avgdvg_device_base::vg_add_clip(int xmin, int ymin, int xmax, int ymax) +{ + if (m_nvect < MAXVECT) + { + m_vectbuf[m_nvect].status = VGCLIP; + m_vectbuf[m_nvect].x = xmin; + m_vectbuf[m_nvect].y = ymin; + m_vectbuf[m_nvect].arg1 = xmax; + m_vectbuf[m_nvect].arg2 = ymax; + m_nvect++; + } +} + + +/************************************* + * + * DVG handler functions + * + *************************************/ + +void dvg_device::update_databus() // dvg_data +{ + // DVG uses low bit of state for address + m_data = m_memspace->read_byte(m_membase + (m_pc << 1) + (m_state_latch & 1)); +} + +u8 dvg_device::state_addr() // dvg_state_addr +{ + u8 addr = ((((m_state_latch >> 4) ^ 1) & 1) << 7) | (m_state_latch & 0xf); + + if (OP3()) + addr |= ((m_op & 7) << 4); + + return addr; +} + +int dvg_device::handler_0() // dvg_dmapush +{ + if (!OP0()) + { + m_sp = (m_sp + 1) & 0xf; + m_stack[m_sp & 3] = m_pc; + } + return 0; +} + +int dvg_device::handler_1() // dvg_dmald +{ + if (OP0()) + { + m_pc = m_stack[m_sp & 3]; + m_sp = (m_sp - 1) & 0xf; + } + else + { + m_pc = m_dvy; + } + + return 0; +} + +void dvg_device::dvg_draw_to(int x, int y, int intensity) +{ + apply_flipping(x, y); + + if (!((x | y) & 0x400)) + vg_add_point_buf( + (m_xmin + x - 512) << 16, + (m_ymin + 512 - y) << 16, + vector_device::color111(7), + intensity << 4); +} + +int dvg_device::handler_2() //dvg_gostrobe +{ + int scale; + + if (m_op == 0xf) + { + scale = (m_scale + + (((m_dvy & 0x800) >> 11) + | (((m_dvx & 0x800) ^ 0x800) >> 10) + | ((m_dvx & 0x800) >> 9))) & 0xf; + + m_dvy &= 0xf00; + m_dvx &= 0xf00; + } + else + { + scale = (m_scale + m_op) & 0xf; + } + + int fin = 0xfff - (((2 << scale) & 0x7ff) ^ 0xfff); + + // Count up or down + const int dx = (m_dvx & 0x400) ? -1 : +1; + const int dy = (m_dvy & 0x400) ? -1 : +1; + + // Scale factor for rate multipliers + const int mx = (m_dvx << 2) & 0xfff; + const int my = (m_dvy << 2) & 0xfff; + + const int cycles = 8 * fin; + int c = 0; + + while (fin--) + { + /* + * The 7497 Bit Rate Multiplier is a 6 bit counter with + * clever decoding of output bits to perform the following + * operation: + * + * fout = m/64 * fin + * + * where fin is the input frequency, fout is the output + * frequency and m is a factor at the input pins. Output + * pulses are more or less evenly spaced so we get straight + * lines. The DVG has two cascaded 7497s for each coordinate. + */ + + int countx = 0; + int county = 0; + + for (int bit = 0; bit < 12; bit++) + { + if ((c & ((1 << (bit+1)) - 1)) == ((1 << bit) - 1)) + { + if (mx & (1 << (11 - bit))) + countx = 1; + + if (my & (1 << (11 - bit))) + county = 1; + } + } + + c = (c + 1) & 0xfff; + + /* + * Since x- and y-counters always hold the correct count + * wrt. to each other, we can do clipping exactly like the + * hardware does. That is, as soon as any counter's bit 10 + * changes to high, we finish the vector. If bit 10 changes + * from high to low, we start a new vector. + */ + + if (countx) + { + // Is y valid and x entering or leaving the valid range? + if (!(m_ypos & 0x400) && ((m_xpos ^ (m_xpos + dx)) & 0x400)) + { + if ((m_xpos + dx) & 0x400) // We are leaving the valid range + dvg_draw_to(m_xpos, m_ypos, m_intensity); + else // We are entering the valid range + dvg_draw_to((m_xpos + dx) & 0xfff, m_ypos, 0); + } + m_xpos = (m_xpos + dx) & 0xfff; + } + + if (county) + { + if (!(m_xpos & 0x400) && ((m_ypos ^ (m_ypos + dy)) & 0x400)) + { + if (!(m_xpos & 0x400)) + { + if ((m_ypos + dy) & 0x400) + dvg_draw_to(m_xpos, m_ypos, m_intensity); + else + dvg_draw_to(m_xpos, (m_ypos + dy) & 0xfff, 0); + } + } + m_ypos = (m_ypos + dy) & 0xfff; + } + } + + dvg_draw_to(m_xpos, m_ypos, m_intensity); + + return cycles; +} + +int dvg_device::handler_3() // dvg_haltstrobe +{ + m_halt = OP0(); + + if (!OP0()) + { + m_xpos = m_dvx & 0xfff; + m_ypos = m_dvy & 0xfff; + dvg_draw_to(m_xpos, m_ypos, 0); + } + return 0; +} + +int dvg_device::handler_7() // dvg_latch3 +{ + m_dvx = (m_dvx & 0xff) | ((m_data & 0xf) << 8); + m_intensity = m_data >> 4; + return 0; +} + +int dvg_device::handler_6() // dvg_latch2 +{ + m_dvx &= 0xf00; + if (m_op != 0xf) + m_dvx = (m_dvx & 0xf00) | m_data; + + if (OP1() && OP3()) + m_scale = m_intensity; + + m_pc++; + return 0; +} + +int dvg_device::handler_5() // dvg_latch1 +{ + m_dvy = (m_dvy & 0xff) | ((m_data & 0xf) << 8); + m_op = m_data >> 4; + + if (m_op == 0xf) + { + m_dvx &= 0xf00; + m_dvy &= 0xf00; + } + + return 0; +} + +int dvg_device::handler_4() // dvg_latch0 +{ + m_dvy &= 0xf00; + if (m_op == 0xf) + handler_7(); //dvg_latch3 + else + m_dvy = (m_dvy & 0xf00) | m_data; + + m_pc++; + return 0; +} + +void dvg_device::vggo() // dvg_vggo +{ + m_dvy = 0; + m_op = 0; +} + +void dvg_device::vgrst() // dvg_vgrst +{ + m_state_latch = 0; + m_dvy = 0; + m_op = 0; +} + + +/******************************************************************** + * + * AVG handler functions + * + * AVG is in many ways different from DVG. The only thing they have + * in common is the state machine approach. There are small + * differences among the AVGs, mostly related to color and vector + * clipping. + * + *******************************************************************/ + +u8 avg_device::state_addr() // avg_state_addr +{ + return (((m_state_latch >> 4) ^ 1) << 7) + | (m_op << 4) + | (m_state_latch & 0xf); +} + + +void avg_device::update_databus() // avg_data +{ + m_data = m_memspace->read_byte(m_membase + (m_pc ^ 1)); +} + +void avg_device::vggo() // avg_vggo +{ + m_pc = 0; + m_sp = 0; +} + + +void avg_device::vgrst() // avg_vgrst +{ + m_state_latch = 0; + m_bin_scale = 0; + m_scale = 0; + m_color = 0; +} + +int avg_device::handler_0() // avg_latch0 +{ + m_dvy = (m_dvy & 0x1f00) | m_data; + m_pc++; + + return 0; +} + +int avg_device::handler_1() // avg_latch1 +{ + m_dvy12 = (m_data >> 4) & 1; + m_op = m_data >> 5; + + m_int_latch = 0; + m_dvy = (m_dvy12 << 12) | ((m_data & 0xf) << 8); + m_dvx = 0; + m_pc++; + + return 0; +} + +int avg_device::handler_2() // avg_latch2 +{ + m_dvx = (m_dvx & 0x1f00) | m_data; + m_pc++; + + return 0; +} + +int avg_device::handler_3() // avg_latch3 +{ + m_int_latch = m_data >> 4; + m_dvx = ((m_int_latch & 1) << 12) + | ((m_data & 0xf) << 8) + | (m_dvx & 0xff); + m_pc++; + + return 0; +} + +int avg_device::handler_4() // avg_strobe0 +{ + if (OP0()) + { + m_stack[m_sp & 3] = m_pc; + } + else + { + /* + * Normalization is done to get roughly constant deflection + * speeds. See Jed's essay why this is important. In addition + * to the intensity and overall time saving issues it is also + * needed to avoid accumulation of DAC errors. The X/Y DACs + * only use bits 3-12. The normalization ensures that the + * first three bits hold no important information. + * + * The circuit doesn't check for dvx=dvy=0. In this case + * shifting goes on as long as VCTR, SCALE and CNTR are + * low. We cut off after 16 shifts. + */ + int i = 0; + while ((((m_dvy ^ (m_dvy << 1)) & 0x1000) == 0) + && (((m_dvx ^ (m_dvx << 1)) & 0x1000) == 0) + && (i++ < 16)) + { + m_dvy = (m_dvy & 0x1000) | ((m_dvy << 1) & 0x1fff); + m_dvx = (m_dvx & 0x1000) | ((m_dvx << 1) & 0x1fff); + m_timer >>= 1; + m_timer |= 0x4000 | (OP1() << 7); + } + + if (OP1()) + m_timer &= 0xff; + } + + return 0; +} + + +int avg_device::avg_common_strobe1() +{ + if (OP2()) + { + if (OP1()) + m_sp = (m_sp - 1) & 0xf; + else + m_sp = (m_sp + 1) & 0xf; + } + return 0; +} + +int avg_device::handler_5() // avg_strobe1 +{ + if (!OP2()) + { + for (int i = m_bin_scale; i > 0; i--) + { + m_timer >>= 1; + m_timer |= 0x4000 | (OP1() << 7); + } + if (OP1()) + m_timer &= 0xff; + } + + return avg_common_strobe1(); +} + + +int avg_device::avg_common_strobe2() +{ + if (OP2()) + { + if (OP0()) + { + m_pc = m_dvy << 1; + + if (m_dvy == 0) + { + /* + * Tempest and Quantum keep the AVG in an endless + * loop. I.e. at one point the AVG jumps to address 0 + * and starts over again. The main CPU updates vector + * RAM while AVG is running. The hardware takes care + * that the AVG doesn't read vector RAM while the CPU + * writes to it. Usually we wait until the AVG stops + * (halt flag) and then draw all vectors at once. This + * doesn't work for Tempest and Quantum so we wait for + * the jump to zero and draw vectors then. + * + * Note that this has nothing to do with the real hardware + * because for a vector monitor it is perfectly okay to + * have the AVG drawing all the time. In the emulation we + * somehow have to divide the stream of vectors into + * 'frames'. + */ + + m_vector->clear_list(); + vg_flush(); + } + } + else + { + m_pc = m_stack[m_sp & 3]; + } + } + else + { + if (m_dvy12) + { + m_scale = m_dvy & 0xff; + m_bin_scale = (m_dvy >> 8) & 7; + } + } + + return 0; +} + +int avg_device::handler_6() // avg_strobe2 +{ + if (!OP2() && !m_dvy12) + { + m_color = m_dvy & 0x7; + m_intensity = (m_dvy >> 4) & 0xf; + } + + return avg_common_strobe2(); +} + +int avg_device::avg_common_strobe3() +{ + int cycles = 0; + + m_halt = OP0(); + + if (!OP0() && !OP2()) + { + if (OP1()) + { + cycles = 0x100 - (m_timer & 0xff); + } + else + { + cycles = 0x8000 - m_timer; + } + m_timer = 0; + + m_xpos += ((((m_dvx >> 3) ^ m_xdac_xor) - 0x200) * cycles * (m_scale ^ 0xff)) >> 4; + m_ypos -= ((((m_dvy >> 3) ^ m_ydac_xor) - 0x200) * cycles * (m_scale ^ 0xff)) >> 4; + } + + if (OP2()) + { + cycles = 0x8000 - m_timer; + m_timer = 0; + m_xpos = m_xcenter; + m_ypos = m_ycenter; + vg_add_point_buf(m_xpos, m_ypos, 0, 0); + } + + return cycles; +} + +int avg_device::handler_7() // avg_strobe3 +{ + const int cycles = avg_common_strobe3(); + + if (!OP0() && !OP2()) + { + vg_add_point_buf( + m_xpos, + m_ypos, + vector_device::color111(m_color), + (((m_int_latch >> 1) == 1) ? m_intensity : m_int_latch & 0xe) << 4); + } + + return cycles; +} + +/************************************* + * + * Tempest handler functions + * + *************************************/ + +int avg_tempest_device::handler_6() // tempest_strobe2 +{ + if (!OP2() && !m_dvy12) + { + // Contrary to previous documentation in MAME, Tempest does not have the m_enspkl bit. + if (m_dvy & 0x800) + m_color = m_dvy & 0xf; + else + m_intensity = (m_dvy >> 4) & 0xf; + } + + return avg_common_strobe2(); +} + +int avg_tempest_device::handler_7() // tempest_strobe3 +{ + const int cycles = avg_common_strobe3(); + + if (!OP0() && !OP2()) + { + const u8 data = m_colorram[m_color]; + const u8 bit3 = BIT(~data, 3); + const u8 bit2 = BIT(~data, 2); + const u8 bit1 = BIT(~data, 1); + const u8 bit0 = BIT(~data, 0); + + const u8 r = bit1 * 0xf3 + bit0 * 0x0c; + const u8 g = bit3 * 0xf3; + const u8 b = bit2 * 0xf3; + + int x = m_xpos; + int y = m_ypos; + + apply_flipping(x, y); + + vg_add_point_buf( + y - m_ycenter + m_xcenter, + x - m_xcenter + m_ycenter, + rgb_t(r, g, b), + (((m_int_latch >> 1) == 1) ? m_intensity : m_int_latch & 0xe) << 4); + } + + return cycles; +} + +#if 0 +void avg_tempest_device::vggo() // tempest_vggo +{ + m_pc = 0; + m_sp = 0; + /* + * Tempest and Quantum trigger VGGO from time to time even though + * the VG runs in an endless loop for these games (see + * avg_common_strobe2). If we don't discard all vectors in the + * current buffer at this point, the screen starts flickering. + */ + m_nvect = 0; +} +#endif + +/************************************* +* +* Mhavoc handler functions +* +*************************************/ + +int avg_mhavoc_device::handler_1() // mhavoc_latch1 +{ + // Major Havoc just has ymin clipping + + if (!m_lst) + vg_add_clip(0, m_ypos, m_xmax << 16, m_ymax << 16); + m_lst = 1; + + return avg_device::handler_1(); //avg_latch1() +} + +int avg_mhavoc_device::handler_6() // mhavoc_strobe2 +{ + if (!OP2()) + { + if (m_dvy12) + { + if (m_dvy & 0x800) + m_lst = 0; + } + else + { + m_color = m_dvy & 0xf; + m_intensity = (m_dvy >> 4) & 0xf; + m_map = (m_dvy >> 8) & 0x3; + + //this is required so that the Alpha CPU can read active paged VROM + if (memregion("bank3")) + machine().root_device().membank("bank3")->set_entry(m_map); + + if (m_dvy & 0x800) + { + m_enspkl = 1; + // sparkle LFSR bits 4,5,6 here come from alpha CPU address bus bits 0,1,2, they're not truly random. + m_spkl_shift = bitswap<4>(m_dvy, 0, 1, 2, 3) | ((machine().rand() & 0x7) << 4); + } + else + { + m_enspkl = 0; + } + + // Major Havoc can do X-flipping by inverting the DAC input + if (m_dvy & 0x400) + m_xdac_xor = 0x1ff; + else + m_xdac_xor = 0x200; + } + } + + return avg_common_strobe2(); +} + +int avg_mhavoc_device::handler_7() // mhavoc_strobe3 +{ + m_halt = OP0(); + int cycles = 0; + + if (!OP0() && !OP2()) + { + if (OP1()) + { + cycles = 0x100 - (m_timer & 0xff); + } + else + { + cycles = 0x8000 - m_timer; + } + m_timer = 0; + const int dx = ((((m_dvx >> 3) ^ m_xdac_xor) - 0x200) * (m_scale ^ 0xff)); + const int dy = ((((m_dvy >> 3) ^ m_ydac_xor) - 0x200) * (m_scale ^ 0xff)); + + if (m_enspkl) + { + for (int i = 0; i < cycles / 8; i++) + { + m_xpos += dx / 2; + m_ypos -= dy / 2; + const u8 data = m_colorram[0xf + bitswap<4>(m_spkl_shift, 0, 2, 4, 6)]; + const u8 bit3 = BIT(~data, 3); + const u8 bit2 = BIT(~data, 2); + const u8 bit1 = BIT(~data, 1); + const u8 bit0 = BIT(~data, 0); + const u8 r = bit3 * 0xcb + bit2 * 0x34; + const u8 g = bit1 * 0xcb; + const u8 b = bit0 * 0xcb; + + int x = m_xpos; + int y = m_ypos; + apply_flipping(x, y); + + vg_add_point_buf( + x, + y, + rgb_t(r, g, b), + (((m_int_latch >> 1) == 1) ? m_intensity : m_int_latch & 0xe) << 4); + m_spkl_shift = (BIT(m_spkl_shift, 6) ^ BIT(m_spkl_shift, 5) ^ 1) | (m_spkl_shift << 1); + + if ((m_spkl_shift & 0x7f) == 0x7f) + m_spkl_shift = 0; + } + } + else + { + m_xpos += (dx * cycles) >> 4; + m_ypos -= (dy * cycles) >> 4; + const u8 data = m_colorram[m_color]; + + const u8 bit3 = BIT(~data, 3); + const u8 bit2 = BIT(~data, 2); + const u8 bit1 = BIT(~data, 1); + const u8 bit0 = BIT(~data, 0); + const u8 r = bit3 * 0xcb + bit2 * 0x34; + const u8 g = bit1 * 0xcb; + const u8 b = bit0 * 0xcb; + + int x = m_xpos; + int y = m_ypos; + apply_flipping(x, y); + + vg_add_point_buf( + x, + y, + rgb_t(r, g, b), + (((m_int_latch >> 1) == 1) ? m_intensity : m_int_latch & 0xe) << 4); + } + } + + if (OP2()) + { + cycles = 0x8000 - m_timer; + m_timer = 0; + m_xpos = m_xcenter; + m_ypos = m_ycenter; + vg_add_point_buf(m_xpos, m_ypos, 0, 0); + } + + return cycles; +} + +void avg_mhavoc_device::update_databus() // mhavoc_data +{ + if (m_pc & 0x2000) + m_data = m_bank_region[(m_map << 13) | ((m_pc ^ 1) & 0x1fff)]; + else + m_data = m_memspace->read_byte(m_membase + (m_pc ^ 1)); +} + +void avg_mhavoc_device::vgrst() // mhavoc_vgrst +{ + avg_device::vgrst(); // avg_vgrst + m_enspkl = 0; +} + + +/************************************* + * + * Starwars handler functions + * + *************************************/ + +void avg_starwars_device::update_databus() // starwars_data +{ + // Avoid interfering with the slapstic + auto dis = machine().disable_side_effects(); + + m_data = m_memspace->read_byte(m_membase + m_pc); +} + +int avg_starwars_device::handler_6() // starwars_strobe2 +{ + if (!OP2() && !m_dvy12) + { + m_intensity = m_dvy & 0xff; + m_color = (m_dvy >> 8) & 0xf; + } + + return avg_common_strobe2(); +} + +int avg_starwars_device::handler_7() // starwars_strobe3 +{ + const int cycles = avg_common_strobe3(); + + if (!OP0() && !OP2()) + { + vg_add_point_buf( + m_xpos, + m_ypos, + vector_device::color111(m_color), + ((m_int_latch >> 1) * m_intensity) >> 3); + } + + return cycles; +} + +/************************************* +* +* Quantum handler functions +* +*************************************/ + +void avg_quantum_device::update_databus() // quantum_data +{ + m_data = m_memspace->read_word(m_membase + m_pc); +} + +void avg_quantum_device::vggo() // tempest_vggo +{ + m_pc = 0; + m_sp = 0; + /* + * Tempest and Quantum trigger VGGO from time to time even though + * the VG runs in an endless loop for these games (see + * avg_common_strobe2). If we don't discard all vectors in the + * current buffer at this point, the screen starts flickering. + */ + m_nvect = 0; +} + +int avg_quantum_device::handler_0() // quantum_st2st3 +{ + /* Quantum doesn't decode latch0 or latch2 but ST2 and ST3 are fed + * into the address controller which increments the PC + */ + m_pc++; + return 0; +} + +int avg_quantum_device::handler_1() // quantum_latch1 +{ + m_dvy = m_data & 0x1fff; + m_dvy12 = (m_data >> 12) & 1; + m_op = m_data >> 13; + + m_int_latch = 0; + m_dvx = 0; + m_pc++; + + return 0; +} + +int avg_quantum_device::handler_2() // quantum_st2st3 +{ + /* Quantum doesn't decode latch0 or latch2 but ST2 and ST3 are fed + * into the address controller which increments the PC + */ + m_pc++; + return 0; +} + +int avg_quantum_device::handler_3() // quantum_latch3 +{ + m_int_latch = m_data >> 12; + m_dvx = m_data & 0xfff; + m_pc++; + + return 0; +} + + +int avg_quantum_device::handler_4() // quantum_strobe0 +{ + if (OP0()) + { + m_stack[m_sp & 3] = m_pc; + } + else + { + // Quantum normalizes to 12 bit + int i = 0; + while ((((m_dvy ^ (m_dvy << 1)) & 0x800) == 0) + && (((m_dvx ^ (m_dvx << 1)) & 0x800) == 0) + && (i++ < 16)) + { + m_dvy = (m_dvy << 1) & 0xfff; + m_dvx = (m_dvx << 1) & 0xfff; + m_timer >>= 1; + m_timer |= 0x2000; + } + } + + return 0; +} + +int avg_quantum_device::handler_5() // quantum_strobe1 +{ + if (!OP2()) + { + for (int i = m_bin_scale; i > 0; i--) + { + m_timer >>= 1; + m_timer |= 0x2000; + } + } + + return avg_common_strobe1(); +} + +int avg_quantum_device::handler_6() // quantum_strobe2 +{ + if (!OP2() && !m_dvy12 && (m_dvy & 0x800)) + { + m_color = m_dvy & 0xf; + m_intensity = (m_dvy >> 4) & 0xf; + } + + return avg_common_strobe2(); +} + +int avg_quantum_device::handler_7() // quantum_strobe3 +{ + int cycles = 0; + + m_halt = OP0(); + + if (!OP0() && !OP2()) + { + const u16 data = m_colorram[m_color]; + const u8 bit3 = BIT(~data, 3); + const u8 bit2 = BIT(~data, 2); + const u8 bit1 = BIT(~data, 1); + const u8 bit0 = BIT(~data, 0); + + const u8 g = bit1 * 0xaa + bit0 * 0x54; + const u8 b = bit2 * 0xce; + const u8 r = bit3 * 0xce; + + cycles = 0x4000 - m_timer; + m_timer = 0; + + m_xpos += (((((m_dvx & 0xfff) >> 2) ^ m_xdac_xor) - 0x200) * cycles * (m_scale ^ 0xff)) >> 4; + m_ypos -= (((((m_dvy & 0xfff) >> 2) ^ m_ydac_xor) - 0x200) * cycles * (m_scale ^ 0xff)) >> 4; + + int x = m_xpos; + int y = m_ypos; + + apply_flipping(x, y); + + vg_add_point_buf( + y - m_ycenter + m_xcenter, + x - m_xcenter + m_ycenter, + rgb_t(r, g, b), + ((m_int_latch == 2) ? m_intensity : m_int_latch) << 4); + } + if (OP2()) + { + cycles = 0x4000 - m_timer; + m_timer = 0; + m_xpos = m_xcenter; + m_ypos = m_ycenter; + vg_add_point_buf(m_xpos, m_ypos, 0, 0); + } + + return cycles; +} + +/************************************* +* +* Bzone handler functions +* +*************************************/ +int avg_bzone_device::handler_1() // bzone_latch1 +{ + /* + * Battle Zone has clipping hardware. We need to remember the + * position of the beam when the analog switches hst or lst get + * turned off. + */ + + if (!m_hst) + { + m_clipx_max = m_xpos; + m_clipy_min = m_ypos; + } + + if (!m_lst) + { + m_clipx_min = m_xpos; + m_clipy_max = m_ypos; + } + + if (!m_lst || !m_hst) + vg_add_clip(m_clipx_min, m_clipy_min, m_clipx_max, m_clipy_max); + m_lst = m_hst = 1; + + return avg_device::handler_1(); // avg_latch1() +} + + +int avg_bzone_device::handler_6() // bzone_strobe2 +{ + if (!OP2() && !m_dvy12) + { + m_intensity = (m_dvy >> 4) & 0xf; + + if (!(m_dvy & 0x400)) + { + m_lst = m_dvy & 0x200; + m_hst = m_lst ^ 0x200; + /* + * If izblank is true the zblank signal gets + * inverted. This behaviour can't be handled with the + * clipping we have right now. Battle Zone doesn't seem to + * invert zblank so it's no issue. + */ + m_izblank = m_dvy & 0x100; + } + } + return avg_common_strobe2(); +} + + +int avg_bzone_device::handler_7() // bzone_strobe3 +{ + // Battle Zone is B/W + const int cycles = avg_common_strobe3(); + + if (!OP0() && !OP2()) + { + vg_add_point_buf( + m_xpos, + m_ypos, + vector_device::color111(7), + (((m_int_latch >> 1) == 1) ? m_intensity : m_int_latch & 0xe) << 4); + } + + return cycles; +} + + +/************************************* + * + * halt functions + * + *************************************/ + +void avgdvg_device_base::vg_set_halt(int dummy) +{ + m_halt = dummy; + m_sync_halt = dummy; +} + +TIMER_CALLBACK_MEMBER(avgdvg_device_base::vg_set_halt_callback) +{ + vg_set_halt(param); +} + + +/******************************************************************** + * + * State Machine + * + * The state machine is a 256x4 bit PROM connected to a latch. The + * address of the next state is generated from the latched previous + * state, an op code and the halt flag. Op codes come from vector + * RAM/ROM. The state machine is clocked with 1.5 MHz. Three bits of + * the state are decoded and used to trigger various parts of the + * hardware. + * + *******************************************************************/ + +TIMER_CALLBACK_MEMBER(avgdvg_device_base::run_state_machine) +{ + int cycles = 0; + + while (cycles < VGSLICE) + { + // Get next state + m_state_latch = (m_state_latch & 0x10) | (m_prom[state_addr()] & 0xf); + + if (ST3()) + { + // Read vector RAM/ROM + update_databus(); + + // Decode state and call the corresponding handler + switch (m_state_latch & 7) + { + case 0 : cycles += handler_0(); break; + case 1 : cycles += handler_1(); break; + case 2 : cycles += handler_2(); break; + case 3 : cycles += handler_3(); break; + case 4 : cycles += handler_4(); break; + case 5 : cycles += handler_5(); break; + case 6 : cycles += handler_6(); break; + case 7 : cycles += handler_7(); break; + } + } + + // If halt flag was set, let CPU catch up before we make halt visible + if (m_halt && !(m_state_latch & 0x10)) + m_vg_halt_timer->adjust(attotime::from_hz(MASTER_CLOCK) * cycles, 1); + + m_state_latch = (m_halt << 4) | (m_state_latch & 0xf); + cycles += 8; + } + + m_vg_run_timer->adjust(attotime::from_hz(MASTER_CLOCK) * cycles); +} + + +/************************************* + * + * VG halt/vggo + * + ************************************/ + +READ_LINE_MEMBER(avgdvg_device_base::done_r) +{ + return m_sync_halt ? 1 : 0; +} + +void avgdvg_device_base::go_w(u8 data) +{ + vggo(); + + if (m_sync_halt && (m_nvect > 10)) + { + /* + * This is a good time to start a new frame. Major Havoc + * sometimes sets VGGO after a very short vector list. That's + * why we ignore frames with less than 10 vectors. + */ + m_vector->clear_list(); + } + vg_flush(); + + vg_set_halt(0); + m_vg_run_timer->adjust(attotime::zero); +} + +void avgdvg_device_base::go_word_w(u16 data) +{ + go_w(data); +} + + +/************************************* + * + * Reset + * + ************************************/ + +void avgdvg_device_base::reset_w(u8 data) +{ + vgrst(); + vg_set_halt(1); +} + +void avgdvg_device_base::reset_word_w(u16 data) +{ + reset_w(data); +} + +/************************************* + * + * Vector generator init + * + ************************************/ + +void avgdvg_device_base::device_start() +{ + if (!m_vector->started()) + throw device_missing_dependencies(); + + m_vg_halt_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(avgdvg_device_base::vg_set_halt_callback), this)); + m_vg_run_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(avgdvg_device_base::run_state_machine), this)); + + m_flip_x = m_flip_y = false; + + save_item(NAME(m_pc)); + save_item(NAME(m_sp)); + save_item(NAME(m_dvx)); + save_item(NAME(m_dvy)); + save_item(NAME(m_stack)); + save_item(NAME(m_data)); + save_item(NAME(m_state_latch)); + save_item(NAME(m_scale)); + save_item(NAME(m_intensity)); + save_item(NAME(m_op)); + save_item(NAME(m_halt)); + save_item(NAME(m_sync_halt)); + save_item(NAME(m_xpos)); + save_item(NAME(m_ypos)); + + save_item(NAME(m_flip_x)); + save_item(NAME(m_flip_y)); +} + +void dvg_device::device_start() +{ + avgdvg_device_base::device_start(); + + const rectangle &visarea = m_vector->screen().visible_area(); + + m_xmin = visarea.min_x; + m_ymin = visarea.min_y; + + m_xcenter = 512; + m_ycenter = 512; +} + +void avg_device::device_start() +{ + avgdvg_device_base::device_start(); + + const rectangle &visarea = m_vector->screen().visible_area(); + + m_xmin = visarea.min_x; + m_ymin = visarea.min_y; + m_xmax = visarea.max_x; + m_ymax = visarea.max_y; + + m_xcenter = ((m_xmax - m_xmin) / 2) << 16; + m_ycenter = ((m_ymax - m_ymin) / 2) << 16; + + m_dvy12 = 0; + m_timer = 0; + m_int_latch = 0; + + m_bin_scale = 0; + m_color = 0; + + /* + * The x and y DACs use 10 bit of the counter values which are in + * two's complement representation. The DAC input is xored with + * 0x200 to convert the value to unsigned. + */ + m_xdac_xor = 0x200; + m_ydac_xor = 0x200; + + save_item(NAME(m_dvy12)); + save_item(NAME(m_timer)); + save_item(NAME(m_int_latch)); + save_item(NAME(m_bin_scale)); + save_item(NAME(m_color)); + save_item(NAME(m_xdac_xor)); + save_item(NAME(m_ydac_xor)); +} + +void avg_mhavoc_device::device_start() +{ + avg_device::device_start(); + + m_enspkl = 0; + m_spkl_shift = 0; + m_map = 0; + + m_lst = 0; + + save_item(NAME(m_enspkl)); + save_item(NAME(m_spkl_shift)); + save_item(NAME(m_map)); + save_item(NAME(m_lst)); +} + +void avg_bzone_device::device_start() +{ + avg_device::device_start(); + + m_hst = 0; + m_lst = 0; + m_izblank = 0; + + m_clipx_min = 0; + m_clipy_min = 0; + m_clipx_max = 0; + m_clipy_max = 0; + + save_item(NAME(m_hst)); + save_item(NAME(m_lst)); + save_item(NAME(m_izblank)); + save_item(NAME(m_clipx_min)); + save_item(NAME(m_clipy_min)); + save_item(NAME(m_clipx_max)); + save_item(NAME(m_clipy_max)); +} + + +avgdvg_device_base::avgdvg_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + m_vector(*this, finder_base::DUMMY_TAG), + m_memspace(*this, finder_base::DUMMY_TAG, -1), + m_membase(0), + m_nvect(0), + m_pc(0), + m_sp(0), + m_dvx(0), + m_dvy(0), + m_stack{ 0, 0, 0, 0 }, + m_data(0), + m_state_latch(0), + m_scale(0), + m_intensity(0), + m_op(0), + m_halt(0), + m_sync_halt(0), + m_xpos(0), + m_ypos(0), + m_prom(*this, "prom"), + m_vg_run_timer(nullptr), + m_vg_halt_timer(nullptr) +{ +} + +dvg_device::dvg_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avgdvg_device_base(mconfig, DVG, tag, owner, clock) +{ +} + +avg_device::avg_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avg_device(mconfig, AVG, tag, owner, clock) +{ +} + +avg_device::avg_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : + avgdvg_device_base(mconfig, type, tag, owner, clock) +{ +} + +avg_tempest_device::avg_tempest_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avg_device(mconfig, AVG_TEMPEST, tag, owner, clock), + m_colorram(*this, "colorram") +{ +} + +avg_mhavoc_device::avg_mhavoc_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avg_device(mconfig, AVG_MHAVOC, tag, owner, clock), + m_colorram(*this, "colorram"), + m_bank_region(*this, DEVICE_SELF) +{ +} + +avg_starwars_device::avg_starwars_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avg_device(mconfig, AVG_STARWARS, tag, owner, clock) +{ +} + +avg_quantum_device::avg_quantum_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avg_device(mconfig, AVG_QUANTUM, tag, owner, clock), + m_colorram(*this, "colorram") +{ +} + +avg_bzone_device::avg_bzone_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + avg_device(mconfig, AVG_BZONE, tag, owner, clock) +{ +} + + + +/************************************* + * + * Device type definitions + * + *************************************/ + +DEFINE_DEVICE_TYPE(DVG, dvg_device, "dvg", "Atari DVG") +DEFINE_DEVICE_TYPE(AVG, avg_device, "avg", "Atari AVG") +DEFINE_DEVICE_TYPE(AVG_TEMPEST, avg_tempest_device, "avg_tempest", "Atari AVG (Tempest)") +DEFINE_DEVICE_TYPE(AVG_MHAVOC, avg_mhavoc_device, "avg_mhavoc", "Atari AVG (Major Havoc)") +DEFINE_DEVICE_TYPE(AVG_STARWARS, avg_starwars_device, "avg_starwars", "Atari AVG (Star Wars)") +DEFINE_DEVICE_TYPE(AVG_QUANTUM, avg_quantum_device, "avg_quantum", "Atari AVG (Quantum)") +DEFINE_DEVICE_TYPE(AVG_BZONE, avg_bzone_device, "avg_bzone", "Atari AVG (Battle Zone)") |