15

I've been trying to implement a blitter in MS-DOS, but before I do that I wanted to test the various graphics modes of the VGA graphics card. (I'm emulating using DOSBox.) According to this paper on VGA, normal drawing to the bitmap screen is done in "replace mode," where data written to VRAM simply overwrites what is currently stored there. There are three other modes which are supposedly accessed by OUTing the correct control code to port 03CEh, but I can't get them to work. From the website:

mov ax,1803h
mov dx,03CEh
out dx,ax

will set the VGA card write mode to XOR.

Unfortunately I can't get this to work. The code below fills the screen with VGA color 09 in replace mode, then in XOR mode, which should result in a black screen since any number XORed with itself equals 0. Yet, the screen just ends up filled with the color I chose at the start. I turned to the OSDev wiki for further guidance but all I could find was this chart which I have trouble understanding.

Is the quote from the first link correct on changing modes? If so, what am I doing wrong? If the reference material is wrong, how do I invoke the other drawing modes?

I've provided the source code below in case I made a mistake and just can't see it:

mov ax,@data
mov ds,ax

mov ax,0A000h
mov es,ax       ;vga vram


mov ax,13h
int 10h

mov al,09h
mov cx,VGA_VRAMSIZE
mov di,0
rep stosb
mov di,0

push ax
push dx
    mov ax,1803h
    mov dx,03CEh
    out dx,ax       ;VGA "XOR" MODE
pop dx
pop ax

mov al,09h
mov cx,VGA_VRAMSIZE
mov di,0
rep stosb
mov di,0

VGA Output. The screen was supposed to be black after all this was done.

2
  • 6
    You're missing a possibility there: Perhaps the reference and your code are correct but DOSBox doesn't emulate these modes.
    – ecm
    Oct 7 at 10:09
  • 4
    IIRC, the logical operations use data latched from a read, so you need a rep movsb with the right setup. And potentially fiddle with set/reset patterns.
    – dirkt
    Oct 7 at 12:50
14

Running your code as-is on a real 286/VGA (Cirrus Logic CL-GD5420) produces this image:

enter image description here

Inserting one dummy read before the second fill cycle (just mov ax, es:[0]) gives the image you were expecting:

enter image description here

So it would seem that at least there might be a difference between the DOSBox implementation and the real hardware. If it does implement logic operations, then see @dirkt’s hint on populating the latch register. Perhaps it just remains zeroed in DOSBox, so xor-ing your 09h with the latched value of zero gives impression the xor is not there at all. While on real hardware it gets something as a byproduct of the previous activity of the system.

3
  • 4
    Thank you! That worked just like you said. (To be honest, I was in the wrong video mode as well! 13h doesn't seem to work with the AND/OR/XOR modes. Oct 7 at 20:06
  • Note that the single read only works because the screen is uniformly colored. The "MOV" instruction added by this answer puts the first byte, and then the second byte (the word read gets split) of screen memory into the EGA/VGA latch registers. You then write the latch contents XOR 9 over the whole screen. If you want to XOR every single screen byte with 9, you need to use rep movsb, with source and destination pointing at the same address (NEEDS to be bytewise!), not a stos instruction. Oct 11 at 9:21
  • Yes, @supercat answer explains the latch register behavior in great details.
    – Vlad
    Oct 11 at 12:05
10

The 16-color modes on the VGA use a hardware design borrowed from the EGA. On the EGA (and VGA), the bitwise drawing modes don't act upon memory directly, but instead act upon values stored in four eight-bit registers, each of which holds a byte of data from one of four color planes. Reading any byte of data from the display will load each of the four latches with the contents of a byte from its associated plane. Writing a byte of data when using a Boolean mode, will for each color plane that is enabled for writing, combine the latched value in its register with the value that being written by the CPU, and store the result to the corresponding byte in memory.

The net effect is that it's possible for the CPU to perform a Boolean operator on one byte in all four planes by performing a byte read followed by a byte write. Requiring the CPU to "manually" read each byte (likely ignoring the result) before each write imposed a non-trivial speed penalty, but allowed simpler hardware than would have been needed to perform the read-modify-write cycle automatically. Unfortunately, this design aged rather badly rather quickly. Use of this complicated latching circuitry improves performance when using an 8-bit display card on a CPU that is slow or has cannot afford to keep a copy of display contents in main memory, but it cannot be used effectively on faster machines because of a few problems:

  1. It is necessary to read and write back each byte before proceeding to the next one. Even when using an 8-bit bus, this degraded performance compared with what would have been possible using 16-bit operations. Even if a 16-bit access to the VGA would have required two bus operations, the number of other operations on the main memory bus would have been cut in half.

  2. Because memory is shared between the CPU and the circuitry that feeds display contents to the monitor, the former can only access it at moments when the latter isn't. Any request by the former must wait until the display hardware has a spare moment available. Although early display cards made the CPU wait on every operation whether it was reading or writing, it didn't take long for card manufacturers to recognize that they could improve performance by a latch which would immediately capture a write request and release the CPU to perform other operations. The display card could then write the data at its leisure and only make the CPU wait if it asked to write another byte while the first byte was still pending. While this greatly improved the performance of write operations, this approach wasn't possible with reads because the CPU needed to know the results of a read request before it could proceed with anything else. What's ironic is that code that was performing read-modify-write operations wouldn't care what value was returned by the read operations, but the CPU would have to spend most of its time waiting for data it was going to ignore.

Incidentally, the original EGA cards, like many other systems, used banks of eight single-bit-wide DRAM chips, and such banks can easily be adapted to allow selective writing of bits within each byte. A lot of circuitry could have been replaced with five eight-bit-wide latch chips, a couple of quad-NAND chips, and a small amount of circuitry to control when those latches are enabled, and such a design would have allowed software to either write data normally or else interpret written data as a bit mask to selectively write bits of memory with a 32-bit (8 bits on each of four bit planes) pattern, without having to use read-modify-write operations. I'd be curious whether any DRAM-based memory systems (whether used as display cards or for any other purpose) supported bit-masked operations in such fashion. While such a design would be locked into using 32 discrete DRAM chips rather than using higher-density 64Kx4 or 256Kx4 chips when they became available, both price and performance would be better than the far more complicated approach used by the VGA.

4
  • You do not necessarily lock into x1 DRAM chips if you want bitwise write masking. x4 VRAM chips often implemented a masked write (sometimes called write-by-bit) feature, see e.g. the HM534253B Series. Newer VRAM chips sometimes have a write mask register that is loadable by dedicated mask load cycles, see e.g. the HM5316123B series. Oct 14 at 1:01
  • @MichaelKarcher: The usage of the mask write here would be to have the masks controlled directly by the bytes of data written by the CPU, so I don't think anything requiring a "mask load" feature would be useful. In any case, x1 DRAM chips continued to be used for quite awhile, so a design based on masked writing could have remained useful for a long time.
    – supercat
    Oct 14 at 14:42
  • The x4 chips I mentioned accept a mask on the same RAS/CAS cycle as the data. The mask is input with the row address, and the data is input with/after the column address. Only if you run page mode cycles or use the x16 chips, you need a dedicated mask load operation. Oct 14 at 14:58
  • @MichaelKarcher: Many CPUs supply an address before they supply data. Delaying /RAS until data is available might not have hurt performance as much as having to do a separate read and write, but I don't think it could be as fast as a design based on x1 chips. It would be interesting to know whether system designers exploited masked write capabilities using either kind of chip, though.
    – supercat
    Oct 14 at 15:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.