454 lines
11 KiB
JavaScript
454 lines
11 KiB
JavaScript
/**
|
|
* A namespace.
|
|
* @namespace VCDParser
|
|
*/
|
|
|
|
const async = require('async');
|
|
const { extend } = require('./utils');
|
|
|
|
/**
|
|
* Parse VCD text content and generate a valid JSON representation.
|
|
* The function returns a promise unless a callback is provided.
|
|
* @name VCDParser~parse
|
|
* @function
|
|
* @param {string} content - The text content of the VCD file
|
|
* @param {VCDParser~Options} [opts = {}] - Optional configuration to customize the parsing process
|
|
* @param {VCDParser~ParseCallback} [cb = null] - Optional callback if you don't prefer to use promises
|
|
* @return {Promise<VCDParser~ParsedData>} Promise object that resolves with the parsed data
|
|
* @example
|
|
* const VCDParser = require('vcd-parser');
|
|
* VCDParser.parse(`
|
|
$date
|
|
Tue Feb 12 14:01:15 2019
|
|
$end
|
|
$version
|
|
Icarus Verilog
|
|
$end
|
|
$timescale
|
|
1ns
|
|
$end
|
|
$scope module test_tb $end
|
|
$var reg 1 ! clk $end
|
|
$var wire 1 " rst $end
|
|
$upscope $end
|
|
$enddefinitions $end
|
|
#0
|
|
$dumpvars
|
|
0"
|
|
0!
|
|
$end
|
|
#15
|
|
1"
|
|
#20
|
|
1!
|
|
#40
|
|
0!
|
|
#60
|
|
1!
|
|
#80
|
|
0!
|
|
#100
|
|
1!
|
|
#115
|
|
`).then(parsedData => {
|
|
* console.log(parsedData);
|
|
// {
|
|
// "date": "Tue Feb 12 14:01:15 2019",
|
|
// "version": "Icarus Verilog",
|
|
// "timescale": "1ns",
|
|
// "endtime": "115",
|
|
// "scale": "1ns",
|
|
// "signal": [
|
|
// {
|
|
// "type": "reg",
|
|
// "size": 1,
|
|
// "refName": "!",
|
|
// "signalName": "clk",
|
|
// "module": "test_tb",
|
|
// "name": "test_tb.clk",
|
|
// "wave": [
|
|
// [
|
|
// "0",
|
|
// "0"
|
|
// ],
|
|
// [
|
|
// "20",
|
|
// "1"
|
|
// ],
|
|
// [
|
|
// "40",
|
|
// "0"
|
|
// ],
|
|
// [
|
|
// "60",
|
|
// "1"
|
|
// ],
|
|
// [
|
|
// "80",
|
|
// "0"
|
|
// ],
|
|
// [
|
|
// "100",
|
|
// "1"
|
|
// ]
|
|
// ]
|
|
// },
|
|
// {
|
|
// "type": "wire",
|
|
// "size": 1,
|
|
// "refName": "\"",
|
|
// "signalName": "rst",
|
|
// "module": "test_tb",
|
|
// "name": "test_tb.rst",
|
|
// "wave": [
|
|
// [
|
|
// "0",
|
|
// "0"
|
|
// ],
|
|
// [
|
|
// "15",
|
|
// "1"
|
|
// ]
|
|
// ]
|
|
// }
|
|
// ]
|
|
// }
|
|
* }).catch(err => {
|
|
* console.error(err);
|
|
* }).
|
|
*/
|
|
const parse = (content, opts = {}, cb) => {
|
|
if (typeof opts === 'function') {
|
|
cb = opts;
|
|
opts = {};
|
|
}
|
|
const wrappedCallback = () => {
|
|
if (typeof cb !== 'function') {
|
|
return null;
|
|
}
|
|
return function() {
|
|
return cb(null, ...arguments);
|
|
};
|
|
};
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(function() {
|
|
process.nextTick(function() {
|
|
const blocks = content
|
|
.split(/\s*\$end\s/gm)
|
|
.map(line => line.replace(/[\n\t]/gm, ' '));
|
|
const States = {
|
|
Init: 0,
|
|
Meta: 1,
|
|
Def: 2,
|
|
Dump: 3
|
|
};
|
|
let modules = [];
|
|
const meta = {};
|
|
const signals = [];
|
|
const signalMap = {};
|
|
let lastIndex;
|
|
for (let i = 0; i < blocks.length; i++) {
|
|
const block = blocks[i].trim();
|
|
if (block === '') {
|
|
continue;
|
|
}
|
|
|
|
const scopeMatches = /\$(\w+?)\b/gm.exec(block);
|
|
if (scopeMatches) {
|
|
const scopeName = scopeMatches[1];
|
|
if (scopeName === 'scope') {
|
|
state = States.Def;
|
|
const scopeDefMatches = /\$(\w+)\s+([\s\S]+)\s+([\s\S]+)/gm.exec(
|
|
block
|
|
);
|
|
if (!scopeDefMatches) {
|
|
return reject({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const modName = scopeDefMatches[3];
|
|
modules.push(modName);
|
|
} else if (scopeName === 'enddefinitions') {
|
|
state = States.Dump;
|
|
lastIndex = i + 1;
|
|
break;
|
|
} else if (scopeName === 'upscope') {
|
|
modules.pop();
|
|
} else if (scopeName === 'var') {
|
|
const varDefMatches = /\$(\w+)\s+([\s\S]+?)\s+(\d+)\s+([\s\S]+?)\s+([\s\S]+)\s*/gm.exec(
|
|
block
|
|
);
|
|
const signalName = varDefMatches[5].replace(
|
|
/\s+/,
|
|
''
|
|
);
|
|
const refName = varDefMatches[4];
|
|
if (!signalMap[refName]) {
|
|
const signal = {
|
|
type: varDefMatches[2],
|
|
size: parseInt(varDefMatches[3]),
|
|
refName,
|
|
signalName,
|
|
module: modules[modules.length - 1] || '',
|
|
name: modules.concat(signalName).join('.'),
|
|
wave: []
|
|
};
|
|
signals.push(signal);
|
|
signalMap[refName] = signal;
|
|
}
|
|
} else {
|
|
const contentMatches = /\$(\w+)\b\s*([\s\S]+)?\s*/gm.exec(
|
|
block
|
|
);
|
|
if (contentMatches) {
|
|
meta[contentMatches[1]] = contentMatches[2];
|
|
}
|
|
}
|
|
} else {
|
|
return reject({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
}
|
|
if (!lastIndex) {
|
|
return reject({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
let currentTime = 0;
|
|
const rem = content.split(/\s*\$enddefinitions\s*/gm)[1];
|
|
if (!rem) {
|
|
return reject({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const lines = rem.split(/\s*\n\s*/gm);
|
|
async.eachSeries(
|
|
lines,
|
|
function(line, callback) {
|
|
(function(line) {
|
|
setTimeout(function() {
|
|
const block = line.trim();
|
|
if (block === '') {
|
|
return callback();
|
|
}
|
|
const timingMatches = /^#(\d+)$/gm.exec(block);
|
|
if (timingMatches) {
|
|
const time = parseInt(timingMatches[1]);
|
|
currentTime = time;
|
|
} else if (block === '$dumpvars') {
|
|
return callback();
|
|
} else if (block === '$end') {
|
|
return callback();
|
|
} else {
|
|
if (block.startsWith('x')) {
|
|
const refName = block.substr(1).trim();
|
|
if (!signalMap[refName]) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const wave = signalMap[refName].wave;
|
|
if (
|
|
!opts.compress ||
|
|
!wave.length ||
|
|
wave[wave.length - 1][1] !== 'x'
|
|
) {
|
|
signalMap[refName].wave.push([
|
|
currentTime.toString(),
|
|
'x'
|
|
]);
|
|
}
|
|
} else if (block.startsWith('b')) {
|
|
const matches = /b([01xz]+)\s+([\s\S]+)/gm.exec(
|
|
block
|
|
);
|
|
if (!matches) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const refName = matches[2];
|
|
if (!signalMap[refName]) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
let value = matches[1];
|
|
if (!opts.expandAmbigousBus) {
|
|
if (/z/gm.test(value)) {
|
|
value = 'z';
|
|
} else if (/x/gm.test(value)) {
|
|
value = 'x';
|
|
}
|
|
}
|
|
const wave = signalMap[refName].wave;
|
|
if (
|
|
!opts.compress ||
|
|
!wave.length ||
|
|
wave[wave.length - 1][1] !== value
|
|
) {
|
|
signalMap[refName].wave.push([
|
|
currentTime.toString(),
|
|
value
|
|
]);
|
|
}
|
|
} else if (block.startsWith('z')) {
|
|
const refName = block.substr(1).trim();
|
|
if (!signalMap[refName]) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const wave = signalMap[refName].wave;
|
|
if (
|
|
!opts.compress ||
|
|
!wave.length ||
|
|
wave[wave.length - 1][1] !== 'z'
|
|
) {
|
|
signalMap[refName].wave.push([
|
|
currentTime.toString(),
|
|
'z'
|
|
]);
|
|
}
|
|
} else if (/^[01]([\s\S]+)/gm.test(block)) {
|
|
const matches = /^([01])([\s\S]+)/gm.exec(
|
|
block
|
|
);
|
|
const refName = matches[2];
|
|
if (!signalMap[refName]) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const converted = parseInt(
|
|
matches[1],
|
|
10
|
|
).toString(2);
|
|
const wave = signalMap[refName].wave;
|
|
if (
|
|
!opts.compress ||
|
|
!wave.length ||
|
|
wave[wave.length - 1][1] !==
|
|
converted
|
|
) {
|
|
signalMap[refName].wave.push([
|
|
currentTime.toString(),
|
|
converted
|
|
]);
|
|
}
|
|
} else if (block.startsWith('r')) {
|
|
const matches = /r((\d+\.?\d*)|(nan)|(x+)|(z+))\s+([\s\S]+)/gm.exec(
|
|
block
|
|
);
|
|
if (!matches) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
let value;
|
|
if (
|
|
matches[1] === 'nan' ||
|
|
matches[1].charAt(0) === 'x'
|
|
) {
|
|
value = 'x';
|
|
} else if (
|
|
matches[1].charAt(0) === 'z'
|
|
) {
|
|
value = 'z';
|
|
} else {
|
|
value = parseFloat(matches[1]);
|
|
}
|
|
const refName = matches[6];
|
|
if (!signalMap[refName]) {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
const wave = signalMap[refName].wave;
|
|
if (
|
|
!opts.compress ||
|
|
!wave.length ||
|
|
wave[wave.length - 1][1] !==
|
|
converted
|
|
) {
|
|
signalMap[refName].wave.push([
|
|
currentTime.toString(),
|
|
isNaN(value)
|
|
? 'x'
|
|
: value.toString()
|
|
]);
|
|
}
|
|
} else {
|
|
return callback({
|
|
error: 'Invalid VCD data'
|
|
});
|
|
}
|
|
}
|
|
return callback();
|
|
}, 0);
|
|
})(line);
|
|
},
|
|
function(err) {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
meta.endtime = currentTime.toString();
|
|
meta.scale = meta.timescale;
|
|
return resolve(
|
|
extend({}, meta, {
|
|
signal: signals
|
|
})
|
|
);
|
|
}
|
|
);
|
|
});
|
|
}, 0);
|
|
})
|
|
.then(wrappedCallback())
|
|
.catch(cb);
|
|
};
|
|
|
|
/**
|
|
* The optional configuration for the VCD parser
|
|
* @typedef {Object} VCDParser~Options
|
|
* @property {boolean} compress - Compress the output wave by ignoring the unchanged values
|
|
* @property {boolean} expandAmbigousBus - If the bus has some ambigous value (z | x), it gets expanded to represent the whole bus signal
|
|
*/
|
|
|
|
/**
|
|
* The value of a signal at a specific point of time, represnted as a tuple [time, value]
|
|
* @typedef {Array<number>} VCDParser~SignalValue
|
|
* @property {number} 0 - The time of the event
|
|
* @property {number} 1 - The value of the signal at that event
|
|
*/
|
|
|
|
/**
|
|
* The object representing one signal data
|
|
* @typedef {Object} VCDParser~Signal
|
|
* @property {string} name - The full name of the signal
|
|
* @property {string} type - The type of the signal, e.g. wire, reg,..etc
|
|
* @property {number} size - The size/width of the signal in bits
|
|
* @property {string} refName - The reference for this signal used inside the VCD file
|
|
* @property {string} module - The name of the top module for which this signal belongs
|
|
* @property {Array<VCDParser~SignalValue>} wave - The values of the signal at different points of time
|
|
*/
|
|
|
|
/**
|
|
* The parsed VCD object generated by the parser
|
|
* @typedef {Object} VCDParser~ParsedData
|
|
* @property {string} [<"meta">] - The values of different initial meta-data, e.g. date, timescale..etc
|
|
* @property {string} endtime - The endtime of the simulation
|
|
* @property {string} scale - The time-scale unit of the simulation
|
|
* @property {Array<VCDParser~Signal>} signal - The signal values of the simulation
|
|
*/
|
|
|
|
/**
|
|
* The callback for the parsing function.
|
|
* @callback VCDParser~ParseCallback
|
|
* @param {error} err - The error generated while parsing
|
|
* @param {ParsedData} parsedJSON - The JSON document generated by the parser
|
|
*/
|
|
|
|
module.exports = { parse };
|