Highly Extensible Hardware Description Library for JavaScript Developers
Hardware is event-driven. And it is functional in nature, having abstractions on abstractions.
Makes sense to have JavaScript emulate it doesn't it?
Motivation
Hardware Description can be fun and very educational, but I had to learn VHDL to be able to do so.
Hence, being a JavaScript Enthusiast, I decided to write this library down for JavaScript Developers who wanted to get into Hardware Description but were reluctant to learn a new language for it.
For people not acquainted with both VHDL and JS, I'm pretty sure the learning curve would be lesser for this library. Although I cannot state that my library is better as it is not easy to compete with a language intended for hardware description, nevertheless, I will keep on hacking this library to see how this experiment goes.
Let's get to business!
Installation
npm install architectjs
Existing Hardware Abstractions
- Gates
- AndGate
- TriInpAndGate
- OrGate
- XorGate
- NotGate
- NandGate
- NorGate
- XnorGate
- Decoders
- Decoder1x2
- Decoder2x4
- Arithmetics
- HalfAdder
- FullAdder
- PipoAdder
- Flip-Flops
- SRFlipFlop
- DFlipFlop
🔌 Plug-n-Play
Use existing abstractions seemlessly.
Let's plug in an AND-Gate
const { wires } = require('architectjs')('Connectors')
const { AndGate } = require('architectjs')('Gates')
const { StringIO } = require('architectjs')('IO')
// provision wires to connect to your hardware
const inputA = wires(1)
const inputB = wires(1)
const output = wires(1)
// initialise the hardware
const hWare = new AndGate(inputA, inputB, output)
// wrap hardware in a I/O BlackBox
// this is compulsory, to be able to do I/O using strings
const ioHandler = new StringIO(hWare)
console.log(ioHandler.input('1', '1')) // prints 1
console.log(ioHandler.input('0', '0')) // prints 0
Say what? AND
is way too easy to be called an abstraction?
No worries, let's plug in this generalised Parallel-in-Parallel-out Adder!
const { wires } = require('architectjs')('Connectors')
const { PipoAdder } = require('architectjs')('Arithmetics')
const { StringIO } = require('architectjs')('IO')
// code for 4-bit adder
const inputA = wires(4)
const inputB = wires(4)
const sum = wires(5)
const hWare = new PipoAdder(inputA, inputB, sum)
const ioHandler = new StringIO(hWare)
console.log(ioHandler.input('1111', '1111')) // prints 11110
Or maybe we want to build something from existing abstractions?
Abstraction Rules and Specs
- Every Class/hardware extends on
Hardware
. - Every initialisation argument to the class instance has to be an array of
Wire
instances (obtained from thewires
method). - An array consisting of I/O
wires
is passed onto the parent classHardware
, with only the last element being the output parameter. It is necessary to provide every input parameter and the output parameter to be able to wrap this in aStringIO
instance to do I/O operations withstring
arguments. - Every class instance has two instance variables available from the parent
Hardware
instance :- internalWiring - Array of
Wire
instances (initially empty). - components - Array of abstractions used to build your hardware (initially empty).
- internalWiring - Array of
- Your entire logic goes into your Class' constructor.
internalWiring
variable is used to initialiseWire
instances that are not a part of the I/O for the hardware but are required to inter-connect the sub-components in your abstraction.components
variable is used to store instances of subcomponents used in your hardware. This helps a designer to quickly refer to all the build blocks that went into making a particular piece of hardware.
Let's build a 4-input AND Gate using the above rules and specifications.
const { wires } = require('architectjs')('Connectors')
const { AndGate } = require('architectjs')('Gates')
const { StringIO } = require('architectjs')('IO')
const { Hardware } = require('architectjs')('Base')
class FourInpAndGate extends Hardware {
constructor(a, b, c, d, o) {
super([a, b, c, d, o])
this.internalWiring = wires(2) // declare wires to be used internally
this.components.push(new AndGate(a, b, this.internalWiring[0]))
this.components.push(new AndGate(c, d, this.internalWiring[1]))
this.components.push(new AndGate(this.internalWiring[0], this.internalWiring[1], o))
}
}
const a = wires(1)
const b = wires(1)
const c = wires(1)
const d = wires(1)
const o = wires(1)
const fourInpAnd = new FourInpAndGate(a, b, c, d, o)
const ioHandler = new StringIO(fourInpAnd)
console.log(ioHandler('0', '1', '1', '1')) // prints 0
console.log(ioHandler('1', '1', '1', '1')) // prints 1
Creating a Declarative Hardware Component
Some Basic Rules
- Every Class/hardware extends on
Hardware
. - All the logic goes inside the
hardware
method of your component's Class. - Event to be listened for must be
signal
.
Let's get started
Every Wire
instance extends on EventEmitter
, thus this library essentially works by registering listeners in a Class instance and binding them to the hardware
method of the Class.
With the help of getSignal
and propagateSignal
methods of Wire
, read changes from input Wire
instances, use your logic on them, and emit result through the output Wire
instance.
Let's set this up with an example taken from this library
const { Hardware } = require('architectjs')('Base')
class AndGate extends Hardware {
constructor(x, y, o) {
if (x.length != 1 || y.length != 1 || o.length != 1) throw new Error('Invalid Connection/s')
super([x, y, o])
this.x = x
this.y = y
this.o = o
this.hardware = this.hardware.bind(this)
x[0].on('signal', this.hardware)
y[0].on('signal', this.hardware)
}
hardware() {
let xSig = this.x[0].getSignal()
let ySig = this.y[0].getSignal()
if (xSig === 0 || ySig === 0) {
this.o[0].propagateSignal(0)
} else if (xSig === undefined || ySig === undefined) {
this.o[0].propagateSignal(undefined)
} else this.o[0].propagateSignal(xSig && ySig)
}
}
Development
New Hardware Component Proposals should be put up as an issue to discuss it's vialibility and modelling. I won't be considering anything else other than component proposals at the moment.
I am also facing some problems in figuring out how to implement clock-edge driven circuits and circuits that have a feedback to them. Most of time, infinte events are triggered due to the feedbacking in the circuits.
There are just so many possibilities to do here! Would love to get contributions from the community 😄