Skip to content

Instantly share code, notes, and snippets.

@ErnWong
Last active January 3, 2018 09:19
Show Gist options
  • Save ErnWong/ed9e519536659dc1facf9d5aa215a875 to your computer and use it in GitHub Desktop.
Save ErnWong/ed9e519536659dc1facf9d5aa215a875 to your computer and use it in GitHub Desktop.
WIP Model of the IBM VGA.

Things yet to add in this little model:

  • Use Offset register to reinitialize the address after horizontal retrace
  • Retrace signals
  • Line Compare register for split screens
  • Start Address for page flipping
  • Display enable signals
  • There is a register field in the Miscellaneous Graphics Register, called the Chain Odd/Even Enable bit. From the manual: "When set to 1, this bit directs the system address bit, A0, to be replaced by a higher-order bit. The odd map is then selected when A0 is 1, and the even map when A0 is 0." What exactly is this higher order bit? If it is bit 16, as people seem to say on https://www.vogons.org/viewtopic.php?f=9&t=53803, then the display buffer addresses will all be odd addresses in VGA mode 4h/5h because all the system address are between 0xB8000 and 0xBBFFF, which does not make sense.
// Work in progress model of the VGA - adding and changing stuff as I continue learning about VGA.
VGAScreen.prototype.graphics_controller_write = function(address_given, value)
{
var plane_dword;
var write_mode = this.planar_mode & 3;
var bitmask = this.apply_feed(this.planar_bitmap);
var setreset_dword = this.apply_expand(this.planar_setreset);
var setreset_enable_dword = this.apply_expand(this.planar_setreset_enable);
// Write modes - see http://www.osdever.net/FreeVGA/vga/graphreg.htm#05
switch(write_mode)
{
case 0:
value = this.apply_rotate(value);
plane_dword = this.apply_feed(value);
plane_dword = this.apply_setreset(plane_dword, setreset_enable_dword);
plane_dword = this.apply_logical(plane_dword, this.latch_dword);
plane_dword = this.apply_bitmask(plane_dword, bitmask);
break;
case 1:
plane_dword = this.latch_dword;
break;
case 2:
plane_dword = this.apply_expand(value);
plane_dword = this.apply_logical(plane_dword, this.latch_dword);
plane_dword = this.apply_bitmask(plane_dword, bitmask);
break;
case 3:
value = this.apply_rotate(value);
bitmask &= this.apply_feed(value);
plane_dword = setreset_dword;
plane_dword = this.apply_bitmask(plane_dword, bitmask);
break;
}
if(this.miscellaneous_register & 2)
{
var address_calculated = (address_given & 0xFFFE) | (address_given >> 16 & 1);
}
else
{
var address_calculated = address_given;
}
this.plane_update(address_given, address_calculated, plane_dword);
};
/**
* Copies data_byte into the four planes, with each plane
* represented by an 8-bit field inside the dword.
* @param {number} data_byte
* @return {number} 32-bit number representing the bytes for each plane.
*/
VGAScreen.prototype.apply_feed = function(data_byte)
{
var dword = data_byte;
dword |= data_byte << 8;
dword |= data_byte << 16;
dword |= data_byte << 24;
return dword;
};
/**
* Expands bits 0 to 3 to ocupy bits 0 to 31. Each
* bit is expanded to 0xFF if set or 0x00 if clear.
* @param {number} data_byte
* @return {number} 32-bit number representing the bytes for each plane.
*/
VGAScreen.prototype.apply_expand = function(data_byte)
{
var dword = data_byte & 0x1 ? 0xFF : 0x00;
dword |= (data_byte & 0x2 ? 0xFF : 0x00) << 8;
dword |= (data_byte & 0x4 ? 0xFF : 0x00) << 16;
dword |= (data_byte & 0x8 ? 0xFF : 0x00) << 24;
return dword;
};
/**
* Planar Write - Barrel Shifter
* @param {number} data_byte
* @return {number}
* @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-01.html#Heading3}
* @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#03}
*/
VGAScreen.prototype.apply_rotate = function(data_byte)
{
var wrapped = data_byte | (data_byte << 8);
var count = this.planar_rotate_reg & 0x7;
var shifted = wrapped >>> count;
return shifted & 0xFF;
};
/**
* Planar Write - Set / Reset Circuitry
* @param {number} data_dword
* @param {number} enable_dword
* @return {number}
* @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-03.html#Heading5}
* @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#00}
*/
VGAScreen.prototype.apply_setreset = function(data_dword, enable_dword)
{
var setreset_dword = this.apply_expand(this.planar_setreset);
data_dword |= enable_dword & setreset_dword;
data_dword &= ~enable_dword | setreset_dword;
return data_dword;
};
/**
* Planar Write - ALU Unit
* @param {number} data_dword
* @param {number} latch_dword
* @return {number}
* @see {@link http://www.phatcode.net/res/224/files/html/ch24/24-01.html#Heading3}
* @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#03}
*/
VGAScreen.prototype.apply_logical = function(data_dword, latch_dword)
{
switch(this.planar_rotate_reg & 0x18)
{
case 0x08:
return data_dword & latch_dword;
case 0x10:
return data_dword | latch_dword;
case 0x18:
return data_dword ^ latch_dword;
}
return data_dword;
};
/**
* Planar Write - Bitmask Unit
* @param {number} data_dword
* @param {number} bitmask_dword
* @return {number}
* @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-01.html#Heading2}
* @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#08}
*/
VGAScreen.prototype.apply_bitmask = function(data_dword, bitmask_dword)
{
var plane_dword = bitmask_dword & data_dword;
plane_dword |= ~bitmask_dword & this.latch_dword;
return plane_dword;
};
VGAScreen.prototype.plane_update = function(address_given, address_calculated, plane_dword)
{
var plane_select;
switch(this.sequencer_memory_mode & 0xC)
{
// Odd/Even
case 0x0:
plane_select = 1 << (address_given & 1);
plane_select *= 0x5;
break;
// Chain 4
// Note: FreeVGA may have mistakenly stated that this bit field is
// for system read only, yet the IBM Open Source Graphics Programmer's
// Reference Manual explicitly states "both read and write".
case 0x8:
case 0xC:
plane_select = 1 << (address_given & 0x3);
break;
}
// Overriding plane masks
plane_select &= this.plane_write_bm;
// See: http://www.osdever.net/FreeVGA/vga/seqreg.htm#02
if(plane_select & 0x1) this.plane0[address_calculated] = (plane_dword >> 0) & 0xFF;
if(plane_select & 0x2) this.plane1[address_calculated] = (plane_dword >> 8) & 0xFF;
if(plane_select & 0x4) this.plane2[address_calculated] = (plane_dword >> 16) & 0xFF;
if(plane_select & 0x8) this.plane3[address_calculated] = (plane_dword >> 24) & 0xFF;
};
VGAScreen.prototype.pixel_clock_tick = function()
{
this.pixel_clock_counter++;
// 8/9 Dot Clocks
this.pixel_clock_counter %= this.clocking_mode & 1 ? 8 : 9;
if(!this.pixel_clock_counter)
{
this.character_clock_tick();
}
this.attribute_controller_tick();
this.dac_tick();
};
VGAScreen.prototype.character_clock_tick = function()
{
this.character_clock_counter++;
this.character_clock_counter &= 3;
// Check row scan counter
this.check_address_increment();
this.update_memory_address();
this.check_serialize();
this.horizontal_character_counter++;
};
VGAScreen.prototype.check_serialize = function()
{
var skip_serialize = 0;
// Shift 4 - Load every fourth character clock.
if(this.clocking_mode & 0x10)
{
skip_serialize |= this.character_clock_counter & 3;
}
// Shift Load - Load every other character clock.
if(this.clocking_mode & 0x4)
{
skip_serialize |= this.character_clock_counter & 1;
}
if(!skip_serialize)
{
this.graphics_controller_serialize();
}
};
VGAScreen.prototype.check_address_increment = function()
{
var skip_address_increment = 0;
// Count by 4 - Increment address every fourth character clock.
// Used for doubleword addresses.
if(this.underline_location & 0x20)
{
skip_address_increment |= this.character_clock_counter & 3;
}
// Count by 2 - Increment address every other character clock.
// Used for creating byte/word refresh address.
else if(this.crt_mode & 0x8)
{
skip_address_increment |= this.cahracter_clock_counter & 1;
}
if(!skip_address_increment)
{
this.memory_address_counter++;
}
};
VGAScreen.prototype.update_memory_address = function()
{
// Doubleword mode - Memory addresses are doubleword addresses
// Frame buffer addresses are interpreted by the frame buffer address
// decoder as being DWord addresses, regardless of the setting of bit 6
// of the CRT Mode Control Register (CR17).
if(this.underline_location & 0x40)
{
// DWord Mode - Addresses from the memory address counter are shifted
// twice to become DWord-aligned.
this.memory_address = this.memory_address_counter << 2;
this.memory_address |= (this.memoy_address_counter >> 12) & 0x3;
}
// Word/Byte Mode
else if(this.crt_mode & 0x40)
{
// Word Mode
this.memory_address = this.memory_address_counter << 1;
// Address Wrap
if(this.crt_mode & 0x20)
{
this.memory_address |= (this.memoy_address_counter >> 15) & 0x1;
}
else
{
this.memory_address |= (this.memoy_address_counter >> 13) & 0x1;
}
}
else
{
// Byte Mode
this.memory_address = this.memory_address_counter;
}
// Compatibility Mode Support - Substitutes bit 13 of the shifted memory
// address with bit 0 of the row scan counter.
// Used for odd scan and even scan pages.
if(!(this.crt_mode & 0x1))
{
this.memory_address &= 0xDFFF;
this.memory_address |= (this.row_scan_counter & 0x1) << 13;
}
// Select Row Scan Counter - Substitutes bit 14 of the shifted memory
// address with bit 1 of the row scan counter.
if(!(this.crt_mode & 0x2))
{
this.memory_address &= 0xBFFF;
this.memory_address |= (this.row_scan_counter & 0x2) << 14;
}
};
VGAScreen.prototype.graphics_controller_serialize = function()
{
var plane0_byte;
var plane1_byte;
var plane2_byte;
var plane3_byte;
if(this.sequencer_memory_mode & 0x8)
{
// Chain 4
plane0_byte = this.plane0[this.memory_address];
plane1_byte = this.plane1[this.memory_address + 1];
plane2_byte = this.plane2[this.memory_address + 2];
plane3_byte = this.plane3[this.memory_address + 3];
}
else if(this.graphics_mode & 0x10)
{
// Odd/Even??????? CGA Emulation??
plane0_byte = this.plane0[this.memory_address];
plane1_byte = this.plane1[this.memory_address + 1];
plane2_byte = this.plane2[this.memory_address];
plane3_byte = this.plane3[this.memory_address + 1];
}
else
{
plane0_byte = this.plane0[this.memory_address];
plane1_byte = this.plane1[this.memory_address];
plane2_byte = this.plane2[this.memory_address];
plane3_byte = this.plane3[this.memory_address];
}
if(this.planar_mode & 0x40)
{
// 256-Color Shift Mode
this.shift_loads.push(plane0_byte >> 4 & 0xF);
this.shift_loads.push(plane0_byte >> 0 & 0xF);
this.shift_loads.push(plane1_byte >> 4 & 0xF);
this.shift_loads.push(plane1_byte >> 0 & 0xF);
this.shift_loads.push(plane2_byte >> 4 & 0xF);
this.shift_loads.push(plane2_byte >> 0 & 0xF);
this.shift_loads.push(plane3_byte >> 4 & 0xF);
this.shift_loads.push(plane3_byte >> 0 & 0xF);
}
else if(this.planar_mode & 0x20)
{
// Packed Shift Mode
// Bios Video Modes 4h and 5h
this.shift_loads.push((plane0_byte >> 6 & 0x3) | (plane2_byte >> 4 & 0xC));
this.shift_loads.push((plane0_byte >> 4 & 0x3) | (plane2_byte >> 2 & 0xC));
this.shift_loads.push((plane0_byte >> 2 & 0x3) | (plane2_byte >> 0 & 0xC));
this.shift_loads.push((plane0_byte >> 0 & 0x3) | (plane2_byte << 2 & 0xC));
this.shift_loads.push((plane1_byte >> 6 & 0x3) | (plane3_byte >> 4 & 0xC));
this.shift_loads.push((plane1_byte >> 4 & 0x3) | (plane3_byte >> 2 & 0xC));
this.shift_loads.push((plane1_byte >> 2 & 0x3) | (plane3_byte >> 0 & 0xC));
this.shift_loads.push((plane1_byte >> 0 & 0x3) | (plane3_byte << 2 & 0xC));
}
else
{
// Planar Shift Mode
// See http://www.osdever.net/FreeVGA/vga/vgaseq.htm
// Shift these, so that the bits for the color are in
// the correct position in the for loop
var plane0_byte <<= 0;
var plane1_byte <<= 1;
var plane2_byte <<= 2;
var plane3_byte <<= 3;
for(var i = 7; i >= 0; i--)
{
this.shift_loads.push(
plane0_byte >> i & 1 |
plane1_byte >> i & 2 |
plane2_byte >> i & 4 |
plane3_byte >> i & 8;
);
}
}
};
VGAScreen.prototype.attribute_controller_tick = function()
{
// Pixel Width/Clock Select
if(this.attribute_mode & 0x40)
{
// Two sets of 4 bits of data are assembled to generate 8 bits of
// video data which is output every other dot clock, and the Palette
// Registers (AR[00:0F]) are bypassed.
if(this.shift_loads.length & 1)
{
// Lower 4 bits - complete
this.color8 = color8_partial | this.shift_loads.shift();
}
else
{
// Higher 4 bits - incomplete
this.color8_partial = this.shift_loads.shift() << 4;
}
}
else
{
this.color8 = this.dac_map[this.shift_loads.shift()];
}
};
VGAScreen.prototype.dac_tick = function()
{
this.color = this.vga256_palette[this.color8];
};
VGAScreen.prototype.update_screen_size = function()
{
var width = this.horizontal_display_enable_end << 3;
// Pixel Width/Clock Select
if(this.attribute_mode & 0x40)
{
width >>= 1;
}
var height = this.vertical_display_enable_end;
// Double Scanning
if(this.maximum_scan_line & 0x80)
{
height >>= 1;
}
// Maximum scan line, aka scan lines per character row.
// This is the number of repeats - 1 for graphic modes.
height /= this.maximum_scan_line & 0x1F;
// Odd and Even Row Scan Counter.
// Despite repeated address counter values, because bit 13 of the shifted
// address is substituted with bit 0 of the row scan counter, a different
// display buffer address is generated instead of repeated.
if(!(this.crt_mode & 0x1))
{
height <<= 1;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment