Skip to content

Instantly share code, notes, and snippets.

@pcornier
Last active January 11, 2023 13:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pcornier/1570cc742e8059cefa6887fc74b5353c to your computer and use it in GitHub Desktop.
Save pcornier/1570cc742e8059cefa6887fc74b5353c to your computer and use it in GitHub Desktop.
debug simple JS file with GTKWave
{
"dependencies": {
"escodegen": "^2.0.0",
"esprima": "^4.0.1",
"estraverse": "^5.3.0",
"minimist": "^1.2.7"
}
}
./vcd.js -s 500 test.js
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const inspector = require('inspector');
const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
const argv = require('minimist')(process.argv.slice(2));
const session = new inspector.Session();
// command line arguments & usage
if (!argv.s) usage();
if (argv._.length != 1) usage();
function usage() {
console.log('Usage: vcd.js -s <steps> <script.js>');
return process.exit(1);
}
let cycles = 0;
let maxCycles = argv.s || 100;
let symbols = {};
let vcd = [];
let symbolCount = 0;
// connect to the session and start Runtime
session.connect();
session.post('Runtime.enable');
// read script file
let scriptName = argv._[0];
let scriptPath = path.resolve(__dirname, scriptName);
let script = fs.readFileSync(scriptPath, 'utf8');
// parse the script and replace 'var' with 'let'
// this part could have an impact on script execution but it seems the only
// way(?) to expose 'var' declarations.
const ast = esprima.parse(script);
estraverse.replace(ast, {
enter: function(node) {
if (node.type === 'VariableDeclaration' && node.kind === 'var') {
node.kind = 'let';
}
}
});
script = escodegen.generate(ast);
// write the VCD file at the end of the processing
let writeVCD = function() {
// VCD header
fs.writeFileSync(`${scriptPath}.vcd`, '$timescale 1ns $end\n', 'utf8');
// Write declarations
Object.keys(symbols).forEach(name => {
let size = 64;
switch (symbols[name].type) {
case 'number': size = 64; break;
case 'string': size = 128*8; break;
case 'boolean': size = 1; break;
}
let type = symbols[name].type == 'string' ? 'reg' : 'wire';
fs.appendFileSync(`${scriptPath}.vcd`, `$var ${type} ${size} ${symbols[name].s} ${name} $end\n`, 'utf8');
});
fs.appendFileSync(`${scriptPath}.vcd`, '$enddefinitions $end\n', 'utf8');
// vardump
vcd.forEach((frame, t) => {
fs.appendFileSync(`${scriptPath}.vcd`, `#${t}\n`, 'utf8');
frame.forEach(v => {
let value = 0;
if (symbols[v[0]].type == 'number') {
value = v[1].toString(2);
}
else if (symbols[v[0]].type == 'string') {
if (typeof v[1] === symbols[v[0]].type) {
value = v[1]
.split('')
.reverse()
.map((c, i) => c.charCodeAt(0) << (i*8))
.reduce((a, c) => a | c, 0)
.toString(2);
}
}
else if (symbols[v[0]].type == 'boolean') {
value = v[1] ? 1 : 0;
}
fs.appendFileSync(`${scriptPath}.vcd`, `b${value} ${symbols[v[0]].s}\n`, 'utf8');
})
});
console.log(`Generated VCD file: ${scriptName}.vcd`);
}
// Add a symbol to the lib and assign a letter to it, for now it is limited to 256-33 symbols max
let addSymbol = function(name, type) {
let symbol = String.fromCharCode(33+symbolCount);
symbols[name] = { s: symbol, v: -194839894982, type: type };
symbolCount++;
console.log(`\nFound symbol "${name}" of type ${type}`);
}
// compile script
session.post('Runtime.compileScript', {
expression: `{${script}}`,
sourceURL: scriptPath,
persistScript: true
}, (err, res) => {
// script compiled, start debugger
session.post('Debugger.enable');
// add a breakpoint at line 0
session.post('Debugger.setBreakpoint', { location: { scriptId: res.scriptId, lineNumber: 0 } });
// breakpoint event
session.on('Debugger.paused', params => {
// paused
process.stdout.write(`.`);
// get callframe and scope id
const callFrame = params.params.callFrames[0];
const scopeId = callFrame.scopeChain[0].object.objectId;
// frame will store values for this execution cycle
let frame = [];
session.post('Runtime.getProperties', {
objectId: scopeId,
ownProperties: true,
accessorPropertiesOnly: false,
generatePreview: true
}, (err, res) => {
// loop each variable in the scope
res.result.forEach(obj => {
// what is the correct condition?
// add the symbol if it's a new one and value is defined
if (obj.value?.value && obj.value.value != undefined && obj.writable && !Object.keys(symbols).includes(obj.name)) {
addSymbol(obj.name, obj.value.type);
}
// save the value if it's in the list of 'watched' symbols
if (Object.keys(symbols).includes(obj.name)) {
let value = obj.value.value || 0;
// save only if value is different than the previous one
if (symbols[obj.name].v != value) {
frame.push([obj.name, value]);
symbols[obj.name].v = value;
}
}
});
// push frame to the list of frames
vcd.push(frame);
});
cycles++;
if (cycles < maxCycles) {
session.post('Debugger.stepOver');
}
else {
console.log(`\nDone after ${cycles} cycles`);
writeVCD();
}
});
session.post('Runtime.runScript', { scriptId: res.scriptId });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment