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