In this blog I share my observations, thoughts and experience about computers, linguistics, philosophy and many other things that interest me.

Showing posts with label GK-208. Show all posts
Showing posts with label GK-208. Show all posts

Wednesday, July 01, 2026

gk208-videobios, part 2/8: The Idea, and a Buried Emulator

Second in a series on building a firmware-free VideoBIOS for RISC-V. In Part 1 I explained why I wanted a RISC-V machine that greets its monitor at power-on the way an old IBM PC does. This post is about the first idea I had for how to get there — and the first-in-the-world compile that it needed.


The story so far. I built a RISC-V personal computer — a SiFive HiFive Unmatched with a real NVIDIA graphics card and a real monitor — and it stayed dark until Linux loaded the nouveau driver. I wanted it to speak to the screen before any operating system. The question was how.

The card already knows how to wake itself

Here is a fact that took me an embarrassingly long time to fully appreciate, even though I had known it in the abstract for years:

Every PC graphics card carries its own little program that knows how to bring itself up.

It is called the video BIOS — the VBIOS — and it lives in a small ROM on the card. When an old PC powers on, the motherboard firmware finds that ROM, and runs it. The VBIOS initializes the card's memory, sets up a display mode, wires up the outputs, and hands back a card that will happily show text on a monitor. That is the reason a PC can greet you on screen before it has loaded a single byte from disk: the intelligence to light the display is already sitting on the card, and the CPU simply executes it.

So my card — a GK208, a modest little NVIDIA Kepler, sold as a GeForce GT 710 — already contained, in ROM, a complete recipe for waking itself into a usable display. I did not have to invent that recipe. It was right there.

There was just one problem, and it is the whole reason this series is not three paragraphs long.

The VBIOS is an x86 program. It is machine code for Intel processors. My computer is RISC-V. My CPU cannot execute a single instruction of it.

What if the RISC-V CPU could pretend to be x86?

The idea that started everything was simple to state: if my RISC-V processor cannot run the card's x86 firmware directly, perhaps it can emulate an x86 processor well enough to run it. Interpret the x86 instructions one by one, in software, on the RISC-V core. Slow, yes — but this program runs exactly once, at boot, for a fraction of a second. Speed does not matter. Correctness does.

It felt almost too obvious. Surely, I thought, this is a solved problem. And in a sense, it was — which brings me to the buried treasure.

The emulator that was already there

U-Boot — the bootloader I use on the Unmatched — turns out to already contain a small x86 emulator. It lives in drivers/bios_emulator/, and its lineage goes back to the old XFree86 / SciTech x86emu, a compact interpreter that was used, years ago, to POST video cards on non-x86 machines — PowerPC Macs, some ARM systems — for this exact purpose: running a card's x86 VBIOS on a CPU that is not an x86.

It was, in other words, precisely the tool I needed. It had just been sitting there, more or less forgotten, aimed at architectures nobody uses for this anymore.

The obvious next step: build it for RISC-V and point it at my GK208.

Except, as far as I could tell, nobody had ever compiled bios_emulator for RISC-V. It had support for x86, PowerPC, MIPS, ARM, SH — the architectures of a previous era. RISC-V simply was not in the code. And when I tried to build it anyway, it did exactly what old, architecture-specific code does when you point it at an architecture it has never met: it fell over.

What follows is the story of the small, stubborn fixes it took to make a decades-old x86 emulator compile and link on RISC-V — as best I can tell, for the first time. None of them are large. Several are almost comically small. Together they are the difference between "does not build" and "builds."

Fix 1: teach the assembler how to write a comment

The very first wall was the smallest imaginable. The emulator's header, x86emu.h, needs to know which character starts a comment in the target's assembler — because parts of it emit inline assembly. It had an entry for x86, for ARM, for MIPS, for PowerPC, for SH… and nothing for RISC-V. So the build stopped before it really started.

The fix is one line — really, one word:

#if defined(CONFIG_ARM)
#define GAS_LINE_COMMENT	"@"
-#elif defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_X86)
+#elif defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_X86) || defined(CONFIG_RISCV)
#define GAS_LINE_COMMENT	"#"

RISC-V uses # for assembler comments, same as x86, MIPS and PowerPC. That was the entire fix. And yet I find it a perfect little emblem of the whole endeavor: the first thing standing between a RISC-V CPU and an x86 BIOS was that the emulator did not know how RISC-V writes down a comment. You have to earn every inch.

Fix 2: MTRRs are an x86 thing

Next, a neighbouring video driver (vesa.c) refused to compile. It used MTRRs — Memory Type Range Registers — which are a specific x86 mechanism for controlling how regions of memory are cached. There is no such thing on RISC-V. The code simply assumed everyone had them.

The fix was to wrap the MTRR use in preprocessor guards so it is compiled only on x86, and skipped everywhere else. Not glamorous, but it is the recurring theme of this entire post: code written when x86 was the only world that needed it.

Fix 3: the PCI config calls had the wrong first argument

Then the emulator's "back-end system" file, besys.c — the part that lets the emulated BIOS talk to real PCI hardware — produced a wall of compiler warnings on RISC-V. A batch of them came from calling U-Boot's pci_read_config_* / pci_write_config_* helpers with the wrong type of first argument. On x86 the old signatures had quietly matched; on a modern RISC-V build they did not. Fixing the argument types cleared a whole class of warnings and, more importantly, made those calls actually correct.

Fix 4: x86 has I/O ports. RISC-V does not.

This one is my favourite, because it is not really a compile fix at all — it is the moment the fundamental mismatch between the two worlds first showed its face.

The x86 architecture has two separate address spaces: normal memory, and a distinct I/O port space that you reach with dedicated IN and OUT instructions. A great deal of old PC hardware — including video cards — is poked through those I/O ports. The emulator models this with a set of macros, PM_inpb / PM_outpb and friends, which on a real x86 turn into actual in/out instructions.

RISC-V has no I/O port space at all: everything is memory-mapped. There is no IN instruction to map those macros onto. And the existing code, on a non-x86 target, did something quietly dangerous: those port accesses would degrade into reads and writes of more or less random memory locations — while also generating a heap of rightful compiler warnings.

I was not ready to properly re-plumb every I/O port access yet, but I absolutely could not leave macros that scribble on random memory. So I replaced them, on non-x86 builds, with small functions that do the safe and honest thing: warn, and move on.

static inline u8 PM_inpb(u16 port)
{
	printf("x86 port 0x%x read attempt, returning 0\n", port);
	return 0;
}

static inline void PM_outpb(u16 port, u8 val)
{
	printf("x86 port 0x%x write attempt, ignoring\n", port);
}

Now, when the emulated BIOS reached for an x86 I/O port that does not exist on my machine, U-Boot would tell me so, plainly, instead of corrupting memory. This turned out to be enormously useful later — those warnings became a running commentary on everything the VBIOS assumed about the machine underneath it. And in hindsight, this little fix is the first faint crack in the whole approach: an x86 BIOS expects an x86-shaped world, and my machine is not one.

Fix 5, 6, 7: includes, malloc, and a BAR that would not fit

Three more, briefly, because bring-up is always a long tail of small things:

  • Missing includes in debug.c. With the emulator's debug tracing turned on, two header files were missing and the compiler complained. Added them. (I would spend a lot of time in that debug tracing later — more on that in the next post.)
  • The SPL malloc pool was too small. The first time I actually asked U-Boot to run the card's BIOS — via its dm_pci_run_vga_bios() path — it stopped dead, reporting that its early-stage memory pool was exhausted. The emulator needs room to work. So I bumped the default pool size when both the emulator and RISC-V are configured.
  • The card's BAR0 did not fit in the memory window. This was a proper head-scratcher. U-Boot's PCI setup was handing the card a small memory region — "region 2," 16 MB, starting at 0x7000000. But the GK208's BAR0 (its main register window) is itself 16 MB and simply would not fit. The fix was to tell U-Boot to use the much larger "region 1" at 0x60090000 instead — which, reassuringly, is the very same address the Linux kernel uses for this card:

    U-Boot should use "region 1" instead (the one which starts at 0x60090000), because it has much bigger size, and easily accommodates BAR0 of the video card. Linux kernel also uses 0x60090000 as bus_start/phys_start.

It compiles — and it went upstream

Somewhere in this sequence, a decades-old x86 emulator quietly compiled and linked for RISC-V, I believe for the first time. That was a genuinely happy evening.

And these were not just private hacks. Several of them I cleaned up and sent to the U-Boot mailing list, and two of them — the malloc-pool bump and the PCI region fix — came back with a Reviewed-by: from a U-Boot maintainer. Small patches, but real ones, now part of the conversation about running video BIOSes on RISC-V. That mattered to me more than the size of the diffs suggests.

But compiling is not running

Here is the sober note to end on. Making the emulator build was the easy half. An emulator that compiles is not an emulator that has successfully brought a graphics card to life. All I had, at this point, was a program that was willing to try — and a card whose x86 firmware had never in its existence been executed by anything other than a real Intel-compatible CPU.

The moment I actually pointed the emulator at the GK208's BIOS and let it start interpreting, a much stranger and harder adventure began — one where I would end up so deep inside the emulator that I had to fix its disassembler just to be able to read what the card's own firmware was doing.

That is the next post.

Next: Teaching a RISC-V Chip to Dream in x86.

Tuesday, June 30, 2026

gk208-videobios, part 1/8: The Personal Computer as a Product

First in a series on building a firmware-free VideoBIOS for RISC-V — teaching a cold graphics card to speak to a monitor before any operating system exists.


There is a small moment I have loved my whole life, and I suspect I am not the only one.

You press the power button. A fan spins up. For a second the screen is black — and then, out of that black, text appears. A memory count. A board name. A line telling you which key opens the setup. Half a second of the machine simply saying hello before it goes off to find an operating system.

I have spent decades around computers that do this, and I never stopped finding it a little bit magical. Not the operating system — the operating system comes later, and everyone talks about the operating system. I mean the moment before it. The moment when the bare machine, with no disk mounted and no kernel loaded, already knows how to talk to you.

This series is the story of how I tried to give that moment to a computer that never had it. But before any of the hex and the heartbreak, I want to explain why it mattered enough to spend the better part of two years on it.

A product, not a kit

When I was young the IBM PC and its compatibles were, to me, a kind of quiet miracle of engineering — not because any single chip was brilliant, but because the whole thing was thought through as a product. Somebody had sat down and decided what a person sitting in front of this machine should experience, from the very first instant of power, and then made a hundred different manufacturers agree on it.

Think about what that actually required. A video card from one company had to light up a monitor from another company, driven by a BIOS from a third, on a motherboard from a fourth — and it all just worked, the moment you switched it on, with no software installed at all. There was a standard for how the card announced itself. A standard for the text the firmware could print. A standard for the keys you pressed to change settings. A standard for how the screen was laid out: 80 columns, 25 rows, an attribute byte for every character.

None of that was inevitable. It was designed. It was oriented, deliberately, at the human being in the chair — usability, compatibility, standardization, all in service of the idea that a personal computer is a finished product you can buy, take home, and use, not a bag of parts you must first understand.

I have carried an enormous respect for that idea ever since.

The machine always started by speaking

Here is the part that seems almost quaint now, and that I miss more than I can justify: every one of those machines started in text mode.

Before the graphical desktop, before the splash screen, before anything decorative, the computer came up talking. Plain characters on a plain background. The firmware greeted you, the boot loader offered you a menu on the monitor, and if something went wrong, it told you so — right there, in text, on the same screen you were already looking at. You did not need another computer, a serial cable, and a terminal program to find out why your machine would not boot. It told you itself.

That text-mode greeting was the machine's native voice. It was the first thing it learned to do and the last thing it would give up. You could lose the disk, lose the OS, lose almost everything — and the box would still come up and speak.

The gap I could not stop noticing

Years later I fell in love with RISC-V — the open instruction set, the idea of a truly open computer from the silicon up. I built and collected RISC-V machines. I ported operating systems to them. I wrote my own. And somewhere in all of that, a small, nagging absence started to bother me more and more.

There is no RISC-V personal computer that greets you the way an IBM PC does.

I do not mean there is no RISC-V hardware — there is plenty, and some of it is wonderful. I mean there is no RISC-V machine you can buy that is finished as a product in the old sense: no firmware setup screen on the monitor, no boot menu you can read on the display, no reassuring line of text at power-on. The early boot happens over a serial port. To watch your own computer start up, you attach a second computer to it. The monitor stays dark — often completely dark — until a full operating system has loaded a full graphics driver.

For a server in a rack, fine. But I did not want a server. I wanted a personal computer. And a personal computer that cannot say a single word to its own monitor until Linux is fully up is, to me, missing something essential — the very thing I had admired all those years.

Building my own — and meeting the dark screen

So in 2021 I did the obvious thing for a person like me: I built the machine myself. A RISC-V board (SiFive Unmatched), a real graphics card, a real monitor, a real keyboard. A personal computer, assembled on my own desk.

And it worked beautifully — with exactly one condition. The screen came alive only after the Linux nouveau driver had loaded. Not one instant before.

Sit with what that means day to day. Every time Linux had trouble booting, the monitor showed me nothing — I had to crawl back to the serial cable to find out what happened. And when I started developing my own operating system on that machine, the situation was worse: my OS had no graphics driver, so my beautiful personal computer, with its beautiful monitor, produced zero output on the screen, ever. Just the serial port, as always. The display sat there, powered, plugged in, and mute.

I had built the RISC-V machine I wanted — and it could not do the one small, magical thing every cheap PC from Early Days did without thinking: come up out of the black and say hello.

The question

That is where this whole adventure begins — with a question that sounds simple and turned out to be anything but:

What would it take to make a cold RISC-V machine speak to its monitor, on its own, before any operating system exists?

Not "load a driver faster." Not "port nouveau." Something closer to what a real PC has: a small, self-contained piece of firmware that wakes up the graphics card from cold silicon, puts a readable, colorful greeting on the screen, and then politely steps aside so the operating system — any operating system — can take over.

It took me down two very different roads. The first one seemed obvious, consumed months, and ended at a wall. The second one meant throwing the first away entirely — and it worked.

In the next post, I'll tell you about the idea that started the first road: the realization that the graphics card already carries a program that knows how to wake itself up… it just happens to be written for the wrong kind of processor.

Next: The Idea, and a Buried Emulator.