|
|
@ -0,0 +1,241 @@ |
|
|
|
# The VICERA: Part I |
|
|
|
|
|
|
|
## Introduction |
|
|
|
|
|
|
|
This project is probably the biggest I have ever done so far in my life. Which |
|
|
|
is not even finished yet. I have already worked on it for now a month and I |
|
|
|
think it deserves it's very own series of blog. |
|
|
|
|
|
|
|
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](https://github.com/vicera/vicera). |
|
|
|
|
|
|
|
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](http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf) |
|
|
|
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](/vicera/p/cpu.html). |
|
|
|
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`x`SCROLLY` 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](https://github.com/benhoyt/inih). |
|
|
|
|
|
|
|
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](http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf) |
|
|
|
[(almost) Z80 Assembly programming for the Gameboy and Gameboy Color](https://www.chibiakumas.com/z80/Gameboy.php) |
|
|
|
[VICERA official GitHub repository](https://github.com/vicera/vicera) |
|
|
|
[VICERA official documentation](https://h3liu.ml/vicera/) |
|
|
|
[SDL 2.0 Wiki](https://wiki.libsdl.org) |
|
|
|
[INIH: A simple C INI parser](https://github.com/benhoyt/inih) |