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)