VOGONS


Using an ISA VGA card on an AVR

Topic actions

First post, by manawyrm

User metadata
Rank Newbie
Rank
Newbie

Hi!

I'm building this slightly weird contraption to learn more about the ISA bus.
I have connected an ISA slot to an Arduino Mega (basically just an AVR microprocessor with a lot of 5V capable I/O pins) and I'm directly writing and reading from an attached ISA card.
One of my targets is to output video over a VGA graphics card.
For this to work it needs to execute the Video BIOS ROM, which consists of (up to) 32KiB of x86 Real-Mode code.

I'm using a simple serial ASCII protocol to offer 4 commands:

r - read from memory
w - write to memory
i - read from I/O space
o - output to I/O space

These commands are new-line seperated and can only do single byte transfers.
Here's the code running on the Arduino: https://github.com/Manawyrm/ISAMega/blob/d80e … ee/src/main.cpp

I've utilized the libx86emu library on a PC to emulate an x86 processor and system:
https://gist.github.com/Manawyrm/d5d9c6d5d7fc … 20ac755aaffdbf6
This code will redirect all I/Os, any memory read/write operations on the video memory and the video option ROM to the ISA card.

It's running a very small piece of code that does a "far call"-jump to 0x0C003 (aka the entry point for the video ROM).

Some video BIOSes will call INT 10h when there isn't an interrupt vector registered yet.
What does a normal BIOS do when INT 10h is called before any video ROM was executed?
I have written a small dummy function that will return 0x12 ("function supported") in the AL register whenever INT 10h is called without any other interrupt vector being present.
But I'm really not sure if this is a good idea / anywhere near correct behaviour.

What happens on 16bit memory read instructions on an 8bit ISA bus?
My current behaviour is just to do 2 bus cycles:

memoutb(addr, *val & 0xFF);
memoutb(addr + 1, (*val << 8) & 0xFF);

But that also causes 2 strobes of the ~MEMR/~MEMW [edit: this said IOR/IOW in a previous version] lines. How does a normal CPU behave here?

I have tested 3 cards:
- ATI VGA Wonder-16
- Trident TVGA8900C
- Trident TVGA9000B

The ATI card will execute the BIOS pretty far and then get stuck in an infinite loop reading from I/O port 0x3DA (status?) waiting for 0x08 (bit 3).
The Trident cards show a strange behaviour: the BIOS ROM will run for a while and will then write to 0x0B0000 (video memory). The Trident card pulls IOCHRDY (aka wait) low permanently, stalling the ISA bus and never returning. Any ideas on why write calls to the video memory might cause IOCHRDY to be low permanently?

Hardware-wise I've connected:
- GND, VCC
- D0-7 to a port on the Arduino
- A0-A19 to 3 ports on the Arduino
- IOCHRDY is connected to a pin on the Arduino
- REFRESH is connected to VCC/5V via a Resistor
- ~MEMW, ~MEMR, ~IOW, ~IOR to pins on the Arduino
- OSC is connected to a square wave oscillator (14.31818MHz, 5V amplitude, 2.5V offset, normal TTL signal)
- CLK pin is unconnected (it's also unconnected on all of my ISA VGA cards).
- (B)ALE pin is pulled HIGH permanently in software (see http://www.malinov.com/Home/sergeys-projects/isa-supervga, v1.0 Errata)
- AEN is connected to the Arduino and pulled LOW in software permanently.
- RESET is also hooked up to the Arduino and gets pulsed at the initial init.

Notes:
- Only 8 bits of the data bus are used (the 16bit part is entirely unconnected)
- I'm missing the -12V and -5V rails. These don't seem to be connected anywhere, though.
- I have tested the cards for 8-bit mode compatibility by covering the 16bit pins with tape and testing them in a regular PC.

I've attached some pictures if anyone is interested in the setup:

pfhwesyutag.jpg
Filename
pfhwesyutag.jpg
File size
1.8 MiB
Views
587 views
File license
CC-BY-4.0
ylkixrhtjpc.jpg
Filename
ylkixrhtjpc.jpg
File size
1.61 MiB
Views
587 views
File license
CC-BY-4.0

Thanks for any advice,
Tobias

Last edited by manawyrm on 2020-09-02, 14:46. Edited 2 times in total.

Reply 1 of 36, by root42

User metadata
Rank Oldbie
Rank
Oldbie

Wow, this locks like a very interesting, very hacky project. I am not nearly deep enough into the technical aspects of this, but I bet some of the emulation experts here could chime in. We have a bunch of people writing PC emulators, who are probably knowledgeable. Commenting mainly so I stay subscribed to see if something comes of this. 😀

YouTube and Bonus
80486DX@33 MHz, 16 MiB RAM, Tseng ET4000 1 MiB, SnarkBarker & GUSar Lite, PC MIDI Card+X2+SC55+MT32, OSSC

Reply 4 of 36, by Benedikt

User metadata
Rank Member
Rank
Member

Interesting project, indeed!

manawyrm wrote on 2020-09-02, 12:12:
What happens on 16bit memory read instructions on an 8bit ISA bus? My current behaviour is just to do 2 bus cycles: […]
Show full quote

What happens on 16bit memory read instructions on an 8bit ISA bus?
My current behaviour is just to do 2 bus cycles:

memoutb(addr, *val & 0xFF);
memoutb(addr + 1, (*val << 8) & 0xFF);

But that also causes 2 strobes of the IOR/IOW lines. How does a normal CPU behave here?

Did you mean the MEMR/MEMW lines? IOR/IOW strobes on memory access sound weird.

manawyrm wrote on 2020-09-02, 12:12:

The ATI card will execute the BIOS pretty far and then get stuck in an infinite loop reading from I/O port 0x3DA (status?) waiting for 0x08 (bit 3).
The Trident cards show a strange behaviour: the BIOS ROM will run for a while and will then write to 0x0B0000 (video memory). The Trident card pulls IOCHRDY (aka wait) low permanently, stalling the ISA bus and never returning. Any ideas on why write calls to the video memory might cause IOCHRDY to be low permanently?

Port 3dah bit 3 is the vertical synchronization status. If the emulation somehow fails to set up the CRT controller registers, there might not be any v-sync pulse.
Does the Trident card write to its own video memory at segment b000h or does the BIOS maybe try to auto-detect the presence of a monochrome card in the same system to prevent interference?

I get the impression that with the Arduino Mega, the serial bridge to the PC, the emulation and everything, there are a few to many places for things to go wrong.
Have you tried to initialize the cards with custom code running on the AVR?
If they are genuinely VGA compatible, you should be able to set all the relevant hardware registers, manually, using values from the specification.

Do you have access to a CGA card? They don't have a BIOS and have the character set in ROM. Getting a picture out of a CGA card via microcontroller should therefore be easier.

Reply 5 of 36, by manawyrm

User metadata
Rank Newbie
Rank
Newbie

Hello Benedikt,

thanks for your answer!

Benedikt wrote on 2020-09-02, 14:42:

Did you mean the MEMR/MEMW lines?

Yes, absolutely. Sorry about that.

Benedikt wrote on 2020-09-02, 14:42:

If the emulation somehow fails to set up the CRT controller registers, there might not be any v-sync pulse.

That's a very interesting question. I thought that the video ROM is executed, then the BIOS will call the "Set video mode" function of Int 10h.
Should I still setup some VGA parameters manually?

Benedikt wrote on 2020-09-02, 14:42:

Does the Trident card write to its own video memory at segment b000h or does the BIOS maybe try to auto-detect the presence of a monochrome card in the same system to prevent interference?

There is no real BIOS in this setup. I'm directly executing the video BIOS on an "empty" x86-CPU. It's very possible that the Video ROMs require some additional info or setup to work properly.
Yes, the Video ROM of the Trident tries to write to B0000h and causes the hang.

Benedikt wrote on 2020-09-02, 14:42:

I get the impression that with the Arduino Mega, the serial bridge to the PC, the emulation and everything, there are a few to many places for things to go wrong.

That's very true. Unfortunately, for both the Trident and ATI cards I do not have proper register level documentation which would allow me to run the card without executing the ROM.
I've found some polish BASCOM code and some assembly from 2 different people how have tried similar things, but they used OKI and Realtek video cards.

Benedikt wrote on 2020-09-02, 14:42:

Do you have access to a CGA card? They don't have a BIOS and have the character set in ROM.

I don't have a CGA card or a CGA compatible monitor 😒

Thanks,
Tobias

Reply 7 of 36, by Benedikt

User metadata
Rank Member
Rank
Member

In case of memory word access via 8-bit bus there have to be two MEMW or MEMR strobes, because you are changing the address in between. If there were only one, you would only access one of the two bytes.
Maybe the interface to the ISA card is somehow flawed. Can you somehow verify that the VGA card ends up outputting a valid signal? (VGA screen, oscilloscope, logic analyzer, etc.) Look for the V-sync signal, first!
By BIOS I meant the card's option ROM, i.e. the VGA BIOS extension. In case of a video card, it should largely replace the PCs video BIOS, meaning that the option ROM is all you need.

If initialization via int 10h does not work, you could indeed try direct initialization via hardware registers.
Your cards should technically be VGA compatible on the register level, which means that you only need the IBM VGA documentation or anything equivalent.
Maybe, however, these cards with their extra functionality need some extra initialization, like choosing a clock source, before they can be programmed like a normal VGA.
Also note that text mode will not work unless you program a font, first.

Reply 8 of 36, by manawyrm

User metadata
Rank Newbie
Rank
Newbie

Yes, I'm not getting any signal on the output of the card at all. I have a monitor connected at all times and an oscilloscope at the Vsync signal.
I think I'll try to set the CRTC / VGA timings up manually (I'm using a modern LCD so no risk of damage) and will report back if anything changes 😀

Reply 9 of 36, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie
manawyrm wrote on 2020-09-02, 12:12:

Hi!

I'm building this slightly weird contraption to learn more about the ISA bus.
I have connected an ISA slot to an Arduino Mega (basically just an AVR microprocessor with a lot of 5V capable I/O pins) and I'm directly writing and reading from an attached ISA card.

nice. I've seen MDA cards on avr/z80 because they dont require a BIOS ROM to setup.

manawyrm wrote on 2020-09-02, 12:12:
Some video BIOSes will call INT 10h when there isn't an interrupt vector registered yet. What does a normal BIOS do when INT 10 […]
Show full quote

Some video BIOSes will call INT 10h when there isn't an interrupt vector registered yet.
What does a normal BIOS do when INT 10h is called before any video ROM was executed?
I have written a small dummy function that will return 0x12 ("function supported") in the AL register whenever INT 10h is called without any other interrupt vector being present.
But I'm really not sure if this is a good idea / anywhere near correct behaviour.

There is always an INT 0x10, the computer wont boot without it. the bios will set it up to internally point to itself.

manawyrm wrote on 2020-09-02, 12:12:

What happens on 16bit memory read instructions on an 8bit ISA bus?

Often the controlling chipset will do two 8 bit reads and concat them (headland 286 etc) where memory often sits behind the isa bus. if your talking a 16bit card in a 8bit bus without chipset (aka old XT's) where memory sits on the ISA bus, they have magic on the card
to support only 8bit reads. not all 16bit cards work in 8bit slots.

ATI VGA Wonder-16 has issues on 8bit bus. (works on a ibm 5160 but not an ibm 5150, could be timing issue).
TVGA9000B, I believe needs to be jumpered for an 8bit bus.
TVGA8900C can be iffy and also needs to be jumpered.

I dug out using MDA on isa bus with a Z80 it might be interesting for you.

https://ciernioo.wordpress.com/2016/03/21/80- … a-new-solution/

--/\-[ Stu : Bloody Cactus :: http://kråketær.com :: http://mega-tokyo.com ]-/\--

Reply 10 of 36, by kalohimal

User metadata
Rank Member
Rank
Member

Imho it seems like you're taking the difficult route of using libx86emu library, as you'll then need to deal with any incompatibility issues. Instead of relying on the card's bios, the simpler way would be to perform the initialization directly via registers programming and ignore the bios completely. VGA registers are standardized and you don't need programming info specific to Trident or ATI. Here are some of them:

https://wiki.osdev.org/VGA_Hardware
https://web.stanford.edu/class/cs140/projects … vga/vga/vga.htm

If I were to to it I would ditch libx86emu and program the required registers directly. And if there are enough i/o available on your arduino (not sure which atmega you're using but if its atmega2560 then it should have enough i/o), I'd connect up all the 16 bit lines (mainly the "Cx" side of the 16-bit ISA connector), to save the headache of dealing with 8- to 16-bit conversion.

Reply 11 of 36, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie
kalohimal wrote on 2020-09-03, 08:34:
Imho it seems like you're taking the difficult route of using libx86emu library, as you'll then need to deal with any incompatib […]
Show full quote

Imho it seems like you're taking the difficult route of using libx86emu library, as you'll then need to deal with any incompatibility issues. Instead of relying on the card's bios, the simpler way would be to perform the initialization directly via registers programming and ignore the bios completely. VGA registers are standardized and you don't need programming info specific to Trident or ATI. Here are some of them:

https://wiki.osdev.org/VGA_Hardware
https://web.stanford.edu/class/cs140/projects … vga/vga/vga.htm

If I were to to it I would ditch libx86emu and program the required registers directly. And if there are enough i/o available on your arduino (not sure which atmega you're using but if its atmega2560 then it should have enough i/o), I'd connect up all the 16 bit lines (mainly the "Cx" side of the 16-bit ISA connector), to save the headache of dealing with 8- to 16-bit conversion.

disagree mostly because vga chipsets are complex things and you dont know what the bios on them sets up with things like ram detection and timings, setting up the ramdac if its external.

--/\-[ Stu : Bloody Cactus :: http://kråketær.com :: http://mega-tokyo.com ]-/\--

Reply 12 of 36, by kalohimal

User metadata
Rank Member
Rank
Member
BloodyCactus wrote on 2020-09-03, 11:20:

disagree mostly because vga chipsets are complex things and you dont know what the bios on them sets up with things like ram detection and timings, setting up the ramdac if its external.

VGA BIOS is not doing some voodoo magic, and it's really not that difficult to perform the initialization. Here is a project that did exactly what the OP is doing (minus the arduino part). Take a look at the tvgainit.c source code and you'll see what I meant.

http://tinyvga.com/avr-isa-vga

Reply 13 of 36, by Benedikt

User metadata
Rank Member
Rank
Member

When in doubt, a disassembler can help. The VGA BIOS is not a black box, after all.
Whether expanding the data bus to 16 bits makes sense depends on whether the ATmega 2560's external memory interface is involved. AFAIK, it supports 8 bit buses, only.

Reply 14 of 36, by kalohimal

User metadata
Rank Member
Rank
Member

Yes with a disassembler (e.g. debug.exe in DOS) you could easily take a peek at the BIOS init code too.

And with a microcontroller you can set up the output latches before issuing the memory or i/o read/write strobe no? Since VGA cards are mostly 16-bit native, I would prefer to make the conversion at the microcontroller side which is relatively easy than at the VGA side. This would ease memory mapped access e.g. at B8000 etc. too.

Reply 15 of 36, by Benedikt

User metadata
Rank Member
Rank
Member
kalohimal wrote on 2020-09-03, 11:53:

Yes with a disassembler (e.g. debug.exe in DOS) you could easily take a peek at the BIOS init code too.

I'd rather read out the entire VGA BIOS at once and use more sophisticated tools like Sourcer Commenting Disassembler v.8 or the Ghidra disassembler/decompiler.

kalohimal wrote on 2020-09-03, 11:53:

And with a microcontroller you can set up the output latches before issuing the memory or i/o read/write strobe no?

With GPIOs, yes, obviously. I'm not sure whether that particular chip's dedicated external memory interface peripheral is flexible enough, though.
This, however, is only relevant if the OP is using it, at all.

Reply 16 of 36, by manawyrm

User metadata
Rank Newbie
Rank
Newbie

This, however, is only relevant if the OP is using it, at all.

I'm not using the External SRAM interface at all, as the real target objective is to build an adapter to use this card on my Z80 machine (which of course only has an 8bit databus).
That will require either some additional logic for indirect addressing or something like an 8255. Not sure which road I'll go there yet.

I haven't made any real progress with initializing the VGA cards yet, unfortunately.
I'm aware of the tinyvga.com project, but the Trident init code has a comment saying that it doesn't work and it doesn't for me, too.
Unfortunately I don't have any Realtek or OKI cards to try as well (my collection of cards is pretty big, but usually I'm into Windows 98 stuff, so it's mostly PCI and AGP)

Reply 17 of 36, by mkarcher

User metadata
Rank Member
Rank
Member
manawyrm wrote on 2020-09-02, 12:12:

Some video BIOSes will call INT 10h when there isn't an interrupt vector registered yet.
What does a normal BIOS do when INT 10h is called before any video ROM was executed?

A normal PC BIOS installs the MDA/CGA compatible video BIOS as INT 10h before the video ROM is called. Those cards come without a ROM on their own.

You should know that the combination VGA + MDA and VGA (with mono monitor) + CGA are supported dual monitor setups. The VGA BIOS will probe for CGA/MDA cards to know what card might be installed together with the VGA card. VGA + MDA (or Hercules) was a quite common setup for DOS developers, allowing the debugger to display the program state on the MDA card while the debugged process runs on the VGA card, and may tweak video modes and regsiters as it likes.

manawyrm wrote on 2020-09-02, 12:12:

I have written a small dummy function that will return 0x12 ("function supported") in the AL register whenever INT 10h is called without any other interrupt vector being present.
But I'm really not sure if this is a good idea / anywhere near correct behaviour.

The original INT10 is mostly used to initialize the "other" video card (mode 3 for CGA or mode 7 for MDA). It should be enough to save the video mode number to the BIOS data area on "set mode" and return it on "get mode". I don't think you need a better stub - but some VGA BIOSes are lazy and forward calls that work for VGA just the same as for CGA (like putpixel in CGA modes, or TTY output of the beep character) to the mainboard BIOS.

manawyrm wrote on 2020-09-02, 12:12:
What happens on 16bit memory read instructions on an 8bit ISA bus? My current behaviour is just to do 2 bus cycles: […]
Show full quote

What happens on 16bit memory read instructions on an 8bit ISA bus?
My current behaviour is just to do 2 bus cycles:

memoutb(addr, *val & 0xFF);
memoutb(addr + 1, (*val << 8) & 0xFF);

But that also causes 2 strobes of the ~MEMR/~MEMW [edit: this said IOR/IOW in a previous version] lines. How does a normal CPU behave here?

This is mostly correct. It should be "(*val >> 8) & 0xFF". Your version will always write zero to the second address. A real 8-bit PC will do just the same. The AT also splits memory transfers like this if the card does not indicate willingness to take the whole 16 bits at once by asserting /MEMCS16.

manawyrm wrote on 2020-09-02, 12:12:

The ATI card will execute the BIOS pretty far and then get stuck in an infinite loop reading from I/O port 0x3DA (status?) waiting for 0x08 (bit 3).

You already got good answers here. The BIOS is waiting for a vertical sync pulse, but never encounters one. This means that either the dot clock is missing or the CRTC setup did not work as expected.

manawyrm wrote on 2020-09-02, 12:12:

The Trident cards show a strange behaviour: the BIOS ROM will run for a while and will then write to 0x0B0000 (video memory).

Sounds like it probes for an MDA card. You should have pull-ups on the ISA data lines and read FF there if the Trident card is not responding.

manawyrm wrote on 2020-09-02, 12:12:

The Trident card pulls IOCHRDY (aka wait) low permanently, stalling the ISA bus and never returning. Any ideas on why write calls to the video memory might cause IOCHRDY to be low permanently?

You have multiple issues that seem to point in the same direction: Somehow, the card does not make progress (does not generate video timing; does not release IOCHRDY). To make progress, the card needs a clock. The clock on those cards is either driven by on-board crystal oscillators or by a frequency synthesizer chip driven by the ISA OSC signal. On a standard VGA cards, only two of the four possible clock settings were valid, chosing other clock settings might result in the chip not getting any clock signal. So my recommendation on this one is:

  1. Double-check that the 14,318MHz oscillator on OSC does actually work
  2. Log write access to port 3C2. The bits that remain after masking with 0x0C select the clock. 00 means 25MHz, and 04 means 28MHz (approximately; I'm too lazy to look up the fractional parts). If 08 or 0C is written into that register, the card might end up without a clock.

Reply 18 of 36, by manawyrm

User metadata
Rank Newbie
Rank
Newbie

Hello mkarcher,

thanks for all that info, very helpful 😀

I've gone ahead and checked the 14 MHz oscillator and it was looking a bit to sinusoidal for my taste, so I've built a simple buffer with a 74-logic gate that made it a pretty perfect square wave again:

hbvdefainju.png
Filename
hbvdefainju.png
File size
35.6 KiB
Views
372 views
File license
Public domain

I'll check all the calls to INT 10h and make sure that it's not trying to call any "special" functions. IIRC it was only calling a function to disable the video output.

About the memoutb bitshift: Whoops, good catch, thanks!

mkarcher wrote on 2020-09-03, 16:44:

Sounds like it probes for an MDA card

Hm, the data bus and the IOCHRDY line are pulled up by my MCU, so the video card has to decode the address at 0xB0000 and pull the line actively low (it's at 0.00V in that state).

mkarcher wrote on 2020-09-03, 16:44:

Log write access to port 3C2.

There's none. That might be part of the problem 😁
I'll look that register up and see if I need to set it before running the BIOS.

Thanks,
Tobias

Reply 19 of 36, by mkarcher

User metadata
Rank Member
Rank
Member
manawyrm wrote on 2020-09-03, 22:02:
mkarcher wrote on 2020-09-03, 16:44:

Log write access to port 3C2.

There's none. That might be part of the problem 😁
I'll look that register up and see if I need to set it before running the BIOS.

You don't need to set that register. The PC/XT/AT BIOS does not touch the register before calling the VGA BIOS POST function at C000:3. I expect the video BIOS to set that register to make sure a valid clock is programmed, but maybe the assumption that this register is accessed at that point is wrong. I also expect the BIOS to set up the memory mapping of the VGA card before touching B000:0 (this means writing to 3CE, index 6).

Some video BIOSes might use 16-bit I/O instructions to write to indexed registers. You need to split them in the same way as 16-bit memory writes.