UNPKG

hdl-js

Version:

Hardware definition language (HDL) and Hardware simulator

478 lines (410 loc) 12.4 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"); } } 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; /** * 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, _ref$workingDirectory = _ref.workingDirectory, workingDirectory = _ref$workingDirectory === undefined ? __dirname : _ref$workingDirectory; _classCallCheck(this, ScriptInterpreter); /** * Script from a file. */ if (file) { this._script = fs.readFileSync(file, 'utf-8'); this._workingDirectory = path.dirname(file); } /** * Script source. */ if (script) { this._script = script; } /** * Working directory to load files. */ if (workingDirectory) { this._workingDirectory = workingDirectory; } /** * Whether the working directory is virtual. */ this._isVirtualDirectory = typeof workingDirectory === 'object'; 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; /** * 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; } /** * 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++]); 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._gate = HDLClassFactory.loadGate(node.arguments[0].replace('.hdl', '')).defaultFromSpec(); break; case 'output-file': this._initOutputFile(node); break; case 'compare-to': this._compareTo = node.arguments[0]; 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: '_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(); } 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; if (column.length < totalLength) { var len = totalLength - column.length; var remain = len % 2 == 0 ? '' : ' '; var pads = ' '.repeat(parseInt(len / 2)); return pads + column + pads + remain; } return column; } }, { 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._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: '_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.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.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; }(); module.exports = ScriptInterpreter;