AI Agents in Low-Level UEFI Development: A Practical Retrospective
This article examines how an AI agent performs in low-level UEFI development under deliberately strict constraints. The experiment used a general-purpose large language model and a minimal development environment to build a graphical Breakout game for the UEFI Shell, including input handling, collision detection, framebuffer rendering, double buffering, and a Chinese-language UI. The results show that AI can act as a primary implementation engine when the task is observable, compilable, and runnable, but it remains limited in areas where hardware behavior, undocumented registers, or physical debugging provide no feedback loop. The article also summarizes reusable practices for using AI in firmware and BIOS work: establish observability first, enforce constraints early, inject local platform knowledge, verify in small steps, and keep humans responsible for goals, trade-offs, and risk.
1. Breaking a Common Assumption: Low-Level Development Is Not Off-Limits to AI
In firmware and BIOS circles, a common view is that AI can handle application-level code, but not low-level development. The usual argument is that low-level work depends on hardware initialization, register manipulation, protocol stack usage, boot timing, and other platform-specific knowledge. Publicly available material is limited, and much of the required detail is buried in vendor-specific platform manuals.
That view is only partly correct. The lack of specialized knowledge is real, but it can be partially compensated by engineering methods. The real difficulty in low-level development has never been only whether code can be written, but whether it can be verified.
It is useful to separate two kinds of low-level work:
- Creative programming: designing data structures, algorithms, and interaction logic from scratch. This is structurally similar to application development, and AI is often highly capable here.
- Porting work: adapting software to new hardware, new platforms, or new chipsets. This depends heavily on proprietary manuals and silicon-level details. In this area, AI is best treated as an assistant for code rewriting and specification checking, not as a replacement for human judgment about hardware behavior.
If these two categories are treated as one, the conclusion becomes overly broad: “AI cannot do low-level development.” A more accurate statement is this: in low-level subdomains that are observable, compilable, and verifiable at runtime, AI can deliver significant productivity gains; in hardware-debugging scenarios without a feedback loop, AI can only assist.
2. Experiment Setup: Writing the Constraints in Advance
To make the conclusions credible, the experiment needed self-imposed constraints. The rules were as follows:
- Minimal environment: on a Windows system, only a general-purpose IDE with a C compiler and basic debugging tools was used. No extra graphics libraries, frameworks, or code generators were installed.
- Zero manual coding: the local code repository was empty, and no code was written by hand.
- No specialized guidance: no UEFI programming tips, header-file locations, or register definitions were provided.
- Clear target: build a graphical Breakout game that runs in the UEFI Shell, supports Chinese UI display, and can be compiled into an artifact copied to a USB drive. After booting into the Shell, the executable could be launched directly.
Breakout was chosen as the benchmark because it has a complete structure: graphics rendering, keyboard input, collision detection, a frame loop, and a state machine. Its rules are also easy to understand, which makes it possible to judge whether the AI truly understands the logic rather than merely assembling plausible-looking code.
A practical consideration in model selection: flagship models had good response speed and clear reasoning, but under token-based billing, the cost for two hours of work could reach several hundred yuan per day, which was not economical for routine development. The analogy is similar to choosing a car: flagship performance is attractive, but most daily commuting needs a reliable, inexpensive, sufficient, and long-running vehicle. In the end, a general-purpose large language model from a cloud vendor’s coding plan was selected as the main model, while flagship models were reserved for architecture-level problems.
3. The Technical Terrain of a UEFI Application: Four Hurdles for AI
Before reviewing the results, it is necessary to clarify the technical constraints of the target platform. These constraints also serve as the baseline for judging whether the AI’s behavior is genuinely professional.
Hurdle 1: There is no operating system, only firmware services.
The program starts through EFI_SYSTEM_TABLE. Protocols are obtained from BootServices, text interaction uses ConIn and ConOut, and time is obtained from RuntimeServices. Even “obvious” library functions, such as memory allocation, string handling, and file I/O, must be obtained explicitly through protocols or basic libraries.
Hurdle 2: Text mode cannot draw graphics.
To create a graphical interface, the program must locate and enable the Graphics Output Protocol, query the mode table, set the resolution, obtain the framebuffer base address and pixel format, and then calculate pixel addresses manually.
Hurdle 3: There is no standard library, no floating-point guarantee, and no exception protection.
Null-pointer accesses and out-of-bounds writes do not throw exceptions. They can corrupt firmware memory or trigger a protection-mode exception that reboots the machine. Without serial output or a debugger, such problems are extremely difficult to diagnose.
Hurdle 4: Chinese text requires a custom font.
The UEFI text protocol does not support Chinese glyphs, and the console does not guarantee CJK rendering. To display Chinese characters reliably, the only safe path was to carry text as wide characters using UTF-16/UCS-2, prepare bitmap font data manually, and draw the pixels bit by bit.
These four hurdles form a natural test for evaluating AI’s low-level capability.
4. Round 1: From Zero to One
4.1 Project Skeleton: Better Than Expected
No hint was given about how to organize the build system. The model nevertheless generated a complete three-layer description-file structure: a package declaration for dependencies and shared definitions, a build description for module lists, target platforms, and compile options, and a module manifest for source files, package dependencies, and required protocol GUIDs.
# Key excerpt from the build description (illustrative)[Defines]PLATFORM_NAME = MyGamePkg[Components]MyGamePkg/Game/Game.inf# Key excerpt from the module manifest (illustrative)[Defines]MODULE_TYPE = UEFI_APPLICATIONENTRY_POINT = GameMain[Sources]Game.c Draw.c Input.c Font_16x16.c[Packages]MdePkg/MdePkg.dec[Protocols]gEfiGraphicsOutputProtocolGuidgEfiSimpleTextInputProtocolGuid
The model also handled several details that are easy to overlook:
- The graphics protocol may not be ready during startup, so handles must be enumerated and matched by device path.
- Supported video modes must be enumerated before setting a mode.
Stalluses microseconds as its unit, so the main-loop delay must be converted accordingly.
These are not mere pattern-matching responses. They reflect an understanding that usually comes from reading the specification.
4.2 Incident 1: File Copying Backfired
The first build failed repeatedly. The errors kept pointing to missing source files. After investigation, the issue turned out to be structural: the model generated source files and build-description files in two non-adjacent directory levels, while the relative paths in the build description assumed a different layout.
After multiple failures, the model fell back to a safer strategy: first output the full directory tree, then verify each path one by one. That approach finally located the problem.
Lesson: asking the AI to declare the directory structure first and self-check it is more reliable than asking it to write files immediately. Constraints must be placed upfront, not after the model has already failed.
4.3 Incident 2: The Wrong Path of Compiling Inside the Shell
I initially assumed that compilation could be completed inside the UEFI Shell, so I asked the AI to generate a build-call sequence for the Shell environment. The result was syntactically valid, but it ignored a fundamental fact: the firmware Shell usually does not contain a complete host compiler toolchain. Environment variables, working directories, and toolchain paths cannot be assumed to behave like those on a desktop system.
The correct path was to perform a cross-build on the host operating system, produce a .efi executable image, copy it to a FAT partition on a USB drive, and then execute it after booting into the Shell.
Lesson: this is a typical case of an assumption outside the AI’s knowledge boundary. The model did not refuse. It followed the incorrect premise and continued reasoning from it. A model often does not proactively correct a false premise supplied by the user, and this is one of the most dangerous failure modes when using AI.
4.4 First Successful Boot
After correcting the directory layout and build path, the program started in the Shell. The screen entered graphics mode. The paddle, brick array, and ball were drawn correctly. The arrow keys controlled the paddle, bricks disappeared after collision, and the next level began after all bricks were cleared. The first-round objective was achieved.
5. Round 2: From Working to Usable
There is still a long distance between “it runs” and “it is usable.” This round focused on improving frame rate and visual quality, supporting a Chinese UI, and making the code maintainable.
5.1 Stage 1: Feature Enhancement
New features included scoring, lives, a level-difficulty curve, pause and restart, and a game-over screen. The AI showed a clear structural advantage here. Instead of stuffing new features into the main loop, it split the program into a state machine with states such as Ready, Playing, Paused, and GameOver, and it extracted collision detection into an independent function.
// Axis-aligned bounding-box collision detection (illustrative)STATICBOOLEANHitTest (IN BRICK *Brick,IN BALL *Ball){return (Ball->X + BALL_R >= Brick->X) &&(Ball->X - BALL_R <= Brick->X + Brick->W) &&(Ball->Y + BALL_R >= Brick->Y) &&(Ball->Y - BALL_R <= Brick->Y + Brick->H);}
5.2 Stage 2: Visual Optimization
The initial version drew pixels directly into the framebuffer, which caused obvious flicker and trailing artifacts. The AI proposed a double-buffering scheme: maintain an off-screen bitmap in memory, compose the entire frame there, and then copy the completed frame to video memory in one pass. It also changed full-screen redraws to dirty-rectangle updates, redrawing only the regions that changed.
// Per-pixel write in BGR order (illustrative)STATICVOIDPutPixel (IN UINT32 X, IN UINT32 Y, IN UINT32 Color){UINT8 *Base = (UINT8 *)(UINTN)Gop->Mode->FrameBufferBase+ Y * Gop->Mode->Info->PixelsPerScanLine * 4+ X * 4;*(UINT32 *)Base = Color; // The real implementation must apply shifts and masks according to the pixel format}
The model also noticed an easily overlooked issue: on some platforms, the framebuffer pixel format may not be simple 32-bit BGR. It therefore added startup-time format detection and a conversion abstraction layer. This abstraction layer was one of the most valuable engineering judgments in the experiment.
5.3 Stage 3: Display Breakthrough, or Debugging a Black Screen with No Output
A successful graphics-mode switch followed by a completely black screen is one of the hardest problems to diagnose. There was no serial port, no log output, and no debugger. The AI adopted the correct fallback strategy:
- First, switch back to text mode and print status line by line to confirm that protocol acquisition and mode selection succeeded.
- Then, switch back to graphics mode and fill the screen with a solid color to verify the framebuffer address.
- Finally, overlay a single pixel to establish a coordinate system.
After these three bisection steps, the root cause was identified: the wrong mode index had been selected, causing a mismatch between the framebuffer base address and the actual display region.
This was the strongest part of the AI’s performance in the entire experiment. Faced with an unobservable system, the model was not misled by code that merely looked correct. Instead, it constructed minimal observable experiments and gradually turned a black box into a white box. This “establish feedback first, then fix the problem” mindset is precisely one of the core competencies of low-level development.
5.4 Incident 3 and Stage 4: Chinese Display Breakthrough
Chinese support was the hardest hurdle, and an encoding incident occurred along the way. Source files were saved with different character encodings at different editing stages, causing character-parsing warnings during the build. In some cases, comment regions even affected preprocessing.
The solution was to unify all source files to a single encoding, normalize line endings, and change comments to ASCII characters. Chinese content was kept only in data resources. Separating data from logic to avoid encoding risk is a pragmatic and professional approach.
The actual Chinese rendering approach was as follows:
- UI text was carried in wide-character constants to avoid narrow-character truncation.
- An offline tool extracted the required Chinese characters into 16×16 monochrome bitmaps. The UI used roughly one hundred Chinese words rather than the full character set, and the generated C array served as the font table.
- During rendering, the character code was used to look up the glyph index. Each bit was read, and foreground color was applied where the bit was 1.
// Illustrative wide-character UI text; the implementation used Chinese strings.CHAR16 *gTitleText = L"BREAKOUT";CHAR16 *gHelpText = L"Arrow keys move the paddle Space to launch ESC to exit";// Illustrative 16x16 bitmap drawingSTATICVOIDDrawGlyph (IN CHAR8 Ch,IN UINT32 OriginX,IN UINT32 OriginY){CONST UINT16 *Rows = &gFont16x16[(UINTN)Ch * 16];UINT32 X, Y;for (Y = 0; Y < 16; Y++) {for (X = 0; X < 16; X++) {if (Rows[Y] & (0x8000U >> X)) {PutPixel (OriginX + X, OriginY + Y, COLOR_FOREGROUND);}}}}
A human decision point must be highlighted here: choosing a subset bitmap font instead of a full CJK font was a trade-off based on artifact size and startup loading overhead. The AI initially preferred embedding the complete font, and the subset approach was adopted only after a human rejected that idea. This is a typical case where human involvement is required: trade-offs involving product constraints and resource budgets belong to goal definition, not implementation.
6. Output Inventory: What the AI Actually Did
The contribution can be roughly divided by attributability. This is a qualitative summary, not a precise statistical measurement.
Mostly completed independently by the AI
- Project skeleton and build-description file structure
- Graphics protocol discovery, mode enumeration, and framebuffer-writing abstraction
- Main loop, state machine, collision detection, and scoring logic
- Design and implementation of double buffering and dirty-rectangle optimization
- Bitmap font parsing and glyph-drawing logic
- Bisection-based diagnosis and repair of the black-screen issue
Assisted by the AI but led by humans
- Unifying source-file encoding and correcting comment character sets
- Deciding the font-set scale and accepting the resulting size trade-off
- Establishing the correct build and run environment: host-side build and USB-drive execution
- Defining acceptance criteria and regression checks
Completely beyond the AI’s coverage
- Timing and initialization behavior of specific hardware platforms
- Semantics of undocumented private registers
- Problems involving physical phenomena such as signal integrity, power consumption, and thermal behavior
7. A Reusable Methodology: Making AI Truly Useful in Low-Level Development
Five practical lessons can be drawn from this experiment.
1. Turning the unobservable into something observable is the first priority.
Before writing features, establish a minimal feedback channel with the AI: serial logs, screen-based debug output, status LEDs, or build-time assertions. Without a feedback loop, both AI and humans are reduced to guessing in low-level tasks.
2. Place constraints upfront instead of patching them later.
Directory layout, encoding rules, build commands, output paths, and prohibited libraries should all be specified in the first prompt. The root causes of the first two incidents were missing constraints, which forced rework.
3. Inject local knowledge to fill the corpus gap.
Platform manuals, internal header files, protocol definitions, and existing code samples should be organized into retrievable local material and provided during the conversation. This is far more efficient than repeatedly correcting the model’s assumptions.
4. Verify in small steps and forbid large, unreviewed generations.
Advance only one verifiable goal at a time: light up one pixel, draw a coordinate system, render one block, connect keyboard input, add collision detection. Each step must produce the expected change on the screen or in the log before moving forward. This keeps the cost of error localization as low as possible.
5. Humans must retain three categories of decision authority.
First, goals and acceptance criteria: what counts as complete. Second, trade-offs and budgets: priorities for size, performance, and compatibility. Third, safety and risk: changes to the boot path, memory and pointer out-of-bounds issues, and operations that may affect machine stability. These three categories must be reviewed by humans and cannot be delegated.
8. Conclusion: Position AI Accurately, and the Benefits Become Real
Returning to the original question: what can AI do in BIOS and low-level development?
The conclusion is this: in low-level subdomains with build environments and runtime feedback, AI can already serve as a primary implementer, compressing the cycle from zero to a runnable prototype to a fraction of what it used to be. In porting scenarios that depend on hardware details, private documentation, and physical debugging, it is better suited as an assistant for code rewriting, specification checking, and documentation organization.
The claim that “AI can only write application-level code” is essentially mistaking a lack of feedback loops for a lack of knowledge. The real dividing line is not the abstraction level of the task, but whether the task can be verified quickly. This also explains why the same model can perform well in an application project with tests but repeatedly run into trouble in bare-metal debugging.
One final point is not about model capability, but about how the model is used: the model will continue writing from a false premise and will not proactively stop. When its proposed solution is syntactically perfect but environmentally absurd, the only remaining defense is your own domain knowledge. Manage AI like a highly capable junior engineer: it implements the goals, you define the standards, and you bear the risks. This division of labor also holds in low-level development, and the benefits there are even more obvious than in application development.
