Building a 16-bit Console

Is it worth building a 16-bit console in 2026? Let's explore the possibilities and challenges of creating a retro-inspired gaming system.

Building a 16-bit Console Modern games can use high-definition assets, physically based rendering, ray tracing, and GPUs that execute billions of operations every second.

Yet old consoles remain interesting precisely because they could not do any of that.

Their limitations gave them a visual language: tiles, sprites, small palettes, and music made from a handful of waveforms.

Programming those systems today is not always pleasant.

Their documentation can be fragmented, their development tools were made for another era, and an accurate emulator must reproduce not only the intended design but also the original hardware's quirks.

I became interested in that last problem after watching 100th Coin's video, "How inaccurate are Nintendo's Official Emulators?".

The video compares Nintendo's emulators with original hardware and shows how tiny timing details and hardware bugs can become observable behavior.

That raised a different question: instead of emulating an old console whose rules are partially defined by historical accidents, what if I designed a small console and its emulator together?

The result is Ember 16, or E16: an emulator-first, cartridge-based 16-bit architecture for making retro-style games with modern documentation and tools.

Its source is available in the E16 repository, while games and test cartridges live in E16Games.

The internal structure of E16 E16 borrows the understandable parts of machines such as the NES and SNES—cartridges, tile graphics, hardware sprites, palettes, and fixed audio channels—without copying either console.

Every component has an explicit contract, and hardware is controlled through memory-mapped I/O rather than hidden emulator APIs.

Diagram of the main Ember 16 components The main components are: A custom 16-bit CPU with 16 general-purpose registers and a compact assembly language.

An 8 MiB cartridge ROM window for executable code, graphics, audio, maps, and other read-only game data.

Flame Integrated Graphics, a fixed-function 2D video processor built around tiles, sprites, palettes, and scrolling.

A six-channel APU with pulse, triangle, noise, wavetable, and PCM sound generation.

Input, DMA, timers, interrupts, BIOS services, and debugging facilities exposed through memory-mapped registers.

This division is important.

The CPU runs game logic, but it does not draw the screen or synthesize every audio sample itself.

It prepares data and configures the dedicated processors, which continue working independently.

The central processing unit The CPU is a 16-bit little-endian processor with registers r0 through r15, plus special registers including the program counter, stack pointer, direct page, interrupt vector table, and flags.

Its instruction set covers data movement, arithmetic, bitwise logic, comparisons, branches, function calls, stack operations, and interrupts.

Byte and word instructions make it possible to work naturally with both packed assets and native 16-bit values.

Some representative instructions are: add r1, r2 adds r2 to r1. sub r1, r2 subtracts r2 from r1. load r1, @address reads a 16-bit word from memory. loadb r1, @address reads a single byte. call function pushes a return address and transfers control to a function. wait pauses execution until an interrupt, which makes it useful in frame loops.

The unusual part is the relationship between 16-bit registers and a 24-bit address space.

E16 can address 16 MiB, from 0x000000 to 0xFFFFFF, even though a general-purpose register only holds 16 bits.

Register-indirect memory operations combine a register with the direct-page base: Here, @r1 resolves to 0xFF1000.

This keeps the common case compact while still allowing code to reach work RAM, video and audio memory, cartridge ROM, BIOS ROM, and MMIO.

The memory map deliberately makes those regions visible.

E16 has 256 KiB of work RAM, 128 KiB of video RAM, 32 KiB of audio RAM, a 64 KiB save-RAM window, the 8 MiB cartridge window, BIOS ROM, and 64 KiB reserved for hardware registers.

There is no operating system hiding the machine from the game: programming E16 means programming the machine.

The accompanying assembler, e16asm, turns .e16 source files into executable cartridge binaries.

It supports labels, constants, includes, expressions, macros, data directives, and reusable source modules, so a project can grow beyond one large assembly file.

Flame Integrated Graphics Flame is not a modern programmable GPU.

It is a deterministic 2D renderer designed to be comfortable for games while retaining meaningful constraints.

It produces a fixed 320 × 180 image at 60 Hz, a 16:9 resolution that scales exactly to 720p and 1080p.

Most graphics use 4-bit indexed color.

Two pixels fit into each byte, and every pixel selects one of 16 colors from a palette.

Flame keeps 16 active palettes of 16 colors, giving 256 simultaneously addressable entries chosen from the larger RGB555 color space.

For sprites, index zero is transparent.

The basic graphical unit is an 8 × 8 tile.

Backgrounds are grids of tile references rather than full-screen images, which saves memory and makes scrolling inexpensive.

Flame provides three background layers—far background, main playfield, and foreground or overlay—plus an optional window layer.

Each can select its own tilemap, tile graphics, scroll position, priority, and transparency behavior.

On top of those backgrounds, Flame can render up to 128 hardware sprites.

A sprite can be 8 × 8, 16 × 16, 32 × 32, or 64 × 64 pixels, with palette selection, horizontal and vertical flipping, and four priority levels.

This is enough to place a player behind foreground scenery, keep projectiles above enemies, and draw interface elements above the world without asking the CPU to composite a framebuffer.

The CPU updates Flame through VRAM, object attribute memory, and registers in the 0xFF1000 MMIO range.

Large asset copies can use DMA, and games normally synchronize visible updates with the vertical blank interrupt.

Those rules make rendering predictable in the emulator and leave a plausible route toward an FPGA implementation later.

The audio processing unit The Ember APU follows the same philosophy: a small fixed-function processor with enough variety to make music and sound effects, but not a hidden digital audio workstation.

It provides six stereo channels: Two pulse channels with 12.5%, 25%, 50%, and 75% duty cycles.

One triangle channel, useful for smoother bass tones.

One noise channel for percussion, impacts, and explosions.

One wavetable channel that reads a custom waveform from audio RAM.

One PCM channel for recorded samples.

Every channel exposes controls for pitch, left and right volume, duration, and channel-specific parameters.

The APU generates audio at the host output rate while a 60 Hz frame sequencer drives musical timing, length counters, and envelopes.

A game can therefore implement a compact music driver that advances notes once per frame while the APU continuously produces the waveform.

For composing, the project also includes E16 Music Maker, a six-channel tracker with live playback, wavetable design, PCM recording and import, and export to callable E16 assembly routines.

This closes an important gap in homebrew development: music can be composed visually and then integrated into a game without manually translating every note into register writes.

From source code to a running cartridge A console is not very useful without a toolchain.

E16 is developed as a group of small tools that share the architecture's rules: 1. e16asm assembles source code into a binary cartridge. 2. e16img, e16spritepack, and e16tilemap convert PNG artwork into the packed formats expected by Flame. 3. e16musicmaker exports music and audio data for the APU. 4. e16emu loads the result, emulates the hardware, and provides debugging and strict-validation modes.

The emulator is the current reference implementation.

That means correctness is measured against the written architecture: instruction decoding, address calculations, layer priority, palette lookup, audio state, interrupts, and timing should all produce deterministic results.

The specification and emulator can evolve together without relying on undocumented behavior from a physical console made decades ago.

BIOS and internal tests When E16 starts, its BIOS initializes the machine, uploads its own graphics and audio assets, validates the cartridge header, and then transfers control to the cartridge entry point.

The startup screen makes the machine feel like a console rather than a program that happened to open an SDL window.

The Ember 16 BIOS startup screen The less glamorous screen below is just as important.

The internal test cartridge exercises the CPU, memory, DMA, graphics, audio, input, interrupts, BIOS behavior, cartridge loading, and timing.

Some checks can produce an automatic result; visual and audio behavior is presented as a readable scene that a person can mark as passing or failing.

The E16 internal test cartridge menu Writing tests for a fictional console may sound circular, but they force every vague idea to become observable behavior.

What happens when a sprite overlaps a foreground tile?

When is an interrupt acknowledged?

Which byte of a 24-bit address comes first?

If a test cannot state the expected result, the architecture is not defined clearly enough yet.

Building a real game: Pong The fastest way to discover whether a console design works is to make a game for it.

Pong began as a small demonstration, but it now exercises much more of the system: controller input, sprite movement, collision detection, scoring, background layers, particle effects, sound effects, and music.

Pong title screen running on Ember 16 Pong gameplay with particle effects That is the value of a deliberately small platform.

Pong does not need a game engine with a scene graph and a general-purpose renderer.

It needs a loop that waits for the next frame, reads the controllers, updates a few values, writes sprite attributes, and advances the audio driver.

The entire path from source instruction to visible pixel remains understandable.

Is it worth building a 16-bit console in 2026?

It is not worth doing because the world needs another way to run Pong.

It is worth doing because a console is a compact lesson in computer architecture.

The project connects instruction encoding, memory layout, assemblers, graphics formats, audio synthesis, input, interrupts, debugging, and game design.

A decision in one component immediately becomes a constraint somewhere else.

Designing the emulator and specification together also changes what “accuracy” means.

E16 does not need to preserve accidental flaws from unavailable hardware.

It needs a precise contract and multiple ways to verify that the implementation follows it.

Constraints can be chosen for creative or educational value instead of inherited blindly.

E16 is still evolving, and that is part of the appeal.

Every test cartridge, tool, and game reveals something that the architecture failed to explain.

The goal is not to imitate one historical console perfectly, but to build a small machine whose complete behavior can be understood—and then use it to make games that have an identity of their own.

Now I have coerced my brother into making sprites for me and we will making some games for the E16 console really soon!

I hope to have a few playable cartridges ready for the next post, so stay tuned.