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