hdl-js
Version:
Hardware definition language (HDL) and Hardware simulator
764 lines (614 loc) • 22 kB
JavaScript
/**
* 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;