Overview

DigitalJS is a digital circuit simulator implemented in JavaScript. It is designed to simulate circuits synthesized by hardware design tools like Yosys, and it has a companion project yosys2digitaljs, which converts Yosys output files to DigitalJS.

It is also intended to be a teaching tool, therefore readability and ease of inspection is one of top concerns for the project.

You can try it out online. The web app is a separate Github project.

Usage

You can use DigitalJS in your project by installing it from NPM:

npm install digitaljs

Or you can use the Webpack bundle directly.

To simulate a circuit represented using the JSON input format (described later) and display it on a div named #paper, you need to run the following JS code (see running example):

// create the simulation object
const circuit = new digitaljs.Circuit(input_goes_here);
// display on #paper
const paper = circuit.displayOn($('#paper'));
// activate real-time simulation
circuit.start();

Input Format

Circuits are represented using JSON. The top-level object has three keys, devices, connectors and subcircuits.

  • Under devices is a list of all devices forming the circuit, represented as an object, where keys are (unique and internal) device names.
  • Each device has a number of properties, which are represented by an object.
  • A mandatory property is type, which specifies the type of the device.

Example device:

"dev1": {
    "type": "And",
    "label": "AND1"
}

Under connectors is a list of connections between device ports, represented as an array of objects with two keys, from and to. Both keys map to an object with two keys, id and port; the first corresponds to a device name, and the second -- to a valid port name for the device. A connection must lead from an output port to an input port, and the bitwidth of both ports must be equal.

Example connection:

{
    "from": {
        "id": "dev1",
        "port": "out"
    },
    "to": {
        "id": "dev2",
        "port": "in"
    }
}

Under subcircuits is a list of subcircuit definitions, represented as an object, where keys are unique subcircuit names. A subcircuit name can be used as a celltype for a device of type Subcircuit; this instantiates the subcircuit. A subcircuit definition follows the representation of whole circuits, with the exception that subcircuits cannot (currently) define their own subcircuits. A subcircuit can include Input and Output devices, these are mapped to ports on a subcircuit instance.

Full Adder Example

Here's how a simple Full Adder might be represented in the digitaljs JSON format:

{
  "devices": {
    "A": { "type": "Button", "label": "A" },
    "B": { "type": "Button", "label": "B" },
    "Cin": { "type": "Button", "label": "Cin" },
    "XOR1": { "type": "Xor" },
    "XOR2": { "type": "Xor" },
    "AND1": { "type": "And" },
    "AND2": { "type": "And" },
    "OR1": { "type": "Or" },
    "Sum": { "type": "Lamp", "label": "Sum" },
    "Cout": { "type": "Lamp", "label": "Cout" }
  },
  "connectors": [
    { "from": { "id": "A", "port": "out" }, "to": { "id": "XOR1", "port": "in1" } },
    { "from": { "id": "B", "port": "out" }, "to": { "id": "XOR1", "port": "in2" } },
    { "from": { "id": "XOR1", "port": "out" }, "to": { "id": "XOR2", "port": "in1" } },
    { "from": { "id": "Cin", "port": "out" }, "to": { "id": "XOR2", "port": "in2" } },
    { "from": { "id": "XOR2", "port": "out" }, "to": { "id": "Sum", "port": "in" } },
    { "from": { "id": "Cin", "port": "out" }, "to": { "id": "AND1", "port": "in1" } },
    { "from": { "id": "XOR1", "port": "out" }, "to": { "id": "AND1", "port": "in2" } },
    { "from": { "id": "A", "port": "out" }, "to": { "id": "AND2", "port": "in1" } },
    { "from": { "id": "B", "port": "out" }, "to": { "id": "AND2", "port": "in2" } },
    { "from": { "id": "AND1", "port": "out" }, "to": { "id": "OR1", "port": "in1" } },
    { "from": { "id": "AND2", "port": "out" }, "to": { "id": "OR1", "port": "in2" } },
    { "from": { "id": "OR1", "port": "out" }, "to": { "id": "Cout", "port": "in" } }
  ]
}

Device Types

Logic Gates

Unary gates: Not, Repeater

  • Attributes: bits (natural number)
  • Inputs: in (bits-bit)
  • Outputs: out (bits-bit)

N-ary gates: And, Nand, Or, Nor, Xor, Xnor

  • Attributes: bits (natural number), inputs (natural number, default 2)
  • Inputs: in1, in2 ... inN (bits-bit, N = inputs)
  • Outputs: out (bits-bit)

Reducing gates: AndReduce, NandReduce, OrReduce, NorReduce, XorReduce, XnorReduce

  • Attributes: bits (natural number)
  • Inputs: in (bits-bit)
  • Outputs: out (1-bit)

Arithmetic & Comparison

Bit shifts: ShiftLeft, ShiftRight

  • Attributes: bits.in1, bits.in2 and bits.out (natural number), signed.in1, signed.in2, signed.out and fillx (boolean)
  • Inputs: in1 (bits.in1-bit), in2 (bits.in2-bit)
  • Outputs: out (bits.out-bit)

Comparisons: Eq, Ne, Lt, Le, Gt, Ge

  • Attributes: bits.in1 and bits.in2 (natural number), signed.in1 and signed.in2 (boolean)
  • Inputs: in1 (bits.in1-bit), in2 (bits.in2-bit)
  • Outputs: out (1-bit)

Number constant: Constant

  • Attributes: constant (binary string)
  • Outputs: out (constant.length-bit)

Unary arithmetic: Negation, UnaryPlus

  • Attributes: bits.in and bits.out (natural number), signed (boolean)
  • Inputs: in (bits.in-bit)
  • Outputs: out (bits.out-bit)

Binary arithmetic: Addition, Subtraction, Multiplication, Division, Modulo, Power

  • Attributes: bits.in1, bits.in2 and bits.out (natural number), signed.in1 and signed.in2 (boolean)
  • Inputs: in1 (bits.in1-bit), in2 (bits.in2-bit)
  • Outputs: out (bits.out-bit)

Multiplexers

Multiplexer: Mux

  • Attributes: bits.in, bits.sel (natural number)
  • Inputs: in0 ... inN (bits.in-bit, N = 2**bits.sel-1), sel (bits.sel-bit)
  • Outputs: out (bits.in-bit)

One-hot multiplexer: Mux1Hot

  • Attributes: bits.in, bits.sel (natural number)
  • Inputs: in0 ... inN (bits.in-bit, N = bits.sel), sel (bits.sel-bit)
  • Outputs: out (bits.in-bit)

Sparse multiplexer: MuxSparse

  • Attributes: bits.in, bits.sel (natural number), inputs (list of natural numbers), default_input (optional boolean)
  • Inputs: in0 ... inN (bits.in-bit, N = inputs.length, +1 if default_input is true)
  • Outputs: out (bits.in-bit)

Memory Elements

D flip-flop: Dff

  • Attributes: bits (natural number), polarity.clock, polarity.arst, polarity.srst, polarity.aload, polarity.set, polarity.clr, polarity.enable, enable_srst (optional booleans), initial (optional binary string), arst_value, srst_value (optional binary string), no_data (optional boolean)
  • Inputs: in (bits-bit), clk (1-bit, if polarity.clock is present), arst (1-bit, if polarity.arst is present), srst (1-bit, if polarity.srst is present), en (1-bit, if polarity.enable is present), set (1-bit, if polarity.set is present), clr (1-bit, if polarity.clr is present), ain (bits-bit, if polarity.aload is present), aload (1-bit, if polarity.aload is present)
  • Outputs: out (bits-bit)

Memory: Memory

  • Attributes: bits, abits, words, offset (natural number), rdports (array of read port descriptors), wrports (array of write port descriptors), memdata (memory contents description)
  • Read port descriptor attributes: enable_polarity, clock_polarity, arst_polarity, srst_polarity (optional booleans), init_value, arst_value, srst_value (optional binary strings), transparent, collision (optional booleans or arrays of booleans)
  • Write port descriptor attributes: enable_polarity, clock_polarity, no_bit_enable (optional booleans)
  • Inputs (per read port): rdKaddr (abits-bit), rdKen (1-bit, if enable_polarity is present), rdKclk (1-bit, if clock_polarity is present), rdKarst (1-bit, if arst_polarity is present), rdKsrst (1-bit, if srst_polarity is present)
  • Outputs (per read port): rdKdata (bits-bit)
  • Inputs (per write port): wrKaddr (abits-bit), wrKdata (bits-bit), wrKen (1-bit (when no_bit_enable is true) or bits-bit (otherwise), if enable_polarity is present), wrKclk (1-bit, if clock_polarity is present)

I/O Elements

Clock source: Clock

  • Outputs: out (1-bit)

Button input: Button

  • Outputs: out (1-bit)

Lamp output: Lamp

  • Inputs: in (1-bit)

Number input: NumEntry

  • Attributes: bits (natural number), numbase (string)
  • Outputs: out (bits-bit)

Number output: NumDisplay

  • Attributes: bits (natural number), numbase (string)
  • Inputs: in (bits-bit)

Subcircuit input: Input

  • Attributes: bits (natural number)
  • Outputs: out (bits-bit)

Subcircuit output: Output

  • Attributes: bits (natural number)
  • Inputs: in (bits-bit)

7 segment display output: Display7

  • Inputs: bits (8-bit only - most significant bit controls decimal point LED)

Bus Operations

Bus grouping: BusGroup

  • Attributes: groups (array of natural numbers)
  • Inputs: in0 (groups[0]-bit) ... inN (groups[N]-bit)
  • Outputs: out (sum-of-groups-bit)

Bus ungrouping: BusUngroup

  • Attributes: groups (array of natural numbers)
  • Inputs: in (sum-of-groups-bit)
  • Outputs: out0 (groups[0]-bit) ... outN (groups[N]-bit)

Bus slicing: BusSlice

  • Attributes: slice.first, slice.count, slice.total (natural number)
  • Inputs: in (slice.total-bit)
  • Outputs: out (slice.count-bit)

Zero- and sign-extension: ZeroExtend, SignExtend

  • Attributes: extend.input, extend.output (natural number)
  • Inputs: in (extend.input-bit)
  • Outputs: out (extend.output-bit)

Finite State Machines

FSM: FSM

  • Attributes: bits.in, bits.out, states, init_state, current_state (natural number), trans_table (array of transition descriptors)
  • Transition descriptor attributes: ctrl_in, ctrl_out (binary strings), state_in, state_out (natural numbers)
  • Inputs: clk (1-bit), arst (1-bit), in (bits.in-bit)
  • Outputs: out (bits.out-bit)

Documentation for DigitalJS

Last updated: 4/29/2025