1 // license:BSD-3-Clause
2 // copyright-holders:Miodrag Milanovic,Karl-Ludwig Deisenhofer
3 /**********************************************************************
4 DEC VT Terminal video emulation
5 [ DC012 and DC011 emulation ]
6
7 01/05/2009 Initial implementation [Miodrag Milanovic]
8 Enhancements (2013 - 2015) by Karl-Ludwig Deisenhofer.
9
10 DEC VIDEO : STATE AS OF JULY 2014
11 ---------------------------------
12 Code developed with Rainbow-100 hardware in mind (a PC-like machine with a video circuit similar to a VT100 equipped w. AVO)
13 Details (termination characters, option hardware, MHFU watchdog) differ when compared to VT.
14
15 As of July 2014, the Rainbow video section is more mature than the 'VT100_VIDEO' part (essentially unchanged since 2009).
16 List of features not yet ported to VT: line doubler; 48 line mode; soft scroll; intensity / palette, AVO (ext.attributes)
17
18 FIXME: work out the differences and identify common code between VT and Rainbow. Unify different code trees.
19
20 - REQUIRED TODOS / TESTS :
21 * do line and character attributes (plus combinations) match real hardware?
22 * how does the AVO fit in?
23
24 - SCROLLING REGIONS / SPLIT SCREEN SCROLLING UNTESTED (if you open > 1 file with the VAX editor EDT)
25 See VT100 Technical Manual: 4.7.4 Address Shuffling to 4.7.9 Split Screen Smooth Scrolling.
26 More on scrolling regions: Rainbow 100 B technical documentation (QV069-GZ) April 1985 page 22
27
28 - NEW - INTERLACED MODE (Rainbow only):
29 Vertical resolution increases from 240 to 480, while the refresh rate halves (flickers on CRTs).
30 To accomplish this, the display controller repeats even lines in odd scans.
31 VTVIDEO activates line doubling in 24 line, interlaced mode only.
32
33 Although the DC12 has the ability to display 48 lines, most units are low on screen RAM and
34 won't even show 80 x 48. -> REASON: (83 x 48 = 3984 Byte) > (screen RAM) minus 'scratch area'
35 On a VT-180, BIOS scratch requires up to 700 bytes used for SETUP, flags, SILO, keyboard.
36
37 - POSSIBLE IMPROVEMENTS:
38
39 * exact colors for different VR201 monitors (for white, green and amber)
40
41 * ACCURATE VIDEO DELAYS:
42 Position of the first visible scanline (relative to the vertical reset) depends on
43 content of fill bytes at the beginning of screen RAM.
44
45 Six invisible, linked lines are initially provided (at location $EE000+ on a Rainbow).
46 Real-world DC hardware parses the (circular) chain until interrupted by blanking.
47 BIOS assumes 2 lines are omitted @ 60 and 5 lines are at 50 Hertz (-> longer blank).
48
49 VTVIDEO keeps it simple and skips up to 6 lines considered 'illegal' (offset < $12).
50 Works even in cases where undocumented delays or lines are poked (SQUINT; VIDEO.PAS).
51
52 Accurate timings for 4 modes (from VT-180 manual 6-30 on):
53 Vertical frequency = (2 * HF = 31.468 Khz) / DIVIDER
54 - 50 NI: (2 * HF = 31.468 Khz) / 630
55 - 50 interlaced: (2 * HF = 31.468 Khz) / 629
56 - 60 NI: (2 * HF = 31.468 Khz) / 524
57 - 60 interlaced: (2 * HF = 31.468 Khz) / 525
58 **********************************************************************/
59
60 #include "emu.h"
61 #include "video/vtvideo.h"
62 #include "screen.h"
63
64 /***************************************************************************
65 PARAMETERS
66 ***************************************************************************/
67
68 #define VERBOSE 1
69 #include "logmacro.h"
70
71
72 DEFINE_DEVICE_TYPE(VT100_VIDEO, vt100_video_device, "vt100_video", "VT100 Video")
73 DEFINE_DEVICE_TYPE(RAINBOW_VIDEO, rainbow_video_device, "rainbow_video", "Rainbow Video")
74
75
vt100_video_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)76 vt100_video_device::vt100_video_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
77 : device_t(mconfig, type, tag, owner, clock)
78 , device_video_interface(mconfig, *this)
79 , m_read_ram(*this)
80 , m_write_vert_freq_intr(*this)
81 , m_write_lba3_lba4(*this)
82 , m_write_lba7(*this)
83 , m_char_rom(*this, finder_base::DUMMY_TAG)
84 , m_palette(*this, "palette")
85 {
86 }
87
88
vt100_video_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)89 vt100_video_device::vt100_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
90 : vt100_video_device(mconfig, VT100_VIDEO, tag, owner, clock)
91 {
92 }
93
94
rainbow_video_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)95 rainbow_video_device::rainbow_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
96 : vt100_video_device(mconfig, RAINBOW_VIDEO, tag, owner, clock)
97 {
98 }
99
100
101 //-------------------------------------------------
102 // device_start - device-specific startup
103 //-------------------------------------------------
104
device_start()105 void vt100_video_device::device_start()
106 {
107 /* resolve callbacks */
108 m_read_ram.resolve_safe(0);
109 m_write_vert_freq_intr.resolve_safe();
110 m_write_lba3_lba4.resolve();
111 m_write_lba7.resolve_safe();
112
113 // LBA7 is scan line frequency update
114 m_lba7_change_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(vt100_video_device::lba7_change), this));
115 m_lba7_change_timer->adjust(clocks_to_attotime(765), 0, clocks_to_attotime(765));
116
117 // LBA3 and LBA4 are first two stages of divide-by-17 counter
118 m_lba3_change_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(vt100_video_device::lba3_change), this));
119
120 screen().register_vblank_callback(vblank_state_delegate(&vt100_video_device::vblank_callback, this));
121
122 save_item(NAME(m_lba7));
123 save_item(NAME(m_scroll_latch));
124 save_item(NAME(m_blink_flip_flop));
125 save_item(NAME(m_reverse_field));
126 save_item(NAME(m_basic_attribute));
127 save_item(NAME(m_columns));
128 save_item(NAME(m_height));
129 save_item(NAME(m_height_MAX));
130 save_item(NAME(m_fill_lines));
131 save_item(NAME(m_is_50hz));
132 save_item(NAME(m_interlaced));
133 }
134
135 //-------------------------------------------------
136 // device_reset - device-specific reset
137 //-------------------------------------------------
138
device_reset()139 void vt100_video_device::device_reset()
140 {
141 m_palette->set_pen_color(0, 0x00, 0x00, 0x00); // black
142 m_palette->set_pen_color(1, 0xff - 100, 0xff - 100, 0xff - 100); // WHITE (dim)
143 m_palette->set_pen_color(2, 0xff - 50, 0xff - 50, 0xff - 50); // WHITE NORMAL
144 m_palette->set_pen_color(3, 0xff, 0xff, 0xff); // WHITE (brighter)
145
146 m_height = 25;
147 m_height_MAX = 25;
148
149 m_lba7 = 0;
150
151 m_scroll_latch = 0;
152 m_scroll_latch_valid = false;
153 m_last_scroll = 0;
154
155 m_blink_flip_flop = 0;
156 m_reverse_field = 0;
157 m_basic_attribute = 0;
158
159 m_columns = 80;
160 m_is_50hz = false;
161
162 m_interlaced = true;
163 m_fill_lines = 2; // for 60Hz
164 recompute_parameters();
165 }
166
167 // ****** RAINBOW ******
168 // 4 color (= monochrome intensities) palette, 24 and 48 line modes.
device_reset()169 void rainbow_video_device::device_reset()
170 {
171 MHFU_FLAG = false;
172 MHFU_counter = 0; // **** MHFU: OFF ON COLD BOOT ! ****
173
174 // (rest of the palette is set in the main program)
175 m_palette->set_pen_color(0, 0x00, 0x00, 0x00); // black
176
177 m_height = 24; // <---- DEC-100
178 m_height_MAX = 48;
179
180 m_lba7 = 0;
181
182 m_scroll_latch = 0;
183 m_scroll_latch_valid = false;
184 m_last_scroll = 0;
185
186 m_blink_flip_flop = 0;
187 m_reverse_field = 0;
188 m_basic_attribute = 0;
189
190 m_columns = 80;
191
192 m_is_50hz = false;
193
194 m_interlaced = true;
195 m_fill_lines = 2; // for 60Hz (not in use any longer -> detected)
196 recompute_parameters();
197 }
198
199 /***************************************************************************
200 IMPLEMENTATION
201 ***************************************************************************/
202
203 // Also used by Rainbow-100 ************
recompute_parameters()204 void vt100_video_device::recompute_parameters()
205 {
206 // RAINBOW: 240 scan lines in non-interlaced mode (480 interlaced). VT-100 : same (?)
207 m_linedoubler = false; // 24 "true" lines (240) -OR- 48 lines with NO ghost lines (true 480)
208 if ((m_interlaced) && (m_height == 24 || m_height == 25))
209 m_linedoubler = true; // 24 lines with 'double scan' (false 480)
210
211 int vert_pix_visible = m_height * (m_linedoubler ? 20 : 10);
212 int vert_pix_total = m_is_50hz ? (m_interlaced ? 629 : 630/2) : (m_interlaced ? 525 : 524/2);
213 attotime frame_period = clocks_to_attotime(vert_pix_total * 1530);
214
215 // display 1 less filler pixel in 132 char. mode
216 int horiz_pix_visible = m_columns * (m_columns == 132 ? 9 : 10);
217 int horiz_pix_total = m_columns == 132 ? 1530 : 1020;
218
219 // dot clock is divided by 1.5 in 80 column mode
220 screen().set_unscaled_clock(m_columns == 132 ? clock() : clock() * 2 / 3);
221 rectangle visarea(0, horiz_pix_visible - 1, 0, vert_pix_visible - 1);
222 screen().configure(horiz_pix_total, vert_pix_total, visarea, frame_period.as_attoseconds());
223
224 LOG("(RECOMPUTE) HPIX: %d (%d) - VPIX: %d (%d)\n", horiz_pix_visible, horiz_pix_total, vert_pix_visible, vert_pix_total);
225 LOG("(RECOMPUTE) FREQUENCY: %f\n", frame_period.as_hz());
226 if (m_interlaced)
227 LOG("(RECOMPUTE) * INTERLACED *\n");
228 if (m_linedoubler)
229 LOG("(RECOMPUTE) * LINEDOUBLER *\n");
230 }
231
READ_LINE_MEMBER(vt100_video_device::lba7_r)232 READ_LINE_MEMBER(vt100_video_device::lba7_r)
233 {
234 return m_lba7;
235 }
236
vblank_callback(screen_device & screen,bool state)237 void vt100_video_device::vblank_callback(screen_device &screen, bool state)
238 {
239 if (state)
240 {
241 m_write_vert_freq_intr(ASSERT_LINE);
242 notify_vblank(true);
243 }
244 }
245
246 // Also used by Rainbow-100 ************
dc012_w(offs_t offset,uint8_t data)247 void vt100_video_device::dc012_w(offs_t offset, uint8_t data)
248 {
249 // Writes to [10C] and [0C] are treated differently
250 // - see 3.1.3.9.5 DC012 Programming Information (PC-100 spec)
251 if ((offset & 0x100) ) // MHFU is disabled by writing a value to port 010C.
252 {
253 // if (MHFU_FLAG == true)
254 // LOG("MHFU *** DISABLED *** \n");
255 MHFU_FLAG = false;
256 }
257 else
258 {
259 // if (MHFU_FLAG == false)
260 // LOG("MHFU ___ENABLED___ %s \n", m_maincpu->pc());
261 MHFU_FLAG = true;
262 MHFU_counter = 0;
263 }
264
265 if (!(data & 0x08))
266 {
267 if (!(data & 0x04))
268 {
269 m_scroll_latch_valid = false;
270 m_scroll_latch = data & 0x03; // LSB is written first.
271 }
272 else // set MSB of scroll_latch
273 {
274 m_scroll_latch = (m_scroll_latch & 0x03) | ((data & 0x03) << 2);
275 m_scroll_latch_valid = true;
276 }
277 }
278 else
279 {
280 switch (data & 0x0f)
281 {
282 case 0x08:
283 // toggle blink flip flop
284 m_blink_flip_flop = !(m_blink_flip_flop) ? 1 : 0;
285 break;
286 case 0x09:
287 // clear vertical frequency interrupt;
288 m_write_vert_freq_intr(CLEAR_LINE);
289 notify_vblank(false);
290 break;
291 case 0x0a:
292 // PDF: reverse field ON
293 m_reverse_field = 0;
294 break;
295 case 0x0b:
296 // PDF: reverse field OFF
297 // SETUP: dark screen selected
298 m_reverse_field = 1;
299 break;
300
301 // Writing a 11XX bit combination clears the blink-flip flop (valid for 0x0C - 0x0F):
302 case 0x0c:
303 // set basic attribute to underline / blink flip-flop off
304 m_blink_flip_flop = 0;
305 m_basic_attribute = 0; // (VT-100 without AVO): reverse video is interpreted as underline (basic_attribute 0)
306 break;
307
308 case 0x0d:
309 // (DEC Rainbow 100 DEFAULT) : reverse video with 24 lines / blink flip-flop off
310 m_blink_flip_flop = 0;
311 m_basic_attribute = 1; // (VT-100 without AVO): reverse video is interpreted as reverse (basic_attribute 1)
312
313 if (m_height_MAX == 25) break; // Abort on VT-100 for now.
314
315 m_height = 24; // (DEC Rainbow 100) : 24 line display
316 recompute_parameters();
317 break;
318
319 case 0x0e:
320 m_blink_flip_flop = 0; // 'unsupported' DC012 command. Turns blink flip-flop off (11XX).
321 break;
322
323 case 0x0f:
324 // (DEC Rainbow 100): reverse video with 48 lines / blink flip-flop off
325 m_blink_flip_flop = 0;
326 m_basic_attribute = 1;
327
328 // 0x0f = 'reserved' on VT 100
329 // Abort on VT-100 for now.
330 if (m_height_MAX == 25) break;
331
332 m_height = 48; // (DEC Rainbow 100) : 48 line display
333 recompute_parameters();
334 break;
335 }
336 }
337 }
338
339 // Writing to DC011 resets internal counters (& disturbs display) on real hardware.
dc011_w(uint8_t data)340 void vt100_video_device::dc011_w(uint8_t data)
341 {
342 if (!BIT(data, 5))
343 {
344 m_interlaced = true;
345
346 if (!BIT(data, 4))
347 m_columns = 80;
348 else
349 m_columns = 132;
350 }
351 else
352 {
353 m_interlaced = false;
354 m_is_50hz = BIT(data, 4);
355 m_fill_lines = m_is_50hz ? 5 : 2;
356 }
357
358 recompute_parameters();
359 }
360
brightness_w(uint8_t data)361 void vt100_video_device::brightness_w(uint8_t data)
362 {
363 //m_palette->set_pen_color(1, data, data, data);
364 }
365
366
367
video_update(bitmap_ind16 & bitmap,const rectangle & cliprect)368 void vt100_video_device::video_update(bitmap_ind16 &bitmap, const rectangle &cliprect)
369 {
370 uint16_t addr = 0;
371 int line = 0;
372 int xpos = 0;
373 int ypos = 0;
374 int x = 0;
375 uint8_t scroll_region = 1; // binary 1
376 uint8_t display_type = 3; // binary 11
377 uint16_t temp = 0;
378
379 if (m_read_ram(0) != 0x7f)
380 return;
381
382 int fill_lines = m_fill_lines;
383 int vert_charlines_MAX = m_height + fill_lines;
384 if (m_linedoubler)
385 {
386 vert_charlines_MAX *= 2;
387 fill_lines *= 2;
388 }
389 while (line < vert_charlines_MAX)
390 {
391 uint8_t code = m_read_ram(addr + xpos);
392 if (code == 0x7f)
393 {
394 // end of line, fill empty till end of line
395 if (line >= fill_lines)
396 {
397 for (x = xpos; x < ((display_type != 3) ? (m_columns / 2) : m_columns); x++)
398 {
399 display_char(bitmap, code, x, ypos, scroll_region, display_type, false, false, false, false, false);
400 }
401 }
402 // move to new data
403 temp = m_read_ram(addr + xpos + 1) * 256 + m_read_ram(addr + xpos + 2);
404 addr = (temp)& 0x1fff;
405 // if A12 is 1 then it is 0x2000 block, if 0 then 0x4000 (AVO - not actually implemented?)
406 /*if (addr & 0x1000)*/ addr &= 0xfff; /*else addr |= 0x2000;*/
407 scroll_region = (temp >> 15) & 1;
408 display_type = bitswap<2>((temp >> 13) & 3, 0, 1);
409 if (line >= fill_lines)
410 ypos++;
411 xpos = 0;
412 line++;
413 if (m_linedoubler)
414 line++;
415 }
416 else
417 {
418 // display regular char
419 if (line >= fill_lines)
420 {
421 uint8_t attr = m_read_ram(0x1000 + addr + xpos);
422 display_char(bitmap, code & 0x7f, xpos, ypos, scroll_region, display_type, BIT(code, 7), !BIT(attr, 2), !BIT(attr, 0), !BIT(attr, 1), false);
423 }
424 xpos++;
425 if (xpos > m_columns)
426 {
427 line++;
428 xpos = 0;
429 }
430 }
431 }
432 }
433
434 // ****** RAINBOW ******
435 // Display 10 scan lines (or 20 in interlaced mode) for one character @ position X/Y
436 // NOTE: X or Y indicate a character position!
437
438 // 5 possible CHARACTER STATES (normal, reverse, bold, blink, underline) are encoded into display_type.
439 // From the VT-180 specs, chapter 6-43 (where multiple attributes are described):
440 // 1) reverse characters [ XOR of reverse video and reverse screen (A) ] normally have dim backgrounds with black characters (B)
441 // 2) bold and reverse together give a background of normal intensity
442
443 // 3) blink controls intensity: normal chars vary between A) normal and dim (B) bold chars vary between bright and normal
444 // 4) blink applied to a
445 // A) reverse character causes it to alternate between normal and reverse video representation
446 // B) non-rev. " : alternate between usual intensity and the next lower intensity
447 // 5) underline causes the 9.th scan to be forced to
448 // A) white of the same intensity as the characters (for nonreversed characters),
449 // b) to black (for reverse characters)
450 // LINE ATTRIBUTE 'double_height' always is interpreted as 'double width + double height'
451
452 // ATTRIBUTES: No attributes = 0x0E
453 // 1 = display char. in REVERSE (encoded as 1 in attribute) HIGH ACTIVE
454 // 0 = display char. in BOLD (encoded as 2 in attribute) LOW ACTIVE
455 // 0 = display char. w. BLINK (encoded as 4 in attribute) LOW ACTIVE
456 // 0 = display char. w. UNDERLINE (encoded as 8 in attribute) LOW ACTIVE
display_char(bitmap_ind16 & bitmap,uint8_t code,int x,int y,uint8_t scroll_region,uint8_t display_type,bool invert,bool bold,bool blink,bool underline,bool blank)457 void vt100_video_device::display_char(bitmap_ind16 &bitmap, uint8_t code, int x, int y, uint8_t scroll_region, uint8_t display_type, bool invert, bool bold, bool blink, bool underline, bool blank)
458 {
459 uint16_t x_preset = x << 3; // x_preset = x * 9 (= 132 column mode)
460 x_preset += x;
461
462 if (m_columns == 80)
463 x_preset += x; // x_preset = x * 10 (80 column mode)
464
465 uint16_t y_preset;
466
467 uint16_t CHARPOS_y_preset = y << 3; // CHARPOS_y_preset = y * 10;
468 CHARPOS_y_preset += y;
469 CHARPOS_y_preset += y;
470
471 uint16_t DOUBLE_x_preset = x_preset << 1; // 18 for 132 column mode, else 20 (x_preset * 2)
472
473 uint8_t line = 0;
474 int bit = 0, j = 0;
475 int fg_intensity;
476 int back_intensity, back_default_intensity;
477
478 display_type = display_type & 3;
479
480 // * SCREEN ATTRIBUTES (see VT-180 manual 6-30) *
481 // 'reverse field' = reverse video over entire screen
482
483 // For reference: a complete truth table can be taken from TABLE 4-6-4 / VT100 technical manual.
484 // Following IF statements implement it in full. Code segments should not be shuffled.
485 invert = invert ^ m_reverse_field ^ m_basic_attribute;
486
487 fg_intensity = bold + 2; // FOREGROUND (FG): normal (2) or bright (3)
488
489 back_intensity = 0; // DO NOT SHUFFLE CODE AROUND !!
490 if ((blink != 0) && (m_blink_flip_flop != 0))
491 fg_intensity -= 1; // normal => dim bright => normal (when bold)
492
493 // INVERSION: background gets foreground intensity (reduced by 1).
494 // _RELIES ON_ on_ previous evaluation of the BLINK signal (fg_intensity).
495 if (invert != 0)
496 {
497 back_intensity = fg_intensity - 1; // BG: normal => dim; dim => OFF; bright => normal
498
499 if (back_intensity != 0) // FG: avoid 'black on black'
500 fg_intensity = 0;
501 else
502 fg_intensity = fg_intensity + 1; // FG: dim => normal; normal => bright
503 }
504
505 // BG: DEFAULT for entire character (underline overrides this for 1 line) -
506 back_default_intensity = back_intensity;
507
508 bool double_width = (display_type != 3) ? true : false; // all except normal: double width
509 bool double_height = (display_type & 1) ? false : true; // 0,2 = double height
510
511 int smooth_offset = 0;
512 if (scroll_region != 0)
513 smooth_offset = m_last_scroll; // valid after VBI
514
515 int i = 0;
516 int extra_scan_line = 0;
517 for (int scan_line = 0; scan_line < (m_linedoubler ? 20 : 10); scan_line++)
518 {
519 y_preset = CHARPOS_y_preset + scan_line;
520
521 // 'i' points to char-rom (plus scroll offset; if active) -
522 // IF INTERLACED: odd lines = even lines
523 i = (m_linedoubler ? (scan_line >> 1) : scan_line) + smooth_offset;
524
525 if (i > 9) // handle everything in one loop (in case of smooth scroll):
526 {
527 extra_scan_line += 1;
528
529 // Fetch appropriate character bitmap (one scan line) -
530 // IF INTERLACED: no odd lines
531 i = smooth_offset - (m_linedoubler ? (extra_scan_line >> 1) : extra_scan_line);
532
533 if (CHARPOS_y_preset >= extra_scan_line) // If result not negative...
534 y_preset = CHARPOS_y_preset - extra_scan_line; // correct Y pos.
535 else
536 {
537 y_preset = (m_linedoubler ? 480 : 240) - extra_scan_line;
538 i = 0; // blank line. Might not work with TCS or other charsets (FIXME)
539 }
540 }
541
542 switch (display_type)
543 {
544 case 0: // bottom half of 'double height, double width' char.
545 j = (i >> 1) + 5;
546 break;
547
548 case 2: // top half of 'double height, double width' char.
549 j = (i >> 1);
550 break;
551
552 default: // 1: double width
553 // 3: normal
554 j = i;
555 break;
556 }
557
558 // modify line since that is how it is stored in rom
559 if (j == 0) j = 15; else j = j - 1;
560
561 line = m_char_rom[(code << 4) + j]; // code * 16
562
563 // UNDERLINED CHARACTERS (CASE 5 - different in 1 line):
564 back_intensity = back_default_intensity; // 0, 1, 2
565 if (underline != 0)
566 {
567 if (i == 8)
568 {
569 if (invert == 0)
570 line = 0xff; // CASE 5 A)
571 else
572 {
573 line = 0x00; // CASE 5 B)
574 back_intensity = 0; // OVERRIDE: BLACK BACKGROUND
575 }
576 }
577 }
578
579 for (int b = 0; b < 8; b++) // 0..7
580 {
581 if (blank)
582 {
583 bit = m_reverse_field ^ m_basic_attribute;
584 }
585 else
586 {
587 bit = BIT((line << b), 7);
588
589 if (bit > 0)
590 bit = fg_intensity;
591 else
592 bit = back_intensity;
593 }
594
595 // Double, 'double_height + double_width', then normal.
596 if (double_width)
597 {
598 bitmap.pix(y_preset, DOUBLE_x_preset + (b << 1) + 1) = bit;
599 bitmap.pix(y_preset, DOUBLE_x_preset + (b << 1)) = bit;
600
601 if (double_height)
602 {
603 bitmap.pix(1 + y_preset, DOUBLE_x_preset + (b << 1) + 1) = bit;
604 bitmap.pix(1 + y_preset, DOUBLE_x_preset + (b << 1)) = bit;
605 }
606 }
607 else
608 {
609 bitmap.pix(y_preset, x_preset + b) = bit;
610 }
611 } // for (8 bit)
612
613 // char interleave (X) is filled with last bit
614 if (double_width)
615 {
616 // double chars: 18 or 20 bits
617 bitmap.pix(y_preset, DOUBLE_x_preset + 16) = bit;
618 bitmap.pix(y_preset, DOUBLE_x_preset + 17) = bit;
619
620 if (m_columns == 80)
621 {
622 bitmap.pix(y_preset, DOUBLE_x_preset + 18) = bit;
623 bitmap.pix(y_preset, DOUBLE_x_preset + 19) = bit;
624 }
625 }
626 else
627 { // normal chars: 9 or 10 bits
628 bitmap.pix(y_preset, x_preset + 8) = bit;
629
630 if (m_columns == 80)
631 bitmap.pix(y_preset, x_preset + 9) = bit;
632 }
633
634 /* The DEC terminals use a single ROM bitmap font and
635 * dot-stretching to synthesize multiple variants that are not
636 * just nearest neighbor resampled. The result is the same
637 * as one would get by fake-bolding; the already doubled raster image;
638 * by rendering twice with 1px horizontal offset.
639 *
640 * For details see: https://vt100.net/dec/vt220/glyphs
641 */
642 int prev_bit = back_intensity;
643 int bits_width = 21;
644 if (!double_width)
645 {
646 if (m_columns == 80)
647 bits_width = 11;
648 else
649 bits_width = 10;
650 }
651 for (int b = 0; b < bits_width; b++)
652 {
653 if (double_width)
654 {
655 if (bitmap.pix(y_preset, DOUBLE_x_preset + b) == fg_intensity)
656 {
657 prev_bit = fg_intensity;
658 }
659 else
660 {
661 if (prev_bit == fg_intensity)
662 bitmap.pix(y_preset, DOUBLE_x_preset + b) = fg_intensity;
663 prev_bit = back_intensity;
664 }
665 }
666 else
667 {
668 if (bitmap.pix(y_preset, x_preset + b) == fg_intensity)
669 {
670 prev_bit = fg_intensity;
671 }
672 else
673 {
674 if (prev_bit == fg_intensity)
675 bitmap.pix(y_preset, x_preset + b) = fg_intensity;
676 prev_bit = back_intensity;
677 }
678 }
679 }
680 } // for (scan_line)
681
682 }
683
684 // ****** RAINBOW ******
video_update(bitmap_ind16 & bitmap,const rectangle & cliprect)685 void rainbow_video_device::video_update(bitmap_ind16 &bitmap, const rectangle &cliprect)
686 {
687 uint16_t addr = 0;
688 uint16_t attr_addr = 0;
689 int line = 0;
690 int xpos = 0;
691 int ypos = 0;
692 uint8_t code;
693 int x = 0;
694 uint8_t scroll_region = 0; // DEFAULT TO 0 = NOT PART OF scroll_region
695 uint8_t display_type = 3; // NORMAL DISPLAY - binary 11
696 uint16_t temp = 0;
697
698 if (m_read_ram(0) != 0xff) // video uninitialized?
699 return;
700
701 // Skip fill (0xFF) lines and put result in ADDR.
702 for (int xp = 1; xp <= 6; xp += 1) // beware of circular references
703 {
704 // Fetch LINE ATTRIBUTE before it is gone
705 attr_addr = 0x1000 | ((addr + 1) & 0x0fff);
706
707 temp = m_read_ram(addr + 2) * 256 + m_read_ram(addr + 1);
708 addr = (temp)& 0x0fff;
709
710 temp = m_read_ram(attr_addr);
711 scroll_region = (temp)& 1;
712 display_type = (temp >> 1) & 3;
713
714 if (addr >= 0x12)
715 break;
716 }
717
718 int vert_charlines_MAX = m_height;
719 if (m_linedoubler)
720 vert_charlines_MAX *= 2;
721 while (line < vert_charlines_MAX)
722 {
723 code = m_read_ram(addr + xpos);
724
725 bool force_blank;
726 if (code == 0x00) // TODO: investigate side effect on regular zero character!
727 force_blank = true; // DEFAULT: filler chars (till end of line) and empty lines (00) will be blanked
728 else
729 force_blank = false; // else activate display.
730
731 if (code == 0xff) // end of line, fill empty till end of line
732 {
733 // All except 3 is DOUBLE WIDTH 40 or 66 chars per line
734 for (x = xpos; x < ((display_type != 3) ? (m_columns / 2) : m_columns); x++)
735 {
736 display_char(bitmap, code, x, ypos, scroll_region, display_type, false, false, false, false, true);
737 }
738
739 // LINE ATTRIBUTE - valid for all chars on next line ** DO NOT SHUFFLE **
740 attr_addr = 0x1000 | ((addr + xpos + 1) & 0x0fff);
741
742 // MOVE TO NEW DATA
743 temp = m_read_ram(addr + xpos + 2) * 256 + m_read_ram(addr + xpos + 1);
744 addr = (temp)& 0x0fff;
745
746 temp = m_read_ram(attr_addr);
747 scroll_region = (temp)& 1;
748 display_type = (temp >> 1) & 3;
749
750 ypos++; // Y + 1 in non-interlaced mode
751 if (m_linedoubler)
752 ypos++; // Y + 2 in 'double scan' mode (see -> 'display_char')
753
754 if (ypos > vert_charlines_MAX) // prevent invalid Y pos.
755 break;
756
757 xpos = 0;
758 line++;
759 }
760 else
761 {
762 attr_addr = 0x1000 | ((addr + xpos) & 0x0fff);
763 temp = (m_read_ram(attr_addr) & 15); // get character attributes
764
765 // see 'display_char' for an explanation of attribute encoding -
766 display_char(bitmap, code, xpos, ypos, scroll_region, display_type, BIT(temp, 0), !BIT(temp, 1), !BIT(temp, 2), !BIT(temp, 3), force_blank);
767
768 xpos++;
769 if (xpos > m_columns)
770 {
771 xpos = 0;
772 line++;
773 }
774 } // (else) valid char
775
776 } // while
777
778 }
779
notify_vblank(bool v)780 void rainbow_video_device::notify_vblank(bool v)
781 {
782 static bool v_last;
783 m_notify_vblank = v;
784
785 if (m_scroll_latch_valid)
786 {
787 // Line linking / unlinking is done during VBI (see 4.7.4 and up in VT manual).
788 if ((v == false) && (v_last == true))
789 m_last_scroll = m_scroll_latch;
790 }
791
792 v_last = v;
793 }
794
palette_select(int choice)795 void rainbow_video_device::palette_select(int choice)
796 {
797 switch (choice)
798 {
799 default:
800 case 0x01:
801 m_palette->set_pen_color(1, 0xff - 100, 0xff - 100, 0xff - 100); // WHITE (dim)
802 m_palette->set_pen_color(2, 0xff - 50, 0xff - 50, 0xff - 50); // WHITE NORMAL
803 m_palette->set_pen_color(3, 0xff, 0xff, 0xff); // WHITE (brighter)
804 break;
805
806 case 0x02:
807 m_palette->set_pen_color(1, 35, 200 - 55, 75); // GREEN (dim)
808 m_palette->set_pen_color(2, 35 + 55, 200, 75 + 55); // GREEN (NORMAL)
809 m_palette->set_pen_color(3, 35 + 110, 200 + 55, 75 + 110); // GREEN (brighter)
810 break;
811
812 case 0x03:
813 m_palette->set_pen_color(1, 213 - 47, 146 - 47, 82 - 47); // AMBER (dim)
814 m_palette->set_pen_color(2, 213, 146, 82); // AMBER (NORMAL)
815 m_palette->set_pen_color(3, 255, 193, 129); // AMBER (brighter)
816 break;
817 }
818 }
819
820 // ****** RAINBOW ******
video_blanking(bitmap_ind16 & bitmap,const rectangle & cliprect)821 void rainbow_video_device::video_blanking(bitmap_ind16 &bitmap, const rectangle &cliprect)
822 {
823 // 'In reverse screen mode, termination forces the beam to the screen background intensity'
824 // Background intensity means 'dim' (1) according to one source.
825 bitmap.fill(((m_reverse_field ^ m_basic_attribute) ? 1 : 0), cliprect);
826 }
827
828 #define MHFU_IS_ENABLED 1
829 #define MHFU_COUNT -1
830 #define MHFU_VALUE -2
831 #define MHFU_RESET_and_ENABLE -100
832 #define MHFU_RESET_and_DISABLE -200
833 #define MHFU_RESET -250
834
MHFU(int ASK)835 int rainbow_video_device::MHFU(int ASK)
836 {
837 switch (ASK)
838 {
839 case MHFU_IS_ENABLED: // "true": RETURN BOOLEAN (MHFU disabled or enabled?)
840 return MHFU_FLAG;
841
842 case MHFU_COUNT: // -1: increment IF ENABLED, return counter value (=> Rainbow.c)
843 if (MHFU_FLAG == true)
844 if (MHFU_counter < 254)
845 MHFU_counter++;
846
847 case MHFU_VALUE:
848 return MHFU_counter;
849
850 case MHFU_RESET: // -250 : RESET counter (NOTHING ELSE!)
851 MHFU_counter = 0;
852 return MHFU_FLAG;
853
854 case MHFU_RESET_and_ENABLE: // -100 : RESET and ENABLE MHFU counter
855 MHFU_counter = 0;
856 MHFU_FLAG = true;
857
858 return -100;
859
860 case MHFU_RESET_and_DISABLE: // -200 : RESET and DISABLE MHFU
861 MHFU_counter = 0;
862 MHFU_FLAG = false;
863
864 return -200;
865
866 default:
867 assert(1);
868 return -255;
869 } // switch
870 }
871
TIMER_CALLBACK_MEMBER(vt100_video_device::lba7_change)872 TIMER_CALLBACK_MEMBER(vt100_video_device::lba7_change)
873 {
874 m_lba7 = (m_lba7) ? 0 : 1;
875 m_write_lba7(m_lba7);
876
877 if (!m_write_lba3_lba4.isnull())
878 {
879 // The first of every eight low periods of LBA 3 is twice as long
880 m_write_lba3_lba4(2);
881 m_lba3_change_timer->adjust(clocks_to_attotime(90), 3);
882 }
883 }
884
TIMER_CALLBACK_MEMBER(vt100_video_device::lba3_change)885 TIMER_CALLBACK_MEMBER(vt100_video_device::lba3_change)
886 {
887 m_write_lba3_lba4(param & 3);
888 if (param <= 16)
889 m_lba3_change_timer->adjust(clocks_to_attotime(45), param + 1);
890 }
891
892
893 //-------------------------------------------------
894 // device_add_mconfig - add device configuration
895 //-------------------------------------------------
896
device_add_mconfig(machine_config & config)897 void vt100_video_device::device_add_mconfig(machine_config &config)
898 {
899 PALETTE(config, m_palette).set_entries(4);
900 }
901