1 // license:BSD-3-Clause
2 // copyright-holders:Zsolt Vasvari, Aaron Giles
3 /****************************************************************************
4 * *
5 * Functions to emulate the TMS34061 video controller *
6 * *
7 * Created by Zsolt Vasvari on 5/26/1998. *
8 * Updated by Aaron Giles on 11/21/2000. *
9 * *
10 * This is far from complete. See the TMS34061 User's Guide available on *
11 * www.spies.com/arcade *
12 * *
13 ****************************************************************************/
14
15 #include "emu.h"
16 #include "tms34061.h"
17
18 #include "screen.h"
19
20 //#define VERBOSE 1
21 #include "logmacro.h"
22
23
24
25 //**************************************************************************
26 // LIVE DEVICE
27 //**************************************************************************
28
29 //-------------------------------------------------
30 // tms34061_device - constructor
31 //-------------------------------------------------
32
33 DEFINE_DEVICE_TYPE(TMS34061, tms34061_device, "tms34061", "TI TMS34061 VSC")
34
tms34061_device(const machine_config & mconfig,const char * tag,device_t * owner,u32 clock)35 tms34061_device::tms34061_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
36 : device_t(mconfig, TMS34061, tag, owner, clock),
37 device_video_interface(mconfig, *this),
38 m_rowshift(0),
39 m_vramsize(0),
40 m_interrupt_cb(*this),
41 m_xmask(0),
42 m_yshift(0),
43 m_vrammask(0),
44 m_vram(nullptr),
45 m_latchram(nullptr),
46 m_latchdata(0),
47 m_shiftreg(nullptr),
48 m_timer(nullptr)
49 {
50 memset(m_regs, 0, sizeof(m_regs));
51 memset(&m_display, 0, sizeof(m_display));
52 }
53
54 //-------------------------------------------------
55 // device_start - device-specific startup
56 //-------------------------------------------------
57
device_start()58 void tms34061_device::device_start()
59 {
60 /* resolve callbak */
61 m_interrupt_cb.resolve();
62
63 /* reset the data */
64 m_vrammask = m_vramsize - 1;
65
66 /* allocate memory for VRAM */
67 m_vram = auto_alloc_array_clear(machine(), u8, m_vramsize + 256 * 2);
68
69 /* allocate memory for latch RAM */
70 m_latchram = auto_alloc_array_clear(machine(), u8, m_vramsize + 256 * 2);
71
72 /* add some buffer space for VRAM and latch RAM */
73 m_vram += 256;
74 m_latchram += 256;
75
76 /* point the shift register to the base of VRAM for now */
77 m_shiftreg = m_vram;
78
79 /* initialize registers to their default values from the manual */
80 m_regs[TMS34061_HORENDSYNC] = 0x0010;
81 m_regs[TMS34061_HORENDBLNK] = 0x0020;
82 m_regs[TMS34061_HORSTARTBLNK] = 0x01f0;
83 m_regs[TMS34061_HORTOTAL] = 0x0200;
84 m_regs[TMS34061_VERENDSYNC] = 0x0004;
85 m_regs[TMS34061_VERENDBLNK] = 0x0010;
86 m_regs[TMS34061_VERSTARTBLNK] = 0x00f0;
87 m_regs[TMS34061_VERTOTAL] = 0x0100;
88 m_regs[TMS34061_DISPUPDATE] = 0x0000;
89 m_regs[TMS34061_DISPSTART] = 0x0000;
90 m_regs[TMS34061_VERINT] = 0x0000;
91 m_regs[TMS34061_CONTROL1] = 0x7000;
92 m_regs[TMS34061_CONTROL2] = 0x0600;
93 m_regs[TMS34061_STATUS] = 0x0000;
94 m_regs[TMS34061_XYOFFSET] = 0x0010;
95 m_regs[TMS34061_XYADDRESS] = 0x0000;
96 m_regs[TMS34061_DISPADDRESS] = 0x0000;
97 m_regs[TMS34061_VERCOUNTER] = 0x0000;
98
99 /* start vertical interrupt timer */
100 m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(tms34061_device::interrupt), this));
101
102 save_item(NAME(m_regs));
103 save_item(NAME(m_xmask));
104 save_item(NAME(m_yshift));
105 save_pointer(NAME(m_vram), m_vramsize);
106 save_pointer(NAME(m_latchram), m_vramsize);
107 save_item(NAME(m_latchdata));
108 }
109
110 //-------------------------------------------------
111 // device_reset - device-specific reset
112 //-------------------------------------------------
113
device_reset()114 void tms34061_device::device_reset()
115 {
116 }
117
118 /*************************************
119 *
120 * Global variables
121 *
122 *************************************/
123
124 static const char *const regnames[] =
125 {
126 "HORENDSYNC", "HORENDBLNK", "HORSTARTBLNK", "HORTOTAL",
127 "VERENDSYNC", "VERENDBLNK", "VERSTARTBLNK", "VERTOTAL",
128 "DISPUPDATE", "DISPSTART", "VERINT", "CONTROL1",
129 "CONTROL2", "STATUS", "XYOFFSET", "XYADDRESS",
130 "DISPADDRESS", "VERCOUNTER"
131 };
132
133
134 /*************************************
135 *
136 * Interrupt handling
137 *
138 *************************************/
139
update_interrupts()140 void tms34061_device::update_interrupts()
141 {
142 /* if we have a callback, process it */
143 if (!m_interrupt_cb.isnull())
144 {
145 /* if the status bit is set, and ints are enabled, turn it on */
146 if ((m_regs[TMS34061_STATUS] & 0x0001) && (m_regs[TMS34061_CONTROL1] & 0x0400))
147 m_interrupt_cb(ASSERT_LINE);
148 else
149 m_interrupt_cb(CLEAR_LINE);
150 }
151 }
152
153
TIMER_CALLBACK_MEMBER(tms34061_device::interrupt)154 TIMER_CALLBACK_MEMBER( tms34061_device::interrupt )
155 {
156 /* set timer for next frame */
157 m_timer->adjust(screen().frame_period());
158
159 /* set the interrupt bit in the status reg */
160 m_regs[TMS34061_STATUS] |= 1;
161
162 /* update the interrupt state */
163 update_interrupts();
164 }
165
166
167
168 /*************************************
169 *
170 * Register writes
171 *
172 *************************************/
173
register_w(offs_t offset,u8 data)174 void tms34061_device::register_w(offs_t offset, u8 data)
175 {
176 int scanline;
177 int regnum = offset >> 2;
178
179 /* certain registers affect the display directly */
180 if ((regnum >= TMS34061_HORENDSYNC && regnum <= TMS34061_DISPSTART) ||
181 (regnum == TMS34061_CONTROL2))
182 screen().update_partial(screen().vpos());
183
184 /* store the hi/lo half */
185 if (regnum < ARRAY_LENGTH(m_regs))
186 {
187 if (offset & 0x02)
188 m_regs[regnum] = (m_regs[regnum] & 0x00ff) | (data << 8);
189 else
190 m_regs[regnum] = (m_regs[regnum] & 0xff00) | data;
191 }
192
193 /* log it */
194 LOG("%s:tms34061 %s = %04x\n", machine().describe_context(), regnames[regnum], m_regs[regnum]);
195
196 /* update the state of things */
197 switch (regnum)
198 {
199 /* vertical interrupt: adjust the timer */
200 case TMS34061_VERINT:
201 scanline = m_regs[TMS34061_VERINT] - m_regs[TMS34061_VERENDBLNK];
202
203 if (scanline < 0)
204 scanline += m_regs[TMS34061_VERTOTAL];
205
206 m_timer->adjust(screen().time_until_pos(scanline, m_regs[TMS34061_HORSTARTBLNK]));
207 break;
208
209 /* XY offset: set the X and Y masks */
210 case TMS34061_XYOFFSET:
211 switch (m_regs[TMS34061_XYOFFSET] & 0x00ff)
212 {
213 case 0x01: m_yshift = 2; break;
214 case 0x02: m_yshift = 3; break;
215 case 0x04: m_yshift = 4; break;
216 case 0x08: m_yshift = 5; break;
217 case 0x10: m_yshift = 6; break;
218 case 0x20: m_yshift = 7; break;
219 case 0x40: m_yshift = 8; break;
220 case 0x80: m_yshift = 9; break;
221 default: logerror("Invalid value for XYOFFSET = %04x\n", m_regs[TMS34061_XYOFFSET]); break;
222 }
223 m_xmask = (1 << m_yshift) - 1;
224 break;
225
226 /* CONTROL1: they could have turned interrupts on */
227 case TMS34061_CONTROL1:
228 update_interrupts();
229 break;
230
231 /* other supported registers */
232 case TMS34061_XYADDRESS:
233 break;
234 }
235 }
236
237
238
239 /*************************************
240 *
241 * Register reads
242 *
243 *************************************/
244
register_r(offs_t offset)245 u8 tms34061_device::register_r(offs_t offset)
246 {
247 int regnum = offset >> 2;
248 u16 result;
249
250 /* extract the correct portion of the register */
251 if (regnum < ARRAY_LENGTH(m_regs))
252 result = m_regs[regnum];
253 else
254 result = 0xffff;
255
256 /* special cases: */
257 switch (regnum)
258 {
259 /* status register: a read here clears it */
260 case TMS34061_STATUS:
261 m_regs[TMS34061_STATUS] = 0;
262 update_interrupts();
263 break;
264
265 /* vertical count register: return the current scanline */
266 case TMS34061_VERCOUNTER:
267 result = (screen().vpos()+ m_regs[TMS34061_VERENDBLNK]) % m_regs[TMS34061_VERTOTAL];
268 break;
269 }
270
271 /* log it */
272 LOG("%s:tms34061 %s read = %04X\n", machine().describe_context(), regnames[regnum], result);
273 return (offset & 0x02) ? (result >> 8) : result;
274 }
275
276
277
278 /*************************************
279 *
280 * XY addressing
281 *
282 *************************************/
283
adjust_xyaddress(int offset)284 void tms34061_device::adjust_xyaddress(int offset)
285 {
286 /* note that carries are allowed if the Y coordinate isn't being modified */
287 switch (offset & 0x1e)
288 {
289 case 0x00: /* no change */
290 break;
291
292 case 0x02: /* X + 1 */
293 m_regs[TMS34061_XYADDRESS]++;
294 break;
295
296 case 0x04: /* X - 1 */
297 m_regs[TMS34061_XYADDRESS]--;
298 break;
299
300 case 0x06: /* X = 0 */
301 m_regs[TMS34061_XYADDRESS] &= ~m_xmask;
302 break;
303
304 case 0x08: /* Y + 1 */
305 m_regs[TMS34061_XYADDRESS] += 1 << m_yshift;
306 break;
307
308 case 0x0a: /* X + 1, Y + 1 */
309 m_regs[TMS34061_XYADDRESS] = (m_regs[TMS34061_XYADDRESS] & ~m_xmask) |
310 ((m_regs[TMS34061_XYADDRESS] + 1) & m_xmask);
311 m_regs[TMS34061_XYADDRESS] += 1 << m_yshift;
312 break;
313
314 case 0x0c: /* X - 1, Y + 1 */
315 m_regs[TMS34061_XYADDRESS] = (m_regs[TMS34061_XYADDRESS] & ~m_xmask) |
316 ((m_regs[TMS34061_XYADDRESS] - 1) & m_xmask);
317 m_regs[TMS34061_XYADDRESS] += 1 << m_yshift;
318 break;
319
320 case 0x0e: /* X = 0, Y + 1 */
321 m_regs[TMS34061_XYADDRESS] &= ~m_xmask;
322 m_regs[TMS34061_XYADDRESS] += 1 << m_yshift;
323 break;
324
325 case 0x10: /* Y - 1 */
326 m_regs[TMS34061_XYADDRESS] -= 1 << m_yshift;
327 break;
328
329 case 0x12: /* X + 1, Y - 1 */
330 m_regs[TMS34061_XYADDRESS] = (m_regs[TMS34061_XYADDRESS] & ~m_xmask) |
331 ((m_regs[TMS34061_XYADDRESS] + 1) & m_xmask);
332 m_regs[TMS34061_XYADDRESS] -= 1 << m_yshift;
333 break;
334
335 case 0x14: /* X - 1, Y - 1 */
336 m_regs[TMS34061_XYADDRESS] = (m_regs[TMS34061_XYADDRESS] & ~m_xmask) |
337 ((m_regs[TMS34061_XYADDRESS] - 1) & m_xmask);
338 m_regs[TMS34061_XYADDRESS] -= 1 << m_yshift;
339 break;
340
341 case 0x16: /* X = 0, Y - 1 */
342 m_regs[TMS34061_XYADDRESS] &= ~m_xmask;
343 m_regs[TMS34061_XYADDRESS] -= 1 << m_yshift;
344 break;
345
346 case 0x18: /* Y = 0 */
347 m_regs[TMS34061_XYADDRESS] &= m_xmask;
348 break;
349
350 case 0x1a: /* X + 1, Y = 0 */
351 m_regs[TMS34061_XYADDRESS]++;
352 m_regs[TMS34061_XYADDRESS] &= m_xmask;
353 break;
354
355 case 0x1c: /* X - 1, Y = 0 */
356 m_regs[TMS34061_XYADDRESS]--;
357 m_regs[TMS34061_XYADDRESS] &= m_xmask;
358 break;
359
360 case 0x1e: /* X = 0, Y = 0 */
361 m_regs[TMS34061_XYADDRESS] = 0;
362 break;
363 }
364 }
365
366
xypixel_w(int offset,u8 data)367 void tms34061_device::xypixel_w(int offset, u8 data)
368 {
369 /* determine the offset, then adjust it */
370 offs_t pixeloffs = m_regs[TMS34061_XYADDRESS];
371 if (offset)
372 adjust_xyaddress(offset);
373
374 /* adjust for the upper bits */
375 pixeloffs |= (m_regs[TMS34061_XYOFFSET] & 0x0f00) << 8;
376
377 /* mask to the VRAM size */
378 pixeloffs &= m_vrammask;
379 LOG("%s:tms34061 xy (%04x) = %02x/%02x\n", machine().describe_context(), pixeloffs, data, m_latchdata);
380
381 /* set the pixel data */
382 m_vram[pixeloffs] = data;
383 m_latchram[pixeloffs] = m_latchdata;
384 }
385
386
xypixel_r(int offset)387 u8 tms34061_device::xypixel_r(int offset)
388 {
389 /* determine the offset, then adjust it */
390 offs_t pixeloffs = m_regs[TMS34061_XYADDRESS];
391 if (offset)
392 adjust_xyaddress(offset);
393
394 /* adjust for the upper bits */
395 pixeloffs |= (m_regs[TMS34061_XYOFFSET] & 0x0f00) << 8;
396
397 /* mask to the VRAM size */
398 pixeloffs &= m_vrammask;
399
400 /* return the result */
401 return m_vram[pixeloffs];
402 }
403
404
405
406 /*************************************
407 *
408 * Core writes
409 *
410 *************************************/
411
write(int col,int row,int func,u8 data)412 void tms34061_device::write(int col, int row, int func, u8 data)
413 {
414 offs_t offs;
415
416 /* the function code determines what to do */
417 switch (func)
418 {
419 /* both 0 and 2 map to register access */
420 case 0:
421 case 2:
422 register_w(col, data);
423 break;
424
425 /* function 1 maps to XY access; col is the address adjustment */
426 case 1:
427 xypixel_w(col, data);
428 break;
429
430 /* function 3 maps to direct access */
431 case 3:
432 offs = ((row << m_rowshift) | col) & m_vrammask;
433 if (m_regs[TMS34061_CONTROL2] & 0x0040)
434 offs |= (m_regs[TMS34061_CONTROL2] & 3) << 16;
435 LOG("%s:tms34061 direct (%04x) = %02x/%02x\n", machine().describe_context(), offs, data, m_latchdata);
436 if (m_vram[offs] != data || m_latchram[offs] != m_latchdata)
437 {
438 m_vram[offs] = data;
439 m_latchram[offs] = m_latchdata;
440 }
441 break;
442
443 /* function 4 performs a shift reg transfer to VRAM */
444 case 4:
445 offs = col << m_rowshift;
446 if (m_regs[TMS34061_CONTROL2] & 0x0040)
447 offs |= (m_regs[TMS34061_CONTROL2] & 3) << 16;
448 offs &= m_vrammask;
449 LOG("%s:tms34061 shiftreg write (%04x)\n", machine().describe_context(), offs);
450
451 memcpy(&m_vram[offs], m_shiftreg, (size_t)1 << m_rowshift);
452 memset(&m_latchram[offs], m_latchdata, (size_t)1 << m_rowshift);
453 break;
454
455 /* function 5 performs a shift reg transfer from VRAM */
456 case 5:
457 offs = col << m_rowshift;
458 if (m_regs[TMS34061_CONTROL2] & 0x0040)
459 offs |= (m_regs[TMS34061_CONTROL2] & 3) << 16;
460 offs &= m_vrammask;
461 LOG("%s:tms34061 shiftreg read (%04x)\n", machine().describe_context(), offs);
462
463 m_shiftreg = &m_vram[offs];
464 break;
465
466 /* log anything else */
467 default:
468 logerror("%s:Unsupported TMS34061 function %d\n", machine().describe_context(), func);
469 break;
470 }
471 }
472
473
read(int col,int row,int func)474 u8 tms34061_device::read(int col, int row, int func)
475 {
476 int result = 0;
477 offs_t offs;
478
479 /* the function code determines what to do */
480 switch (func)
481 {
482 /* both 0 and 2 map to register access */
483 case 0:
484 case 2:
485 result = register_r(col);
486 break;
487
488 /* function 1 maps to XY access; col is the address adjustment */
489 case 1:
490 result = xypixel_r(col);
491 break;
492
493 /* function 3 maps to direct access */
494 case 3:
495 offs = ((row << m_rowshift) | col) & m_vrammask;
496 result = m_vram[offs];
497 break;
498
499 /* function 4 performs a shift reg transfer to VRAM */
500 case 4:
501 offs = col << m_rowshift;
502 if (m_regs[TMS34061_CONTROL2] & 0x0040)
503 offs |= (m_regs[TMS34061_CONTROL2] & 3) << 16;
504 offs &= m_vrammask;
505
506 memcpy(&m_vram[offs], m_shiftreg, (size_t)1 << m_rowshift);
507 memset(&m_latchram[offs], m_latchdata, (size_t)1 << m_rowshift);
508 break;
509
510 /* function 5 performs a shift reg transfer from VRAM */
511 case 5:
512 offs = col << m_rowshift;
513 if (m_regs[TMS34061_CONTROL2] & 0x0040)
514 offs |= (m_regs[TMS34061_CONTROL2] & 3) << 16;
515 offs &= m_vrammask;
516
517 m_shiftreg = &m_vram[offs];
518 break;
519
520 /* log anything else */
521 default:
522 logerror("%s:Unsupported TMS34061 function %d\n", machine().describe_context(),
523 func);
524 break;
525 }
526
527 return result;
528 }
529
530
531
532 /*************************************
533 *
534 * Misc functions
535 *
536 *************************************/
537
latch_r()538 u8 tms34061_device::latch_r()
539 {
540 return m_latchdata;
541 }
542
543
latch_w(u8 data)544 void tms34061_device::latch_w(u8 data)
545 {
546 LOG("tms34061_latch = %02X\n", data);
547 m_latchdata = data;
548 }
549
550
get_display_state()551 void tms34061_device::get_display_state()
552 {
553 m_display.blanked = (~m_regs[TMS34061_CONTROL2] >> 13) & 1;
554 m_display.vram = m_vram;
555 m_display.latchram = m_latchram;
556 m_display.regs = m_regs;
557
558 /* compute the display start */
559 m_display.dispstart = (m_regs[TMS34061_DISPSTART] << (m_rowshift - 2)) & m_vrammask;
560 }
561