- An Arduous Endeavor (Part 1): Background and Yak Shaving
- An Arduous Endeavor (Part 2): Display Emulation
- An Arduous Endeavor (Part 3): CPU Emulation
- An Arduous Endeavor (Part 4): Input Handling
- An Arduous Endeavor (Part 5): Buzzes and Beeps
- An Arduous Endeavor (Part 6): Save States and Rewind
- An Arduous Endeavor (Part 7): Automated Builds
Next up was to emulate the CPU: an ATmega32U4 for standard Arduboy units. I started to implement this in a similar fashion to how I implemented the display because “It’s an 8-bit RISC processor, how hard can it be?” And then I saw how many opcodes there were – or rather, how many opcodes there might be, because it’s not totally clear what differentiates one opcode from the other – and I decided that maybe I should rely on someone else’s work for this part.
Then I discovered there were a number of AVR emulators, written with some degree of intended integration into other projects. simavr seemed the most promising for my intended use – I just had to figure out how to integrate it. So I figured it was time to learn a little bit more about CMake.
I know how to bring in external libraries in python – I use pip or poetry or something, install the library in the virtualenv, and bam, I’m good to go. However, I think (though I don’t feel especially confident about this) that for a Libretro core, I want to ensure the library is linked into my resulting target. To do this in a repeatable way, I needed to find a way to grab the code, build it for my target platform, and do the linking.
CMake has a module called ExternalProject that appears to be useful for fetching, configuring, and building projects from various sources, while keeping them out of your own source control. I put some extra lines in a .cmake file that I included to set up a target for simavr:
include(ExternalProject) find_program(MAKE_EXE NAMES gmake nmake make) ExternalProject_Add( simavr PREFIX simavr GIT_REPOSITORY https://github.com/buserror/simavr.git GIT_SHALLOW true GIT_TAG v1.7 CONFIGURE_COMMAND "" BUILD_COMMAND ${MAKE_EXE} build-simavr build-parts BUILD_IN_SOURCE true INSTALL_COMMAND "" PATCH_COMMAND git apply ${CMAKE_SOURCE_DIR}/external/patches/simvar/0001_makefile_common.patch ${CMAKE_SOURCE_DIR}/external/patches/simvar/0002_extern.patch || git apply ${CMAKE_SOURCE_DIR}/external/patches/simvar/0001_makefile_common.patch ${CMAKE_SOURCE_DIR}/external/patches/simvar/0002_extern.patch --reverse --check && echo already applied )
Some notes about the ExternalProject_Add call:
- Specifying
GIT_REPOSITORY
,GIT_SHALLOW
, andGIT_TAG
let me fetch just a specific tag pretty quickly, without having to do a full download of the entire repo. CONFIGURE_COMMAND
must be specified if the project does not use CMake.- I had to set
BUILD_IN_SOURCE true
to work with the project layout ofsimavr
. - I had to make some patches to get simavr built/working with my project – thankfully, ExternalProject supports
PATCH_COMMAND
to naively run some shell commands to apply the patches.
I also had to tweak the include directories and link libraries to be able to link it all together:
find_library(LIB_ELF elf) add_dependencies(arduous_libretro simavr) target_include_directories( arduous_libretro PUBLIC include PUBLIC ${CMAKE_BINARY_DIR}/simavr/src/simavr/simavr/sim PUBLIC ${CMAKE_BINARY_DIR}/simavr/src/simavr/examples/parts ) target_link_libraries( arduous_libretro ${CMAKE_BINARY_DIR}/simavr/src/simavr/simavr/obj/libsimavr.a ${CMAKE_BINARY_DIR}/simavr/src/simavr/examples/parts/obj/libsimavrparts.a ${CMAKE_BINARY_DIR}/simavr/src/simavr/examples/parts/obj/ssd1306_virt.o ${LIB_ELF} )
Actually using simavr, though, proved pretty complicated. As before, this would probably be straightforward for someone more used to C/C++, but usage seemed pretty out there to my untrained eye.
From looking at some examples (especially an example that focused on the SSD1306… hello old friend), I gathered that I needed to:
- Initialize an
avr_t
object by callingavr_make_mcu_by_name
andavr_init
- Populate the flash memory
- “Wire” up the virtual SSD1306 by hooking into various callbacks
While I was in here, I decided to swap my SSD1306 implementation for the simavr example one, at least for the time being, because it already figured out the callback part of the above steps.
From there, every time I emulated a “frame” in my core, I needed to call avr_run a bunch of times – ideally just the right number of times to get the equivalent of 1/60 of a second. And when everything was connected…