/********************************************************************** DEC VT Terminal video emulation [ DC012 and DC011 emulation ] 01/05/2009 Initial implementation [Miodrag Milanovic] Copyright MESS Team. Visit http://mamedev.org for licensing and usage restrictions. **********************************************************************/ #include "emu.h" #include "vtvideo.h" /*************************************************************************** PARAMETERS ***************************************************************************/ #define VERBOSE 0 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ typedef struct _vt_video_t vt_video_t; struct _vt_video_t { devcb_resolved_read8 in_ram_func; devcb_resolved_write8 clear_video_interrupt; screen_device *screen; /* screen */ UINT8 *gfx; /* content of char rom */ int lba7; // dc012 attributes UINT8 scroll_latch; UINT8 blink_flip_flop; UINT8 reverse_field; UINT8 basic_attribute; // dc011 attributes UINT8 columns; UINT8 height; UINT8 skip_lines; UINT8 frequency; UINT8 interlaced; }; /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ INLINE vt_video_t *get_safe_token(device_t *device) { assert(device != NULL); assert(device->type() == VT100_VIDEO); return (vt_video_t *)downcast(device)->token(); } INLINE const vt_video_interface *get_interface(device_t *device) { assert(device != NULL); assert(device->type() == VT100_VIDEO); return (const vt_video_interface *) device->static_config(); } /*************************************************************************** IMPLEMENTATION ***************************************************************************/ static void vt_video_recompute_parameters(device_t *device) { vt_video_t *vt = get_safe_token(device); int horiz_pix_total = 0; int vert_pix_total = 0; rectangle visarea; horiz_pix_total = vt->columns * 10; vert_pix_total = 25 * 10; visarea.set(0, horiz_pix_total - 1, 0, vert_pix_total - 1); vt->screen->configure(horiz_pix_total, vert_pix_total, visarea, vt->screen->frame_period().attoseconds); } READ8_DEVICE_HANDLER( vt_video_lba7_r ) { vt_video_t *vt = get_safe_token(device); return vt->lba7; } WRITE8_DEVICE_HANDLER( vt_video_dc012_w ) { vt_video_t *vt = get_safe_token(device); if ((data & 0x08)==0) { if ((data & 0x04)==0) { // set lower part scroll vt->scroll_latch = (vt->scroll_latch & 0x0c) | (data & 0x03); } else { // set higher part scroll vt->scroll_latch = (vt->scroll_latch & 0x03) | ((data & 0x03) << 2); } } else { switch( data & 0x0f) { case 0x08: // toggle blink flip flop vt->blink_flip_flop = (vt->blink_flip_flop==0) ? 1 : 0; break; case 0x09: // clear vertical frequency interrupt; vt->clear_video_interrupt(0, 0); break; case 0x0A: // set reverse field on vt->reverse_field = 1; break; case 0x0B: // set reverse field off vt->reverse_field = 0; break; case 0x0C: // set basic attribute to underline vt->basic_attribute = 0; vt->blink_flip_flop = 0; break; case 0x0D: // set basic attribute to reverse video vt->basic_attribute = 1; vt->blink_flip_flop = 0; break; case 0x0E: case 0x0F: // reserved for future specification vt->blink_flip_flop = 0; break; } } } WRITE8_DEVICE_HANDLER( vt_video_dc011_w ) { vt_video_t *vt = get_safe_token(device); if (BIT(data,5)==0) { UINT8 col = vt->columns; if (BIT(data,4)==0) { vt->columns = 80; } else { vt->columns = 132; } if (col!=vt->columns) { vt_video_recompute_parameters(device); } vt->interlaced = 1; } else { if (BIT(data,4)==0) { vt->frequency = 60; vt->skip_lines = 2; } else { vt->frequency = 50; vt->skip_lines = 5; } vt->interlaced = 0; } } WRITE8_DEVICE_HANDLER( vt_video_brightness_w ) { //palette_set_color_rgb(device->machine(), 1, data, data, data); } static void vt_video_display_char(device_t *device,bitmap_ind16 &bitmap, UINT8 code, int x, int y,UINT8 scroll_region,UINT8 display_type) { UINT8 line=0; int i,b,bit=0,prevbit,invert=0,j; int double_width = (display_type==2) ? 1 : 0; vt_video_t *vt = get_safe_token(device); for (i = 0; i < 10; i++) { switch(display_type) { case 0 : // bottom half, double height j = (i >> 1)+5; break; case 1 : // top half, double height j = (i >> 1); break; case 2 : // double width case 3 : // normal j = i; break; default : j = 0; break; } // modify line since that is how it is stored in rom if (j==0) j=15; else j=j-1; line = vt->gfx[(code & 0x7f)*16 + j]; if (vt->basic_attribute==1) { if ((code & 0x80)==0x80) invert = 1; else invert = 0; } for (b = 0; b < 8; b++) { prevbit = bit; bit = (((line << b) & 0x80) ? 1 : 0); if (double_width) { bitmap.pix16(y*10+i, x*20+b*2) = (bit|prevbit)^invert; bitmap.pix16(y*10+i, x*20+b*2+1) = bit^invert; } else { bitmap.pix16(y*10+i, x*10+b) = (bit|prevbit)^invert; } } prevbit = bit; // char interleave is filled with last bit if (double_width) { bitmap.pix16(y*10+i, x*20+16) = (bit|prevbit)^invert; bitmap.pix16(y*10+i, x*20+17) = bit^invert; bitmap.pix16(y*10+i, x*20+18) = bit^invert; bitmap.pix16(y*10+i, x*20+19) = bit^invert; } else { bitmap.pix16(y*10+i, x*10+8) = (bit|prevbit)^invert; bitmap.pix16(y*10+i, x*10+9) = bit^invert; } } } void vt_video_update(device_t *device, bitmap_ind16 &bitmap, const rectangle &cliprect) { vt_video_t *vt = get_safe_token(device); UINT16 addr = 0; int line = 0; int xpos = 0; int ypos = 0; UINT8 code; int x = 0; UINT8 scroll_region = 1; // binary 1 UINT8 display_type = 3; // binary 11 UINT16 temp =0; if (vt->in_ram_func(0) !=0x7f) return; while(line < (vt->height + vt->skip_lines)) { code = vt->in_ram_func(addr + xpos); if (code == 0x7f) { // end of line, fill empty till end of line if (line >= vt->skip_lines) { for(x = xpos; x < ((display_type==2) ? (vt->columns / 2) : vt->columns); x++ ) { vt_video_display_char(device,bitmap,code,x,ypos,scroll_region,display_type); } } // move to new data temp = vt->in_ram_func(addr+xpos+1)*256 + vt->in_ram_func(addr+xpos+2); addr = (temp) & 0x1fff; // if A12 is 1 then it is 0x2000 block, if 0 then 0x4000 (AVO) if (addr & 0x1000) addr &= 0xfff; else addr |= 0x2000; scroll_region = (temp >> 15) & 1; display_type = (temp >> 13) & 3; if (line >= vt->skip_lines) { ypos++; } xpos=0; line++; } else { // display regular char if (line >= vt->skip_lines) { vt_video_display_char(device,bitmap,code,xpos,ypos,scroll_region,display_type); } xpos++; if (xpos > vt->columns) { line++; xpos=0; } } } } static void rainbow_video_display_char(device_t *device,bitmap_ind16 &bitmap, UINT8 code, int x, int y,UINT8 scroll_region,UINT8 display_type) { UINT8 line=0; int i,b,bit=0,j; int double_width = (display_type==2) ? 1 : 0; vt_video_t *vt = get_safe_token(device); for (i = 0; i < 10; i++) { switch(display_type) { case 0 : // bottom half, double height j = (i >> 1)+5; break; case 1 : // top half, double height j = (i >> 1); break; case 2 : // double width case 3 : // normal j = i; break; default : j = 0; break; } // modify line since that is how it is stored in rom if (j==0) j=15; else j=j-1; line = vt->gfx[code*16 + j]; if (vt->basic_attribute==1) { if ((code & 0x80)==0x80) { line = line ^ 0xff; } } for (b = 0; b < 8; b++) { bit = ((line << b) & 0x80) ? 1 : 0; if (double_width) { bitmap.pix16(y*10+i, x*20+b*2) = bit; bitmap.pix16(y*10+i, x*20+b*2+1) = bit; } else { bitmap.pix16(y*10+i, x*10+b) = bit; } } // char interleave is filled with last bit if (double_width) { bitmap.pix16(y*10+i, x*20+16) = bit; bitmap.pix16(y*10+i, x*20+17) = bit; bitmap.pix16(y*10+i, x*20+18) = bit; bitmap.pix16(y*10+i, x*20+19) = bit; } else { bitmap.pix16(y*10+i, x*10+8) = bit; bitmap.pix16(y*10+i, x*10+9) = bit; } } } void rainbow_video_update(device_t *device, bitmap_ind16 &bitmap, const rectangle &cliprect) { vt_video_t *vt = get_safe_token(device); UINT16 addr = 0; UINT16 attr_addr = 0; int line = 0; int xpos = 0; int ypos = 0; UINT8 code; int x = 0; UINT8 scroll_region = 1; // binary 1 UINT8 display_type = 3; // binary 11 UINT16 temp =0; while(line < (vt->height + vt->skip_lines)) { code = vt->in_ram_func(addr + xpos); if (code == 0xff) { // end of line, fill empty till end of line if (line >= vt->skip_lines) { for(x = xpos; x < ((display_type==2) ? (vt->columns / 2) : vt->columns); x++ ) { rainbow_video_display_char(device,bitmap,code,x,ypos,scroll_region,display_type); } } // move to new data temp = vt->in_ram_func(addr+xpos+2)*256 + vt->in_ram_func(addr+xpos+1); addr = (temp) & 0x0fff; attr_addr = ((temp) & 0x1fff) - 2; // if A12 is 1 then it is 0x2000 block, if 0 then 0x4000 (AVO) if (temp & 0x1000) attr_addr &= 0xfff; else attr_addr |= 0x1000; temp = vt->in_ram_func(attr_addr); scroll_region = (temp) & 1; display_type = (temp>> 1) & 3; if (line >= vt->skip_lines) { ypos++; } xpos=0; line++; } else { // display regular char if (line >= vt->skip_lines) { rainbow_video_display_char(device,bitmap,code,xpos,ypos,scroll_region,display_type); } xpos++; if (xpos > vt->columns) { line++; xpos=0; } } } } /*------------------------------------------------- DEVICE_START( vt_video ) -------------------------------------------------*/ static TIMER_CALLBACK(lba7_change) { device_t *device = (device_t *)ptr; vt_video_t *vt = get_safe_token(device); vt->lba7 = (vt->lba7) ? 0 : 1; } static DEVICE_START( vt_video ) { vt_video_t *vt = get_safe_token(device); const vt_video_interface *intf = get_interface(device); /* resolve callbacks */ vt->in_ram_func.resolve(intf->in_ram_func, *device); vt->clear_video_interrupt.resolve(intf->clear_video_interrupt, *device); /* get the screen device */ vt->screen = device->machine().device(intf->screen_tag); assert(vt->screen != NULL); vt->gfx = device->machine().root_device().memregion(intf->char_rom_region_tag)->base(); assert(vt->gfx != NULL); // LBA7 is scan line frequency update device->machine().scheduler().timer_pulse(attotime::from_nsec(31778), FUNC(lba7_change), 0, (void *) device); } /*------------------------------------------------- DEVICE_RESET( vt_video ) -------------------------------------------------*/ static DEVICE_RESET( vt_video ) { vt_video_t *vt = get_safe_token(device); palette_set_color_rgb(device->machine(), 0, 0x00, 0x00, 0x00); // black palette_set_color_rgb(device->machine(), 1, 0xff, 0xff, 0xff); // white vt->height = 25; vt->lba7 = 0; vt->scroll_latch = 0; vt->blink_flip_flop = 0; vt->reverse_field = 0; vt->basic_attribute = 0; vt->columns = 80; vt->frequency = 60; vt->interlaced = 1; vt->skip_lines = 2; // for 60Hz } const device_type VT100_VIDEO = &device_creator; vt100_video_device::vt100_video_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, VT100_VIDEO, "VT100 Video", tag, owner, clock) { m_token = global_alloc_array_clear(UINT8, sizeof(vt_video_t)); } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void vt100_video_device::device_config_complete() { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void vt100_video_device::device_start() { DEVICE_START_NAME( vt_video )(this); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void vt100_video_device::device_reset() { DEVICE_RESET_NAME( vt_video )(this); }