CircuitOnline/public/js/verilog.js

506 lines
18 KiB
JavaScript

/*
# Primary Developers
1) James H-J Yeh, Ph.D.
2) Satvik Ramaprasad
refer verilog_documentation.md
*/
function generateVerilog() {
var data = verilog.exportVerilog();
console.log(data);
download(projectName + ".v", data);
}
verilog = {
// Entry point to verilog generation
// scope = undefined means export all circuits
exportVerilog:function(scope = undefined){
var dependencyList = {};
// Reset Verilog Element State
for (var i = 0; i < circuitElementList.length; i++) {
if (window[circuitElementList[i]].resetVerilog) {
window[circuitElementList[i]].resetVerilog();
}
}
// List of devices under test for which testbench needs to be created
var DUTs = [];
var SubCircuitIds = new Set();
// Generate SubCircuit Dependency Graph
for (id in scopeList) {
dependencyList[id] = scopeList[id].getDependencies();
for (var i = 0; i < scopeList[id].SubCircuit.length; i++) {
SubCircuitIds.add(scopeList[id].SubCircuit[i].id);
}
}
for(id in scopeList) {
if(!SubCircuitIds.has(id))
DUTs.push(scopeList[id]);
}
// DFS on SubCircuit Dependency Graph
var visited = {};
var elementTypesUsed = {};
var output = "";
if(scope) {
// generate verilog only for scope
output += this.exportVerilogScope(scope.id,visited,dependencyList, elementTypesUsed);
}
else {
// generate verilog for everything
for (id in scopeList) {
output += this.exportVerilogScope(id,visited,dependencyList, elementTypesUsed);
}
}
// Add Circuit Element - Module Specific Verilog Code
for(var element in elementTypesUsed) {
// If element has custom verilog
if (window[element].moduleVerilog) {
output += window[element].moduleVerilog();
}
}
var report = this.generateReport(elementTypesUsed) + "\n";
var testbench = this.generateTestBenchCode(DUTs);
return report + testbench + output;
},
generateReport: function(elementTypesUsed) {
var output = "";
output += "/**\n"
output += " * This is an autogenerated netlist code from CircuitVerse. Verilog Code can be\n"
output += " * tested on https://www.edaplayground.com/ using Icarus Verilog 0.9.7. This is an\n"
output += " * experimental module and some manual changes make need to be done in order for\n"
output += " * this to work.\n"
output += " *\n"
output += " * If you have any ideas/suggestions or bug fixes, raise an issue\n"
output += " * on https://github.com/CircuitVerse/CircuitVerse/issues/new/choose\n"
output += " */\n"
output += "\n";
output += '/*\n';
output += sp(1) + "Element Usage Report\n";
for(var elem in elementTypesUsed) {
if(elem == "Node")continue;
output += `${sp(2)}${elem} - ${elementTypesUsed[elem]} times\n`;
}
output += '*/\n';
output += "\n";
var instructions = "";
output += '/*\n';
output += sp(1) + "Usage Instructions and Tips\n";
instructions += sp(2) + "Labels - Ensure unique label names and avoid using verilog keywords\n";
instructions += sp(2) + "Warnings - Connect all optional inputs to remove warnings\n";
for(var elem in elementTypesUsed) {
// If element has custom instructions
if (window[elem].verilogInstructions) {
instructions += indent(2, window[elem].verilogInstructions());
}
}
output += instructions
output += '*/\n';
return output;
},
generateTestBenchCode: function(DUTs) {
if(DUTs.length == 0)return "";
var output = "// Sample Testbench Code - Uncomment to use\n";
output += "\n/*\n";
output += "module TestBench();\n";
var registers = {};
var wires = {};
for(var i = 1; i <= 32; i++)
registers[i] = new Set();
for(var i = 1; i <= 32; i++)
wires[i] = new Set();
var clocks = new Set();
var inputs = new Set();
var outputs = new Set();
var deviceInstantiations = "";
for(var i = 0; i < DUTs.length; i++) {
var DUT = DUTs[i];
for(var j = 0;j < DUT.Input.length; j++){
var inp = DUT.Input[j];
registers[inp.bitWidth].add(inp.label);
inputs.add(inp.label);
}
for(var j = 0;j < DUT.Output.length; j++){
var out = DUT.Output[j];
wires[out.bitWidth].add(out.label);
outputs.add(out.label);
}
for(var j=0;j<DUT.Clock.length;j++){
var inp = DUT.Clock[j];
registers[1].add(inp.label);
clocks.add(inp.label);
}
var circuitName = this.sanitizeLabel(DUT.name);
var dutHeader = this.generateHeaderHelper(DUT);
deviceInstantiations += `${sp(1)}${circuitName} DUT${i}${dutHeader}\n`;
}
output += "\n"
// Generate Reg Initialization Code
for (var bitWidth = 1; bitWidth<= 32; bitWidth++){
if(registers[bitWidth].size == 0)
continue;
var regArray = [...registers[bitWidth]];
if(bitWidth == 1)
output += `${sp(1)}reg ${regArray.join(", ")};\n`;
else
output += `${sp(1)}reg [${bitWidth-1}:0] ${regArray.join(", ")};\n`;
}
output += "\n"
// Generate Wire Initialization Code
for (var bitWidth = 1; bitWidth<= 32; bitWidth++){
if(wires[bitWidth].size == 0)
continue;
var wireArray = [...wires[bitWidth]];
if(bitWidth == 1)
output += `${sp(1)}wire ${wireArray.join(", ")};\n`;
else
output += `${sp(1)}wire [${bitWidth-1}:0] ${wireArray.join(", ")};\n`;
}
output += "\n"
output += deviceInstantiations;
if(clocks.size) {
output += `${sp(1)}always begin\n`;
output += `${sp(2)}#10\n`;
for(var clk of clocks)
output += `${sp(2)}${clk} = 0;\n`;
output += `${sp(2)}#10\n`;
for(var clk of clocks)
output += `${sp(2)}${clk} = 1;\n`;
output += `${sp(1)}end\n`;
output += '\n';
}
output += `${sp(1)}initial begin\n`;
// Reset inputs to 0
for(var inp of inputs){
output += `${sp(2)}${inp} = 0;\n`;
}
output += '\n';
output += `${sp(2)}#15\n`;
for(var out of outputs) {
output += `${sp(2)}$display("${out} = %b", ${out});\n`;
}
output += '\n';
output += `${sp(2)}#10\n`;
for(var out of outputs) {
output += `${sp(2)}$display("${out} = %b", ${out});\n`;
}
output += '\n';
output += `${sp(2)}$finish;\n\n`;
output += `${sp(1)}end\n`;
output += "endmodule\n";
output += "\n*/\n";
return output;
},
// Recursive DFS function
exportVerilogScope: function(id,visited,dependencyList, elementTypesUsed){
// Already Visited
if (visited[id]) return "";
// Mark as Visited
visited[id] = true;
var output = "";
// DFS on dependencies
for (var i = 0; i < dependencyList[id].length; i++)
output += this.exportVerilogScope(dependencyList[id][i],visited,dependencyList, elementTypesUsed)+"\n";
var scope = scopeList[id];
// Initialize labels for all elements
this.resetLabels(scope);
this.setLabels(scope);
output += this.generateHeader(scope);
output += this.generateOutputList(scope); // generate output first to be consistent
output += this.generateInputList(scope);
// Note: processGraph function populates scope.verilogWireList
var res = this.processGraph(scope, elementTypesUsed);
// Generate Wire Initialization Code
for (var bitWidth = 1; bitWidth<= 32; bitWidth++){
var wireList = scope.verilogWireList[bitWidth];
// Hack for splitter
wireList = wireList.filter(x => !x.includes("["))
if(wireList.length == 0)
continue;
if(bitWidth == 1)
output += " wire " + wireList.join(", ") + ";\n";
else
output += " wire [" +(bitWidth-1)+":0] " + wireList.join(", ") + ";\n";
}
// Append Wire connections and module instantiations
output += res;
// Append footer
output += "endmodule\n";
return output;
},
// Performs DFS on the graph and generates netlist of wires and connections
processGraph: function(scope, elementTypesUsed){
// Initializations
var res = "";
scope.stack = [];
scope.verilogWireList = [];
for(var i = 0; i <= 32; i++)
scope.verilogWireList.push(new Array());
var verilogResolvedSet = new Set();
// Start DFS from inputs
for (var i = 0; i < inputList.length; i++) {
for (var j = 0; j < scope[inputList[i]].length; j++) {
scope.stack.push(scope[inputList[i]][j]);
}
}
// Iterative DFS on circuit graph
while (scope.stack.length) {
if (errorDetected) return;
var elem = scope.stack.pop();
if(verilogResolvedSet.has(elem))
continue;
// Process verilog creates variable names and adds elements to DFS stack
elem.processVerilog();
// Record usage of element type
if(elem.objectType != "Node") {
if(elementTypesUsed[elem.objectType])elementTypesUsed[elem.objectType]++;
else elementTypesUsed[elem.objectType] = 1;
}
if(elem.objectType!="Node" && elem.objectType!="Input" && elem.objectType!="Clock") {
verilogResolvedSet.add(elem);
}
}
// Generate connection verilog code and module instantiations
for(var elem of verilogResolvedSet) {
res += " " + elem.generateVerilog() + "\n";
}
return res;
},
resetLabels: function(scope){
for(var i=0;i<scope.allNodes.length;i++){
scope.allNodes[i].verilogLabel="";
}
},
// Sets labels for all Circuit Elements elements
setLabels: function(scope=globalScope){
/**
* Sets a name for each element. If element is already labeled,
* the element is used directly, otherwise an automated label is provided
* sanitizeLabel is a helper function to escape white spaces
*/
for(var i=0;i<scope.Input.length;i++){
if(scope.Input[i].label=="")
scope.Input[i].label="inp_"+i;
else
scope.Input[i].label=this.sanitizeLabel(scope.Input[i].label)
// copy label to node
scope.Input[i].output1.verilogLabel = scope.Input[i].label;
}
for(var i=0;i<scope.ConstantVal.length;i++){
if(scope.ConstantVal[i].label=="")
scope.ConstantVal[i].label="const_"+i;
else
scope.ConstantVal[i].label=this.sanitizeLabel(scope.ConstantVal[i].label)
// copy label to node
scope.ConstantVal[i].output1.verilogLabel=scope.ConstantVal[i].label;
}
// copy label to clock
for(var i = 0; i < scope.Clock.length; i++) {
if (scope.Clock[i].label == "")
scope.Clock[i].label = "clk_"+i;
else
scope.Clock[i].label=this.sanitizeLabel(scope.Clock[i].label);
scope.Clock[i].output1.verilogLabel = scope.Clock[i].label;
}
for(var i=0;i<scope.Output.length;i++){
if(scope.Output[i].label=="")
scope.Output[i].label="out_"+i;
else
scope.Output[i].label=this.sanitizeLabel(scope.Output[i].label)
}
for(var i=0;i<scope.SubCircuit.length;i++){
if(scope.SubCircuit[i].label=="")
scope.SubCircuit[i].label=scope.SubCircuit[i].data.name+"_"+i;
else
scope.SubCircuit[i].label=this.sanitizeLabel(scope.SubCircuit[i].label)
}
for(var i=0;i<moduleList.length;i++){
var m = moduleList[i];
for(var j=0;j<scope[m].length;j++){
scope[m][j].verilogLabel = this.sanitizeLabel(scope[m][j].label) || (scope[m][j].verilogName()+"_"+j);
}
}
},
generateHeader:function(scope=globalScope){
// Example: module HalfAdder (a,b,s,c);
var res="\nmodule " + this.sanitizeLabel(scope.name);
res += this.generateHeaderHelper(scope);
return res;
},
generateHeaderHelper:function(scope=globalScope) {
// Example: (a,b,s,c);
var res="(";
var pins = [];
for(var i=0;i<scope.Output.length;i++){
pins.push(scope.Output[i].label);
}
for(var i=0;i<scope.Clock.length;i++){
pins.push(scope.Clock[i].label);
}
for(var i=0;i<scope.Input.length;i++){
pins.push(scope.Input[i].label);
}
res += pins.join(", ");
res += ");\n";
return res;
},
generateInputList:function(scope=globalScope){
var inputs={}
for(var i = 1; i <= 32; i++)
inputs[i] = [];
for(var i=0;i<scope.Input.length;i++){
inputs[scope.Input[i].bitWidth].push(scope.Input[i].label);
}
for(var i=0;i<scope.Clock.length;i++){
inputs[scope.Clock[i].bitWidth].push(scope.Clock[i].label);
}
var res="";
for (bitWidth in inputs){
if(inputs[bitWidth].length == 0) continue;
if(bitWidth==1)
res+=" input "+ inputs[1].join(", ") + ";\n";
else
res+=" input ["+(bitWidth-1)+":0] "+ inputs[bitWidth].join(", ") + ";\n";
}
return res;
},
generateOutputList:function(scope=globalScope){
// Example 1: output s,cout;
var outputs={}
for(var i=0;i<scope.Output.length;i++){
if(outputs[scope.Output[i].bitWidth])
outputs[scope.Output[i].bitWidth].push(scope.Output[i].label);
else
outputs[scope.Output[i].bitWidth] = [scope.Output[i].label];
}
var res="";
for (bitWidth in outputs){
if(bitWidth==1)
res+=" output "+ outputs[1].join(", ") + ";\n";
else
res+=" output ["+(bitWidth-1)+":0] "+ outputs[bitWidth].join(", ") + ";\n";
}
return res;
},
sanitizeLabel: function(name){
// return name.replace(/ Inverse/g, "_inv").replace(/ /g , "_");
var temp = name;
//if there is a space anywhere but the last place
//replace spaces by "_"
//last space is required for escaped id
if (temp.search(/ /g) < temp.length-1 && temp.search(/ /g) >= 0) {
temp = temp.replace(/ Inverse/g, "_inv");
temp = temp.replace(/ /g , "_");
}
//if first character is not \ already
if (temp.substring(0,1).search(/\\/g) < 0) {
//if there are non-alphanum_ character, or first character is num, add \
if (temp.search(/[\W]/g) > -1 || temp.substring(0,1).search(/[0-9]/g) > -1)
temp = "\\" + temp + " ";
}
return temp;
},
/*
sanitizeLabel: function(name){
// Replace spaces by "_"
name = name.replace(/ /g , "_");
// Replace Hyphens by "_"
name = name.replace(/-/g , "_");
// Replace Colons by "_"
name = name.replace(/:/g , "_");
// replace ~ with inv_
name = name.replace(/~/g , "inv_");
// Shorten Inverse to inv
name = name.replace(/Inverse/g , "inv");
// If first character is a number
if(name.substring(0, 1).search(/[0-9]/g) > -1) {
name = "w_" + name;
}
// if first character is not \ already
if (name[0] != '\\') {
//if there are non-alphanum_ character, add \
if (name.search(/[\W]/g) > -1)
name = "\\" + name;
}
return name;
},
*/
generateNodeName: function(node, currentCount, totalCount) {
if(node.verilogLabel) return node.verilogLabel;
var parentVerilogLabel = node.parent.verilogLabel;
var nodeName;
if(node.label) {
nodeName = verilog.sanitizeLabel(node.label);
}
else {
nodeName = (totalCount > 1) ? "out_" + currentCount: "out";
}
if (parentVerilogLabel.substring(0,1).search(/\\/g) < 0)
return (parentVerilogLabel) + "_" + nodeName;
else
return (parentVerilogLabel.substring(0,parentVerilogLabel.length-1)) + "_" + nodeName + " ";
}
}
/*
Helper function to generate spaces for indentation
*/
function sp(indentation) {
return " ".repeat(indentation * 2);
}
/*
Helper function to indent paragraph
*/
function indent(indentation, string) {
var result = string.split('\n');
if(result[result.length - 1] == '') {
result.pop();
result = result.map(x => sp(indentation) + x).join("\n");
result += '\n';
return result;
}
return result.map(x => sp(indentation) + x).join("\n");
}