UNPKG

hdl-js

Version:

Hardware definition language (HDL) and Hardware simulator

360 lines (287 loc) 9.97 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 _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 _require = require('../../util/numbers'), getBitAt = _require.getBitAt, getBitRange = _require.getBitRange, int16 = _require.int16, setBitAt = _require.setBitAt, setBitRange = _require.setBitRange; /** * Maximum word size. */ var WORD_SIZE = 16; /** * Represents a pin (node) in a gate. * * If `size` is 1 (default), it's a single pin ("wire"), otherwise, * it's a "bus" (set of pins/wires). * * Encoded as a simple number with bitwise operations for needed bits. * * Emits 'change' event on `setValue`. */ var Pin = function (_EventEmitter) { _inherits(Pin, _EventEmitter); function Pin(_ref) { var name = _ref.name, _ref$size = _ref.size, size = _ref$size === undefined ? 1 : _ref$size, _ref$value = _ref.value, value = _ref$value === undefined ? 0 : _ref$value; _classCallCheck(this, Pin); var _this = _possibleConstructorReturn(this, (Pin.__proto__ || Object.getPrototypeOf(Pin)).call(this)); _this._name = name; if (size < 1 || size > WORD_SIZE) { throw new Error('Invalid "size" for ' + name + ' pin, should be ' + ('in 1-' + WORD_SIZE + ' range.')); } _this._size = size; _this._maxAllowed = Math.pow(2, _this._size) - 1; _this.setValue(value); // There might be more than 11 pins (default in Node). _this.setMaxListeners(Infinity); // The pins which listen to 'change' event of this pin. _this._listeningPinsMap = new Map(); // The pin which is connected to this pin, and provides source value. _this._sourcePin = null; return _this; } /** * Returns name of this pin. */ _createClass(Pin, [{ key: 'getName', value: function getName() { return this._name; } /** * Returns size of this bus. */ }, { key: 'getSize', value: function getSize() { return this._size; } /** * Sets the value for this pin/bus. */ }, { key: 'setValue', value: function setValue(value) { var oldValue = this._value; if (typeof value === 'string') { value = Number.parseInt(value, 2); } if (value > this._maxAllowed) { throw new TypeError('Pin "' + this.getName() + '": value ' + value + ' doesn\'t match pin\'s width. ' + ('Max allowed is ' + this._maxAllowed + ' (size ' + this._size + ').')); } this._value = int16(value); this.emit('change', this._value, oldValue); } /** * Returns value of this pin bus. */ }, { key: 'getValue', value: function getValue() { return this._value; } /** * Updates the value of a particular bit in this bus. */ }, { key: 'setValueAt', value: function setValueAt(index, value) { this._checkIndex(index); var oldValue = this._value; // Always adjust to int16 on individual bits set this._value = int16(setBitAt(this._value, index, value)); this.emit('change', this._value, oldValue, index); } /** * Returns the value of a particular bit of this bus. */ }, { key: 'getValueAt', value: function getValueAt(index) { this._checkIndex(index); return getBitAt(this._value, index); } /** * Returns range (sub-bus) of this bus. */ }, { key: 'getRange', value: function getRange(from, to) { this._checkIndex(from); this._checkIndex(to); return getBitRange(this._value, from, to); } /** * Sets a value of a range. * * Value: 0b1010 * Range: 0b101 * From: 0 * To: 2 * * Result: 0b1101 */ }, { key: 'setRange', value: function setRange(from, to, range) { this._checkIndex(from); this._checkIndex(to); var oldValue = this._value; this._value = setBitRange(this._value, from, to, range); this.emit('change', this._value, oldValue, from, to); } /** * Connects this pin to another one. The other pin * then listens to the 'change' event of this pin. * * If the specs are passed, the values are updated according * to these specs. E.g. {index: 3} spec updates a[3] bit, * and {range: {from: 1, to: 3}} updates a[1..3]. */ }, { key: 'connectTo', value: function connectTo(pin) { var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref2$sourceSpec = _ref2.sourceSpec, sourceSpec = _ref2$sourceSpec === undefined ? {} : _ref2$sourceSpec, _ref2$destinationSpec = _ref2.destinationSpec, destinationSpec = _ref2$destinationSpec === undefined ? {} : _ref2$destinationSpec; if (this._listeningPinsMap.has(pin)) { return; } var thisPinValueGetter = createPinValueGetter(this, sourceSpec); var pinValueSetter = createPinValueSetter(pin, destinationSpec); var listener = function listener() { return pinValueSetter(thisPinValueGetter()); }; var connectInfo = { listener: listener, sourceSpec: sourceSpec, destinationSpec: destinationSpec }; this._listeningPinsMap.set(pin, connectInfo); pin._sourcePin = this; this.on('change', listener); return this; } /** * Unplugs this pin from other pin. */ }, { key: 'disconnectFrom', value: function disconnectFrom(pin) { if (!this._listeningPinsMap.has(pin)) { return; } var _listeningPinsMap$get = this._listeningPinsMap.get(pin), listener = _listeningPinsMap$get.listener; this._listeningPinsMap.delete(pin); pin._sourcePin = null; this.removeListener('change', listener); return this; } /** * Returns listening pins map. The key is a pin, the * value is a ConnectionInfo, which includes the listener * function, and connection properties (index, range, etc). */ }, { key: 'getListeningPinsMap', value: function getListeningPinsMap() { return this._listeningPinsMap; } /** * Returns the pin which is a source value provider * for this pin. Usually the source is set via `connectTo` * method. */ }, { key: 'getSourcePin', value: function getSourcePin() { return this._sourcePin; } /** * Builds a full name of a pin or pin bus: * * 'a' -> 'a' * {name: 'a'} -> 'a' * {name: 'a', size: 1} -> 'a' * {name: 'a', size: 16} -> 'a[16]' */ }, { key: '_checkIndex', /** * Checks the bounds of the index. */ value: function _checkIndex(index) { if (index < 0 || index > this._size - 1) { throw new TypeError('Pin "' + this.getName() + '": out of bounds index ' + index + ', ' + ('while the size is ' + this._size + '.')); } } }], [{ key: 'toFullName', value: function toFullName(name) { // Simple string name from Spec. if (typeof name === 'string') { return name; } return name.size > 1 ? name.name + '[' + name.size + ']' : name.name; } }]); return Pin; }(EventEmitter); // ---------------------------------------------------------------- // Updates pin value according to spec: full, index or range. // function createPinValueSetter(pin, spec) { if (spec.hasOwnProperty('index')) { return function (value) { return pin.setValueAt(spec.index, value); }; } else if (spec.range) { return function (value) { return pin.setRange(spec.range.from, spec.range.to, value); }; } else { return function (value) { return pin.setValue(value); }; } } // ---------------------------------------------------------------- // Extracts pin value according to spec: full, index or range. // function createPinValueGetter(pin, spec) { if (spec.hasOwnProperty('index')) { return function () { return pin.getValueAt(spec.index); }; } else if (spec.range) { return function () { return pin.getRange(spec.range.from, spec.range.to); }; } return function () { return pin.getValue(); }; } /** * Special $clock "pin". Used to count clock cycles. * Usually contains rising: +0, +1, +2, ..., and falling * -0, -1, -2, ... edge values. */ Pin.CLOCK = '$clock'; module.exports = Pin;