An Arduous Endeavor (Part 3): CPU Emulation

Arduino Micro Isometric View
This entry is part 3 of 7 in the series An Arduous Endeavor

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, and GIT_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 of simavr.
  • 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:

  1. Initialize an avr_t object by calling avr_make_mcu_by_name and avr_init
  2. Populate the flash memory
  3. “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…

Series Navigation<< An Arduous Endeavor (Part 2): Display EmulationAn Arduous Endeavor (Part 4): Input Handling >>

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.