The VICERA: Part I
Original publishing date: Sun 13 Sep 2020 06:50 PM
Introduction
VICERA is a 8-bit fantasy console heavily inspired by many aspects of the
Gameboy architecture (mostly in the CPU). All the core components are written
in standard C. Only main.c
and the SDL front-end aren't. That means it is
pretty much portable and writing your own front-end should not be a difficult
task.
To people who doesn't know what fantasy consoles are: They are most likely game engines running on a virtual machine specifically designed for it simulating the harsh hardware limitations of retro consoles or computers.
So far, the console, still in development, has arrived at a point of stability and it can now be used. At the time of writing, no games has been created on it yet. You can build it yourself from source by cloning this repository.
This blog will be divided into multiple parts.
NOTE: This blog is really long.
0. The console
So the projects starts here. Nothing, just my whiteboard to start designing the whole project. I first started designing the whole thing in a "quick" way to get an approximative idea of how the project is going to look like.
So I started thinking, and I thought about a simple console, easy to implement which would be also easily portable with pretty harsh hardware limitations.
At this point I started looking up this Gameboy Programming Manual and this is where I started having a more precise idea of the CPU.
1. The CPU
So that said, the CPU will be close to the Gameboy architecture, but with less instructions, registers and flags so it is easy to implement (plus 2 instructions taken from the Intel x86 architecture).
To know about the CPU architecture, you can look up the VICERA documentation. It documents everything about the CPU.
The implementation wasn't a difficult task but more of a boring, repetitive task most of the time. I had to write a function for every instructions and then write a huge switch-case block.
The CPU has 7 registers, a stack pointer, a program counter, 2 flags and needs
64 KiB of memory. So I have made a struct
to carry all this information
through all the functions:
struct CPU {
// 64kb Memory bank.
BYTE memory[0x10000];
// Registers
BYTE registers[REGSIZE];
// Program Counter
WORD pc;
// Stack pointer
WORD sp;
// Flags
BYTE flags;
// Is it running?
BYTE running;
}
Oh I forgot to mention that I have defined BYTE
and WORD
to shorten the
definition of specific integers,
// Types definitions
typedef unsigned char BYTE;
typedef unsigned short int WORD;
Every instruction has it's own function. We will use the xor
instruction as an
example for my blog:
// xor r
// XOR instruction with a register as an argument.
void xor_r(struct CPU* cpu, int reg_a)
{
// Takes the register from an intermediate function named get_register
BYTE *r = get_register(cpu, reg_a);
// Performs a XOR operation with the taken value
cpu->registers[REG_A] ^= *r;
}
// xor n
// Same thing but with a direct value.
void xor_n(struct CPU* cpu, BYTE byte_a)
{
cpu->registers[REG_A] ^= byte_a;
}
As mentionned in this block of code, I have used a few intermediate functions to avoid repetition and makes development easier. Here two of those:
// BYTE, BYTE -> WORD
// converts two bytes into a word.
WORD btoword(BYTE byte_a, BYTE byte_b)
{
return (byte_a * 0x100) + byte_b;
}
// struct *CPU -> WORD
// returns a 16-bit value from the two following bytes
// in the RAM starting from (PC + 1)
WORD memword(struct CPU *cpu)
{
return btoword(cpu->memory[cpu->pc + 1],
cpu->memory[cpu->pc + 2]);
}
I have also made two debugging functions to dump memory or registers.
Both named respectively dump_memory
and dump_registers
.
There comes the really long O' big switch-case block. I have setted up a big
enum
with all the instructions then bound every opcode into their matching
function.
Now I have finished implementing the CPU, what now? How can I program it? To answer this question, I have wrote a little toy assembler (which can be found in the GitHub repo, refer to the External Links at the bottom of the blog.) which has the very basic features of a typical assembler, such as assembling bytecode (of course), labels and that's it. Yea this is it, just bytecode and labels. But it was enough to play arround with the CPU and write some basic programs. But having just a CPU is boring, we need more...
The GPU
Of course, what would be a video game console without a display? The GPU was pretty easy to implement. It took me only an hour or two to implement just the GPU and half an hour to implement the SDL front-end.
The GPU will have a 256x256 which only 160x160 of the canvas can be displayed by the console screen. It has 3 registers in the memory. One that increments on screen refresh, and two to scroll arround the screen, letting the programmer point where to display.
Like the CPU, the GPU has it's own struct so I can carry arround all the needed data when calling functions,
// GPU struct
struct GPU
{
// The screen itself.
char screen[SCREEN_X][SCREEN_Y];
// Scrolling
struct GPU_Point scroll;
// Pointer to CPU struct
struct CPU *cpu;
}
The GPU works with tiles and sprites, that means that there is no direct modification of the screen buffer from the CPU. The GPU don't even have any direct communication with the CPU, it is independent. The usage of the CPU struct is only to access the memory.
So the rest of implementation is inserting in the buffer the tiles and sprites specified in the memory between 0x8000 and 0x9000 (VRAM, again, for all that technical information, you can refer to the documentation in the External Links)
The SDL impl. was also really simple:
- Spawn a renderer and a window.
- Make a loop.
- Call
render_screen
from the GPU. - Place pixels from the buffer from
SCROLLX
xSCROLLY
to(SCROLLX + 160) % 256
x(SCROLLY + 160) % 256
- Update screen
- Continue the loop until the CPU halts or the user exits the app.
The SPU
There is not much information about it since I haven't really worked on it yet. The actual design is that there are 3 channels, one for melody, one for bass, and one for noise (cool for SFXs).
There is a small impl. that you can look up on the GitHub repo (spu.c
and
spu.h
) but no SDL front-end for now.
Configuration
The configuration was not an easy task since I have never really parsed strings in C. So I decided to make a configuration system that is convenient to use and easy to implement. I thought about the INI files, so I did a simplified version of INI. (basically INI without sections)
The implementation was easy like I expected it to be, just a few strtok
calls
and an handler. You write a function that consumes two strings as arguments and
the parser will call this function everytime it parses a value. I got inspired
from this simple INI Parser.
Here is the default configuration for the VICERA:
# This is the default configuration
# Directional arrows -> Keyboard arrows
# A, B -> Z, X
# Start, Select -> Enter, Backspace
# Directional arrows
controller.left = 1073741903
controller.down = 1073741905
controller.right = 1073741904
controller.up = 1073741906
# Buttons
controller.a = 122
controller.b = 120
controller.start = 13
controller.select = 8
Conclusion
I have learned a lot from this project that is not even finished. I have learned more about how a CPU works, how these old handheld Nintendo classics worked (I did lots of research while working on this project) and how to plan and maintain a project properly.
I also got more confident writing programs in C by writing this fantasy console.
Right now I am improving the programming experience by optimizing a few things in the CPU and by writing an assembler.
Once done, I will try and port a C compiler for the CPU.
See you later for the Part 2 if ever I write it!
External Links
Here is a few links and websites I have visited while programming the VICERA.
The Gameboy CPU Manual
(almost) Z80 Assembly programming for the Gameboy and Gameboy Color
VICERA official GitHub repository
(dead link) VICERA official documentation
SDL 2.0 Wiki
INIH: A simple C INI parser
Tags: repost, architecture, programming, c