1 // license:BSD-3-Clause
2 // copyright-holders:Alex Pasadyn, Zsolt Vasvari, Ernesto Corvi, Aaron Giles
3 // thanks-to:Kurt Mahan
4 /*************************************************************************
5
6 Williams/Midway Y/Z-unit system
7
8 **************************************************************************/
9
10 #include "emu.h"
11 #include "includes/midyunit.h"
12 #include "screen.h"
13
14
15 /* compile-time options */
16 #define LOG_DMA 0 /* DMAs are logged if the 'L' key is pressed */
17
18
19 /* constants for the DMA chip */
20 enum
21 {
22 DMA_COMMAND = 0,
23 DMA_ROWBYTES,
24 DMA_OFFSETLO,
25 DMA_OFFSETHI,
26 DMA_XSTART,
27 DMA_YSTART,
28 DMA_WIDTH,
29 DMA_HEIGHT,
30 DMA_PALETTE,
31 DMA_COLOR
32 };
33
34
35
36 /*************************************
37 *
38 * Video startup
39 *
40 *************************************/
41
VIDEO_START_MEMBER(midyunit_state,common)42 VIDEO_START_MEMBER(midyunit_state,common)
43 {
44 /* allocate memory */
45 m_cmos_ram = std::make_unique<uint16_t[]>((0x2000 * 4)/2);
46 m_local_videoram = make_unique_clear<uint16_t[]>(0x80000/2);
47 m_pen_map = std::make_unique<pen_t[]>(65536);
48
49 m_nvram->set_base(m_cmos_ram.get(), 0x2000 * 4);
50
51 m_dma_timer = timer_alloc(TIMER_DMA);
52 m_autoerase_line_timer = timer_alloc(TIMER_AUTOERASE_LINE);
53
54 /* reset all the globals */
55 m_cmos_page = 0;
56 m_autoerase_enable = 0;
57 m_yawdim_dma = 0;
58
59 /* reset DMA state */
60 memset(m_dma_register, 0, sizeof(m_dma_register));
61 memset(&m_dma_state, 0, sizeof(m_dma_state));
62
63 /* register for state saving */
64 save_item(NAME(m_autoerase_enable));
65 save_pointer(NAME(m_local_videoram), 0x80000/2);
66 save_pointer(NAME(m_cmos_ram), (0x2000 * 4)/2);
67 save_item(NAME(m_videobank_select));
68 save_item(NAME(m_dma_register));
69 }
70
71
VIDEO_START_MEMBER(midyunit_state,midyunit_4bit)72 VIDEO_START_MEMBER(midyunit_state,midyunit_4bit)
73 {
74 int i;
75
76 VIDEO_START_CALL_MEMBER(common);
77
78 /* init for 4-bit */
79 for (i = 0; i < 65536; i++)
80 m_pen_map[i] = ((i & 0xf000) >> 8) | (i & 0x000f);
81 m_palette_mask = 0x00ff;
82 }
83
84
VIDEO_START_MEMBER(midyunit_state,midyunit_6bit)85 VIDEO_START_MEMBER(midyunit_state,midyunit_6bit)
86 {
87 int i;
88
89 VIDEO_START_CALL_MEMBER(common);
90
91 /* init for 6-bit */
92 for (i = 0; i < 65536; i++)
93 m_pen_map[i] = ((i & 0xc000) >> 8) | (i & 0x0f3f);
94 m_palette_mask = 0x0fff;
95 }
96
97
VIDEO_START_MEMBER(midyunit_state,mkyawdim)98 VIDEO_START_MEMBER(midyunit_state,mkyawdim)
99 {
100 VIDEO_START_CALL_MEMBER(midyunit_6bit);
101 m_yawdim_dma = 1;
102 }
103
104
VIDEO_START_MEMBER(midyunit_state,midzunit)105 VIDEO_START_MEMBER(midyunit_state,midzunit)
106 {
107 int i;
108
109 VIDEO_START_CALL_MEMBER(common);
110
111 /* init for 8-bit */
112 for (i = 0; i < 65536; i++)
113 m_pen_map[i] = i & 0x1fff;
114 m_palette_mask = 0x1fff;
115 }
116
117
118
119 /*************************************
120 *
121 * Banked graphics ROM access
122 *
123 *************************************/
124
midyunit_gfxrom_r(offs_t offset)125 uint16_t midyunit_state::midyunit_gfxrom_r(offs_t offset)
126 {
127 offset *= 2;
128 if (m_palette_mask == 0x00ff)
129 return m_gfx_rom[offset] | (m_gfx_rom[offset] << 4) |
130 (m_gfx_rom[offset + 1] << 8) | (m_gfx_rom[offset + 1] << 12);
131 else
132 return m_gfx_rom[offset] | (m_gfx_rom[offset + 1] << 8);
133 }
134
135
136
137 /*************************************
138 *
139 * Video/color RAM read/write
140 *
141 *************************************/
142
midyunit_vram_w(offs_t offset,uint16_t data,uint16_t mem_mask)143 void midyunit_state::midyunit_vram_w(offs_t offset, uint16_t data, uint16_t mem_mask)
144 {
145 offset *= 2;
146 if (m_videobank_select)
147 {
148 if (ACCESSING_BITS_0_7)
149 m_local_videoram[offset] = (data & 0x00ff) | (m_dma_register[DMA_PALETTE] << 8);
150 if (ACCESSING_BITS_8_15)
151 m_local_videoram[offset + 1] = (data >> 8) | (m_dma_register[DMA_PALETTE] & 0xff00);
152 }
153 else
154 {
155 if (ACCESSING_BITS_0_7)
156 m_local_videoram[offset] = (m_local_videoram[offset] & 0x00ff) | (data << 8);
157 if (ACCESSING_BITS_8_15)
158 m_local_videoram[offset + 1] = (m_local_videoram[offset + 1] & 0x00ff) | (data & 0xff00);
159 }
160 }
161
162
midyunit_vram_r(offs_t offset)163 uint16_t midyunit_state::midyunit_vram_r(offs_t offset)
164 {
165 offset *= 2;
166 if (m_videobank_select)
167 return (m_local_videoram[offset] & 0x00ff) | (m_local_videoram[offset + 1] << 8);
168 else
169 return (m_local_videoram[offset] >> 8) | (m_local_videoram[offset + 1] & 0xff00);
170 }
171
172
173
174 /*************************************
175 *
176 * Shift register read/write
177 *
178 *************************************/
179
TMS340X0_TO_SHIFTREG_CB_MEMBER(midyunit_state::to_shiftreg)180 TMS340X0_TO_SHIFTREG_CB_MEMBER(midyunit_state::to_shiftreg)
181 {
182 memcpy(shiftreg, &m_local_videoram[address >> 3], 2 * 512 * sizeof(uint16_t));
183 }
184
185
TMS340X0_FROM_SHIFTREG_CB_MEMBER(midyunit_state::from_shiftreg)186 TMS340X0_FROM_SHIFTREG_CB_MEMBER(midyunit_state::from_shiftreg)
187 {
188 memcpy(&m_local_videoram[address >> 3], shiftreg, 2 * 512 * sizeof(uint16_t));
189 }
190
191
192
193 /*************************************
194 *
195 * Y/Z-unit control register
196 *
197 *************************************/
198
midyunit_control_w(offs_t offset,uint16_t data,uint16_t mem_mask)199 void midyunit_state::midyunit_control_w(offs_t offset, uint16_t data, uint16_t mem_mask)
200 {
201 /*
202 * Narc system register
203 * ------------------
204 *
205 * | Bit | Use
206 * --+-FEDCBA9876543210-+------------
207 * | xxxxxxxx-------- | 7 segment led on CPU board
208 * | --------xx------ | CMOS page
209 * | ----------x----- | - OBJ PAL RAM select
210 * | -----------x---- | - autoerase enable
211 * | ---------------- | - watchdog
212 *
213 */
214
215 if (ACCESSING_BITS_0_7)
216 {
217 /* CMOS page is bits 6-7 */
218 m_cmos_page = ((data >> 6) & 3) * 0x1000;
219
220 /* video bank select is bit 5 */
221 m_videobank_select = (data >> 5) & 1;
222
223 /* handle autoerase disable (bit 4) */
224 m_autoerase_enable = ((data & 0x10) == 0);
225 }
226 }
227
228
229
230 /*************************************
231 *
232 * Palette handlers
233 *
234 *************************************/
235
midyunit_paletteram_w(offs_t offset,uint16_t data,uint16_t mem_mask)236 void midyunit_state::midyunit_paletteram_w(offs_t offset, uint16_t data, uint16_t mem_mask)
237 {
238 int newword;
239
240 COMBINE_DATA(&m_generic_paletteram_16[offset]);
241 newword = m_generic_paletteram_16[offset];
242 m_palette->set_pen_color(offset & m_palette_mask, pal5bit(newword >> 10), pal5bit(newword >> 5), pal5bit(newword >> 0));
243 }
244
245
246
247 /*************************************
248 *
249 * DMA drawing routines
250 *
251 *************************************/
252
dma_draw(uint16_t command)253 void midyunit_state::dma_draw(uint16_t command)
254 {
255 struct dma_state_t &dma_state = m_dma_state;
256 int dx = (command & 0x10) ? -1 : 1;
257 int height = dma_state.height;
258 int width = dma_state.width;
259 uint8_t *base = m_gfx_rom;
260 uint32_t offset = dma_state.offset >> 3;
261 uint16_t pal = dma_state.palette;
262 uint16_t color = pal | dma_state.color;
263 int x, y;
264
265 /* we only need the low 4 bits of the command */
266 command &= 0x0f;
267
268 /* loop over the height */
269 for (y = 0; y < height; y++)
270 {
271 int tx = dma_state.xpos;
272 int ty = dma_state.ypos;
273 uint32_t o = offset;
274 uint16_t *dest;
275
276 /* determine Y position */
277 ty = (ty + y) & 0x1ff;
278 offset += dma_state.rowbytes;
279
280 /* determine destination pointer */
281 dest = &m_local_videoram[ty * 512];
282
283 /* check for overruns if they are relevant */
284 if (o >= 0x06000000 && command < 0x0c)
285 continue;
286
287 /* switch off the zero/non-zero options */
288 switch (command)
289 {
290 case 0x00: /* draw nothing */
291 break;
292
293 case 0x01: /* draw only 0 pixels */
294 for (x = 0; x < width; x++, tx += dx)
295 if (base[o++] == 0)
296 dest[tx] = pal;
297 break;
298
299 case 0x02: /* draw only non-0 pixels */
300 for (x = 0; x < width; x++, tx += dx)
301 {
302 int pixel = base[o++];
303 if (pixel != 0)
304 dest[tx] = pal | pixel;
305 }
306 break;
307
308 case 0x03: /* draw all pixels */
309 for (x = 0; x < width; x++, tx += dx)
310 dest[tx] = pal | base[o++];
311 break;
312
313 case 0x04: /* color only 0 pixels */
314 case 0x05: /* color only 0 pixels */
315 for (x = 0; x < width; x++, tx += dx)
316 if (base[o++] == 0)
317 dest[tx] = color;
318 break;
319
320 case 0x06: /* color only 0 pixels, copy the rest */
321 case 0x07: /* color only 0 pixels, copy the rest */
322 for (x = 0; x < width; x++, tx += dx)
323 {
324 int pixel = base[o++];
325 dest[tx] = (pixel == 0) ? color : (pal | pixel);
326 }
327 break;
328
329 case 0x08: /* color only non-0 pixels */
330 case 0x0a: /* color only non-0 pixels */
331 for (x = 0; x < width; x++, tx += dx)
332 if (base[o++] != 0)
333 dest[tx] = color;
334 break;
335
336 case 0x09: /* color only non-0 pixels, copy the rest */
337 case 0x0b: /* color only non-0 pixels, copy the rest */
338 for (x = 0; x < width; x++, tx += dx)
339 {
340 int pixel = base[o++];
341 dest[tx] = (pixel != 0) ? color : (pal | pixel);
342 }
343 break;
344
345 case 0x0c: /* color all pixels */
346 case 0x0d: /* color all pixels */
347 case 0x0e: /* color all pixels */
348 case 0x0f: /* color all pixels */
349 for (x = 0; x < width; x++, tx += dx)
350 dest[tx] = color;
351 break;
352 }
353 }
354 }
355
356
357
358 /*************************************
359 *
360 * Timer callbacks
361 *
362 *************************************/
363
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)364 void midyunit_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
365 {
366 switch (id)
367 {
368 case TIMER_DMA:
369 dma_callback(ptr, param);
370 break;
371 case TIMER_AUTOERASE_LINE:
372 autoerase_line(ptr, param);
373 break;
374 default:
375 throw emu_fatalerror("Unknown id in midyunit_state::device_timer");
376 }
377 }
378
TIMER_CALLBACK_MEMBER(midyunit_state::dma_callback)379 TIMER_CALLBACK_MEMBER(midyunit_state::dma_callback)
380 {
381 m_dma_register[DMA_COMMAND] &= ~0x8000; /* tell the cpu we're done */
382 m_maincpu->set_input_line(0, ASSERT_LINE);
383 }
384
385
386
387 /*************************************
388 *
389 * DMA reader
390 *
391 *************************************/
392
midyunit_dma_r(offs_t offset)393 uint16_t midyunit_state::midyunit_dma_r(offs_t offset)
394 {
395 return m_dma_register[offset];
396 }
397
398
399
400 /*************************************
401 *
402 * DMA write handler
403 *
404 *************************************/
405
406 /*
407 * DMA registers
408 * ------------------
409 *
410 * Register | Bit | Use
411 * ----------+-FEDCBA9876543210-+------------
412 * 0 | x--------------- | trigger write (or clear if zero)
413 * | ---184-1-------- | unknown
414 * | ----------x----- | flip y
415 * | -----------x---- | flip x
416 * | ------------x--- | blit nonzero pixels as color
417 * | -------------x-- | blit zero pixels as color
418 * | --------------x- | blit nonzero pixels
419 * | ---------------x | blit zero pixels
420 * 1 | xxxxxxxxxxxxxxxx | width offset
421 * 2 | xxxxxxxxxxxxxxxx | source address low word
422 * 3 | xxxxxxxxxxxxxxxx | source address high word
423 * 4 | xxxxxxxxxxxxxxxx | detination x
424 * 5 | xxxxxxxxxxxxxxxx | destination y
425 * 6 | xxxxxxxxxxxxxxxx | image columns
426 * 7 | xxxxxxxxxxxxxxxx | image rows
427 * 8 | xxxxxxxxxxxxxxxx | palette
428 * 9 | xxxxxxxxxxxxxxxx | color
429 */
430
midyunit_dma_w(offs_t offset,uint16_t data,uint16_t mem_mask)431 void midyunit_state::midyunit_dma_w(offs_t offset, uint16_t data, uint16_t mem_mask)
432 {
433 struct dma_state_t &dma_state = m_dma_state;
434 uint32_t gfxoffset;
435 int command;
436
437 /* blend with the current register contents */
438 COMBINE_DATA(&m_dma_register[offset]);
439
440 /* only writes to DMA_COMMAND actually cause actions */
441 if (offset != DMA_COMMAND)
442 return;
443
444 /* high bit triggers action */
445 command = m_dma_register[DMA_COMMAND];
446 m_maincpu->set_input_line(0, CLEAR_LINE);
447 if (!(command & 0x8000))
448 return;
449
450 if (LOG_DMA)
451 {
452 if (machine().input().code_pressed(KEYCODE_L))
453 {
454 logerror("----\n");
455 logerror("DMA command %04X: (xflip=%d yflip=%d)\n",
456 command, (command >> 4) & 1, (command >> 5) & 1);
457 logerror(" offset=%08X pos=(%d,%d) w=%d h=%d rb=%d\n",
458 m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16),
459 (int16_t)m_dma_register[DMA_XSTART], (int16_t)m_dma_register[DMA_YSTART],
460 m_dma_register[DMA_WIDTH], m_dma_register[DMA_HEIGHT], (int16_t)m_dma_register[DMA_ROWBYTES]);
461 logerror(" palette=%04X color=%04X\n",
462 m_dma_register[DMA_PALETTE], m_dma_register[DMA_COLOR]);
463 }
464 }
465
466 g_profiler.start(PROFILER_USER1);
467
468 /* fill in the basic data */
469 dma_state.rowbytes = (int16_t)m_dma_register[DMA_ROWBYTES];
470 dma_state.xpos = (int16_t)m_dma_register[DMA_XSTART];
471 dma_state.ypos = (int16_t)m_dma_register[DMA_YSTART];
472 dma_state.width = m_dma_register[DMA_WIDTH];
473 dma_state.height = m_dma_register[DMA_HEIGHT];
474 dma_state.palette = m_dma_register[DMA_PALETTE] << 8;
475 dma_state.color = m_dma_register[DMA_COLOR] & 0xff;
476
477 /* determine the offset and adjust the rowbytes */
478 gfxoffset = m_dma_register[DMA_OFFSETLO] | (m_dma_register[DMA_OFFSETHI] << 16);
479 if (command & 0x10)
480 {
481 if (!m_yawdim_dma)
482 {
483 gfxoffset -= (dma_state.width - 1) * 8;
484 dma_state.rowbytes = (dma_state.rowbytes - dma_state.width + 3) & ~3;
485 }
486 else
487 dma_state.rowbytes = (dma_state.rowbytes + dma_state.width + 3) & ~3;
488 dma_state.xpos += dma_state.width - 1;
489 }
490 else
491 dma_state.rowbytes = (dma_state.rowbytes + dma_state.width + 3) & ~3;
492
493 /* apply Y clipping */
494 if (dma_state.ypos < 0)
495 {
496 dma_state.height -= -dma_state.ypos;
497 dma_state.offset += (-dma_state.ypos * dma_state.rowbytes) << 3;
498 dma_state.ypos = 0;
499 }
500 if (dma_state.ypos + dma_state.height > 512)
501 dma_state.height = 512 - dma_state.ypos;
502
503 /* apply X clipping */
504 if (!(command & 0x10))
505 {
506 if (dma_state.xpos < 0)
507 {
508 dma_state.width -= -dma_state.xpos;
509 dma_state.offset += -dma_state.xpos << 3;
510 dma_state.xpos = 0;
511 }
512 if (dma_state.xpos + dma_state.width > 512)
513 dma_state.width = 512 - dma_state.xpos;
514 }
515 else
516 {
517 if (dma_state.xpos >= 512)
518 {
519 dma_state.width -= dma_state.xpos - 511;
520 dma_state.offset += (dma_state.xpos - 511) << 3;
521 dma_state.xpos = 511;
522 }
523 if (dma_state.xpos - dma_state.width < 0)
524 dma_state.width = dma_state.xpos;
525 }
526
527 /* determine the location and draw */
528 if (gfxoffset < 0x02000000)
529 gfxoffset += 0x02000000;
530 {
531 dma_state.offset = gfxoffset - 0x02000000;
532 dma_draw(command);
533 }
534
535 /* signal we're done */
536 m_dma_timer->adjust(attotime::from_nsec(41 * dma_state.width * dma_state.height));
537
538 g_profiler.stop();
539 }
540
541
542
543 /*************************************
544 *
545 * Core refresh routine
546 *
547 *************************************/
548
TIMER_CALLBACK_MEMBER(midyunit_state::autoerase_line)549 TIMER_CALLBACK_MEMBER(midyunit_state::autoerase_line)
550 {
551 int scanline = param;
552
553 if (m_autoerase_enable && scanline >= 0 && scanline < 510)
554 memcpy(&m_local_videoram[512 * scanline], &m_local_videoram[512 * (510 + (scanline & 1))], 512 * sizeof(uint16_t));
555 }
556
557
TMS340X0_SCANLINE_IND16_CB_MEMBER(midyunit_state::scanline_update)558 TMS340X0_SCANLINE_IND16_CB_MEMBER(midyunit_state::scanline_update)
559 {
560 uint16_t const *const src = &m_local_videoram[(params->rowaddr << 9) & 0x3fe00];
561 uint16_t *const dest = &bitmap.pix(scanline);
562 int coladdr = params->coladdr << 1;
563
564 /* adjust the display address to account for ignored bits */
565 for (int x = params->heblnk; x < params->hsblnk; x++)
566 dest[x] = m_pen_map[src[coladdr++ & 0x1ff]];
567
568 /* handle autoerase on the previous line */
569 autoerase_line(nullptr, params->rowaddr - 1);
570
571 /* if this is the last update of the screen, set a timer to clear out the final line */
572 /* (since we update one behind) */
573 if (scanline == screen.visible_area().max_y)
574 m_autoerase_line_timer->adjust(screen.time_until_pos(scanline + 1), params->rowaddr);
575 }
576