I'm excited to announce that we've released v1.0.0 of our Hardcaml MIPS CPU simulator. We've now fully recreated all functionality from the Verilog design we created last semester, and found/fixed a lot of bugs along the way! In this post, I wanted to recap some observations and thoughts on the project.
What does/doesn't our CPU do?
As initially planned, we recreated a school project from last semester in Hardcaml. We designed a simplified 32-bit, 5-stage MIPS CPU. It includes:
- Support for most arithmetic and memory instructions
- Implementations of forwarding and stalling
- Support for basic branching and jumping instructions
We did not attempt to include:
- Support for multiplication or division
- Any branch predictions mechanisms
- Implementations of kernel vs user mode
- Traps or interrupts
If we eventually decide to revisit any of these, we'd probably start by rewriting this from a MIPS to a RISC-V processor, as the RISC-V spec has a definitive reference.
We tested our design on the same program used to evaluate our Verilog CPU last semester, and found that the simulation worked as expected. We also included automated test suites for all the subcircuits in our Hardcaml design.
- My biggest frustration with Verilog was the lack of tooling / static analysis, which let too many errors through. Hardcaml has safeguards in place through the typesystem, compilation checks, and runtime checks when running Verilog generation. This lets you focus on implementing a design rather than making sure you've avoided typos. It eliminates entire classes of errors, and in my opinion, this alone gives it a clear edge over Verilog.
- Functional programming is a very intuitive way to think about hardware design and circuit definitions.
- The abstraction of Hardcaml interfaces is absolutely brilliant. In theory, anything that can be mapped to/from a sequential wire representation could be an interface, and therefore, be represented through Hardcaml.
- As a corollary, the ability to group related signals into records makes code clearer and more organized.
- Hardcaml's support for automated testing, as well as it's simulation / interactive waveform viewing tools, are absolutely top notch. When we did encounter bugs, they were much more pleasant to debug in Hardcaml than in Verilog.
- The team behind Hardcaml is fantastic. Over the course of the project, we had several questions / suggestions, and got very fast, thorough responses to the GitHub issues we opened.
- Hardcaml comes with built-in functions for muxes, registers, memory, and other common subcircuits. This significantly cuts down on boilerplate.
Most of these aren't really problems with Hardcaml as much as tradeoffs compared to raw Verilog.
- Hardcaml has a steep learning curve, especially for those who don't know OCaml. That being said, there isn't really a way around this. The existing documentation is pretty good, and hopefully this blog can be a useful resource to those learning Hardcaml in the future.
- At times, it can bae challenging to express designs within the constraints of Hardcaml's DSL. For example, mapping signals to/from an enum representation wasn't immediately obvious to us. Sometimes, you need to "think the Hardcaml way" to come up with a correct solution.
- One thing that can help a lot is reading through the Hardcaml source code
.mli files. Effectively, Hardcaml defines its own data types, and a set of functions on those data types. If there exists a way to accomplish something, it must be in one of those functions.
- In order to use Hardcaml, you need to understand the basics of hardware design and Verilog. You can't really skip that step, but once you've moved from raw Verilog to Hardcaml, your life becomes a lot easier.
- As with any HDL, you need to think in terms of hardware. There's no such thing as "calling" a circuit: instead, the code you write is a description of a design. Functional programming makes this a bit easier, but it can still be a challenge to get in the right mindset.
- There doesn't seem to be a community around Hardcaml outside of Jane Street. Other than our project, I've only found a few people that have publicly used it.
OCaml is Awesome!
We have really enjoyed learning OCaml. The typesystem is incredibly expressive, and eliminates entire classes of potential bugs. And due to type inference, you get typesafety benefits without having to write huge amounts of boilerplate.
The module system is also very neat. Our project didn't really need complex functors or polymorphism, but some of the Hardcaml source code we read was very creative: for example, a large set of operations is automatically available for all Hardcaml interfaces as long as they implement several basic operations.
Also, the concept of using
ppxs for metaprogramming is powerful, and different from languages we've worked with before. These effectively rewrite your source code behind the scenes, further cutting down on boilerplate.
Another interesting aspect of OCaml is that it feels very "mathy". This isn't surprising given that it's a functional programming language, but it's very different from what we're used to. In OOP, you have objects with behaviors, and business logic essentially runs/executes those behaviors. In functional programming, a type feels analogous to a mathematical set, and the functions defined for that type are like operators over that set. Most business logic is then a sequence of transformations from inputs to outputs, using the operators defined for the types involved.
That being said, OCaml still provides access to some imperative tools (e.g. loops for running tests) when truly needed. This lets you use whatever paradigm is more appropriate for the particular problem you're working on.
Blogging Reflections - Sasha
I wasn't originally planning to make a blog out of this project, but I'm glad I decided to do so. Aside from hopefully being a useful resource for those learning Hardcaml in the future, writing and explaining concepts helped me understand them better. A big part of software engineering is converting "business" requirements into code, which forces you to build a formal system of abstraction and concrete logic. In a similar vein, explaining complicated technical concepts forces you to understand what you're doing, and why you're doing it.
On a side note, now that I have a blog set up, I'll probably write about other stuff from time to time. If any of this has been interesting, stay tuned!
Blogging Reflections - Mayur
I thoroughly enjoyed blogging and learned a lot about technical writing in the process. Describing things in terms of hardware was very different than what I was used to. At first I kept writing things like 'calling a circuit' which, as previously mentioned, isn't correct. I hope that this blog has helped someone better understand Hardcaml and thank you for reading.
One thing that could be really interesting is a post / video / discussion on the design choices behind some parts of Hardcaml. There's a lot of really brilliant architecture, and we would be curious to learn about the journey of how that came to be.
All in all, this has been a great experience, and we walk away with a better understanding of hardware, functional programming, and technical writing. Regarding the original goal of the project, our takeaway is that Hardcaml and OCaml are significantly better for hardware design than raw Verilog.