28 November, 2021

Why did I write Arviss, a RISC-V instruction set simulator?

Why I wrote Arviss

A formative moment in my career as a programmer came when Byte magazine devoted their December 1981 issue to “Computer Games”. The game that most caught my attention was RobotWar written by Silas Warner.

RobotWar was based around programming robots to fight it out in an arena. The robots were programmed in Battle Language, a custom assembly language that was invented for the game, much in the same way that Zachtronics games such as Shenzhen I/O and TIS 100 use their own custom assembly languages to solve puzzles. I suspect that if anyone were to make a spiritual successor to RobotWar then it would probably be Zachtronics, as their games have a knack for making assembly language programming fun.

At the time, what fascinated me about the game was how it could run multiple programs simultaneously, as that just wasn’t something that home computers did back then. Robot battles were also a fun idea, and twenty years later I spent quite a bit of my spare time playing Robocode, a similar game in which the robots were programmed in Java.

But sometimes I would find myself returning to the question of how to write such a game myself, largely as a thought exercise. I concluded that each robot would need its own emulated CPU, but it would need to meet the following requirements.

  1. It would have to be fair. To ensure that no robot could gain more than its fair share of time, each robot’s CPU would have to be capable of being run for exactly n instructions or clock cycles at a time.
  2. It would need a C compiler. It’s very useful to be able to read assembly language and occasionally write it, but it’s very time consuming to write.
  3. It would have to be small and fast, as I had a grand vision of a game with potentially hundreds of robots.

Designing my own ISA was initially attractive, but I dismissed it as being impractical as not only would I have to write the emulator, but I’d also have to write the tools to support it, including the C compiler.

Using an existing CPU emulator also seemed like a good option. I’d grown up programming Z80, 6502, and 68000, and there are plenty of battle-hardened emulators for all of them. But picking such old CPUs ruled out good C compiler support. As for modern CPUs, they may have good C compiler support, but many of them are hugely complicated with thousands of instructions, so they didn’t seem like they’d be a good target for emulation.

At this point I turned to RISC-V. I didn’t know much about it, but it was a modern ISA with a much reduced instruction set (hint - it’s in the name), and it had good C compiler support. There were also several interesting looking RISC-V emulators, which was encouraging.

However, many of the RISC-V emulators seemed to be designed for running in the large, such as running Linux on QEMU. This is in itself is an interesting thing to do, but ultimately I couldn’t find a RISC-V emulator written in a language callable from C that met both the the fairness requirement and the size requirement.

At this point I realised that it would be a good learning exercise to write my own RISC-V emulator that met my requirements, and so Arviss was born.