Index Projects Blog
Logicworks

Logicworks

2023-03-26

Logicworks was a project to make a logic simulator, similar to Turing Complete, with a focus on emulation performance. I worked on it for my A level computer science project, but ultimately switched to working on Hypertunnel instead.

It consisted of three programs:

A node-based drag-and-drop editor. This was initially Svelte+Pixi.js, then it was Godot with GDScript, then it was Godot with Rust, and finally I settled on Rust with Bevy.

An optimising compiler, which makde decisions about multithreading points, converted components into higher level representations, and performed boolean algebra simplification. It also formed a DAG (Directed Acyclic Graph), then carried out a topological sort and stored the result to reduce runtime overhead. This was written in Rust.

An executor that ingested build artifacts from the compiler and executed them, written in Rust.

I ultimately didn't get to the point of actually executing circuits before switching to Hypertunnel, which I found far more interesting. I worked on the UI first, so the executor and compiler didn't get particularly far.

I have some rough notes on ideas for optimisations, which may be useful for people working on similar projects:

  • Compile to LLVM IBC?
  • Have a clear distinction between fundamental, stateful, and deterministic components
  • Stateless components can be executed backwards, cached, etc
  • Stateful ones must be executed in-order
  • Look into Verilog's codegen approach: https://www.youtube.com/watch?v=en8JMz7v3LU
  • Build evaluation order at compilation time
  • We can link the outputs of one function to a pointer, which is the input of another, then place the elements-to-evaluate in a vector. We can iterate linearly with piping for free and good cache coherency.
  • GPU compute could be useful
  • We can make decisions on where to multithread at compile time
  • See https://docs.rs/slotmap/latest/slotmap/ https://docs.rs/phf https://rust-unofficial.github.io/patterns/ https://nnethercote.github.io/perf-book/benchmarking.html
  • https://en.wikipedia.org/wiki/Topological_sorting
  • Compiler optimisation:
    • If below a certain size, premake a hashmap cache
    • If a hashmap cache matches a known truth table turn it into a fundamental component
    • Identify ALUs based on control bits/input-output, and try to optimise away most operations.
    • Boolean algebra optimisation
    • Identify components that are never run because everything they're used in is fully optimised, and tell the executor not to load them into memory, but keep the build artifact
  • Executor optimisation:
    • Multithreading in expensive components
    • Store serialised types as ints
    • Cache truth tables on-the-fly with some form of cache clearance