A Minimal IR for RTL

fourier541 pts0 comments

A minimal IR for RTL

Skip to main content

A minimal IR for RTL

23 July 2026

I started working on RTL design for ASIC after some years doing web development.<br>Coming from an ecosystem that's mature and constantly evolving in languages,<br>methodologies and frameworks, moving into a field that mostly still feels stuck<br>in the 1980s really threw me off.

As opinionated as I am, I spent many of my working hours complaining about<br>Verilog semantics, UVM, tool failures and backwards workflows (anyone<br>unfortunate enough to have worked with me can confirm this). After many years<br>complaining, at the beginning of 2026 I discovered the virtues of agentic<br>coding and decided to give a try to an idea I had in mind for a long time: a<br>verilog-like HDL, without all the pain of the original language, with similar<br>syntax but with extended capabilities.

Being completely unfamiliar with programming language and compiler development<br>(especially as an electronics engineer by training) I decided to start by<br>developing a “Verilog compiler,” which could then be “easily extended” into a<br>new language.

Little did I know that this side project would grow into a much larger<br>undertaking than I had expected. The result is Mate IR<br>🧉, an intermediate representation for<br>synthesizable RTL that now includes a nearly complete SystemVerilog compiler and<br>simulator.

"Compiling" Verilog

One of the things that always feels odd about writing SystemVerilog/VHDL, is how<br>often the word "infer" is said. You don't declare a flop: you write procedural<br>code that is then inferred to be a flop. To me this always was so absurd!

A simple 8-bit counter looks like this

logic [8-1:0] counter;<br>always @(posedge clk) counter counter+1;<br>Instead of something much more obvious like

flop [8-1:0] counter;

assign counter.clk = clk;<br>assign counter.d = counter.q;<br>These can probably feel like a such small difference or even "syntactic sugar",<br>but it's not!

On SystemVerilog we have no difference at the language level between a flop<br>or a combinational output: they are both logic or reg (we also have wire but<br>it's a different story)

We don't declare the clock, it must be inferred from the sensitivity list

Same applies to async reset: we must include it in the sensitivity list and<br>code the procedural logic in a particular way (giving reset priority)

We must use Non-Blocking Assignment (NBA) statement inside the procedural<br>block if we want to infer clocked logic

The beautiful thing about all these "rules", is that there are not really rules!<br>They are conventions we must follow if we want Synthesizable RTL. Do we get a<br>loud and immediate error message if we don't? Of course not! We may get a<br>warning in the synthesis or linter logs, buried among thousands of other ones.

Of course that with experience, one stops making this mistakes, and can catch<br>them early in the case of reviewing another's work. My point here is how<br>inefficient the whole ecosystem is, since we are full of these kind of<br>footguns all over the place.

These kind of issues are specially problematic when onboarding someone new to<br>the field: explaining that a "reg" is not actually a register (nice!),<br>constantly reminding the NBA vs Blocking assign, etc.

Making the easy difficult (and the difficult impossible)

With industry standard HDLs like SystemVerilog, things that should be easy<br>like defining a register's clock and reset, the clock domain of a signal or the<br>reset value of a register, are actually not possible to do directly in the<br>language, meaning these are not direct properties we set with the language<br>constructs. Instead, these fundamental properties are inferred from the<br>procedural programming constructs we use, making what should be the first-class<br>citizens of the language some inferred properties behind an unnecessary<br>indirection layer.

You might think that, given this indirection layer and its downsides, maybe we<br>gain something useful from it, like powerful abstractions and high code reuse.<br>Well of course not! This was the status of HDL in the 80s and we never evolved<br>from it. The following are common features of all modern programming languages<br>and frameworks but impossible to do in SystemVerilog/VHDL (maybe possible per<br>LRM but impossible due to tooling status):

Parametrizable functions

Parametrizable datatypes

Polymorphism/multiple dispatch

Iterating over interfaces/structs (reflection in general)

Parametrizing with file contents (e.g. regmap module parametrizable by<br>systemRDL file)

Instead of HDLs focusing on these useful code reuse concepts while making the<br>basic definitions easy to do, we have languages which are very limited in their<br>features, and provide the most contrived ways to define the most basic elements.

This shows up concretely in how large designs actually get built. On ASIC, teams<br>end up writing as much tooling and code-generation infrastructure as RTL itself,<br>just to get the reuse the language won't give them directly. On FPGA, the same<br>gap gets filled by vendor...

language counter like from systemverilog code

Related Articles