dmitrysoshnikov / hdl-js Goto Github PK
View Code? Open in Web Editor NEWHardware description language (HDL) parser, and Hardware simulator.
License: MIT License
Hardware description language (HDL) parser, and Hardware simulator.
License: MIT License
The code generator is an inverse procedure for parsing: having an AST, we need to generate HDL code.
This will be used to generate HDL specs from gate instances, and exporting a gate built in the UI interface to an HDL file.
const hdl = require('hdl-js');
const ast = hdl.parseFile('./examples/And.hdl');
const {generator} = hdl;
// Gives a code close to the original ./examples/And.hdl
const hdlCode = generator.fromAST(ast);
console.log(hdlCode);
/*
Output:
CHIP And {
IN a, b;
OUT out;
PARTS:
Nand(a=a, b=b, out=n);
Nand(a=n, b=n, out=out);
}
*/
The generated code should be readable and pretty-printed with 2 spaces indentation.
Example of a very similar generator in the regexp-tree
project: https://github.com/DmitrySoshnikov/regexp-tree/blob/master/src/generator/index.js Basically need to handle each AST node type, and return string representation of it.
I run the test under windows and find an error. What is the reason?
F:\hdl-js>node bin/hdl-js -g examples/MipsAlu16.hdl -e '[{a: 2, b: 3, op: 2}]' -f dec -c a,b,out
evalmachine.:1
('[{a:)
^^^^^^
SyntaxError: Invalid or unexpected token
at createScript (vm.js:80:10)
at Object.runInNewContext (vm.js:135:10)
at parseInputData (F:\hdl-js\dist\bin\hdl-js-cli.js:117:19)
at loadAndProcessData (F:\hdl-js\dist\bin\hdl-js-cli.js:110:10)
at main (F:\hdl-js\dist\bin\hdl-js-cli.js:520:16)
at Object. (F:\hdl-js\bin\hdl-js:5:34)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
When a Pin
instance changes its value, we should emit 'change'
event (for this Pin
class should extend EventEmitter
).
Example:
const a = new Pin({name: 'a', size: 16});
a.on('change', (newValue, oldValue) => {
console.log(`Pin "a" changed value from ${oldValue} to ${newValue}.`);
});
a.setValue(255);
/*
Result:
Pin "a" changed value from 0 to 255.
*/
Values like a[5]
are currently not support in the parser. Example is in Add16.hdl.
This should be supported for IN
, OUT
, and chip-arguments.
The following gates have to be implemented as internal chips:
This is a specified ALU based on the MIPS architecture. See design doc here:
ALU_AppB.pdf.
MipsAlu.js
MipsAlu16.js
IN a, b, na, nb, cin, less, op[2];
a
- 1-bit input
b
- 1-bit input
na
- negate a
?
nb
- negate b
?
less
- special bit for the "less than operation" (propagated directly, calculated in the 16-bit ALU)
cin
- carry in (from previous operation)
op[2]
- 2-bits opcode
00
- AND01
- OR10
- AND (done with a full-adder, carry-in, carry-out)11
- propagate less
input directlyOUT out, cout, set;
out
- result outputcout
- carry out from the full adderset
- adder result, used to set less
value for the LSB in 16-bit ALUA 16-bit ALU consists of 16 1-Bit ALUs.
The inputs (semantics is the same as in 1-bit ALU above):
IN a[16], b[16], na, nb, cin, op[2];
Outputs:
OUT out[16], overflow, zero;
overflow
- whether there was an overflow in math (add/sub) operationzero
- whether the out
result is 0
(used for comparisons)Hello.
I'm taking the nand2tetris course and I'm using hdl-js' gate scripting feature to execute tests more conveniently from the terminal.
I'm having the following error with multi-way 16-bit chips, Mux4Way16
and Mux8Way16
:
Script error: TypeError: Pin "Mux1": value 4660 doesn't match pin's width. Max allowed is 1 (size 1).
at Pin.setValue (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/Pin.js:112:15)
at eval (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/Pin.js:330:18)
at Pin.listener (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/Pin.js:217:16)
at EventEmitter.emit (https://hdl-js.w.staticblitz.com/blitz.1f021b18268b32e6c6b2095e039ac8c9f88b0d52.js:6:155573)
at Pin.setValue (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/Pin.js:116:12)
at Mux16._eval [as _originalEval] (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/builtin-gates/Mux16.js:104:31)
at Mux16._evalEmit (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/Gate.js:428:12)
at GateClass._eval [as _originalEval] (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/CompositeGate.js:105:16)
at GateClass._evalEmit (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/Gate.js:428:12)
at GateClass._eval [as _originalEval] (/home/projects/hdl-js/node_modules/hdl-js/dist/emulator/hardware/CompositeGate.js:105:16)
I'm not having this problem on nand2tetris' Hardware Simulator 2.5:
Mux4Way16 | Mux8Way16 |
---|---|
I reproduced the error on StackBlitz.
Thanks in advance.
Passing the --clock-rate
should set the clock rate (number of cycles per second) of the SystemClock
object.
Gates in the emulator support execOnData
method which accepts a table with input data (or the full input truth table), executes the gate on this dat, and returns resulting (actual) truth table. If the output pins are passed, returns also conflicting data, if actual values differ from the passed ones.
We should expose it as --exec-on-data
(-e
) option.
./bin/hdl-js -g And -e '[{"a": 1, "b": 0}]'
Output:
Truth table for the input:
┌───┬───┬─────┐
│ a │ b │ out │
├───┼───┼─────┤
│ 1 │ 0 │ 0 │
└───┴───┴─────┘
Passing expected output pins (invalid in this case):
./bin/hdl-js -g And -e '[{"a": 1, "b": 0, "out": 1}]'
Result:
Found conflicts in:
- row 0, pins: out
┌───┬───┬─────┐
│ a │ b │ out │
├───┼───┼─────┤
│ 1 │ 0 │ 0 │
└───┴───┴─────┘
The 0
should be colored in red.
The --table
(-t
) option should print a truth table of a gate, applying to all possible inputs.
./bin/hdl-js --file examples/And.hdl --table
Result:
Truth table for "And":
┌───┬───┬─────┐
│ a │ b │ out │
├───┼───┼─────┤
│ 0 │ 0 │ 0 │
├───┼───┼─────┤
│ 0 │ 1 │ 0 │
├───┼───┼─────┤
│ 1 │ 0 │ 0 │
├───┼───┼─────┤
│ 1 │ 1 │ 1 │
└───┴───┴─────┘
It is safe to default the value of a Pin
to 0
(currently it's defaulted to undefined
). This will allows passing only values of interest, ignoring inputs which can be defaulted to 0
.
When the --columns
options is passed (comma-separated list), only this whitelist of truth table columns is shown.
All columns in the result table (default):
./bin/hdl-js -g examples/And.hdl -e '[{a: 1, b: 1}]'
Truth table for data:
┌───┬───┬───┬─────┐
│ a │ b │ n │ out │
├───┼───┼───┼─────┤
│ 1 │ 1 │ 0 │ 1 │
└───┴───┴───┴─────┘
Only whitelist:
./bin/hdl-js -g examples/And.hdl -e '[{a: 1, b: 1}]' --columns a,b,out
Truth table for data:
┌───┬───┬─────┐
│ a │ b │ out │
├───┼───┼─────┤
│ 1 │ 1 │ 1 │
└───┴───┴─────┘
FPGA world suffers a lot from fragmentation - some tools produce Verilog, some VHDL, some - only subsets of them, creating low-level LLVM-like alternative will help everyone, so HDL implementations will opt only for generating this low-level HDL and routing/synthesizers accept it. LLVM or WebAssembly - you can see how many languages and targets are supported now by both. With more open source tools for FPGA this is more feasible now than ever. Most of the people suggest to adapt FIRRTL for this. Please check the discussion and provide a feedback if you have any. There is a good paper on FIRRTL design and its reusability across different tools and frameworks.
See f4pga/ideas#19
The --exec-on-data
already allows accepting testing data, and validating the outputs. In addition we can provide an ability to do scripted testing of the pins, and manually call some events (like tick
, tock
, etc) in the scripts. This can be compatible with the nand2tetris script testing.
TODO:
Example testing And
:
load And.hdl,
output-file And.out,
compare-to And.cmp,
output-list a%B3.1.3 b%B3.1.3 out%B3.1.3;
set a 0,
set b 0,
eval,
output;
set a 0,
set b 1,
eval,
output;
set a 1,
set b 0,
eval,
output;
set a 1,
set b 1,
eval,
output;
Output:
| a | b | out |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Currently multi-bit pins have to be set manually (see Add16 example). It'd be great to support for
-loop in HDL.
CHIP And16 {
IN a[16], b[16];
OUT out[16];
PARTS:
for i = 0..15 {
And(a=a[i], b=b[i], out=out[i]);
}
}
Currently built-in gates are handled by the --gate
(-g
) option, while custom gates from HDL files -- via --file
(-f
) option. We can unify, and use only --gate
option for both, and reserve -f
to future --format
option.
Built-in:
./bin/hdl-js --gate And --describe
Custom:
./bin/hdl-js --gate examples/And.hdl --describe
Currently one have to pass the data for the --exec-on-data
(-e
) directly in the command line. We can extend it to accept file names as well which contain data in the extended JSON-notation.
Via direct data (currently supported):
./bin/hdl-js -g And -e '[{a: 1, b: 0}]'
Truth table for data:
┌───┬───┬─────┐
│ a │ b │ out │
├───┼───┼─────┤
│ 1 │ 0 │ 0 │
└───┴───┴─────┘
File ~/my-data.dat
:
[
{a: 1, b: 0},
]
Via filename (need to add support for):
./bin/hdl-js -g And -e ~/my-data.dat
... same output ...
Currently constants have to be passed as logical false
, and true
in the ChipCall
argument values. We need to support also 0
, and 1
aliases.
Current:
MyChip(a=false, b=true);
Add also support for:
MyChip(a=0, b=1);
Currently from Node one have to manually create Pin
instances before passing to a gate constructor:
const and = new And({
inputPins: [
new Pin({name: 'a'}),
new Pin({name: 'b'}),
],
outputPins: [
new Pin({name: 'out'}),
],
});
For convenience gate's constructor can automatically create Pin
instances:
const and = new And({
inputPins: ['a', 'b'],
outputPins: ['out'],
});
For pin bus:
const and16 = new And({
inputPins: [
{name: 'a', size: 16},
{name: 'b', size: 16},
],
outputPins: [
{name: 'out', size: 16},
],
});
The parser module produces an AST from and HDL file. We should create a corresponding gate class (extending CompositeGate
) from the AST.
We should expose: eval
, clockUp
, and clockDown
for the external observers.
The BUILTIN
directive allows overriding the gate implementation with loading a built-in gate for it.
Example:
CHIP And {
IN a, b;
OUT out;
BUILTIN And;
}
In this case the "custom" And
chip described in the HDL-file doesn't contain any PARTS
implementation, and loads instead the built-in (canonical) And
gate as a backend for its implementation.
Another example:
CHIP Mux {
IN a, b, sel;
OUT out;
PARTS:
Not(in=sel, out=nel);
And(a=a, b=nel, out=A);
And(a=b, b=sel, out=B);
Or(a=A, b=B, out=out);
BUILTIN And, Or;
}
In this case the And
, and Or
gates are explicitly marked as built-in, and hence will be loaded from the built-ins directory. However, the Not
gate will be searched first in the local directory for the Not.hdl
file (and only if it's not found, the built-in gate will be searched).
The later use case allows faster debugging, when one builds a chip, and in order to exclude potential issue in some custom gate implementation (e.g. And.hdl
), they choose to specify it as a built-in. Once the code is debugged, a developer can switched again to the custom And.hdl
, removing And
from BUILTIN
.
The --run
(-r
) command can be useful for "oscillating" (sequential/clocked) chips. When passing --exec-on-data
, instead of showing the whole output table, we can show only one row at a (clock) time -- this will make it easier to observe changing over time values (e.g. changing values and control bits of the Bit
, Register
, or PC
gates).
Implementation detail:
When a new HDL example implementation is introduced in the examples directory, it is good to add an accommodate test for it.
Moving to higher level, we'll need to support Assembler from n2t format.
This issue is extracted from #3.
ALU.js
)ALU design will affect the resulting format of the CPU instructions. Initially we can build the ALU from the nand2tetris course (named as ALU
). The spec is as follows:
/**
* The ALU (Arithmetic Logic Unit).
* Computes one of the following functions:
* x+y, x-y, y-x, 0, 1, -1, x, y, -x, -y, !x, !y,
* x+1, y+1, x-1, y-1, x&y, x|y on two 16-bit inputs,
* according to 6 input bits denoted zx,nx,zy,ny,f,no.
* In addition, the ALU computes two 1-bit outputs:
* if the ALU output == 0, zr is set to 1; otherwise zr is set to 0;
* if the ALU output < 0, ng is set to 1; otherwise ng is set to 0.
*/
// Implementation: the ALU logic manipulates the x and y inputs
// and operates on the resulting values, as follows:
// if (zx == 1) set x = 0 // 16-bit constant
// if (nx == 1) set x = !x // bitwise not
// if (zy == 1) set y = 0 // 16-bit constant
// if (ny == 1) set y = !y // bitwise not
// if (f == 1) set out = x + y // integer 2's complement addition
// if (f == 0) set out = x & y // bitwise and
// if (no == 1) set out = !out // bitwise not
// if (out == 0) set zr = 1
// if (out < 0) set ng = 1
CHIP ALUN2T {
IN
x[16], y[16], // 16-bit inputs
zx, // zero the x input?
nx, // negate the x input?
zy, // zero the y input?
ny, // negate the y input?
f, // compute out = x + y (if 1) or x & y (if 0)
no; // negate the out output?
OUT
out[16], // 16-bit output
zr, // 1 if (out == 0), 0 otherwise
ng; // 1 if (out < 0), 0 otherwise
BUIlTIN ALU;
}
The implementation ALUN2T.js
should be in the directory of all other builtin gates.
See also #21 for another ALU based on MIPS.
To make a sub-bus, one could assign: Add16(a[0...7]=lsb, ...)
. The parser needs to support a[0...7]
ranges. See also #1.
Currently we show in the tables data only in binary format, e.g. 1111111111111111
:
./bin/hdl-js -g And16 -d
We should implement --format
(-f
) CLI option (and passing format
parameter to printTruthTable
or to the TablePrinter
instance), and output data in either binary, hex (FFFF
), or decimal (-1
for signed, 65535
for unsigned) formats.
Currently we use Pin
for single "wire" pin (with values 0
, and 1
), and PinBus
for multiple wires in a bus, to handle sized values (such as 0101
, etc).
We can treat Pin
as special bus with size 1
, and use the single Pin class, which handles generic buses, of sizes from 1
to 16
.
// Default size 1, 'a'
new Pin({name: 'a'});
// Size 1, 'a'
new Pin({name: 'a', size: 1});
// Size 16, 'a[16]'
new Pin({name: 'a', size: 16});
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.