summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/s2636.cpp
blob: 3d29e76b09901d8f3cb716880a5961433b2f645c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/**********************************************************************

    Signetics 2636 Programmable Video Interface

    This emulation is pretty low-level.  For the most part it models
    the kinds of counters and flags you'd use if you were actually
    implementing it in programmable logic or on silicon.  I'm sure
    there are potential performance improvements in the drawing and
    collision detection code.

    At present the entire internal space of 256 bytes can be read and
    written.  This isn't accurate as some registers are read- or write-
    only, and there are several unused blocks in the address range.  We
    should be returning some fixed value on attempt to read unreadable
    locations.

    This implementation really needs proper display timing information
    information to work properly.  Audio pitch will be wrong if the
    screen's scan time is inaccurate.  Positioning objects depends on
    the screen's visible area representing the pixel clock periods
    periods between blanking pulses.  You also need to call the line
    rendering function at appropriate times if something expects to see
    collision or completion flags.

    The crude "offset" system for adjusting the position of the image
    has been maintained, but it's clearly inadequate for what games are
    doing with it, and it will inevitably lead to bugs.  If an object
    is positioned outside the visible area using the offset mechanism,
    it won't be displayed and neither will its duplicates.

    If what's happening in the games right now is correct, the hardware
    must  be feeding something other than the actual vertical and
    horizontal blanking signals to the S2623(s) so they start drawing
    inside the blanking region and have advanced to the desired
    location on reaching the visible area.  Implementing this properly
    would require decoupling the S2623 from the screen and giving it
    some other means to determine when it believes the blanking periods
    begin and end.

    Sorry, analog input isn't currently supported, and neither is
    interrupt request/acknowledge.  I have't got to them yet, and I'm
    still not sure whether reading a status register clears a pending
    interrupt.  The address decoding features still aren't implemented
    but they'd be pretty hard to fit in the MAME framework.


    ADDRESS MAP
    -----------
    start   end     description
    F00     F0D     object descriptor 1
    F0E     F0F     2 bytes scratch
    F10     F1D     object descriptor 2
    F1E     F1F     2 bytes scratch
    F20     F2D     object descriptor 3
    F2E     F3F     -
    F40     F4D     object descriptor 4
    F4E     F6D     32 bytes scratch
    F6E     F7F     -
    F80     FA7     background vertical bar definition
    FA8     FAC     background horizontal bar definition
    FAD     FAD     1 byte scratch
    FAE     FBF     -
    FC0     FCF     I/O and control
    FD0     FDF     I/O and control (mirror)
    FE0     FEF     I/O and control (mirror)
    FF0     FFF     I/O and control (mirror)


    OBJECT DESCRIPTOR
    -----------------
    0   9   basic shape of object (10 lines of 8 horizontal clocks)
    A   A   horizontal coordinate of object (HC)
    B   B   horizontal coordinate of duplicates (HCB)
    C   C   vertical coordinate of object (VC)
    D   D   vertical offset of duplicates (VCB)


    I/O AND CONTROL
    ---------------

          |              bit              |R/W| description
    byte  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |   |
          |                               |   |
    FC0   | size 4| size 3| size 2| size 1| W | size of 4 objects (scale 2^x)
          |                               |   |
    FC1   |       |C1 |C2 |C3 |C1 |C2 |C3 | W | colors of 4 objects
          |       |  color 1  |  color 2  |   |
    FC2   |       |C1 |C2 |C3 |C1 |C2 |C3 | W |
          |       |  color 3  |  color 4  |   |
          |                               |   |
    FC3   |                       |frm|pos| W | score format and position
          |                               |   |
    FC4   |                               | - |
    FC5   |                               | - |
          |                               |   |
    FC6   |   |C1 |C2 |C3 |BG | scrn colr | W | background enable and color
          |   |backg colr |enb|C1 |C2 |C3 |   |
          |                               |   |
    FC7   |            sound              | W | sound square wave period
          |                               |   |
    FC8   |       N1      |      N2       | W | values of the four score digits
    FC9   |       N3      |      N4       | W |
          |                               |   |
          | obj/backgrnd  |object complete| R | object/background and inter-object
    FCA   | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 |   | collision status and object display
          |                               |   | completion status
    FCB   |   |VR-|   object collisions   | R | VRLE set at leading edge of VRST,
          |   |LE |1/2|1/3|1/3|1/4|2/4|3/4|   | all bits reset when read or at
          |                               |   | trailing edge of VRST
          |                               |   |
    FCC   |           A/D POT 1           | R | POT1 and POT2 A/D conversion value
    FCD   |           A/D POT 2           | R | valid during VRST only
          |                               |   |
    FCE   |                               | - |
    FCF   |                               | - |

*************************************************************/

#include "emu.h"
#include "machine/s2636.h"

#include "screen.h"


int const s2636_device::OFFS_OBJ[s2636_device::OBJ_COUNT] = { 0x000, 0x010, 0x020, 0x040 };

int const s2636_device::SCORE_START_X[2][s2636_device::SCORE_DIGITS] { { 48, 64, 96, 112 }, { 48, 64, 80, 96 } };
int const s2636_device::SCORE_START_Y[2] = { 20, 200 };

uint16_t const s2636_device::SCORE_FONT[16][5] =
{
	{ 0x0fff, 0x0f0f, 0x0f0f, 0x0f0f, 0x0fff },
	{ 0x0f00, 0x0f00, 0x0f00, 0x0f00, 0x0f00 },
	{ 0x0fff, 0x0f00, 0x0fff, 0x000f, 0x0fff },
	{ 0x0fff, 0x0f00, 0x0fff, 0x0f00, 0x0fff },
	{ 0x0f0f, 0x0f0f, 0x0fff, 0x0f00, 0x0f00 },
	{ 0x0fff, 0x000f, 0x0fff, 0x0f00, 0x0fff },
	{ 0x0fff, 0x000f, 0x0fff, 0x0f0f, 0x0fff },
	{ 0x0fff, 0x0f00, 0x0f00, 0x0f00, 0x0f00 },
	{ 0x0fff, 0x0f0f, 0x0fff, 0x0f0f, 0x0fff },
	{ 0x0fff, 0x0f0f, 0x0fff, 0x0f00, 0x0fff },
	{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
	{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }
};

/*************************************
 *
 *  Device interface
 *
 *************************************/

DEFINE_DEVICE_TYPE(S2636, s2636_device, "s2636", "Signetics 2636 PVI")

s2636_device::s2636_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, S2636, tag, owner, clock)
	, device_video_interface(mconfig, *this)
	, device_sound_interface(mconfig, *this)
	, m_divider(1)
	, m_y_offset(0)
	, m_x_offset(0)
	, m_intreq_cb(*this)
	, m_vrst(false)
	, m_screen_line(0)
	, m_vis_line(0)
	, m_intreq(CLEAR_LINE)
	, m_intack(CLEAR_LINE)
	, m_stream(nullptr)
	, m_sample_cnt(0)
	, m_sound_lvl(false)
{
	for (auto &elem : m_registers) elem = 0;
	for (auto &elem : m_obj_cnt) elem = 0;
	for (auto &elem : m_obj_disp) elem = false;
	for (auto &elem : m_obj_dup) elem = false;
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void s2636_device::device_start()
{
	m_bitmap.resize(screen().width(), screen().height());

	save_item(NAME(m_bitmap));

	save_item(NAME(m_registers));

	save_item(NAME(m_vrst));
	save_item(NAME(m_screen_line));
	save_item(NAME(m_vis_line));

	save_item(NAME(m_obj_cnt));
	save_item(NAME(m_obj_disp));
	save_item(NAME(m_obj_dup));

	save_item(NAME(m_intreq));
	save_item(NAME(m_intack));

	m_stream = machine().sound().stream_alloc(*this, 0, 1, machine().sample_rate());
	save_item(NAME(m_sample_cnt));
	save_item(NAME(m_sound_lvl));

	m_intreq_cb.resolve_safe();
}


//-------------------------------------------------
//  backwards-compatible update method
//-------------------------------------------------

bitmap_ind16 const &s2636_device::update(const rectangle &cliprect)
{
	m_vrst = true;
	m_screen_line = screen().visible_area().min_y;
	while (m_screen_line <= screen().visible_area().max_y)
		render_next_line();

	return m_bitmap;
}


//-------------------------------------------------
//  render the first line into the bitmap
//-------------------------------------------------

void s2636_device::render_first_line()
{
	m_screen_line = 0;
	render_next_line();
}


//-------------------------------------------------
//  render next line into the bitmap
//-------------------------------------------------

void s2636_device::render_next_line()
{
	assert(m_screen_line < m_bitmap.height());

	// pre-clear the line for convenience
	rectangle const &vis_area = screen().visible_area();
	uint16_t *const   row = &m_bitmap.pix16(m_screen_line);
	m_bitmap.plot_box(0, m_screen_line, m_bitmap.width(), 1, 0);

	if ((vis_area.min_y > m_screen_line) || (vis_area.max_y < m_screen_line))
	{
		if (!m_vrst)
		{
			m_registers[REG_VBL_COL_OBJ] |= 0x40;
			m_vrst = true;
			update_intreq(ASSERT_LINE);
		}
	}
	else
	{
		// check for first visible line
		if (m_vrst)
		{
			// latch vertical position primary objects
			for (int i = 0; i < OBJ_COUNT; i++)
			{
				m_obj_cnt[i] = m_registers[OFFS_OBJ[i] + OFFS_VC] + m_y_offset;
				m_obj_disp[i] = false;
				m_obj_dup[i] = false;
			}

			// collision, completion and vertical reset flags are cleared here
			m_registers[REG_COL_BG_CMPL] = 0x00;
			m_registers[REG_VBL_COL_OBJ] = 0x00;

			// set our internal tracking flags and clear interrupt
			m_vrst = false;
			m_vis_line = 0;
			update_intreq(CLEAR_LINE);
		}

		// work out what object pixels belong in this line
		for (int i = 0; i < OBJ_COUNT; i++)
		{
			// repurpose counter and set flag when we've skipped enough lines
			if (!m_obj_cnt[i])
			{
				m_obj_cnt[i] = OBJ_HEIGHT << 3;
				m_obj_disp[i] = true;
			}

			if (m_obj_disp[i])
			{
				int const obj_inc = 1 << (3 - object_scale(i));
				m_obj_cnt[i] -= obj_inc;

				// fetch appropriate line from object
				uint8_t const     obj_bits = m_registers[OFFS_OBJ[i] + OBJ_HEIGHT - 1 - (m_obj_cnt[i] >> 3)];
				uint16_t const    obj_clr = object_color(i) | 0x08 | (0x10 << i);

				// blit it to the line ignoring intermediate pixels
				int const obj_h_cnt = m_registers[OFFS_OBJ[i] + (m_obj_dup[i] ? OFFS_HCB : OFFS_HC)] + m_x_offset;
				for (int x = 0, screen_col = vis_area.min_x + (obj_h_cnt * m_divider); (OBJ_WIDTH << 3) > x && (vis_area.max_x >= screen_col); )
				{
					bool const bit = bool((obj_bits << (x >> 3)) & 0x80);
					if (bit && (vis_area.min_x <= screen_col)) row[screen_col] |= obj_clr;
					x += obj_inc;
					screen_col += m_divider;
				}

				// if that's the last line of the object, flag completion and prepare for duplicates
				if (!m_obj_cnt[i])
				{
					m_registers[REG_COL_BG_CMPL] |= 0x01 << (3 - i);
					m_obj_cnt[i] = 1 + m_registers[OFFS_OBJ[i] + OFFS_VCB];
					m_obj_disp[i] = false;
					m_obj_dup[i] = true;
					update_intreq(ASSERT_LINE);
				}
			}
			else
			{
				// count down lines to display object
				m_obj_cnt[i]--;
			}
		}

		// let's take a look at the score display
		uint16_t const    bg_clr = m_registers[REG_BG_ENB_CLR] & 0x07;
		int const       score_row = m_vis_line - m_y_offset - SCORE_START_Y[m_registers[REG_SCORE_FMT] & 0x01];
		if ((0 <= score_row) && (SCORE_HEIGHT > score_row))
		{
			int const (&score_start_x)[SCORE_DIGITS] = SCORE_START_X[(m_registers[REG_SCORE_FMT] >> 1) & 0x01];
			for (int i = 0; i < SCORE_DIGITS; i++)
			{
				uint16_t  score_bits = SCORE_FONT[score_digit(i)][score_row >> 2];
				int     screen_col = vis_area.min_x + ((score_start_x[i] + m_x_offset) * m_divider);
				while (score_bits && (vis_area.max_x >= screen_col))
				{
					if (score_bits & 0x0001) row[screen_col] |= bg_clr | 0x08;
					score_bits >>= 1;
					screen_col += m_divider;
				}
			}
		}

		// work out how the background pattern will be drawn
		bool const      bg_enable = bool(m_registers[REG_BG_ENB_CLR] & 0x08);
		int const       bg_row = m_vis_line - m_y_offset - BG_START_Y;
		bool const      bg_draw = bg_enable && (0 <= bg_row) && (BG_HEIGHT > bg_row);
		int const       bg_vbar_offs = OFFS_VBAR_DEF + ((bg_row / 20) << 2) + (((bg_row % 20) >= 2) ? 2 : 0);
		int const       bg_hbar_offs = OFFS_HBAR_DEF + (bg_row / 40);
		uint16_t const    bg_vbar_bits = (uint16_t(m_registers[bg_vbar_offs]) << 8) | uint16_t(m_registers[bg_vbar_offs + 1]);
		uint8_t const     bg_hbar_bits = m_registers[bg_hbar_offs];
		bool const      bg_hbar_stretch = bool(bg_hbar_bits & (1 << ((((bg_row % 40) >= 20) ? 3 : 0) + (((bg_row % 20) >= 11) ? 2 : ((bg_row % 20) >= 2) ? 1 : 0))));
		int const       bg_hbar_width = bg_hbar_stretch ? 8 : (0xc0 == (bg_hbar_bits & 0xc0)) ? 4 : (0x40 == (bg_hbar_bits & 0xc0)) ? 2 : 1;
		uint16_t const    scrn_clr = bg_enable ? ((m_registers[REG_BG_ENB_CLR] >> 4) & 0x07) : 0x00;

		for (int screen_col = vis_area.min_x, x = 0; vis_area.max_x >= screen_col; x++)
		{
			// check object-object collisions
			if ((row[screen_col] & 0x10) && (row[screen_col] & 0x20)) m_registers[REG_VBL_COL_OBJ] |= 0x20;
			if ((row[screen_col] & 0x10) && (row[screen_col] & 0x40)) m_registers[REG_VBL_COL_OBJ] |= 0x10;
			if ((row[screen_col] & 0x10) && (row[screen_col] & 0x80)) m_registers[REG_VBL_COL_OBJ] |= 0x08;
			if ((row[screen_col] & 0x20) && (row[screen_col] & 0x40)) m_registers[REG_VBL_COL_OBJ] |= 0x04;
			if ((row[screen_col] & 0x20) && (row[screen_col] & 0x80)) m_registers[REG_VBL_COL_OBJ] |= 0x02;
			if ((row[screen_col] & 0x40) && (row[screen_col] & 0x80)) m_registers[REG_VBL_COL_OBJ] |= 0x01;

			// work out if the background hits this pixel
			int const   bg_col = x - m_x_offset - BG_START_X;
			bool const  bg = bool(bg_vbar_bits & (1U << (15 - (bg_col >> 3))));
			if (bg_draw && (0 <= bg_col) && (BG_WIDTH > bg_col) && bg && (bg_hbar_width > (bg_col & 0x07)))
			{
				// do object-background collisions
				if (row[screen_col] & 0x10) m_registers[REG_COL_BG_CMPL] |= 0x80;
				if (row[screen_col] & 0x20) m_registers[REG_COL_BG_CMPL] |= 0x40;
				if (row[screen_col] & 0x40) m_registers[REG_COL_BG_CMPL] |= 0x20;
				if (row[screen_col] & 0x80) m_registers[REG_COL_BG_CMPL] |= 0x10;
				if (!(row[screen_col] & 0x08)) row[screen_col] = bg_clr;
			}
			else if (!(row[screen_col] & 0x08))
			{
				// draw screen background if not obscured
				row[screen_col] = scrn_clr;
			}

			// clear collision crud and deal with pixel clock divider ratio
			row[screen_col++] &= 0x0f;
			for (int i = 1; (i < m_divider) && (vis_area.max_x >= screen_col); i++, screen_col++)
			{
				row[screen_col] = row[screen_col - 1];
			}
		}
	}

	m_screen_line++;
	m_vis_line++;
}


//-------------------------------------------------
//  bus access handlers
//-------------------------------------------------

READ8_MEMBER( s2636_device::read_data )
{
	mask_offset(offset);
	uint8_t data = m_registers[offset];

	// TODO: some locations are not readable, should return some garbage

	switch (offset)
	{
	case REG_COL_BG_CMPL:
	case REG_VBL_COL_OBJ:
		if (!machine().side_effects_disabled())
			m_registers[offset] = 0x00; // collision/completion/VRESET flags reset on read
		break;
	}

	return data;
}

WRITE8_MEMBER( s2636_device::write_data )
{
	mask_offset(offset);

	if (REG_SND_PERIOD == offset)
		m_stream->update();

	m_registers[offset] = data;
}

WRITE_LINE_MEMBER( s2636_device::write_intack )
{
	assert((ASSERT_LINE == state) || (HOLD_LINE == state) || (CLEAR_LINE == state));

	// pretend interrupt acknowledge is handled instantaneously
	m_intack = state;
	update_intreq(m_intreq);
	if (ASSERT_LINE != m_intreq)
		m_intack = CLEAR_LINE;
}


//-------------------------------------------------
//  sound_stream_update - generate audio output
//-------------------------------------------------

void s2636_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	stream_sample_t *buffer = outputs[0];
	while (samples--)
	{
		if (!m_sample_cnt)
		{
			if (m_registers[REG_SND_PERIOD])
			{
				m_sample_cnt = (machine().sample_rate() * (m_registers[REG_SND_PERIOD] + 1) * screen().scan_period()).seconds();
				m_sound_lvl = !m_sound_lvl;
			}
			else
			{
				m_sample_cnt = 1;
				m_sound_lvl = false;
			}
		}

		*buffer++ = m_sound_lvl ? 0x7fff : 0x0000;
		m_sample_cnt--;
	}
}


void s2636_device::update_intreq(int value)
{
	int const new_value = m_intack ? CLEAR_LINE : value;
	if (new_value != m_intreq)
	{
		m_intreq = new_value;
		m_intreq_cb(m_intreq);
	}
}