UNPKG

hdl-js

Version:

Hardware definition language (HDL) and Hardware simulator

413 lines (333 loc) 13.5 kB
/** * The MIT License (MIT) * Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com> */ 'use strict'; 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;