Fantasy Console Emulator: System Integration @LowByteProductions
Fantasy Console Emulator: System Integration  @LowByteProductions
Uploaded March 2021 | Updated September 2026, 2 weeks ago
In this installment of the Fantasy Game Console Series (an extension to the 16-Bit Virtual Machine Series), we're integrating the system. We'll be covering building out the memory mapped devices, integrating the CPU, taking and mapping input to the address space, and wiring in the graphics pipeline and execution model. We explore the data format for games by setting up all the required data and code in memory, including configuring the interrupt vector and handlers. As a bonus, we implement a caching solution for graphics which greatly improves the performance of drawing and thus of the entire fantasy game console!


The fantasy console serves as a very simple model for an emulator of a classic game console like the NES or the GameBoy, only without all the tricky real world details and limitations that come with such systems. It allows us to contextualise all the topics we've been learning about while building the VM, and gain some understanding about how it must have been to be a programmer in the days of true resource constraint!


00:00 - Improving graphics performance with caching
03:10 - Writing the caching code
09:00 - Testing the cache performance
09:40 - Discussing the offscreen changes
12:13 - Creating memory mapped devices
13:52 - Setting up tile memory
14:47 - Setting up background and foreground memory
16:12 - Setting up the sprite table
16:57 - Building the memory mapped controller input device
17:55 - Caching tiles
18:27 - Writing some assembly code to bootstrap the machine
19:48 - Setting up the interrupt vector
20:23 - Initialising a CPU instance
20:40 - Setting up the game loop and graphics pipeline

=[💻 Changes Made Offscreen 💻]=

__New Instructions__


A few new instructions we're added to the VM, and all were to make it easier to move 8 bit values around, instead of the native 16 bit values. These are:

- move 8-bit literal value to register
- move 8-bit value in memory to register
- move low 8-bits of register to memory
- move high 8-bits of register to memory
- move 8-bit value, pointed to by register, to register
- move low 8-bits in register to address pointed to by register

__CreateRAM / CreateROM__

The old function `createMemory` has been replaced by 2 new functions: `createRAM` and `createROM`. `createRAM` is essentially the same as the older function, but adding more to the exposed interface (the interface being the DataView). Namely, it adds some new methods:

- load: which takes an array of bytes and places them into the memory buffer
- slice: which returns a continuous subset of the bytes contained in the memory buffer

The `createROM` function is like `createRAM` in every way, except that the exposed `setUint16` and `setUint8` methods do not actually set data - therefore a programmer writing assembly code for this system cannot alter what is in the memory. The emulator, however, can still use the `.load` method to set the initial value of the device. You can imagine that this will be done when the emulator decodes a "cartridge ROM".
As mentioned in the video, the controller input data is also modeled as a ROM - though it's not actually a ROM in the traditional sense. What we're really doing is using the interface to stop the programmer writing to this protected memory space, while using the `.load` method to update the input values every frame.

__Memory Mapper__

A small change that allows us to provide a start address and a size, instead of a start address and an end address. It's a better interface, (a) because you tend to think of devices in terms of their size anyway, and (b) because you're never sure whether start/end arguments are inclusive of the final element!

__Assembler__

This one was pretty self explanatory: The functionality of the assembler was simply wrapped up in a function, which instead of finding it's code directly in the file (which we did during testing), instead takes it from the argument. The offset argument (entry point is probably a better name), simply allows us to "position" the code in memory, by calculating the addresses of all labels and other symbols in reference to this initial offset.

=[ 🔗 Links 🔗 ]=

🎞 Series Playlist: youtube.com/playlist?list=PLP29wDx6QmW5DdwpdwHCRJsEubS5NrQ9b&playnext=1&index=1
💌 Updates to your inbox: tinyletter.com/lowleveljavascript
🗣 Discord: discord.gg/FPWaVgk
⭐️ Patreon: patreon.com/lowleveljavascript
💻 Github Repo: github.com/LowLevelJavaScript/16-Bit-Virtual-Machine
Fantasy Console Emulator: System IntegrationFrogger In Assembly: Fantasy Console Part 3Addition In Digital Logic [The Bits And Bytes Of Binary ep. 4]Hardware RISC-V CPU in TypeScript: First StepsCryptographically Signed Firmware :: Bare Metal Programming Series 14Designing A Packet Protocol :: Bare Metal Programming Series 7.1Ints, Uints, and Parsing an IP Packet Header [Parser Combinators From Scratch] Episode 7A parser for every instruction? (16-Bit VM in JavaScript 010)What is a spinlock? //Source Dive// 002Ones and Zeros: Binary Part 1 [Parser Combinators from Scratch] Episode 6Writing A Game For The Fantasy Console EmulatorBootloader Firmware Update Mechanism :: Bare Metal Programming Series 10
Low Byte Productions |

Fantasy Console Emulator: System Integration

SHARE TO X SHARE TO REDDIT SHARE TO FACEBOOK WALLPAPER