返回资讯中心
外部精选
软件工程
#开发者实时流

A Haskell processor running on an FPGA

I follow Lennart Augustsson’s approach when he was presenting MicroHs — give the demo first.

Hacker News 最新AriFordsham2026/10/31 · 今天22 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

I follow Lennart Augustsson’s approach when he was presenting MicroHs — give the demo first.

This is a Haskell program running on a specialised processor, which is hosted on an FPGA and talks through a UART port:

There is no software runtime or “kernel trap” underneath, everything is handled by the hardware processor.

There is a more detailed demo video. If you want to reproduce this demo, either by running it on your FPGA or using our simulator, see the guidance.

My PhD study topic is about specialised hardware design for non-strict functional languages. This post talks about my recent work on adding IO capability into our Haskell processor and putting it on an FPGA, with a goal of convincing people that a Haskell processor is a cute idea.

Specialised hardware for functional languages actually has a lot of interesting history, especially in the 80s. This idea was largely abandoned during the 90s, because of better software implementation and the evolving speed of standard CPUs at the time.

For reasons well known, domain-specific architectures are becoming interesting again. We try to investigate the possibility of this old idea in today’s context. I will present the hardware architecture, IO handling, FPGA implementation results, and some limitations of this work.

Our processor is called Ouros, a combinator-based graph reduction machine. Many design details, including representation of combinators, thread management (Ouros can do automatic multi-threading!) and garbage collection, can be found in our paper1. The figure below is an overview of the processor architecture.

Unlike a conventional processor, Ouros does not execute a sequence of instructions. Instead, a Haskell program is compiled into combinator graphs, and execution is a process of repeatedly reducing function applications. Each application (we call it an App) is represented by a fixed-size word containing a function and its arguments. Larger expressions are stored in the heap as multiple Apps connected by pointers.

The main components are the Dereference Heap, which follows pointers and manages threads; the Reducer, which performs combinator reduction; and the ALU, which handles arithmetic. Apps move between these components as data tokens, and each component processes them whenever its input is ready. This is why we call Ouros dataflow-driven, rather than instruction-driven. Ouros also supports relatively large combinators, which can save quite a few reduction steps. Readers interested in the combinators and how we compile Haskell into them can refer to our earlier paper2 on structured combinators and our MicroHs fork.

Another interesting feature is automatic fine-grained multi-threading. For example, when an arithmetic operation needs two independent values, Ouros can evaluate them in separate threads without any explicit threading code in the Haskell program. These threads share the same hardware pipeline. There is also a concurrent hardware garbage collector, which reclaims unused heap cells alongside normal execution.

Adding IO allows the Ouros processor to become a standalone computer, rather than just a machine for evaluating pure expressions. The question is how to introduce side effects without hurting the graph reduction model.

We use the familiar state-passing representation of the IO monad3:

type World = Int newtype IO a = IO (World -> (a, World))

runIO :: IO a -> World -> (a, World) runIO (IO f) = f

instance Monad IO where return a = IO (\w -> (a, w))

(IO m) >>= k = IO (\w -> case m w of (a, w') -> runIO (k a) w')

Here World is just a dummy Int, not a representation of the actual world. The important part is the dependency: each IO operation consumes a World token and produces one for the next operation. This gives the operations an order, even though the processor evaluates Haskell lazily.

The low-level primitive IO interface is intentionally kept simple:

writeAXIReg :: Int -> Int -> IO () writeAXIReg = primitive "IO.Write"

readAXIReg :: Int -> IO Int readAXIReg = primitive "IO.Read"

They allow a Haskell program to read and write a set of registers. The compiler turns these primitives into special IO operations understood by the hardware. When an IO operation is ready, a dedicated IO unit performs the corresponding AXI4-Lite transaction, and the result goes back into the normal graph reduction pipeline. The operation only finishes after the bus transaction completes.

The AXI4-Lite details are hidden from the Haskell program. From the programmer’s point of view, it is simply reading and writing registers, some of which are connected to the outside world. This is how we get actual side effects without a software runtime.

Currently the FPGA demo only uses a UART, with the Haskell program polling its registers. The same interface could also be connected to GPIOs, timers, a hardware random number generator, etc.

The FPGA board I am using is AUP-ZU3, which was kindly donated by people from AMD.

The current design runs at 100 MHz on this board. Its resource usage is approximately 27% of LUTs, 7% of registers and 40% of BRAM. The BRAM usage is mainly determined by the heap and code memories, so it can be adjusted by changing their sizes.

What I find quite encouraging is that the logic footprint is roughly in the range of a small pipelined RISC-V core. Of course, this is not a like-for-like comparison, and Ouros has many limitations (see below). Still, it is nice to see that we can have direct hardware support for graph reduction, automatic threading and garbage collection within a fairly small circuit budget.

An immediate question someone might ask is: how does this machine compare to a PC running GHC-compiled code? This is not easy to answer. If we only care about wall-clock speed, the FPGA demo is likely to be several times slower than a modern laptop. However, the implementation technology, circuit size, and power consumption model are all very different, making a fair comparison difficult. Also, since Ouros has a rather unusual memory organisation, we cannot simply assume that its performance would improve by several times if implemented as an ASIC using a 7nm process.

Apart from being a cute toy, does this FPGA demo show any practical value? One possibility I can imagine is something like a Raspberry Pi, but with Haskell as its bare-metal programming language. Being able to run Haskell directly on a small embedded processor might be useful in certain scenarios. More interestingly, I think a native Haskell processor could open up some research possibilities that have been difficult to explore in practice. For example, what would a small operating system written entirely in Haskell look like?

There are four related repositories for Ouros:

To compile Haskell programs, first build our MicroHs fork from the repository root:

cabal install --overwrite-policy=always

The UART demo program lives in ouros-uart-demo in that repository; follow the README there to compile it. You can also try your own Haskell programs in the same way. Copy the compiled program image into ouros-chisel/src/main/scala/benchmarks or ouros-simulator/src/hardware/ouros/benchmarks, depending on whether you want to use the hardware implementation or the simulator.

For the Chisel implementation, run this from the ouros-chisel root:

sbt "runMain Generate uart"

This generates the SystemVerilog output under fpga-gen/.

For the simulator, the UART demo can be run directly from the ouros-simulator root:

cargo run --release -- --mode=terminal --prog-name=UART

To reproduce the demo on the AUP-ZU3 FPGA board, follow the README in the Vivado repository. It covers the FPGA build and boot image generation.

Xie, Yukang, Craig Ramsay, Robert Stewart, and Hans-Wolfgang Loidl. “Ouros: A Dataflow-Driven Processor for Lazy Functional Programming Languages.” In 2026 IEEE 37th International Conference on Application-specific Systems, Architectures and Processors (ASAP), pp. 85-92. IEEE, 2026. doi: 10.1109/ASAP71856.2026.00023. ↩︎

Xie, Yukang, Craig Ramsay, Robert Stewart, and Hans-Wolfgang Loidl. “From Haskell to a new structured combinator processor.” In International Symposium on Trends in Functional Programming, pp. 139-164. Cham: Springer Nature Switzerland, 2025. doi: 10.1007/978-3-031-99751-8_7. ↩︎

Peyton Jones, Simon L., and Philip Wadler. “Imperative functional programming.” In Proceedings of the 20th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pp. 71-84. 1993. doi: 10.1145/158511.158524. ↩︎

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →