UNPKG

hdl-js

Version:

Hardware definition language (HDL) and Hardware simulator

553 lines (472 loc) 15.1 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 _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; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var fs = require('fs'); var HDLClassFactory = require('../HDLClassFactory'); var path = require('path'); var Pin = require('../Pin'); var scriptParser = require('./script-parser'); var _require = require('../Clock'), SystemClock = _require.SystemClock; var _require2 = require('../../../util/numbers'), toSignedString = _require2.toSignedString; var _require3 = require('../../../util/string-util'), centerString = _require3.centerString; /** * Number format. */ var formatRadix = { B: 2, X: 16, D: 10 }; /** * Evaluates a script code, testing gates logic. */ var ScriptInterpreter = function () { function ScriptInterpreter(_ref) { var script = _ref.script, file = _ref.file, workingDirectory = _ref.workingDirectory; _classCallCheck(this, ScriptInterpreter); /** * Working directory to load files. */ this._workingDirectory = workingDirectory; this._isVirtualDirectory = typeof workingDirectory === 'object'; /** * Script from a file. */ if (file) { this._script = fs.readFileSync(file, 'utf-8'); if (!this._workingDirectory) { this._workingDirectory = path.dirname(file); } } if (!this._workingDirectory) { this._workingDirectory = __dirname; } /** * Script source. */ if (script) { this._script = script; } if (this._isVirtualDirectory) { HDLClassFactory.setVirtualDirectory(this._workingDirectory); } /** * Parsed AST to evaluate. */ this._ast = scriptParser.parse(this._script); /** * Program counter (current command to eval). */ this._pc = 0; /** * Loaded gate instance. */ this._gate = null; /** * Output file. By default to the console. */ this._outputFile = 'console'; /** * Compare file. */ this._compareTo = null; this._compareToLines = null; this._actualLines = []; /** * Collected error list. */ this._errorList = []; /** * Format of the output columns. */ this._outputListMap = null; /** * Current container to evaluate commands. * Containainers are: Script, bodies of the while, and repeat loops. */ this._container = this._ast; // Reset System clock on each script run: SystemClock.reset(); } /** * Returns gate. */ _createClass(ScriptInterpreter, [{ key: 'getGate', value: function getGate() { return this._gate; } /** * Executes the full script. */ }, { key: 'exec', value: function exec() { this.eval(this._ast); return this; } /** * Executes next full step (until the `;` terminator). */ }, { key: 'nextStep', value: function nextStep() { var shouldBreak = false; while (this._hasMoreCommands(this._container)) { this.nextCommand(); if (shouldBreak) { break; } if (this._isLastCommandInStep()) { shouldBreak = true; } } return this; } /** * Executes next command within step (until `,` terminator). */ }, { key: 'nextCommand', value: function nextCommand() { this.eval(this._container.commands[this._pc++]); var allExecuted = this._container === this._ast && !this._hasMoreCommands(); if (allExecuted && this._errorList.length > 0) { throw new ScriptError({ errorList: this._errorList, compareTo: this._compareTo, header: this._compareToLines[0] }); } return this; } }, { key: 'eval', value: function _eval(node) { return node ? this[node.type](node) : null; } }, { key: 'Script', value: function Script(node) { this._container = node; this._pc = 0; while (this._hasMoreCommands()) { this.nextStep(); } } }, { key: '_hasMoreCommands', value: function _hasMoreCommands() { return this._pc < this._container.commands.length; } }, { key: '_isLastCommandInStep', value: function _isLastCommandInStep() { if (!this._hasMoreCommands()) { return true; } return this._container.commands[this._pc].terminator === ';'; } }, { key: 'ControllerCommand', value: function ControllerCommand(node) { switch (node.name) { case 'load': this._loadGate(node); break; case 'output-file': this._initOutputFile(node); break; case 'compare-to': this._createCompareTo(node); break; case 'output-list': this._createOutputListMap(node.arguments); break; case 'output': this._evalOutput(); break; case 'echo': console.info(node.arguments[0]); break; case 'clear-echo': // Unimplemented, ignore. break; case 'while': this._evalWhile(node); break; case 'repeat': this._evalRepeat(node); break; default: throw TypeError('Unrecognized controller command: "' + node.name + '".'); } } }, { key: 'SimulatorCommand', value: function SimulatorCommand(node) { switch (node.name) { case 'set': this._setPinValue(node.arguments[0], node.arguments[1]); break; case 'eval': this._gate.eval(); break; case 'tick': SystemClock.tick(); break; case 'tock': SystemClock.tock(); break; case 'ticktock': SystemClock.cycle(); break; default: throw TypeError('Unrecognized simulator command: "' + node.name + '".'); } } }, { key: '_loadGate', value: function _loadGate(node) { this._gate = HDLClassFactory.loadGate(node.arguments[0].replace('.hdl', ''), this._workingDirectory).defaultFromSpec(); } }, { key: '_createCompareTo', value: function _createCompareTo(node) { if (this._isVirtualDirectory) { this._compareTo = node.arguments[0]; } else { this._compareTo = this._workingDirectory + '/' + node.arguments[0]; } this._compareToLines = this._readFileContents(this._compareTo).split(/\r?\n/); } }, { key: '_initOutputFile', value: function _initOutputFile(node) { var outputFile = node.arguments[0]; if (outputFile === 'console') { this._outputFile = outputFile; return; } if (this._isVirtualDirectory) { this._outputFile = outputFile; this._workingDirectory[outputFile] = ''; return; } this._outputFile = this._workingDirectory + '/' + outputFile; fs.writeFileSync(this._outputFile, '', 'utf-8'); } }, { key: '_createOutputListMap', value: function _createOutputListMap(rawList) { var _this = this; this._outputListMap = {}; rawList.forEach(function (column, idx) { _this._outputListMap[column.column] = column; _this._outputListMap[column.column].index = idx; }); } }, { key: '_evalOutput', value: function _evalOutput() { this._printHeader(); var line = ['|']; for (var column in this._outputListMap) { var _outputListMap$column = this._outputListMap[column], right = _outputListMap$column.right, middle = _outputListMap$column.middle, left = _outputListMap$column.left, format = _outputListMap$column.format; var radix = formatRadix[format]; var actualPinColumn = column === 'time' ? Pin.CLOCK : column; var pinInfo = this._gate.getClass().getPinInfo(actualPinColumn); var value = this._gate.getPin(actualPinColumn).getValue(); var content = void 0; // Special case for `time` column, which is an alias // for the `$clock`. The `time` uses string representation, // e.g. '1' string for negative, and '1+' for positive, while // `$clock` uses -1 for negative, and +1 for positive. if (column === 'time') { content = value >= 0 ? value + '+' : '' + Math.abs(value); content = content.padEnd(middle, ' '); } else if (column === Pin.CLOCK) { content = toSignedString(value).padEnd(middle, ' '); } else if (format === 'S') { // Explicit string format. content = ('' + value).padEnd(middle, ' '); } else { content = (radix !== 10 ? value >>> 0 : value).toString(radix).padStart(radix !== 10 ? pinInfo.size : 0, '0').padStart(radix === 10 ? middle : 0, ' ').toUpperCase(); if (content.length > middle) { content = content.slice(-middle); } } line.push(' '.repeat(right), content, ' '.repeat(left), '|'); } this._printLine(line.join('')); } }, { key: '_centerHeaderColumn', value: function _centerHeaderColumn(columnInfo) { var right = columnInfo.right, middle = columnInfo.middle, left = columnInfo.left, column = columnInfo.column; var totalLength = right + middle + left; return centerString(column, totalLength); } }, { key: '_printHeader', value: function _printHeader() { // Header is printed only once. if (this._headerPrinted) { return; } var line = ['|']; for (var column in this._outputListMap) { var centerColumn = this._centerHeaderColumn(this._outputListMap[column]); line.push(centerColumn, '|'); } this._printLine(line.join('')); this._headerPrinted = true; } }, { key: '_printLine', value: function _printLine(line) { if (this._compareTo) { this._actualLines.push(line); var compareIdx = this._actualLines.length - 1; var expected = this._compareToLines[compareIdx]; var actual = this._actualLines[compareIdx]; if (expected !== actual) { this._errorList.push({ actual: actual, expected: expected, line: compareIdx + 1 }); } } if (this._outputFile === 'console') { console.info(line); return; } if (this._isVirtualDirectory) { this._workingDirectory[this._outputFile] += line + '\n'; return; } fs.appendFileSync(this._outputFile, line + '\n', 'utf-8'); } }, { key: '_readFileContents', value: function _readFileContents(file) { if (this._isVirtualDirectory) { return this._workingDirectory[file]; } return fs.readFileSync(file, 'utf-8'); } }, { key: '_evalWhile', value: function _evalWhile(node) { var savedContainer = this._container; var savedPC = this._pc; this._container = node; this._pc = 0; while (this.eval(node.condition)) { while (this._hasMoreCommands()) { this.nextStep(); } } this._container = savedContainer; this._pc = savedPC; } }, { key: 'RelationalExpression', value: function RelationalExpression(node) { var left = this._getPinValue(node.left); var right = this._getConditionValue(node.right); switch (node.operator) { case '=': return left === right; case '<>': return left !== right; case '<=': return left <= right; case '>=': return left >= right; case '<': return left < right; case '>': return left > right; default: throw TypeError('Unrecognized condition operator: "' + node.operator + '".'); } } }, { key: '_getPinValue', value: function _getPinValue(node) { var pin = this._gate.getPin(node.value); // a[1] if (node.hasOwnProperty('index')) { return pin.getValueAt(node.index); } // a return pin.getValue(); } }, { key: '_setPinValue', value: function _setPinValue(node, value) { var pin = this._gate.getPin(node.value); // a[1] if (node.hasOwnProperty('index')) { return pin.setValueAt(node.index, value.value); } // a return pin.setValue(value.value); } }, { key: '_getConditionValue', value: function _getConditionValue(node) { if (node.type === 'Name') { return this._getPinValue(node); } return node.value; } }, { key: '_evalRepeat', value: function _evalRepeat(node) { var savedContainer = this._container; var savedPC = this._pc; this._container = node; for (var i = 0; i < node.times.value; i++) { this._pc = 0; while (this._hasMoreCommands()) { this.nextStep(); } } this._container = savedContainer; this._pc = savedPC; } }]); return ScriptInterpreter; }(); var ScriptError = function (_Error) { _inherits(ScriptError, _Error); function ScriptError(errorData) { _classCallCheck(this, ScriptError); var _this2 = _possibleConstructorReturn(this, (ScriptError.__proto__ || Object.getPrototypeOf(ScriptError)).call(this, 'Script comparison error.')); _this2.errorData = errorData; return _this2; } return ScriptError; }(Error); module.exports = ScriptInterpreter;