hdl-js
Version:
Hardware definition language (HDL) and Hardware simulator
360 lines (287 loc) • 9.97 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 _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;