In Part I, I began work on an emulator for the Vectrex video game system - if you have not read Part I you should do that first. In this post I will start out by creating a boilerplate layout for the project and fill in some user interface parts - so there will be a basis for the emulator.
Now it’s time to start writing some code… (Follow along with me on github beardypig/vectrexia-emulator)
I will be writing this emulator using C++, as it is object oriented, fast and somewhat portable - I plan to use some C++14 features which might not be available on all systems. I will be using CMake to build the project and I will be compiling with gcc on Linux and clang on Mac. I will primarily be doing the testing with an Ubuntu 14.04 VM - once it’s working in Ubuntu I will make it work in MacOS, Windows, Android and on the RaspberryPi (not necessarily in that order).
I think writing portable software is important and I don’t like reinventing the wheel (or writing UIs), so I have chosen to write this emulator as a libretro core. One of the reasons I choose to emulate the Vectrex is that there aren’t many Vectrex emulators that run on linux, MacOS, etc. (the SDL and existing
libretro ports of VecX, M.E.S.S., and ParaJVE being the exceptions). Using libretro means that I won’t have to worry too much about writing UI code and just concentrate on the actual emulation!
Boring stuff first, the boilerplate for the project, it will be laid out like so:
. ├── LICENSE ├── CMakeLists.txt ├── README.md ├── link.T ├── tests │ ├── CMakeLists.txt │ └── test_runner.cpp └── src ├── libretro │ ├── libretro.h │ └── libretro.cpp ├── CMakeLists.txt └── [classes].cpp
libretro.h file is from the
libretro project and provides the header for the
libretro API. The CMake files build the retroarch core, currently this will only build on MacOS and Linux with a compiler that supports C++14 - I plan to add support for more platforms later.
The first thing to do is think about how the Vectrex will be represented. As C++ is an object oriented programming language, most of the components will be modelled as objects.
There will need to be a few classes, one for the Vectrex itself, called
Vectrex class will be the conductor of the other components. As discussed in the previous post the Vectrex has a M6809 CPU, some built in System RAM and ROM, address decoding, a 6522 VIA, an AY-3-8910 PSG, a multiplexed DAC, custom vector drawing circuitry, and user input circuitry.
Most of these components will be represented by an object; the CPU, the game Cartridge, the 6522, the AY-3-8910, the vector drawing hardware, and user input. The other components will be emulated inside the
Vectrex class as they glue the other components together or they are so trivial that it doesn’t warrant a separate class. The system rom and ram, for example, are best represented by a simply array of bytes.
The CPU class will need to be able to access the memory of the rest of the system and will be able to do so via read/write callback methods defined in the
Vectrex class. These methods will effectively emulate the address decoding and the memory bus. The other components are also connected to the memory bus and will need to be accessed by the CPU.
There will need to be able to reset the machine, execute a number of CPU cycles, load a cartridge rom and unload a cartridge. Aside from the
Cartridge class, which will be use for loading and unloading ROM, we will not define any extra classes yet and will create them as they are required.
The best thing to do now would be to clone the vectrexia repo and take a look at the files to get an idea of where we are heading. You should also try to compile and run the libretro core in retroarch.
vectrexia.h you will see a class that represents the whole Vectrex machine, the
Vectrex class. We will add to this class as we go along. You will also see the
Cartridge class that will manage the loaded ROM and provide access to it. From the documentation we know that the cart ROM has a maximum addressable size of 32K, so for the base case we will simply use an array of 32K bytes.
libretro is an API that is designed for creating emulators and games.
libretro projects are compiled in to a library file called a
libretro core. That core can be loaded by a front-end that supports the
libretro API, the front-end is responsible for video, audio, user input, etc. This means when you write a
libretro core you don’t have to worry about the specifics of rendering a framebuffer on the Wii or on an Android phone, etc. and this makes life a lot simpler :)
I will get the
libretro part working first so that
retroarch can start the
vectrexia core without giving any errors, but it won’t actually do anything. First we need to stub out some functions for
libretro, there are a fair number of API functions that have to be implemented - I won’t list them all here as you can see them in the source code with some explanation. All these functions are implemented in
There are two functions that return information about the system,
retro_get_system_av_info. These need to set information about the system that is being emulated.
retro_get_system_info populates a
retro_system_info struct that has information about the core; the name, the version and the rom files types it can load.
retro_get_system_av_info populates a
retro_system_av_info struct with specific information about the Vectrex AV systems; the sample rate for the audio we want (44.1kHz) and the frame rate (50 fps, same as PAL refresh rate) and the resolution of the display. With a vector display the resolution is a bit strange, according to gamasutra the resolution is 330x410, so we’ll go with that for now.
Next we need to be able to load a ROM, and then unload it. These functions are performed by
The Vectrex came with a built-in game, “Mine Storm”, that means that the emulator can we started without loading a game.
libretro needs to be configured to allow the emulator core to start without loading a ROM, this can be done by setting a
That setting is
RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, this means that when the game is loaded the
retro_game_info struct may be set to
NULL. The built-in game is part of the system ROM which also provide some functions for controling the vector drawing electronics, the system ROM will be loaded in the
If a ROM is specified with the emulator core then it needs to be loaded in to the cartridge memory space, the ROMs (.vec/.bin) are simply dumps of ROM chips in the cartridge with no header or packing so they do not need to be preprocessed at all.
To do this we will need to implement a couple of methods in the
Cartridge class, one to load and one to unload the cartridge - these will be called from the Vectrex class via
Vectrex class, there needs to be an instance of Cartridge, for that we defined a
unique_ptr<Cartridge> to store the reference to the Cartridge and then assign it in the
Unloading is simple, we just clear the cart memory array and then reset the Vectrex.
Run the ROM
retro_reset control stepping the Vectrex and resetting it.
retro_run will run a single frame of emulation; during which the CPU, vector circuitry, sound chip, etc. will need to be updated, then the frame is rendered to the screen and the sound generated is output. For that will need the functions for stepping and reseting the Vectrex. We will stub out the implementation of these functions as they don’t have anything to do yet.
Vectrex::Run will run the machine for the number of clock cycles provided, and
Vectrex::Reset will reset the Vectrex.
retro_run is called once per frame at the frame rate we requested earlier (50.0 fps), so the Vectrex needs to run for the number of cycles in a frame. This is calculated by taking the clock speed (1.5MHz), the time for one frame (20ms - at 50 fps it is 1/50 of a second) and multiplying them. The number of cycles per frame should, therefore, be 30,000.
retro_run function should generate the video and audio for one frame, the video data is sent back to the
libretro front-end using
video_cb, and the audio is sent via
audio_cb. The video frame is a frame buffer with the dimensions the same as those defined in
retro_get_system_av_info, one element for each pixel. The pixel format is
0RGB1555 by default, however this is deprecated and
RGB565 is recommended - more details can be found in
retro_pixel_format. A placeholder
framebuffer is defined so that we can generate a blank screen in the
One thing to note is that some front-ends (retroarch, at least) will run at the rate of the audio samples if VSYNC is not enabled, this means without the dummy call to
audio_cb the core would run at the maximum FPS that it could.
retro_reset just resets the Vectrex.
The pixel format needs to be changed in
retro_get_system_av_info, in this case it will be set in
retro_get_system_av_info as all the games will have the same pixel format.
This will now give us a
libretro core that will compile and run in
retroarch! It won’t do anything useful, but the basis for a working core is there.
To compile and run the core you will need an Ubuntu 14.04 (or similar) machine with
cmake and new version of
clang (>=3.5) or
When you run the core with
retroarch you will just see a black screen running at ~50fps :)
Next time I will tackle emulation of the CPU.