Reply 140 of 166, by SoftCat
reenigne wrote on 2026-01-29, 21:54:Won't this one do?
This is absolutely super! I also have a 3-color female cat.
reenigne wrote on 2026-01-29, 21:54:Won't this one do?
This is absolutely super! I also have a 3-color female cat.
I've just adjusted my own implementation of CGA/MDA a bit. Now it always is in planar mode (in VGA terms). So basically, the registers function like they would as if addressing memory directly.
Luckily, the fetching of character bytes already used a scheme to load even and odd bytes. I just needed to modify the attribute controller byte to be fetched from plane 0 (linear memory in this case) instead of plane 1 (odd/even memory).
And of course modify odd/even mode to linear mode in all cases.
Then I just need to modify the graphics mode loading to always load from plane 0 (where the start address register points to directly). Essentially CGA memory mapping done using VGA internals in my emulator's case (essentially all is the same as on the Tseng graphics cards (using VGA with half clocking functionality added), except for some adjustments for odd/even loading and loading pixels every 4(non-mode 6) or 8(mode 6) clocks (which my latest implementation of the CGA/MDA already performs)).
I'm wondering though, what happens when you're in text mode and you make the start address register a odd number while moving said memory one byte ahead? Does that work as expected (display rendering the text/attributes normally)?
Edit: Interlacing seems to be the same as on VGA, with memory address 13 (when in planar mode or 'linear' mode in this case) being from the row scan counter bit 0?
But somehow, the scanlines aren't rendered correctly now (some are, some periodically aren't)?
Edit: OK. Part of it was the VGA-calculated offset between rows. It's working in 40 column mode now.
Now I just need to fix the monochrome graphics mode horizontal timings now.
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io
OK. I'm seeing some weird stuff.
Currently I'm using the horizontal total (times 2) to calculate the size of bytes to add to the current scanline start to obtain the next scanline to load in the VRAM. Perhaps horizontal total isn't the correct register to use, but instead register 1? But that contains 28h in mode 6(640x200 monochrome graphics)? That's only half of what is needed (40 columns)?
Does the hardware do something strange in monochrome graphics mode?
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io
superfury wrote on 2026-01-30, 21:21:OK. I'm seeing some weird stuff.
Currently I'm using the horizontal total (times 2) to calculate the size of bytes to add to the current scanline start to obtain the next scanline to load in the VRAM. Perhaps horizontal total isn't the correct register to use, but instead register 1? But that contains 28h in mode 6(640x200 monochrome graphics)? That's only half of what is needed (40 columns)?
Does the hardware do something strange in monochrome graphics mode?
Nothing strange is going on. Each CRTC character corresponds to 2 VRAM bytes in all modes.
reenigne wrote on 2026-01-30, 22:28:Nothing strange is going on. Each CRTC character corresponds to 2 VRAM bytes in all modes.
Which means 16 pixels per CRTC character in monochrome graphics mode. This applies to the Hercules card (45 characters for 720 pixels) as well as the CGA card.
reenigne wrote on 2026-01-30, 22:28:Nothing strange is going on. Each CRTC character corresponds to 2 VRAM bytes in all modes.
By the way, on EGA and VGA in mode 6, each CRTC character corresponds to 1 byte of VRAM.
Well, behind the scenes it's partly using VGA registers for various functionality.
VRAM is basically always mapped like mode 6 VGA (64K VRAM, planar mode), except timings for text mode use a half clock to fetch bytes from plane 0 (2 per character clock). Same for 4 color mode. 8 color mode is exactly handled like the VGA, but there is an issue with it being programmed for 40 horizontal clocks, or slightly more for horizontal total, which it uses for the VGA-compatible offset register calculation. By fixing all modes to double, that seems fixed for the vertical timing, but the horizontal timing is still messing up (on the CRTC main clock part, which in turn drives the active display/overscan clocks (which read from RAM every divided dot clock).
Also, I've improved the dot clock to double display width for rendered pixels, essentially ticking every other pixel (for text modes on clock 1 (character) and 4(attribute), with clock 8 using the data). That's in VGA dot clock terms (pixel clock div 2=dot clock, pixel clock div 8=character clock (CRT POV), dot clock div 8=character clock (VRAM POV).
The CRT POV is what's going wrong (only half in monochrome graphics mode only).
Is the amount of bytes to the next scanline based off CRTC reg 00h (current implementation, horizontal total) or 01h(end horizontal display) on a CGA?
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io
superfury wrote on 2026-01-30, 23:15:Is the amount of bytes to the next scanline based off CRTC reg 00h (current implementation, horizontal total) or 01h(end horizontal display) on a CGA?
The number of words (2 bytes each) per line is specified on the CGA in register R1. For graphics modes (formally there are 100 lines), this must be multiplied by 2, since there is one line from each bank.
superfury wrote on 2026-01-30, 23:15:Is the amount of bytes to the next scanline based off CRTC reg 00h (current implementation, horizontal total) or 01h(end horizontal display) on a CGA?
01. If I understood the 6845 datasheet correctly, the address counter inside the CRTC is incremented on every clock, even during blanking, but it is sampled at display end, and restored to that sampled value on the start of the next scanline.
mkarcher wrote on 2026-01-31, 08:12:superfury wrote on 2026-01-30, 23:15:Is the amount of bytes to the next scanline based off CRTC reg 00h (current implementation, horizontal total) or 01h(end horizontal display) on a CGA?
01. If I understood the 6845 datasheet correctly, the address counter inside the CRTC is incremented on every clock, even during blanking, but it is sampled at display end, and restored to that sampled value on the start of the next scanline.
So, unlike the EGA/VGA, there is no 'offset register' addition during horizontal total and the normal dot clock during active display is the one that increases it to the next scanline? How are the row scan counter scanlines handled, then? Since they need to reset the counter to the start of the scanline for another row scan? Or is that done by the CRTC itself?
Edit: OK. Just made the CRTC itself perform that function now.
So it will increase the memory address clock accordingly in both text and graphics modes. Then when it needs to duplicate a scanline (for double scanning or next row scan counter), it will simply set the start of the new scanline to use to the current memory address counter, which is substracted by one in text mode (since the attribute fetch of the last character clock left it one too late). Graphics modes function normally, since they tick as needed (no double clocking for fetching multiple bytes from RAM).
Edit: Just ran Ultima II again. It renders correctly in it's graphics and text modes now! 😁
Edit: And after a bit of VGA-compatible planar memory fixes (various register settings being incorrect for planar mode), the graphics mode on the emulated CGA/MDA all work correctly again! 😁
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io
Ran the CGA compatibility tester on my emulator again.
It runs fine, except for the CGA card determination test.
In one case I get garbled junk. In another case I get the correct screen, but it starts rolling from right to left, from top to bottom on the screen. Almost like the final clocks of a frame aren't loading a new frame start address correctly somehow?
Edit: OK. The horizontal timings are seemingly handled correctly. It's just that the final scanline of the frame isn't detected properly, thus never triggering required vertical total effects (like reloading the address counter or start address etc.).
Edit: The issue was in the scanline comparator logic for the registers 4/5 handling. Fixed that, now no more rolling occurs.
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io
It's possible to achieve 320x200 16-color mode on CGA. However, this will result in attribute conflicts, and a special character generator will be required. Mode 3 is set, the character height is programmed to 2 dots, and all 256 combinations are used, with horizontal dot duplication.
0011001111001100
Since each character can be used with its inverse, 128 characters will suffice.
I understand that this is completely irrelevant now. But CGA could be much better for the same price.
superfury wrote on 2026-01-31, 10:34:mkarcher wrote on 2026-01-31, 08:12:superfury wrote on 2026-01-30, 23:15:Is the amount of bytes to the next scanline based off CRTC reg 00h (current implementation, horizontal total) or 01h(end horizontal display) on a CGA?
01. If I understood the 6845 datasheet correctly, the address counter inside the CRTC is incremented on every clock, even during blanking, but it is sampled at display end, and restored to that sampled value on the start of the next scanline.
So, unlike the EGA/VGA, there is no 'offset register' addition during horizontal total and the normal dot clock during active display is the one that increases it to the next scanline? How are the row scan counter scanlines handled, then? Since they need to reset the counter to the start of the scanline for another row scan? Or is that done by the CRTC itself?
You are correct. There is no "offset register". Furthermore, the address counter may only advance to the next scan line ist the maximum scanline has been reached, so correct my claim "the address counter is sampled at display end" to "the address counter is sampled at display end on the last scan line of a text row, and likely some extra logic to set that latch to the configured start address for the first text row. Possibly, the start address is latched into both the "current address counter" at frame start, and if the latch is transparent and fast enough, the "current address counter" output can then be latched into the "next row start address counter" as well.
Tell me, does the snow suppression system on the CGA slow down the CPU using the READY signal?
Yes, sort of. If the CGA card didn't use the READY signal at all then the CPU would run at full speed but snow would be much worse (and happen in all modes). The CGA card instead limits CPU accesses to overlap with one of the four internal phases of the CGA card, so the CPU can only access CGA memory 1/4 of the time - on a phase that is only used in 80-column mode. This wait state is the same for all modes.
Except in 80-column mode the CGA is accessing memory all the time, resulting in snow, correct?
Exactly.
reenigne wrote on 2026-06-18, 20:38:Yes, sort of. If the CGA card didn't use the READY signal at all then the CPU would run at full speed but snow would be much worse (and happen in all modes). The CGA card instead limits CPU accesses to overlap with one of the four internal phases of the CGA card, so the CPU can only access CGA memory 1/4 of the time - on a phase that is only used in 80-column mode. This wait state is the same for all modes.
I see, thank you very much for your reply!
reenigne wrote on 2026-06-18, 20:38:Yes, sort of. If the CGA card didn't use the READY signal at all then the CPU would run at full speed but snow would be much worse (and happen in all modes). The CGA card instead limits CPU accesses to overlap with one of the four internal phases of the CGA card, so the CPU can only access CGA memory 1/4 of the time - on a phase that is only used in 80-column mode. This wait state is the same for all modes.
I was surprised by "1 of 4", as I assumed "1 of 2". Actually, it depends on how you count the "internal phases". I wrote a detailed description of the CGA schematic that explains what happens in the fine detail. I'm sure reenigne already knows all of this, but it's non-obvious for other readers.
The clocking
The main timing is generated by U4/U5 on page 3 of 6. These chips are six-channel D-type flip-flops. They sample their inputs on the rising edge of the clock signal and provide the sampled signals at the output "shortly after". It is perfectly valid to connect an output of these flip-flops back to an input, and it is guaranteed that the input sampling is not disturbed by if the input changes in response to the clock signal. The 12 flip-flops latches are divided in a group of 8 (all 6 flip-flops of U4 and the first two of U5), a special signal on the third flip-flop of U5, a group of 1 at the fourth flip-flop of U5 and a group of 2 at the sixth flip-flop of U5. All these groups are connected as rings with an inverter where the ring is closed. Lets take a look at the longest ring, and use the 0-based channel numbering from the KiCad schematic (the IBM one uses a 1-based numbering scheme)
After reset, all of these flip-flops are cleared to low, which means all inputs for the next clock are zero, except U4 D0, because there is an inverter. At the next clock after reset, the first output is high, and all other outputs are still low. In the next clock, the first two outputs are high with all other outputs still being low. The "wave of highs" propagates until all outputs are high, and at that point in time, as the output Q1 of U5 got high, the input into D0 of Q4 will become low, and further clocking will shift in a "wave of lows". If we just write the sequence of output codes, we get 0000.0000 after reset, then 10000.000, then 11000.000, 11100.000 11110.000, 11111.000, 11111.100, 1111.1110, 1111.1111, 0111.11111, 0011.11111, 0001.1111, 0000.1111, 0000.0111, 0000.0011, 0000.0001 and then the sequence repeats. This is a cycle of 16 patterns, and each individual bit is low 50% of the time and high the other 50% of the time. The output of those flip-flops thus indicate an 8-clock interval inside a 16-clock cycle.
Let's arbitrarily call the pattern where only the first output is high the "initial clock of the period". This first output is Q0 of U4, and this signal is named -LCLK (IBM) or /LCLK (KiCad), which is to be read as "inverter"/"active low" Low-res character CLocK. This signal high the "first 8" clocks (0-7) and low the "second 8" (8-15) clocks. This signal is also inverted by U6A to yield +LCLK (IBM) or LCLK (KiCad, no slash in front), which is to be read as "non-inverted"/"active high" Low-res character CLocK. Further signals are also derived from this ring/shift register: Q4 is high for clocks 3-10; Q5 is high for clocks 4-11; /Q2' (the prime character indicating U5 instead of U4) is high for clocks 0-6 and clock 15. Q1' is high for clocks 6-13, and low for clocks 0-5 and 14/15. U1B is another flip-flop, in this case a JK-type flip-flop, but in combination with the inverter U26B, it behaves like a D-type flip-flop. It samples Q5, but on the falling edge of the clock, so the output of U1B is half a clock cycle delayed compared to Q5.
A couple of XOR gates is used to derive further signals:
Please note the following observations: We get a "high-resolution" character clock at a period of 8 clocks (4 high, 4 low), and a "low-resolution" character clock at a period of 16 clocks (8 high, 8 low). The clock numbering I chose has both clocks start with their rising edge at clock 0. This means the inverted high-resolution character /HCLK starts with the falling edge at clock 0. All RAM related signals (CAS_CC, RAS, /CAS) repeat every 8 clocks, which means 1 "memory cycle" per high-resolution chacter an 2 "memory cycles" per low-resolution character. The nature of a CGA "memory cycle" is discussed below.
The one-element group at flip-flop 3 of U5 generates a 7.16MHz signal /7MHZ, which is low at "even numbered" clocks and high at "odd numbered" clocks.
The two-element groups at flip-flops 4 and 5 of U5 generates a 3.58MHz signal 3.58MHZ, which is high at clocks 0&1, at clocks 4&5, at clocks 8&9 and at clocks 12&13.
CGA memory cycle
overview
As described above, the signals RAS and /CAS repeat every 8 clocks, and I am going to call that a "memory cycle". During that cycle, /RAS has one active period and one inactive period: RAS is active the first 6 clocks, and inactive the last 2. I was surprised to find that /CAS has two active periods, indicating two RAM accesses using Page Mode. The IBM schematics call for a 2118-4 RAM chip. The non-military data sheet for the 2118 that I found does not explain the speed grade "-4" (it has -10, -12, -15 with their usual meaning), but indeed specifies page mode operation. The M2118 data sheet I found does have the speed grade "-4" (for 120ns) and "-7" (for 150ns), but does not mention page mode at all. So I guess the idea is to perform the character and attribute read using a single /RAS cycle. The fact that there is a CAS_CC signal which is active during the first CAS pulse and likely refers to the "character code" (CC) supports this theory.
address multiplexing
The CGA memory uses the standard row/column multiplexing scheme used by most dynamic RAMs. The 16K*1 chips use 7 row address and 7 column address bits. The multiplexing is not implemented using multiplexer chips, but using four separate address latch chips, U58-U61, of which only one is enabled to drive the memory address lines. The four address latch chips latch these address parts: The row address of the current character (U59), the column address of the current character (U58), the row address of the current ISA bus memory cycle (U60) and the column address of the current ISA bus memory cycle (U61). The logic that choses which latch gets to drive the memory address bus is at the top of page 4. The chip driving the output enable pins of the latches is U60, a quad NAND gate, which is basically used as a 2-to-4 decoder. U1A is used to select whether a row address latch or a column address latch is supposed to be enabled, while U47B (with assistance of U26C) is used to select whether the address is sourced from the ISA bus or the CRTC character counter. U1A is basically creating a copy of RAS which is delayed by half a clock. The intention is that when RAS goes low at clock 0 (and the RAM chip samples the row address), the "delayed RAS" at pin 5 (Q) of that chip is still high and allows U46B or U46D to enable one of the row address latches. At the second half of clock 0, the delayed RAS goes low, and thus causes a switch to the column address latches, so that when /CAS goes low, the corresponding row address is visible. This is a standard way to implement row/column multiplexing in a clocked system and not worth further discussion.
ISA/CRTC multiplexing
The ISA/CRTC selection logic is way more interesting, though. It uses U47B to select the address source. That AND/OR chip is used a 1-out-of-2 multiplexer here, with HRES being the signal that choses the input, and Q1 (in low-res mode) or the output of U2A (in high-res mode) being the signal that decides which address is to be used. U47B inverts the output, and it selects the CRTC address if its output is high, and the ISA address if its output is low. In low res mode, in which Q1' is passed (inverted), the CRTC address is selected if Q1' is low, and the ISA address is selected of Q1' is high. Going back to the clocking section, I determined that Q1' is low (selects CRTC) during clocks 14 and 15, as well as during clocks 0 to 5. This thus obviously includes clock 0 (in which /RAS is asserted for the first "memory cycle"), clock 1 (in which /CAS is asserted for the first access in th the first "memory cycle") as well as clock 4+ (in which /CAS is asserted for the second access in the first memory cycle). This is all addressing performed in the first cycle (in the first 8 clocks of a 16-clock period), while the ISA bus address is used during the second memory cycle, yielding a 50:50 split of RAS cycles between ISA and the CRTC. Let's look at high-res mode later.
ISA bus interaction
Ready generation
U2A samples whether an ISA memory cycle is pending (as indicated by /CPU_MEM_SEL being low), and does so when Q1' goes high, which is at clock 6, that is just after /RAS was raised for the first memory cycle, and the preparations for the second memory cycle that is dedicated to the ISA bus starts. This feature is independent of the card being in high-res or in low-res mode. Furthermore, CPU_MEM_SEL is negated in U29C and fed into the "set" input of the ready generation flip-flop U2B. While U29C had the "set" input active, it was forced into the "not ready" state, and it will stay in that state for a moment. In this "not ready" state, "Q" is high and "/Q" is low. This state does not activate the active low "reset" input, so the flip-flop is allowed to stay active, and it also does not block U2A. The card keeps in the "not ready" state until there is a rising edge of /RAS. If there is a rising edge on /RAS, U2B samples the active low "request pending" output of U2A, so if a memory request is pending, it switches into the "ready" state. Switching to the ready state means Q drops to low, which will activate the reset input of the ready flip-flop U2B (keeping it in the ready state, no matter what happens at C(LK) and D), and activate the set input of the "request pending" flip-flop, which will clear the current "pending" flag and prevent re-recognition of the current request. The CGA card remains in "ready" state as long as /CPU_MEM_SEL stays active. This state, in which U2A and U2B are "blocked" ends when /CPU_MEM_SEL goes high, and thus the active low "set" input of U2B goes active. For a very brief moment, both the "set" and the "reset" input at U2B are driven, which is specified to pull both the "Q" and the "/Q" output high, which will remove the "reset" input, so the "set" input wins, "Q" stays high and "/Q" goes low, entering a clean not-ready state again.
The ready generation logic is required to synchronize the ISA bus to the fixed CGA memory access patterns, and removing the ready logic will not just generate snow, it will totally break the ISA access to CGA memory.
Snow-generation feature
In high-res text mode, a character/attribue combination needs to be fetched during every CGA memory cycle. Thus the ISA/CRTC switch signal is no longer just Q1, but the inverted output of U2A, the flip-flop that registers a pending ISA request. As long as no ISA request is pending, the CRTC keeps control over the address bus during all the memory cycles. This is correct behaviour, because the text mode display logic samples a new character/attribute combination during every memory cycle. As soon as an ISA request is pending, though, the address is taken from the ISA bus, and the ISA cycle is performed. The display logic samples the data from the memory bus anyways, and thus uses the data transferred over the ISA bus as character code and as attribute code. This is obviously not the correct character and attribute to display, but that data has not been fetched and is not available.
So, is it 1 of 4 or is it 1 of 2?
Well, actually it might also be 2 of 4. A pending ISA bus cycle needs to determine the address of a full /RAS cycle (that is two /CAS cycles), but it will only transfer one byte. The simple memory timing generation logic will still generate two /CAS cycles. While I didn't look at what happens exactly while writing, during a read cycle the first /CAS cycle will be ignored and the second /CAS cycle is the cycle during which /RAS is de-asserted and the ready-generation circuit gets triggered. The byte from video memory gets latched by U37 until the ISA cycle ends. The CGA card can continue displaying pixels while the ISA read cycle is still active. So performing one ISA cycle consumes two CGA /CAS cycles ("2 of 4"), yet you can only perform one ISA data transfer in the time of four CGA /CAS cycles ("1 of 4"), and you hijack one out of two /RAS cycles for the ISA access ("1 of 2").