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
487
|
// 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, const XTAL &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 = stream_alloc(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.pix(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
//-------------------------------------------------
uint8_t s2636_device::read_data(offs_t offset)
{
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;
}
void s2636_device::write_data(offs_t offset, uint8_t data)
{
mask_offset(offset);
if (REG_SND_PERIOD == offset)
m_stream->update();
m_registers[offset] = data;
}
void s2636_device::write_intack(int state)
{
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, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
auto &buffer = outputs[0];
for (int sampindex = 0; sampindex < buffer.samples(); sampindex++)
{
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.put(sampindex, m_sound_lvl ? 1.0 : 0.0);
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);
}
}
|