hdl-js
Version:
Hardware definition language (HDL) and Hardware simulator
413 lines (333 loc) • 13.5 kB
JavaScript
/**
* The MIT License (MIT)
* Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var CompositeGate = require('./CompositeGate');
var fs = require('fs');
var parser = require('../../parser');
var path = require('path');
var Pin = require('./Pin');
/**
* Cache map from file names to gates class.
*/
var fileNamesToGateClasses = {};
/**
* Cache map from HDL code to gate classes.
*/
var hdlCodeToGateClasses = {};
/**
* Virtual directory when no access to filesystem
*/
var virtualDirectory = null;
var virtualDirectoryGateClasses = {};
/**
* This factory creates a gate class from the parsed HDL.
* The resulting class inherits from the `CompositeGate`.
*/
var HDLClassFactory = {
/**
* Creates a gate class from HDL file.
*
* The directory of the file is used further as a working
* directory to load other gates from it.
*/
fromHDLFile: function fromHDLFile(fileName) {
var cacheKey = path.resolve(fileName);
if (!fileNamesToGateClasses.hasOwnProperty(cacheKey)) {
fileNamesToGateClasses[cacheKey] = this.fromHDL(fs.readFileSync(fileName, 'utf-8'), path.dirname(fileName));
}
return fileNamesToGateClasses[cacheKey];
},
/**
* Creates a gate class from HDL chip definition.
*
* If working directory is passed, it's used to load
* other gates from it.
*/
fromHDL: function fromHDL(hdl) {
var workingDir = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : __dirname;
var cacheKey = hdl + ':' + workingDir;
if (!hdlCodeToGateClasses.hasOwnProperty(cacheKey)) {
hdlCodeToGateClasses[cacheKey] = this.fromAST(parser.parse(hdl), workingDir);
}
return hdlCodeToGateClasses[cacheKey];
},
/**
* Creates a gate class from an AST.
*
* If working directory is passed, it's used to load
* other gates from it.
*/
fromAST: function fromAST(ast) {
var workingDir = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : __dirname;
// Check if the built-in version should be used as a backend.
if (shouldUseBuiltinGate(ast.name, ast)) {
return loadBuiltinGate(ast.name);
}
var _analyzeParts = analyzeParts(ast, workingDir),
_analyzeParts2 = _slicedToArray(_analyzeParts, 2),
internalPinsSpec = _analyzeParts2[0],
partsClasses = _analyzeParts2[1];
// Out chip is clocked if any of the part chips is clocked.
var _isClocked = partsClasses.some(function (partClass) {
return partClass.isClocked();
});
// Gate class, corresponding to the HDL file.
var GateClass = function (_CompositeGate) {
_inherits(GateClass, _CompositeGate);
function GateClass() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, GateClass);
if (!options.inputPins) {
options.inputPins = createPins(ast.inputs);
}
options.inputPins = CompositeGate.toPins(options.inputPins);
if (!options.outputPins) {
options.outputPins = createPins(ast.outputs);
}
options.outputPins = CompositeGate.toPins(options.outputPins);
// Internal pins used in PARTS.
options.internalPins = [];
// Create instances used in PARTS implementation.
var parts = instantiateParts(ast, options, partsClasses);
return _possibleConstructorReturn(this, (GateClass.__proto__ || Object.getPrototypeOf(GateClass)).call(this, Object.assign(options, {
name: ast.name,
parts: parts,
ast: ast
})));
}
_createClass(GateClass, null, [{
key: 'isClocked',
value: function isClocked() {
return _isClocked;
}
}]);
return GateClass;
}(CompositeGate);
// Override `name` property to reflect class name.
Object.defineProperty(GateClass, 'name', { value: ast.name });
var pinsToGateSpec = function pinsToGateSpec(value) {
return { name: value.value, size: value.size || 1 };
};
GateClass.Spec = {
name: ast.name,
description: 'Compiled from HDL composite Gate class "' + ast.name + '".',
inputPins: ast.inputs.map(pinsToGateSpec),
outputPins: ast.outputs.map(pinsToGateSpec),
internalPins: internalPinsSpec,
truthTable: []
};
return GateClass;
},
/**
* Sets virtual directory.
*
* It's a JS object from gate name to source code.
*/
setVirtualDirectory: function setVirtualDirectory(directory) {
virtualDirectory = directory;
virtualDirectoryGateClasses = {};
return this;
},
/**
* Loads part gate: custom (in the current working directory),
* or, if a gate doesn't exist in this directory, loads the built-in.
*/
loadGate: function loadGate(name) {
var workingDir = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : __dirname;
var ast = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
// Explicit override to use a built-in gate for this name.
if (ast && shouldUseBuiltinGate(name, ast)) {
return loadBuiltinGate(name);
}
// If virtual directory is set, use it:
if (virtualDirectory) {
return loadVirtualGate(name, workingDir);
}
// Else, check first if we have an HDL-implementation.
var hdlFile = path.join(workingDir, name + '.hdl');
if (fs.existsSync(hdlFile)) {
return HDLClassFactory.fromHDLFile(hdlFile);
}
// Otherwise, load a built-in gate.
return loadBuiltinGate(name);
}
};
function loadVirtualGate(name, workingDir) {
if (!virtualDirectory.hasOwnProperty(name) && !virtualDirectory.hasOwnProperty(name + '.hdl')) {
return loadBuiltinGate(name);
}
if (!virtualDirectoryGateClasses.hasOwnProperty(name)) {
virtualDirectoryGateClasses[name] = HDLClassFactory.fromHDL(virtualDirectory[name] || virtualDirectory[name + '.hdl'], workingDir);
}
return virtualDirectoryGateClasses[name];
}
/**
* Creates pins from AST data.
*/
function createPins(pinsData) {
var pins = pinsData.map(function (pinSpec) {
return new Pin({
name: pinSpec.value,
size: pinSpec.size || 1
});
});
return pins;
}
/**
* Creates pins map from actual Pins instances,
* or from AST data.
*/
function createPinsMap(pins) {
var pinsMap = {};
pins.forEach(function (pin) {
var name = pin instanceof Pin ? pin.getName() : pin.value;
pinsMap[name] = pin;
});
return pinsMap;
}
/**
* Returns a value corresponding to a constant:
* false, true, 0, 1.
*/
function getConstantValue(value) {
if (value.type !== 'Constant') {
return null;
}
return value.value;
}
/**
* Extracts spec info for internal pins,
* and also create gate classes for parts.
*/
function analyzeParts(ast, workingDir) {
var internalPinsSpec = [];
var partsClasses = [];
var inputPinsMap = createPinsMap(ast.inputs);
var outputPinsMap = createPinsMap(ast.outputs);
var internalPinsMap = {};
ast.parts.forEach(function (part) {
partsClasses.push(HDLClassFactory.loadGate(part.name, workingDir, ast));
part.arguments.forEach(function (partArg) {
var name = partArg.name,
value = partArg.value;
var constantValue = getConstantValue(value);
var isInternalPin = !inputPinsMap.hasOwnProperty(value.value) && !outputPinsMap.hasOwnProperty(value.value) && constantValue === null;
if (isInternalPin && !internalPinsMap.hasOwnProperty(value.value)) {
var pinSpec = {
name: value.value,
size: getInternalPinSize(name)
};
internalPinsSpec.push(internalPinsMap[value.value] = pinSpec);
}
});
});
return [internalPinsSpec, partsClasses];
}
/**
* Loads built-in gate by name.
*/
function loadBuiltinGate(name) {
return require('./builtin-gates/' + name);
}
/**
* Whether a built-in backend should be used.
*/
function shouldUseBuiltinGate(name, ast) {
if (ast.builtins.length === 0) {
return false;
}
return ast.builtins.find(function (_ref) {
var value = _ref.value;
return value === name;
});
}
/**
* Loads gate classes used in the `PARTS` implementation.
* These may include built-in, and custom classes from HDL.
*/
function instantiateParts(ast, options, partsClasses) {
var inputPins = options.inputPins,
outputPins = options.outputPins,
internalPins = options.internalPins;
var inputPinsMap = createPinsMap(inputPins);
var outputPinsMap = createPinsMap(outputPins);
var internalPinsMap = {};
var parts = ast.parts.map(function (part, idx) {
// Gate class (built-ins, or custom from HDL).
var PartGateClass = partsClasses[idx];
// Instance.
var partGateInstance = PartGateClass.defaultFromSpec({
// Part pins are manually updated for `tick/tock` events
// from the main parent gate.
manualClock: true
});
// Handle arguments.
part.arguments.forEach(function (partArg) {
handlePartArg(partArg, partGateInstance, PartGateClass, inputPinsMap, outputPinsMap, internalPins, internalPinsMap);
});
return partGateInstance;
});
return parts;
}
/**
* Returns a size of an internal pin based on
* the output value which is connected to this pin.
*/
function getInternalPinSize(outputArg) {
var internalPinSize = 1;
if (outputArg.size) {
internalPinSize = outputArg.size;
} else if (outputArg.range) {
internalPinSize = outputArg.to - outputArg.from + 1;
}
return internalPinSize;
}
// ----------------------------------------------------------------
// Handle arguments, and connect input/output pins
// to the main inputs, and internal pins.
//
function handlePartArg(partArg, partGateInstance, PartGateClass, inputPinsMap, outputPinsMap, internalPins, internalPinsMap) {
var name = partArg.name,
value = partArg.value;
var pin = partGateInstance.getPin(name.value);
var pinInfo = PartGateClass.getPinInfo(name.value);
// Constant values: And(a=true, b=false)
var constantValue = getConstantValue(value);
// Create new (internal) pin, which is not part of inputs/outputs.
var isInternalPin = !inputPinsMap.hasOwnProperty(value.value) && !outputPinsMap.hasOwnProperty(value.value) && constantValue === null;
if (isInternalPin && !internalPinsMap.hasOwnProperty(value.value)) {
var internalPin = new Pin({
name: value.value,
size: getInternalPinSize(name),
value: 0
});
internalPins.push(internalPinsMap[value.value] = internalPin);
}
// Set always fixed value.
if (constantValue !== null) {
pin.setValue(constantValue);
} else if (pinInfo.kind === 'input') {
// When main pins change, update all dependent inputs.
var sourcePin = inputPinsMap[value.value] || outputPinsMap[value.value] || internalPinsMap[value.value];
sourcePin.connectTo(pin, {
sourceSpec: value,
destinationSpec: name
});
} else if (pinInfo.kind === 'output') {
// When the output of the part pin changes,
// update outer pins: main out or internal pins.
var destPin = internalPinsMap[value.value] || outputPinsMap[value.value];
pin.connectTo(destPin, {
sourceSpec: name,
destinationSpec: value
});
}
}
module.exports = HDLClassFactory;