{"slug": "a-minimal-ir-for-rtl", "title": "A Minimal IR for RTL", "summary": "Miguel9554 released Mate IR, an intermediate representation for synthesizable RTL that includes a nearly complete SystemVerilog compiler and simulator, aiming to address inefficiencies in traditional HDLs like SystemVerilog by eliminating the need to infer registers and clocks from procedural code. The project, started in early 2026, grew from a side project into a full compiler that directly declares flops and clock domains, reducing footguns for new users.", "body_md": "# A minimal IR for RTL\n\nI started working on RTL design for ASIC after some years doing web development. Coming from an ecosystem that's mature and constantly evolving in languages, methodologies and frameworks, moving into a field that mostly still feels stuck in the 1980s really threw me off.\n\nAs opinionated as I am, I spent many of my working hours complaining about Verilog semantics, UVM, tool failures and backwards workflows (anyone unfortunate enough to have worked with me can confirm this). After many years complaining, at the beginning of 2026 I discovered the virtues of agentic coding and decided to give a try to an idea I had in mind for a long time: a verilog-like HDL, without all the pain of the original language, with similar syntax but with extended capabilities.\n\nBeing completely unfamiliar with programming language and compiler development (especially as an electronics engineer by training) I decided to start by developing a “Verilog compiler,” which could then be “easily extended” into a new language.\n\nLittle did I know that this side project would grow into a much larger\nundertaking than I had expected. The result is [Mate IR\n🧉](https://github.com/miguel9554/mateIR), an intermediate representation for\nsynthesizable RTL that now includes a nearly complete SystemVerilog compiler and\nsimulator.\n\n## \"Compiling\" Verilog\n\nOne of the things that always feels odd about writing SystemVerilog/VHDL, is how\noften the word \"infer\" is said. You don't declare a flop: you write procedural\ncode that is then *inferred* to be a flop. To me this always was so absurd!\n\nA simple 8-bit counter looks like this\n\n```\nlogic [8-1:0] counter;\nalways @(posedge clk) counter <= counter+1;\n```\n\nInstead of something much more obvious like\n\n```\nflop [8-1:0] counter;\n\nassign counter.clk = clk;\nassign counter.d = counter.q;\n```\n\nThese can probably feel like a such small difference or even \"syntactic sugar\", but it's not!\n\n- On SystemVerilog we have no difference at the\n*language*level between a flop or a combinational output: they are both logic or reg (we also have wire but it's a different story) - We don't\n*declare*the clock, it must be*inferred*from the sensitivity list - Same applies to async reset: we must include it in the sensitivity list\n*and*code the procedural logic in a particular way (giving reset priority) - We\n*must*use Non-Blocking Assignment (NBA) statement inside the procedural block if we want to infer clocked logic\n\nThe beautiful thing about all these \"rules\", is that there are not really rules!\nThey are *conventions* we *must* follow if we want Synthesizable RTL. Do we get a\nloud and immediate error message if we don't? Of course not! We may get a\nwarning in the synthesis or linter logs, buried among thousands of other ones.\n\nOf course that with experience, one stops making this mistakes, and can catch\nthem early in the case of reviewing another's work. My point here is how\n*inefficient* the whole ecosystem is, since we are full of these kind of\nfootguns all over the place.\n\nThese kind of issues are specially problematic when onboarding someone new to the field: explaining that a \"reg\" is not actually a register (nice!), constantly reminding the NBA vs Blocking assign, etc.\n\n## Making the easy difficult (and the difficult impossible)\n\nWith industry standard HDLs like SystemVerilog, things that *should* be easy\nlike defining a register's clock and reset, the clock domain of a signal or the\nreset value of a register, are actually not possible to do *directly* in the\nlanguage, meaning these are not direct properties we set with the language\nconstructs. Instead, these fundamental properties are *inferred* from the\nprocedural programming constructs we use, making what should be the first-class\ncitizens of the language some inferred properties behind an unnecessary\nindirection layer.\n\nYou might think that, given this indirection layer and its downsides, maybe we gain something useful from it, like powerful abstractions and high code reuse. Well of course not! This was the status of HDL in the 80s and we never evolved from it. The following are common features of all modern programming languages and frameworks but impossible to do in SystemVerilog/VHDL (maybe possible per LRM but impossible due to tooling status):\n\n- Parametrizable functions\n- Parametrizable datatypes\n- Polymorphism/multiple dispatch\n- Iterating over interfaces/structs (reflection in general)\n- Parametrizing with file contents (e.g. regmap module parametrizable by systemRDL file)\n\nInstead of HDLs focusing on these useful code reuse concepts while making the basic definitions easy to do, we have languages which are very limited in their features, and provide the most contrived ways to define the most basic elements.\n\nThis shows up concretely in how large designs actually get built. On ASIC, teams end up writing as much tooling and code-generation infrastructure as RTL itself, just to get the reuse the language won't give them directly. On FPGA, the same gap gets filled by vendor GUIs and IP wizards, which solve the problem by hiding the language entirely instead of fixing it.\n\nAn IR sitting underneath the HDL is what actually breaks this tradeoff. Instead of a single language having to be both minimal enough to guarantee synthesizability and expressive enough for real reuse, you can split the job:\n\n- The IR and it's compiler takes care of guaranteeing that the description is valid synthesizable RTL code\n- The HDL can focus on providing high level constructs useful for the developer. Knowing how to lower any of this constructs into the IR is sufficient to guarantee synthesizability.\n\n## Mealy is all you need\n\nSynthesizable RTL actually *is* extremely simple. Any RTL block, be it a simple\ncounter or the top level of a SoC with RISCV cores and custom hardware\naccelerators, can be modeled by the following extremely simple system\n\nJust two components:\n\n- A set of registers: these are the elements holding the state (flops, latches). They interact with the async signals (clocks, resets), and with the sync signals via its data port\n- A transfer function: has as inputs the module inputs and the registers\noutputs, and as outputs the module outputs and the flops inputs. This single\nlogic function describes the behavior of the\n*whole*system.\n\nBy defining just these two elements we can implement *any* digital block:\ncomplex DSP systems, advanced NoCs, SoCs with millions of flops. These are the\ntwo elements we need to define\n\nIf this sounds suspiciously simple, that's because it's not a new idea: this is just a Mealy machine, formalized decades ago in automata theory. Turns out reinventing the wheel is sometimes a good idea when the wheel was actually good.\n\n## A minimal IR for Synthesizable RTL\n\nGiven we can model *any* Synthesizable RTL block with a mealy machine, a minimal\nIR should \"just\" hold these two elements, and nothing else. Everything else we\ncan build is an upper layer over these basic elements.\n\nSome of the current IRs for synchronous hardware (CIRCT, FIRRTL) contain many\nprimitives that are above this minimal Mealy layer. For example, FIFOs and\nMemory ares modeled, with concepts such as FIFO depth or memory size, signaling\nprotocols (FIFO empty/full, read/write protocols) and read/write latencies being\nincorporated. On a minimal IR, none of these concepts exists, since we are just\nworking with registers and the transfer functions connecting them. The details\nof the logic implemented by these transfer functions are not relevant. All of\nthese concepts belong to the upper *functional* layer.\n\nTo make a comparison, I find an IR for synthesizable RTL having a primitive for a\nFIFO being equivalent to an IR for programming languages having a primitive for\nwriting contents to a file. The IR should focus on the basic primitives that\nallow *any* program (circuit) to be generated: a particular program like a file\nwriter (FIFO) should be made up of these fundamental primitives, not be one of\nthem.\n\nI find the approach of keeping the IR minimal and as \"dumb\" as possible, and\ndelegating the encapsulation/sharing of these concepts in a capable HDL that can\nhandle these level of abstraction. The definition of the read/write operations,\ntheir latency and size should be able to be encoded in a generic way in the HDL\nitself, *not* in an IR layer.\n\n## Introducing Mate IR 🧉\n\nWith the idea of seeing how far I could take this, I sat down and wrote (or, more honestly, prompted Claude to write) a compiler for it.\n\nThe IR itself is exactly the Mealy machine from before: registers, and a combinational network connecting them to the module's inputs and outputs. Getting a real compiler to target that shape meant working through a few decisions the \"just two elements\" pitch conveniently skips over.\n\nThe combinational network ended up being a single, global graph instead of one per module. This wasn't a decision make upfront but rather a fix to a problem encountered along the way. The initial approach was to lower each HDL module independently into registers-plus-DAG, wire the modules together, and you ended up getting what looks exactly like a combinational loop at every module boundary. Merging everything into one graph across the whole design is what makes that go away.\n\nThat graph is also strictly word-level, built from a deliberately small set of operators: arithmetic (SUB, ADD, MUL), muxing and slicing, and bitwise ops (AND, OR, XOR). Every one of them has an obvious bit-level lowering, nothing \"clever\" is representable at this level. Keeping the operator set this small keeps the DFG easy to analyze and has an obvious lowering path for synthesis.\n\nParametrization was one of the SystemVerilog features that was much more work than it had any right to be. Parameters are resolved entirely at compile time, so nothing survives into the IR except baked-in constants, but computing them correctly meant writing something close to a small expression evaluator inside the compiler. \"Just substitute the values in\" turned out to be doing a lot of hiding.\n\nMaking clock domains global instead of local labels also took way more work than expected, but it payed off immediately: once every register's clock is resolved to a small set of unique global clocks, the crossing points, the registers where one clock domain hands off to another, just fall out of compilation. No separate CDC tool was needed for this, it was a fact obtained early in the compilation process.\n\nOf course we can't move forward with whatever crossings we find. In this initial compiler version, you have to provide a file \"waiving\" each crossing as intentional, at the same level of hierarchy as the design itself, or compilation simply doesn't proceed. It's a blunt mechanism for now, just a declared list of valid crossings, but it's already enough to move CDC from something you check for after the fact to something you have to state up front. The natural next step is a static tool that actually reasons about the IR and validates whether a given crossing is safe, instead of just trusting the waiver. But even without that yet, the shift in when you're forced to think about timing intent, at the start of writing the RTL instead of somewhere in signoff, is the part I care about most.\n\nAfter compiling smaller designs and a handful of small cores off GitHub, the\nreal milestone was getting [Ibex Core](https://github.com/lowRISC/ibex) to\ncompile. It's a real RISC-V core that leans hard on packages, typedefs, and\nparametrization. Getting it compiled into Mate IR showed that a Mealy machine\nwas truly all we need!\n\n### Current status\n\nThe SystemVerilog compiler handles a pretty complete slice of the language at this point: unpacked arrays, enums, structs, interfaces, parametrization, functions.\n\nOn the consumer side there are two things working today. A simulator, which codegens a C++ model from the resolved IR and wraps it in a small SystemVerilog shim called over DPI, so it can be dropped straight into any existing SystemVerilog testbench. And a static analysis tool that, for now, just reports the hierarchy of registers referencing each global clock. It's a basic shell more than a checker, but it's built directly on the clock resolution described above, so the real static checks, the ones that would replace the waiver file entirely, have a clean foundation to be added on top of.\n\n## Summary\n\nWhat started some months ago as seeing how far I could take an idea with a coding agent on my command, ended up being a pretty complete IR for synthesizable RTL. It is far from being complete or actually useful but it's a nice simplification over several facts that are currently over complicated.\n\nIf any of this is useful or interesting to you, the repo is at\n[github.com/miguel9554/mateIR](https://github.com/miguel9554/mateIR). Try to run\nit on some design you have, report any errors, tell me why do you think this is\nbad (or good!). I'll be writing more about the internals of the IR and the most\ncomplicated parts to get right.", "url": "https://wpnews.pro/news/a-minimal-ir-for-rtl", "canonical_source": "https://miguel9554.github.io/blog/posts/a-minimal-ir-for-rtl/", "published_at": "2026-07-23 22:57:08+00:00", "updated_at": "2026-07-23 23:22:33.139631+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Mate IR", "SystemVerilog", "Miguel9554"], "alternates": {"html": "https://wpnews.pro/news/a-minimal-ir-for-rtl", "markdown": "https://wpnews.pro/news/a-minimal-ir-for-rtl.md", "text": "https://wpnews.pro/news/a-minimal-ir-for-rtl.txt", "jsonld": "https://wpnews.pro/news/a-minimal-ir-for-rtl.jsonld"}}