VOGONS


IBM VGA BIOS initialization

Topic actions

First post, by GloriousCow

User metadata
Rank Member
Rank
Member

Hello all,

I've been writing yet another hobby IBM PC emulator, and several posts here have been incredibly useful resources on CGA timings, etc. I was a bit hesitant to ask but this seems like the place to get the answer, so, here's my first post.

I've encountered a bit of a puzzling roadblock during the IBM VGA BIOS expansion POST.

The VGA BIOS sets up a 720x400 resolution and then proceeds to check for 400 scanlines (Rather any value between 390 and 410) between VSYNC periods as measured by the Vertical Retrace bit of Input Status Register #1. Check is at C200:024A.

This doesn't make sense to me. VerticalTotal is set to 447, so there will be more than 400 lines between vertical retraces. We have the Vertical Blank period (lines 406-441) and the Vertical Retrace period itself doesn't start until scanline 412. Setting the vertical retrace bit only between scanlines Vertical Retrace Start to End produces a count of 432, which the BIOS rejects and goes to one long, three short beeps.

Curious if anyone who's gotten the IBM VGA to post in their emulator might be able to shine some light on this. Thanks!

Attachments

  • registers.png
    Filename
    registers.png
    File size
    76.01 KiB
    Views
    2291 views
    File comment
    register state during POST check
    File license
    Public domain

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 1 of 29, by superfury

User metadata
Rank l33t++
Rank
l33t++

You can run UniPCemu and dump the VGA state when it's running the retrace test.
It'll dump the precalcs as well, where you can see when stuff starts and ends (for all possible VGA signals), seperated for horizontal and vertical timings.
447 sounds familiar. Don't know the retrace from the top of my head.
Do note that horizontal timings continue as normal, even past the end of the active display (so 800 'pixels' per line ticked, even though nothing is rendered).
Also, the end minus one stuff for some timings starting/ending at or after the specified horizontal clock or vertical line (modulo).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 2 of 29, by kdr

User metadata
Rank Member
Rank
Member

I seem to recall that "Vertical Retrace" (both the status bit and the optional interrupt) is triggered by the scanline counter reaching Vertical Displayed End. The intention must have been to inform the program "okay now you can write to video memory without worrying about screen tearing". Frustratingly, the exact timing of the vertical retract interrupt and status bit is not actually documented, even in the (otherwise very excellent) CL-GD542x technical reference.

Reply 3 of 29, by GloriousCow

User metadata
Rank Member
Rank
Member
superfury wrote on 2022-09-12, 20:40:

You can run UniPCemu and dump the VGA state when it's running the retrace test.

I'm afraid I haven't had much luck getting UniPCemu to run at all... but say I get it running where would this VGA state dump feature be found?

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 4 of 29, by superfury

User metadata
Rank l33t++
Rank
l33t++
GloriousCow wrote on 2022-09-12, 23:10:
superfury wrote on 2022-09-12, 20:40:

You can run UniPCemu and dump the VGA state when it's running the retrace test.

I'm afraid I haven't had much luck getting UniPCemu to run at all... but say I get it running where would this VGA state dump feature be found?

It's in the Video settings menu, under the Advanced menu.
It dumps all kinds of data (precalcs for horizontal and vertical timing, it's register-based precalc numbers(like htotal=800 pixel clocks etc.) and the graphical contents (as bmp images, which are the VRAM fonts(in black/white), the used DAC decoding(256-color lookup table), text/graphics attribute decoding (with stuff like blink on/off etc.)) and files with raw dumps of the VGA register contents itself.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 5 of 29, by Jo22

User metadata
Rank l33t++
Rank
l33t++
kdr wrote on 2022-09-12, 20:50:

I seem to recall that "Vertical Retrace" (both the status bit and the optional interrupt) is triggered by the scanline counter reaching Vertical Displayed End. The intention must have been to inform the program "okay now you can write to video memory without worrying about screen tearing". Frustratingly, the exact timing of the vertical retract interrupt and status bit is not actually documented, even in the (otherwise very excellent) CL-GD542x technical reference.

Wow, that brings back memories of this old thread over here :
The myth of the vertical retrace interrupt on EGA/VGA

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 6 of 29, by GloriousCow

User metadata
Rank Member
Rank
Member
kdr wrote on 2022-09-12, 20:50:

I seem to recall that "Vertical Retrace" (both the status bit and the optional interrupt) is triggered by the scanline counter reaching Vertical Displayed End. The intention must have been to inform the program "okay now you can write to video memory without worrying about screen tearing". Frustratingly, the exact timing of the vertical retract interrupt and status bit is not actually documented, even in the (otherwise very excellent) CL-GD542x technical reference.

Well, it would explain why it's looking for exactly 400 scanlines.
It's easy enough to try, at least. After that I hit the second part of the POST timing check. The BIOS expects to see those 400 lines within 30 ms.
Specifically, it checks for a PIT timer value between 6E8Ch and 871Dh counting down from FFFF since the previous vblank. That works out to ~29-31 ms, if i'm doing my math right. So it expects those 400 lines to take 30ms?

HorizontalTotal is 95, and I understand on the VGA you add 5 to that. The character clock is set for 9-pixel wide characters, so that's 900 clocks x 400 or 360,000 clocks.

The clock appears to be set for 25.175 Mhz in the Misc Output Register, but we're at half clock in the Clocking Mode Sequencer register (seriously does VGA have enough registers?). 1/12.5875 * 360000 / 1000 = 28.6ms? Barely outside the range the BIOS is looking for, so I suspect my math is off somewhere...

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 7 of 29, by superfury

User metadata
Rank l33t++
Rank
l33t++
GloriousCow wrote on 2022-09-13, 22:08:
Well, it would explain why it's looking for exactly 400 scanlines. It's easy enough to try, at least. After that I hit the secon […]
Show full quote
kdr wrote on 2022-09-12, 20:50:

I seem to recall that "Vertical Retrace" (both the status bit and the optional interrupt) is triggered by the scanline counter reaching Vertical Displayed End. The intention must have been to inform the program "okay now you can write to video memory without worrying about screen tearing". Frustratingly, the exact timing of the vertical retract interrupt and status bit is not actually documented, even in the (otherwise very excellent) CL-GD542x technical reference.

Well, it would explain why it's looking for exactly 400 scanlines.
It's easy enough to try, at least. After that I hit the second part of the POST timing check. The BIOS expects to see those 400 lines within 30 ms.
Specifically, it checks for a PIT timer value between 6E8Ch and 871Dh counting down from FFFF since the previous vblank. That works out to ~29-31 ms, if i'm doing my math right. So it expects those 400 lines to take 30ms?

HorizontalTotal is 95, and I understand on the VGA you add 5 to that. The character clock is set for 9-pixel wide characters, so that's 900 clocks x 400 or 360,000 clocks.

The clock appears to be set for 25.175 Mhz in the Misc Output Register, but we're at half clock in the Clocking Mode Sequencer register (seriously does VGA have enough registers?). 1/12.5875 * 360000 / 1000 = 28.6ms? Barely outside the range the BIOS is looking for, so I suspect my math is off somewhere...

A frame should actually be htotal (converted to pixel clocks, so times 8 or 9 dot clocks) times vtotal (in lines). The lines past horizontal/vertical display end are counted just like normally displayed lines, just not rendering anything at that point. So 800 horizontal clocks times the 500ish vertical lines for a whole frame.

http://tinyvga.com/vga-timing shows a lot of information for the actual results when using VGA-compatible timings, which all modes from 0-13h should have.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 8 of 29, by GloriousCow

User metadata
Rank Member
Rank
Member
superfury wrote on 2022-09-14, 13:13:

A frame should actually be htotal (converted to pixel clocks, so times 8 or 9 dot clocks) times vtotal (in lines). The lines past horizontal/vertical display end are counted just like normally displayed lines, just not rendering anything at that point. So 800 horizontal clocks times the 500ish vertical lines for a whole frame.

http://tinyvga.com/vga-timing shows a lot of information for the actual results when using VGA-compatible timings, which all modes from 0-13h should have.

But it's not counting an entire frame, it's counting enables between vblank periods. Otherwise it wouldn't be looking for 400 lines, either.

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 9 of 29, by superfury

User metadata
Rank l33t++
Rank
l33t++
GloriousCow wrote on 2022-09-14, 22:58:
superfury wrote on 2022-09-14, 13:13:

A frame should actually be htotal (converted to pixel clocks, so times 8 or 9 dot clocks) times vtotal (in lines). The lines past horizontal/vertical display end are counted just like normally displayed lines, just not rendering anything at that point. So 800 horizontal clocks times the 500ish vertical lines for a whole frame.

http://tinyvga.com/vga-timing shows a lot of information for the actual results when using VGA-compatible timings, which all modes from 0-13h should have.

But it's not counting an entire frame, it's counting enables between vblank periods. Otherwise it wouldn't be looking for 400 lines, either.

Still doesn't affect what I mentioned. VRetrace still is n lines from vertical retrace start (until at/after modulo line occurs). And each of those lines is htotal character units(multiplied by 8 or 9 dots the amount of clocks for the actual length in 25/28 MHz clocks) long.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 10 of 29, by GloriousCow

User metadata
Rank Member
Rank
Member

I got things more or less working, I think.

One surprising thing in Mode 13h, is that the CRTC is set up for 80 characters and 400 lines instead of the expected 320x200. I don't see any of the bits that would affect the clock enabled. How does the VGA know to double the resolution?

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 11 of 29, by mkarcher

User metadata
Rank l33t
Rank
l33t
GloriousCow wrote on 2022-09-18, 16:47:

I got things more or less working, I think.

One surprising thing in Mode 13h, is that the CRTC is set up for 80 characters and 400 lines instead of the expected 320x200. I don't see any of the bits that would affect the clock enabled. How does the VGA know to double the resolution?

First the most basic info: We go down from 400 lines to 200 lines by setting the maximum scanline register to 1. As in non-CGA modes, the scanline counter isn't used to address the video memory in graphics mode, this causes double scanning.

For the horizontal timing: That's a very good question to learn about the VGA architecture. Actually, in Mode 13h, all components up to the attribute controller work just like the VGA card would display a 640x200 16-color mode. The attribute controller is put into a special 256-color mode where it merges the two 4-bit pixel color codes for two subsequent pixels into a single 8-bit color code that is being sent to the RAMDAC.

If you understand what's happening here, you can understand some hardware hacks that can be done:

  • So you could just clear the 256-color mode bit in AC[0x10].6, but keep everything else as in mode 13. Obviously this will turn into the 640x200 16-color mode I just described. Unlike Mode 0Eh, this mode does not need any banking, but provides a plain 64K video buffer. We know the first 8 bits sent to the attribute controller are from the byte at A000:0, so this byte will be used for the first two 16-color pixels. As you are not supposed to disable 256-color mode in the attribute controller only, but keep this bit in sync with GFX[5].6. This bit is the magic sauce that switches the serialization process from a planar one (1 bit from each plane) to a linear one (nibble-wise scanout one byte per plane). There is no agreed standard whether the GFX controller sends the low or the high nibble first to the AC, it's just that the GFX and the AC need to agree on the order for the 256-color mode to work. In the end, you get a Tandy/PCjr-like 16-color mode with a nibble per pixel, but depending on the VGA card, the first pixel in in the low or the high byte of A000:0
  • Of course, you can perform the magic the other way too: You can enable 256-color mode AC in non-256-color modes to combine two pixels into one 256-color pixels. In 8-pixel-per-char character modes, this switches from a 8-pixel one-color character cell to a 4-pixel four-color character cell.

Reply 12 of 29, by GloriousCow

User metadata
Rank Member
Rank
Member

Great info, and all that makes sense. I am a little puzzled as to why the Maximum Scan Line is set differently in the CGA 320x200 mode (1) vs EGA 320x200 mode (0). Or what the purpose of the '2T4' bit is if it's set for those modes but not mode 13h.

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 13 of 29, by mkarcher

User metadata
Rank l33t
Rank
l33t
GloriousCow wrote on 2022-09-18, 19:57:

Great info, and all that makes sense. I am a little puzzled as to why the Maximum Scan Line is set differently in the CGA 320x200 mode (1) vs EGA 320x200 mode (0). Or what the purpose of the '2T4' bit is if it's set for those modes but not mode 13h.

I don't understand what you mean by "2T4". Maybe you are talking about GFX, index 9, bit 7? We need a double-scan mode independent of the text-mode scan-line counter for CGA-compatible 200-line text modes, that uses double-scanning in combination with counting text-mode scan lines. Actually, the same is true for the CGA graphics mode: We need double-scanning to get a 400-line image. Apart from that, we also need the maximum scan line feature for the CGA dual-bank addressing scheme. It works like this:

  • Line 0: "row 0", "scan line 0", iteration 1. Scanned from B800:0000
  • Line 1: "row 0", "scan line 0", iteration 2. Scanned from B800:0000
  • Line 2: "row 0", "scan line 1", iteration 1. Scanned from BA00:0000
  • Line 3: "row 0", "scan line 1", iteration 2. Scanned from BA00:0000
  • Line 4: "row 1", "scan line 0", iteration 1. Scanned from B800:0050
  • Line 5: "row 1", "scan line 0", iteration 2. Scanned from B800:0050
  • Line 6: "row 1", "scan line 1", iteration 1. Scanned from BA00:0050

The card is set up to scan 100 (not 200!) rows, consisting of 2 scan lines each, and then double-scans all the scan lines. The offset of the scan start address is incremented by 80 (0x50) every row (just like in text mode: All scan lines are scanned from the same offset). But unlike text modes, in CGA graphics mode, GFX, index 0x17, bit 0 is cleared, causing the segment address to be changed to BA00 instead of B800 when scanning the second line of a row.

The actual reason for the CGA addressing scheme is that the CGA CRTC didn't count scan lines, but text rows, and only had a 7-bit counter to do so. It thus was unable to generate a 200-row image, and instead was programmed to generate a 100-row image which each row consisting of 2 scan lines. The total image (including sync) contained of 127 rows (254 scanlines), if I remember correctly. As the EGA/VGA CRTC counts scan lines instead of text rows, the effect of the CGA image being 100 2-line rows instead of 200 single-line row is much less visible in graphics card programming.

Reply 14 of 29, by GloriousCow

User metadata
Rank Member
Rank
Member
mkarcher wrote on 2022-09-18, 21:01:

I don't understand what you mean by "2T4". Maybe you are talking about GFX, index 9, bit 7?

Sorry, using the field names from the Ferraro book. It's a bit 7 in the Maximum Scanline Register. I really appreciate you taking the time to reply in such depth.

MartyPC: A cycle-accurate IBM PC/XT emulator | https://github.com/dbalsom/martypc

Reply 15 of 29, by bananaboy

User metadata
Rank Newbie
Rank
Newbie
GloriousCow wrote on 2022-09-12, 20:30:

I've encountered a bit of a puzzling roadblock during the IBM VGA BIOS expansion POST.

The VGA BIOS sets up a 720x400 resolution and then proceeds to check for 400 scanlines (Rather any value between 390 and 410) between VSYNC periods as measured by the Vertical Retrace bit of Input Status Register #1. Check is at C200:024A.

This doesn't make sense to me. VerticalTotal is set to 447, so there will be more than 400 lines between vertical retraces. We have the Vertical Blank period (lines 406-441) and the Vertical Retrace period itself doesn't start until scanline 412. Setting the vertical retrace bit only between scanlines Vertical Retrace Start to End produces a count of 432, which the BIOS rejects and goes to one long, three short beeps.

Sorry for necroing an old post but I was digging through this IBM VGA BIOS code and I think I understand a bit about what it's doing here. I think your summary isn't quite right. It's counting the number of scanlines in the active display area, not the number of scanlines between vsync periods. It looks like this in pseudocode:

1. Wait for any active vertical retrace to end.
2. Wait for the next vertical retrace to start.
3. Wait for the vertical retrace to end and the display to be re-enabled (bit 0 of Input Status Register 1 = 0 means the display is enabled). This means we're now at the top of the active display area.
4. Wait for horizontal retrace to start (i.e. the first scanline has been shown).
5. Wait for the horizontal retrace to finish (goto 6), or for vertical retrace to start (goto 7).
6. Horizontal retrace is finished, so increment the scanline counter in `bx` and go back to 4.
7. Vertical retrace has started, so we're out of the active display area.

Now `bx` contains the number of scanlines. However this count also includes both the bottom border and display blanked areas (which occur before the vertical retrace starts). It seems to me this would push the count up to 412 (i.e. the scanline where vertical retrace starts) which would then fail the "greater than 410" test so it's still a little confusing to me how it can work! Btw it looks like bits of the VGA BIOS code are similar to the EGA BIOS code (available https://www.os2museum.com/wp/reconstructing-the-ega-bios/).

Reply 16 of 29, by superfury

User metadata
Rank l33t++
Rank
l33t++
bananaboy wrote on 2022-12-30, 13:42:
Sorry for necroing an old post but I was digging through this IBM VGA BIOS code and I think I understand a bit about what it's d […]
Show full quote
GloriousCow wrote on 2022-09-12, 20:30:

I've encountered a bit of a puzzling roadblock during the IBM VGA BIOS expansion POST.

The VGA BIOS sets up a 720x400 resolution and then proceeds to check for 400 scanlines (Rather any value between 390 and 410) between VSYNC periods as measured by the Vertical Retrace bit of Input Status Register #1. Check is at C200:024A.

This doesn't make sense to me. VerticalTotal is set to 447, so there will be more than 400 lines between vertical retraces. We have the Vertical Blank period (lines 406-441) and the Vertical Retrace period itself doesn't start until scanline 412. Setting the vertical retrace bit only between scanlines Vertical Retrace Start to End produces a count of 432, which the BIOS rejects and goes to one long, three short beeps.

Sorry for necroing an old post but I was digging through this IBM VGA BIOS code and I think I understand a bit about what it's doing here. I think your summary isn't quite right. It's counting the number of scanlines in the active display area, not the number of scanlines between vsync periods. It looks like this in pseudocode:

1. Wait for any active vertical retrace to end.
2. Wait for the next vertical retrace to start.
3. Wait for the vertical retrace to end and the display to be re-enabled (bit 0 of Input Status Register 1 = 0 means the display is enabled). This means we're now at the top of the active display area.
4. Wait for horizontal retrace to start (i.e. the first scanline has been shown).
5. Wait for the horizontal retrace to finish (goto 6), or for vertical retrace to start (goto 7).
6. Horizontal retrace is finished, so increment the scanline counter in `bx` and go back to 4.
7. Vertical retrace has started, so we're out of the active display area.

Now `bx` contains the number of scanlines. However this count also includes both the bottom border and display blanked areas (which occur before the vertical retrace starts). It seems to me this would push the count up to 412 (i.e. the scanline where vertical retrace starts) which would then fail the "greater than 410" test so it's still a little confusing to me how it can work! Btw it looks like bits of the VGA BIOS code are similar to the EGA BIOS code (available https://www.os2museum.com/wp/reconstructing-the-ega-bios/).

One error there:

DD -- Display Disabled
"When set to 1, this bit indicates a horizontal or vertical retrace interval. This bit is the real-time status of the inverted 'display enable' signal. Programs have used this status bit to restrict screen updates to the inactive display intervals in order to reduce screen flicker. The video subsystem is designed to eliminate this software requirement; screen updates may be made at any time without screen degradation."

So bit 0 isn't indicating horizontal retrace only. Vertical retrace affects it as well (basically OR-ed).
So anything past the last active display scanline keeps it the same as during retracing horizontally, because it's an inverted display-enable(active display) signal output, which is always true past the last scanline.
So it's set after the last scanline until the next frame starts it's display enable again, thus no extra scanlines in BX, with vertical retrace somewhere within it's timing usually.

Basically just keep a flag that indicates active display is rendering (from active display skew to horizontal retrace, cleared at horizontal display end until the next frame starts active display).
Then just report it inverted to the app(XOR 1) in the input status 1 register.
The vertical retrace works much in the same way, from vertical retrace start until a scanline matches after it (but not the same scanline it starts!).
Anything of course after horizontal&vertical retrace is plain active display (you can put retrace anywhere after all), only affected by blanking otherwise (which basically masks VRAM generated output). That's how the image gets centered on the screen with extra borders.

https://wiki.osdev.org/Video_Signals_And_Timing displays it correctly. Although the overscan before scanline counter 0 (previous iteration) is missing from the rendering output timings, it's archieved by adding overscan to the right and bottom combined with blanking to clear a part of it to create the centered image with black borders.

Past active display doesn't toggle display enable. So when it reaches horizontal retrace of vertical display end, it doesn't flip back until the next frame starts with horizontal&vertical counters reset and horizontal display skew clocks after that.

So with 640x400 active display, what software sees:
1. 400 times horizontal retrace. The 400th 'retrace' gets stuck a long time.
2. During that long time being stuck, vertical retrace flips on and off (usually, can happen anytime though).
3. Bit 0 clears and a new frame is rendering at horizontal display skew.
4. Back to step 1.

And yes, you can trigger retrace in the middle of active display to create a weird split screen at the cost of VRAM. Retracing forces blanking essentially.
The same with blanking start/end.

You can even prevent blank/retrace from occurring by placing it past/at the total register. 8088MPH did this if I remember correctly, for odd/even double scanline rendering tricks?

Last edited by superfury on 2023-01-04, 00:22. Edited 1 time in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 17 of 29, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote on 2023-01-02, 12:28:

You can even prevent blank/retrace from occurring by placing it past/at the total register. 8088MPH did this if I remember correctly, for odd/even double scanline rendering tricks?

No, 8088MPH used a different trick. The 1K colour mode consists of lots of very short CRTC frames, with 1 scanline per row and 2 rows per frame. With each new CRTC frame the start address is reprogrammed to be 80 more than that of the previous frame. So the first scanline on each frame shows the same data as the second scanline on the frame above, giving a scanline-doubling effect using only the top scanline of each character.

Reply 18 of 29, by superfury

User metadata
Rank l33t++
Rank
l33t++
reenigne wrote on 2023-01-03, 09:58:
superfury wrote on 2023-01-02, 12:28:

You can even prevent blank/retrace from occurring by placing it past/at the total register. 8088MPH did this if I remember correctly, for odd/even double scanline rendering tricks?

No, 8088MPH used a different trick. The 1K colour mode consists of lots of very short CRTC frames, with 1 scanline per row and 2 rows per frame. With each new CRTC frame the start address is reprogrammed to be 80 more than that of the previous frame. So the first scanline on each frame shows the same data as the second scanline on the frame above, giving a scanline-doubling effect using only the top scanline of each character.

But wasn't the vertical retrace start put out-of-range for almost all but the last scanline to archieve correct vertical retrace if I remember correctly (to prevent vertical retrace from occurring each 2 scanlines)? That was what I was referring to.
Although it's been a while since I was messing with the CGA emulation in my emulator (since it's running perfectly since a while now, back when I was still getting 8088MPH to render correctly).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 19 of 29, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote on 2023-01-03, 20:48:

But wasn't the vertical retrace start put out-of-range for almost all but the last scanline to archieve correct vertical retrace if I remember correctly (to prevent vertical retrace from occurring each 2 scanlines)? That was what I was referring to.
Although it's been a while since I was messing with the CGA emulation in my emulator (since it's running perfectly since a while now, back when I was still getting 8088MPH to render correctly).

Oh, I see what you mean. Yes, in that case we did use that technique. I was thinking of one that was used on the Sugarlumps Amstrad CPC demo (amongst others) where you can duplicate rows by setting the "horizontal displayed" register to larger than "horizontal total" for the last scanline of the row.