UNPKG

hdl-js

Version:

Hardware definition language (HDL) and Hardware simulator

764 lines (614 loc) 22 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; }; }(); function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 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 EventEmitter = require('events'); var Pin = require('./Pin'); var TablePrinter = require('../../table-printer'); var _require = require('./Clock'), SystemClock = _require.SystemClock; var _require2 = require('../../util/numbers'), int16 = _require2.int16, isNegativeZero = _require2.isNegativeZero, toSignedString = _require2.toSignedString; /** * Abstract gate class, base for `BuiltInGate`, and `CompositeGate`. * * Emits events for `eval`, `clockUp`, and `clockDown`. */ var Gate = function (_EventEmitter) { _inherits(Gate, _EventEmitter); /** * Creates a gate instance with the given name. */ function Gate(_ref) { var _ref$name = _ref.name, name = _ref$name === undefined ? null : _ref$name, inputPins = _ref.inputPins, outputPins = _ref.outputPins, _ref$manualClock = _ref.manualClock, manualClock = _ref$manualClock === undefined ? false : _ref$manualClock; _classCallCheck(this, Gate); // Infer name from the class if not passed explicitly. var _this = _possibleConstructorReturn(this, (Gate.__proto__ || Object.getPrototypeOf(Gate)).call(this)); if (!name) { name = _this.getClass().Spec.name; } _this._name = name; _this._inputPins = Gate.toPins(inputPins); _this._outputPins = Gate.toPins(outputPins); _this._buildNamesToPinsMap(); // Setup emitter hooks: _this._setupEventHooks(); // Extra user-initialization code: _this.init(); // Subscribe to the clock events for clocked gates, unless // `manualClock` is set (for part gates used in composite gates). if (_this.getClass().isClocked() && !manualClock) { SystemClock.on('tick', function () { return _this.tick(); }); SystemClock.on('tock', function () { return _this.tock(); }); SystemClock.on('change', function (value) { _this.getPin(Pin.CLOCK).setValue(value); }); } return _this; } /** * Any extra initialization a gate may provide. Called at construction * and reset signal. */ _createClass(Gate, [{ key: 'init', value: function init() { // Noop. return; } /** * Creates an default instance of this gate from the spec. */ }, { key: 'getName', /** * Returns the name of this gate. */ value: function getName() { return this._name; } /** * Returns input pins of this gate. */ }, { key: 'getInputPins', value: function getInputPins() { return this._inputPins; } /** * Returns output pins of this gate. */ }, { key: 'getOutputPins', value: function getOutputPins() { return this._outputPins; } /** * Returns a pin (input or output) by name. */ }, { key: 'getPin', value: function getPin(name) { if (!this._namesToPinsMap.hasOwnProperty(name)) { throw new Error('Pin "' + name + '" is not registered on "' + this._name + '" gate.'); } return this._namesToPinsMap[name]; } /** * Returns pin info from Spec. */ }, { key: 'setPinValues', /** * Sets values of the input/ouput pins. */ value: function setPinValues(values) { for (var pinName in values) { this.getPin(pinName).setValue(values[pinName]); } return this; } /** * Sets values of the input/ouput pins. */ }, { key: 'getPinValues', value: function getPinValues() { var data = {}; for (var pinName in this._namesToPinsMap) { data[pinName] = this.getPin(pinName).getValue(); } return data; } /** * Tests this gate on the input/output data. * * If only inputs are provided in the data, * evaluates the output. * * If both inputs, and outputs are provided, evaluates * the outputs, and also returns found conflicts if some * evaluated output doesn't equal to the provided. */ }, { key: 'execOnData', value: function execOnData(inputData) { var _this2 = this; var result = []; // Entries with conflicting data: {row, pins}. var conflicts = []; inputData.forEach(function (row, index) { // Always use all input pins: if some pin is not passed, it's set to 0. row = Object.assign({}, _this2._defaultInputValues, row); // Evaluate the row. _this2.setPinValues(row); // The -0 is a setup row, don't execute on it, // otherwise, emulate next clock half-cycle (tick or tock). if (_this2.getClass().isClocked() && !isNegativeZero(row[Pin.CLOCK])) { SystemClock.next(); } else { _this2.eval(); } var outputRow = {}; var conflictsForRow = {}; for (var pinName in _this2._namesToPinsMap) { var expectedValue = int16(row[pinName]); var actualValue = _this2.getPin(pinName).getValue(); outputRow[pinName] = actualValue; // If the (output) pin is provided, validate it. if (row.hasOwnProperty(pinName) && expectedValue !== actualValue) { conflictsForRow[pinName] = { expected: expectedValue, actual: actualValue }; } } if (Object.keys(conflictsForRow).length > 0) { conflicts.push({ row: index, pins: conflictsForRow }); } result.push(outputRow); }); return { result: result, conflicts: conflicts }; } /** * Prints truth table of this gate. * * If `transformValue` function is passed, it's called with * the current row, column, and value. * * formatRadix is: 2 (bin), 16 (hex), or 10 (dec). * formatStringLengh is the max string length of a number in this format. * * If `columns` whitelist is passed, only the columns from this list * are shown. */ }, { key: 'generateTruthTable', /** * Generates a (random) truth table for this gate. */ value: function generateTruthTable() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref2$enforceRandom = _ref2.enforceRandom, enforceRandom = _ref2$enforceRandom === undefined ? false : _ref2$enforceRandom; var GateClass = this.getClass(); var inputPins = GateClass.Spec.inputPins; var isSimple = inputPins.every(function (input) { return typeof input === 'string' || input.size === 1; }); var inputData = []; // For simple tables generate all permutations. if (isSimple && !enforceRandom) { // Number of rows. var n = Math.pow(2, inputPins.length); var _loop = function _loop(i) { var row = {}; // Use 2-radix to get a binary number, and get `0`s, and `1`s // for the table from it. i.toString(2).padStart(inputPins.length, '0').split('').forEach(function (bit, idx) { var key = typeof inputPins[idx] === 'string' ? inputPins[idx] : inputPins[idx].name; row[key] = Number(bit); }); inputData.push(row); }; for (var i = 0; i < n; i++) { _loop(i); } } else { var _loop2 = function _loop2(i) { var row = {}; inputPins.forEach(function (input) { var size = input.size || 1; var name = typeof input === 'string' ? input : input.name; row[name] = randomNumberInRange(0, Math.pow(2, size) - 1); }); inputData.push(row); }; // Else, generate random input numbers for 5 rows. for (var i = 0; i < 5; i++) { _loop2(i); } } var _execOnData = this.execOnData(inputData), result = _execOnData.result; return result; } /** * Creates a Pin instance from a spec, or propagates * if it's already a Pin instance. */ }, { key: '_buildNamesToPinsMap', /** * Builds a map from a pin name to the pin instance. */ value: function _buildNamesToPinsMap() { var _this3 = this; this._namesToPinsMap = {}; if (this.getClass().isClocked()) { this._namesToPinsMap[Pin.CLOCK] = new Pin({ name: Pin.CLOCK, value: SystemClock.getValue(), size: 16 }); } // All inputs are defaulted to 0. In the `execOnData`, if // some pin is not passed it's set to 0. this._defaultInputValues = {}; this._inputPins.forEach(function (pin) { _this3._namesToPinsMap[pin.getName()] = pin; _this3._defaultInputValues[pin.getName()] = 0; }); if (this._internalPins) { this._internalPins.forEach(function (pin) { return _this3._namesToPinsMap[pin.getName()] = pin; }); } this._outputPins.forEach(function (pin) { return _this3._namesToPinsMap[pin.getName()] = pin; }); } /** * Evaluates the output values of this gate. */ }, { key: 'eval', value: function _eval() { throw new Error('Abstract method `Gate#eval` should be implemented in a concrete class.'); } /** * Updates the internal state of a clocked gate according to the gate's functionality. * (outputs are not updated). */ }, { key: 'clockUp', value: function clockUp() { throw new Error('Abstract method `Gate#clockUp` should be implemented ' + 'in a concrete class.'); } /** * Updates the outputs of the gate according to its internal state. */ }, { key: 'clockDown', value: function clockDown() { throw new Error('Abstract method `Gate#clockDown` should be implemented ' + 'in a concrete class.'); } /** * Sets up hooks for `eval`, `clockUp`, and `clockDown` by * decorating the original methods with the emitter code. */ }, { key: '_setupEventHooks', value: function _setupEventHooks() { // `eval`: this._originalEval = this.eval; this.eval = this._evalEmit; // `clockUp`: this._originalClockUp = this.clockUp; this.clockUp = this._clockUpEmit; this._originalClockDown = this.clockDown; this.clockDown = this._clockDownEmit; } /** * Decorated `eval` with the emitter code. */ }, { key: '_evalEmit', value: function _evalEmit() { this._originalEval(); this.emit('eval'); } /** * Decorated `clockUp` with the emitter code. */ }, { key: '_clockUpEmit', value: function _clockUpEmit(clockValue) { this._originalClockUp(clockValue); this.emit('clockUp', clockValue); } /** * Decorated `clockDown` with the emitter code. */ }, { key: '_clockDownEmit', value: function _clockDownEmit(clockValue) { this._originalClockDown(clockValue); this.emit('clockDown', clockValue); } /** * Returns a class object of this gate instance. */ }, { key: 'getClass', value: function getClass() { return this.constructor; } /** * Whether this gate is clocked. */ }, { key: 'tick', /** * Clock rising edge. * * First computes the gate's output (from non-clocked information), and * then updates the internal state of the gate (which doesn't * affect the outputs). */ value: function tick() { this.eval(); this.clockUp(); return this; } /** * Clock falling edge. * * First updates the gate's outputs according to the internal state * of the gate, and then computes the outputs from non-clocked information. */ }, { key: 'tock', value: function tock() { this.clockDown(); this.eval(); return this; } /** * Full clock cycle. */ }, { key: 'clockCycle', value: function clockCycle() { SystemClock.cycle(); return this; } }], [{ key: 'defaultFromSpec', value: function defaultFromSpec() { var extraOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var _validateSpec = this.validateSpec(this.Spec), inputPins = _validateSpec.inputPins, outputPins = _validateSpec.outputPins; var toPin = function toPin(name) { return typeof name === 'string' ? new Pin({ name: name }) : new Pin({ name: name.name, size: name.size }); }; return new this(Object.assign({ inputPins: inputPins.map(toPin), outputPins: outputPins.map(toPin) }, extraOptions)); } }, { key: 'getPinInfo', value: function getPinInfo(name) { var _this4 = this; if (!this._pinsInfoMap) { this._pinsInfoMap = _defineProperty({}, Pin.CLOCK, { kind: 'special', name: Pin.CLOCK }); var spec = this.validateSpec(this.Spec); var inputPins = spec.inputPins, outputPins = spec.outputPins, internalPins = spec.internalPins; var processPins = function processPins(pins, kind) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = pins[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var pin = _step.value; var _name = typeof pin === 'string' ? pin : pin.name; _this4._pinsInfoMap[_name] = { kind: kind, name: _name }; if (typeof pin !== 'string' && pin.hasOwnProperty('size')) { _this4._pinsInfoMap[_name].size = pin.size; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } }; processPins(inputPins, 'input'); processPins(outputPins, 'output'); if (internalPins) { processPins(internalPins, 'internal'); } } if (!this._pinsInfoMap.hasOwnProperty(name)) { throw new Error('Pin "' + name + '" is not in Spec of "' + this.name + '" gate.'); } return this._pinsInfoMap[name]; } }, { key: 'printTruthTable', value: function printTruthTable(_ref3) { var _this5 = this; var table = _ref3.table, _ref3$columns = _ref3.columns, columns = _ref3$columns === undefined ? [] : _ref3$columns, _ref3$formatRadix = _ref3.formatRadix, formatRadix = _ref3$formatRadix === undefined ? 2 : _ref3$formatRadix, _ref3$formatStringLen = _ref3.formatStringLengh, formatStringLengh = _ref3$formatStringLen === undefined ? 16 : _ref3$formatStringLen, _ref3$transformValue = _ref3.transformValue, transformValue = _ref3$transformValue === undefined ? null : _ref3$transformValue; var spec = this.validateSpec(this.Spec); var inputPins = spec.inputPins, outputPins = spec.outputPins, _spec$internalPins = spec.internalPins, internalPins = _spec$internalPins === undefined ? [] : _spec$internalPins; var columnsMap = {}; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = columns[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var column = _step2.value; columnsMap[column] = true; } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } var toHeaderColumn = function toHeaderColumn(name) { return { content: Pin.toFullName(name), hAlign: 'center' }; }; var clockHeader = this.isClocked() ? [Pin.CLOCK] : []; var whitelistHeader = function whitelistHeader(list) { // All columns. if (columns.length === 0) { return list; } // Only whitelist. return list.filter(function (pin) { return columnsMap.hasOwnProperty(pin.name || pin); }); }; var whitelistColumns = function whitelistColumns(row) { // All columns. if (columns.length === 0) { return row; } // Only whitelist. var whitelistRow = {}; for (var _column in row) { if (columnsMap.hasOwnProperty(_column)) { whitelistRow[_column] = row[_column]; } } return whitelistRow; }; var printer = new TablePrinter({ head: [].concat(_toConsumableArray(whitelistHeader(clockHeader)), _toConsumableArray(whitelistHeader(inputPins).map(toHeaderColumn)), _toConsumableArray(whitelistHeader(internalPins).map(toHeaderColumn)), _toConsumableArray(whitelistHeader(outputPins).map(toHeaderColumn))) }); table.forEach(function (row, index) { row = whitelistColumns(row); var tableRow = Object.keys(row).map(function (name) { var pinInfo = _this5.getPinInfo(name); var content = void 0; // Special pin ($clock, etc). if (pinInfo.kind === 'special') { content = toSignedString(row[name]); } else { // Normal pin. content = (formatRadix !== 10 ? row[name] >>> 0 : row[name]).toString(formatRadix).padStart(formatRadix !== 10 ? pinInfo.size : 0, '0').toUpperCase(); if (content.length > formatStringLengh) { content = content.slice(-formatStringLengh); } if (transformValue) { content = transformValue(content, index, name); } } return { content: content, hAlign: 'center' }; }); printer.push(tableRow); }); console.info(printer.toString()); console.info(''); } }, { key: 'validateSpec', value: function validateSpec(spec) { var _this6 = this; var specProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['name', 'inputPins', 'outputPins']; if (!spec) { throw new Error('All gates should implement "Spec" property.'); } specProps.forEach(function (prop) { if (!spec.hasOwnProperty(prop)) { throw new Error('"' + _this6.name + '" gate: "Spec" should impelment' + ('all properties: ' + specProps.join(', ') + '.')); } }); return spec; } }, { key: 'toPins', value: function toPins(pinSpecs) { return pinSpecs.map(function (pin) { if (pin instanceof Pin) { return pin; } var spec = typeof pin === 'string' ? { name: pin } : pin; return new Pin(spec); }); } }, { key: 'isClocked', value: function isClocked() { throw new Error('Abstract static method `Gate.isClocked` should be implemented ' + 'in a concrete class.'); } }]); return Gate; }(EventEmitter); /** * Returns a random integer number in range. */ function randomNumberInRange(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } module.exports = Gate;