npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

irtl

v1.0.3

Published

IR for RTL in JavaScript

Readme

NPM version Linux MacOS Windows Coverage Status

IR for RTL in JavaScript

irtl is a small intermediate representation (IR) for register-transfer-level (RTL) hardware, written in JavaScript. You describe modules — their ports, wires, registers, and logic — as plain JavaScript, assemble them into a hierarchy, and emit either FIRRTL or Verilog.

Install

npm install irtl
const irtl = require('irtl');

A pre-bundled standalone build (build/irtl.js, exposed as irtl via unpkg) is available for the browser.

Quick start

Build a module, wrap it in a circuit, and emit both targets:

const irtl = require('irtl');
const {input, output, wire, and, xor, or, buf, repeat} = irtl.elements;

const m = irtl.createModule('bar');

// ports
m.clk  = input.Clock();       // input  clk  : Clock
m.rst  = input(1);            // input  rst  : UInt<1>
m.arst = input.AsyncReset();  // input  arst : AsyncReset
m.inp1 = input(1);
m.inp2 = input(32);
m.out1 = output(11);

// wires and registers
m.tmp1 = wire(1);
m.tmp2 = {width: 8,  clock: m.clk};                              // register
m.tmp3 = {width: 16, clock: m.clk, reset: m.rst};               // sync reset
m.tmp4 = {width: 32, clock: m.clk, reset: m.arst, resetValue: 1}; // async reset

// literals and logic
m.lit1 = 5;                                   // width inferred
m.out1 = and(m.inp1, m.tmp1, m.tmp2, m.tmp2); // variadic
m.tmp8 = xor(m.tmp3, or(m.tmp4, m.lit1, 15));
m.tmp3 = buf(m.out1);
m.out2 = repeat(m.tmp1, 5);

// wrap the module(s) into a circuit hierarchy
const circuit = irtl.createCircuit('top_mod', [m]);

console.log(irtl.emitVerilog(circuit));
console.log(irtl.emitFirrtl(circuit));

Emitted Verilog:

// circuit top_mod
module bar (
  input              clk,
  input              rst,
  input              arst,
  input              inp1,
  input       [31:0] inp2,
  output      [10:0] out1
);
wire               tmp1;
wire         [2:0] lit1;
wire               tmp8;
wire               out2;
reg          [7:0] tmp2;
reg         [15:0] tmp3;
reg         [31:0] tmp4;
assign lit1 = 3'd5;
assign out1 = (inp1 & tmp1 & tmp2 & tmp2);
assign tmp8 = (tmp3 ^ (tmp4 | lit1 | 4'd15));
always @(posedge clk or posedge rst) if (rst) tmp3 <= 16'd0; else tmp3 <= out1;
assign out2 = {5{tmp1}};
endmodule

Emitted FIRRTL:

circuit top_mod:
  module bar:
    input  clk: Clock
    input  rst: UInt<1>
    input  arst: AsyncReset
    input  inp1: UInt<1>
    input  inp2: UInt<32>
    output out1: UInt<11>
    wire   tmp1: UInt<1>
    wire   lit1: UInt<3>
    wire   tmp8: UInt
    wire   out2: UInt
    reg    tmp2: UInt<8>, clk
    reg    tmp3: UInt<16>, clk with: (reset => (rst, 0))
    reg    tmp4: UInt<32>, clk with: (reset => (arst, 1))
    lit1 <= UInt<3>(5)
    out1 <= and(inp1, and(tmp1, and(tmp2, tmp2)))
    tmp8 <= xor(tmp3, or(tmp4, or(lit1, UInt<4>(15))))
    tmp3 <= out1
    out2 <= cat(tmp1, cat(tmp1, cat(tmp1, cat(tmp1, tmp1))))

Concepts

Modules are Proxies

createModule(name) returns a Proxy. Assigning a property defines a signal; reading a property returns a reference to that signal you can wire into logic. The right-hand side of an assignment decides what the signal is:

| RHS | Result | | --- | --- | | input(w) / output(w) / wire(w) | a port or wire, optional bit width w | | input.Clock() / input.AsyncReset() | a typed input port | | {width, clock} | a register clocked by clock | | {width, clock, reset[, resetValue]} | a register with reset (sync if reset is a plain input, async if the reset signal is AsyncReset) | | a number | a literal; width is inferred as ceil(log2(value + 1)) | | an operation, e.g. and(a, b) | combinational logic driving the signal |

Referencing an undefined property (e.g. m.foo before assignment) lazily creates an Int signal, so signals may be used before they are formally declared.

Builder API (explicit alternative)

irtl.build(module) wraps a module in an explicit, chainable, validating facade over the same Proxy — both styles interoperate and emit identical RTL. It is handy for generated code, and for tools (or LLMs) that prefer named methods over Proxy assignment:

const {add} = irtl.elements;

const m = irtl.build(irtl.createModule('adder'))
  .addInput('clk', {type: 'Clock'})
  .addInput('a', {width: 8})
  .addInput('b', {width: 8})
  .addOutput('sum', {width: 9})
  .addReg('acc', {width: 9, clock: 'clk'}); // clock by name or by signal

m.assign('sum', add(m.signal('a'), m.signal('b')));

// hierarchy without nested arrays:
const top = irtl.build(irtl.createModule('top')).instantiate(m);
const circuit = irtl.createCircuit('top', top); // accepts a builder or a tree

The facade validates as you go (addInput('1bad') and re-declaring a port both throw actionable errors) and exposes introspection: hasPort, getPortWidth, getPortDirection, getSignalKind, listInputs, listOutputs.

Serialization

irtl.serialize(circuit) returns a plain, cycle-free JSON object (the live IR is full of back-references and cannot be JSON.stringify-ed directly). Useful for snapshots, diffing, or feeding the design to other tools:

JSON.stringify(irtl.serialize(circuit), null, 2);

Elements and operations

irtl.elements provides the signal constructors (input, output, wire) and a set of variadic operation builders. Each operation returns a plain node {op, items: [...]} that nests freely:

asUInt asSInt asClock asAsyncReset asReset cvt neg not
andr orr xorr nandr norr xnorr
land lor lnot
bits tail head pad
add sub mul div rem
lt leq gt geq eq neq
shl shr dshl dshr
and or xor xnor cat
mux
assert assume cover
repeat buf

Variadic ops fold right: and(a, b, c) emits as and(a, and(b, c)) in FIRRTL and (a & b & c) in Verilog. irtl.variadics re-exports the binary/arithmetic subset for convenience.

Verilog operator mapping

Each op lowers to native Verilog rather than a function call:

| Category | Ops | Verilog | | --- | --- | --- | | arithmetic | add sub mul div rem | + - * / % | | comparison | lt leq gt geq eq neq | < <= > >= == != | | bitwise | and or xor xnor | & \| ^ ~^ | | logical | land lor lnot | && \|\| ! | | reduction | andr orr xorr nandr norr xnorr | &(x) \|(x) ^(x) ~&(x) ~\|(x) ~^(x) | | unary | not neg | ~(x) -(x) | | shift | shl shr dshl dshr | << >> (>>> when operand is signed) | | casts | asUInt asSInt cvt | $unsigned(x) / $signed(x) | | select / slice | mux bits head tail pad cat repeat | ? :, part-select, replicate, concat |

Signed right shift. shr/dshr emit an arithmetic shift ($signed(x) >>> n) when the operand carries signed: true, otherwise a logical >>. FIRRTL infers this from the SInt/UInt type, so its output is unchanged.

Ops without a FIRRTL primop. xnor, land, lor, lnot, and the nandr/norr/xnorr reductions have no direct FIRRTL primop, so the FIRRTL emitter lowers them to an equivalent primop tree — e.g. xnor(a, b)not(xor(a, b)), land(a, b)and(orr(a), orr(b)), lnot(x)not(orr(x)). The Verilog emitter throws on any unrecognized op rather than emitting garbage.

Circuits, hierarchy, and plumbing

createCircuit(name, tree) assembles modules into an instance hierarchy. The tree is a nested array where the first element is the parent module and the rest are its children:

// r ─ a ─┬─ b ─┬─ c
//        │     ├─ d ─ e
//        │     └─ f ─ h ─ i
//        └─ g
const [r, a, b, c, d, e, f, g, h, i] =
  'r a b c d e f g h i'.split(' ').map(irtl.createModule);

f.sig = 100;          // driven in f
e.foo = buf(f.sig);   // used in e — different subtree
g.bar = buf(f.sig);   // used in g — another subtree

const circuit = irtl.createCircuit('top_mod',
  [r, [a, [b, [c], [d, [e]], [f, [h, [i]]]], [g]]]);

When a signal is driven in one module and used in another, plumbing (lib/plumb.js) automatically routes it through the hierarchy: it finds the lowest common ancestor of the driver and each user, then inserts the intermediate output/wire/input ports (named _<path>_<sig>) and instance bindings needed to connect them. You write the intent; irtl generates the port list.

API

| Export | Description | | --- | --- | | createModule(name) | Create a module (a Proxy); assign properties to define signals. | | createCircuit(name, tree) | Build the instance hierarchy and run plumbing; accepts a nested-array tree or a build(...) root. | | build(module) | Wrap a module in the explicit, validating builder facade. | | serialize(circuit) | Convert a circuit to plain, cycle-free JSON. | | elements | Signal constructors (input, output, wire) and operation builders. | | variadics | Arithmetic/logic operation builders (subset of elements). | | emitVerilog(circuit) | Render the circuit to Verilog RTL (string). | | emitFirrtl(circuit) | Render the circuit to FIRRTL (string). | | plumb(mods) | Lower-level pass that inserts cross-module routing (run by createCircuit). | | identity | Symbol used internally to unwrap a module Proxy to its target. | | version | Package version. |

Development

npm test        # eslint + node:test with coverage
npm run bundle  # esbuild standalone browser bundle -> build/irtl.js

Tests live in test/ and double as worked examples — test/emit.js covers a single feature-rich module, test/hier.js covers hierarchy and plumbing across several topologies, and test/builder.js exercises the builder facade, validation, and serialization.

License

MIT