juanmard/icestudio

View on GitHub
services/compiler.js

Summary

Maintainability
F
2 wks
Test Coverage
angular
  .module('icestudio')
  .service('compiler', function (common, utils, _package) {
    'use strict';

    this.generate = function (target, project, opt) {
      var content = '';
      var files = [];
      switch (target) {
        case 'verilog':
          content += header('//', opt);
          content += '`default_nettype none\n\n';
          content += verilogCompiler('main', project, opt);
          files.push({
            name: 'main.v',
            content: content,
          });
          break;
        case 'pcf':
          content += header('#', opt);
          content += pcfCompiler(project, opt);
          files.push({
            name: 'main.pcf',
            content: content,
          });
          break;
        case 'lpf':
          content += header('#', opt);
          content += lpfCompiler(project, opt);
          files.push({
            name: 'main.lpf',
            content: content,
          });
          break;

        case 'list':
          files = listCompiler(project);
          break;
        case 'testbench':
          content += header('//', opt);
          content += testbenchCompiler(project);
          files.push({
            name: 'main_tb.v',
            content: content,
          });
          break;
        case 'gtkwave':
          content += header('[*]', opt);
          content += gtkwaveCompiler(project);
          files.push({
            name: 'main_tb.gtkw',
            content: content,
          });
          break;
        default:
          break;
      }
      return files;
    };

    function header(comment, opt) {
      var header = '';
      var date = new Date();
      opt = opt || {};
      if (opt.header !== false) {
        header +=
          comment + ' Code generated by Icestudio ' + _package.version + '\n';
        if (opt.datetime !== false) {
          header += comment + ' ' + date.toUTCString() + '\n';
        }
        header += '\n';
      }
      return header;
    }

    function module(data) {
      var code = '';
      if (data && data.name && data.ports) {
        // Header
        code += '\nmodule ' + data.name;

        //-- Parameters

        var params = [];
        for (var p in data.params) {
          if (data.params[p] instanceof Object) {
            params.push(
              ' parameter ' +
                data.params[p].name +
                ' = ' +
                (data.params[p].value ? data.params[p].value : '0')
            );
          }
        }

        if (params.length > 0) {
          code += ' #(\n';
          code += params.join(',\n');
          code += '\n)';
        }

        //-- Ports

        var ports = [];
        for (var i in data.ports.in) {
          var _in = data.ports.in[i];
          ports.push(' input ' + (_in.range ? _in.range + ' ' : '') + _in.name);
        }
        for (var o in data.ports.out) {
          var _out = data.ports.out[o];
          ports.push(
            ' output ' + (_out.range ? _out.range + ' ' : '') + _out.name
          );
        }

        if (ports.length > 0) {
          code += ' (\n';
          code += ports.join(',\n');
          code += '\n)';
        }

        if (params.length === 0 && ports.length === 0) {
          code += '\n';
        }

        code += ';\n';

        // Content

        if (data.content) {
          var content = data.content.split('\n');

          content.forEach(function (element, index, array) {
            array[index] = ' ' + element;
          });

          code += content.join('\n');
        }

        // Footer

        code += '\nendmodule\n';
      }

      return code;
    }

    function getParams(project) {
      var params = [];
      var graph = project.design.graph;

      for (var i in graph.blocks) {
        var block = graph.blocks[i];
        if (block.type === 'basic.constant') {
          params.push({
            name: utils.digestId(block.id),
            value: block.data.value,
          });
        } else if (block.type === 'basic.memory') {
          var name = utils.digestId(block.id);
          params.push({
            name: name,
            value: '"' + name + '.list"',
          });
        }
      }

      return params;
    }

    function getPorts(project) {
      var ports = {
        in: [],
        out: [],
      };
      var graph = project.design.graph;

      for (var i in graph.blocks) {
        var block = graph.blocks[i];
        if (block.type === 'basic.input') {
          ports.in.push({
            name: utils.digestId(block.id),
            range: block.data.range ? block.data.range : '',
          });
        } else if (block.type === 'basic.output') {
          ports.out.push({
            name: utils.digestId(block.id),
            range: block.data.range ? block.data.range : '',
          });
        }
      }

      return ports;
    }

    function getContent(name, project) {
      var i, j, w;
      var content = [];
      var graph = project.design.graph;
      var connections = {
        localparam: [],
        wire: [],
        assign: [],
      };
      // We need to rearrange internal design specification to compile it
      // Convert virtual labels to wire and some stuff .

      var vwiresLut = {};
      var lidx, widx, lin, vw;
      var twire;

      //Create virtual wires

      //First identify sources and targets and create a look up table to work easy with it
      if (
        typeof graph !== 'undefined' &&
        graph.blocks.length > 0 &&
        graph.wires.length > 0
      ) {
        for (lidx in graph.blocks) {
          lin = graph.blocks[lidx];
          if (lin.type === 'basic.inputLabel') {
            for (widx in graph.wires) {
              vw = graph.wires[widx];
              if (vw.target.block === lin.id) {
                if (typeof vwiresLut[lin.data.name] === 'undefined') {
                  vwiresLut[lin.data.name] = {
                    source: [],
                    target: [],
                  };
                }
                twire = vw.source;
                twire.size = vw.size;
                vwiresLut[lin.data.name].source.push(twire);
              }
            }
          }
          if (lin.type === 'basic.outputLabel') {
            for (widx in graph.wires) {
              vw = graph.wires[widx];
              if (vw.source.block === lin.id) {
                if (typeof vwiresLut[lin.data.name] === 'undefined') {
                  vwiresLut[lin.data.name] = {
                    source: [],
                    target: [],
                  };
                }

                twire = vw.target;
                twire.size = vw.size;
                vwiresLut[lin.data.name].target.push(twire);
              }
            }
          }
        } //for lin
      } // if typeof....

      //Create virtual wires
      for (widx in vwiresLut) {
        vw = vwiresLut[widx];
        if (vw.source.length > 0 && vw.target.length > 0) {
          for (var vi = 0; vi < vw.source.length; vi++) {
            for (var vj = 0; vj < vw.target.length; vj++) {
              graph.wires.push({
                tcTodelete: true,
                size: vw.size,
                source: vw.source[vi],
                target: vw.target[vj],
                vertices: undefined,
              });
            }
          }
        }
      }

      // Remove virtual blocks
      // Save temporal wires and delete it

      graph.wiresVirtual = [];
      var wtemp = [];
      var iwtemp;
      var wi;
      for (wi = 0; wi < graph.wires.length; wi++) {
        if (
          graph.wires[wi].source.port === 'outlabel' ||
          graph.wires[wi].target.port === 'outlabel' ||
          graph.wires[wi].source.port === 'inlabel' ||
          graph.wires[wi].target.port === 'inlabel'
        ) {
          graph.wiresVirtual.push(graph.wires[wi]);
        } else {
          iwtemp = graph.wires[wi];
          if (typeof iwtemp.source.size !== 'undefined') {
            iwtemp.size = iwtemp.source.size;
          }
          wtemp.push(iwtemp);
        }
      }
      graph.wires = utils.clone(wtemp);
      // End of rearrange design connections for compilation

      for (w in graph.wires) {
        var wire = graph.wires[w];
        if (
          wire.source.port === 'constant-out' ||
          wire.source.port === 'memory-out'
        ) {
          // Local Parameters
          var constantBlock = findBlock(wire.source.block, graph);
          var paramValue = utils.digestId(constantBlock.id);
          if (paramValue) {
            connections.localparam.push(
              'localparam p' + w + ' = ' + paramValue + ';'
            );
          }
        } else {
          // Wires
          var range = wire.size ? ' [0:' + (wire.size - 1) + '] ' : ' ';
          connections.wire.push('wire' + range + 'w' + w + ';');
        }
        // Assignations
        for (i in graph.blocks) {
          var block = graph.blocks[i];
          if (block.type === 'basic.input') {
            if (wire.source.block === block.id) {
              connections.assign.push(
                'assign w' + w + ' = ' + utils.digestId(block.id) + ';'
              );
            }
          } else if (block.type === 'basic.output') {
            if (wire.target.block === block.id) {
              if (
                wire.source.port === 'constant-out' ||
                wire.source.port === 'memory-out'
              ) {
                // connections.assign.push('assign ' + digestId(block.id) + ' = p' + w + ';');
              } else {
                connections.assign.push(
                  'assign ' + utils.digestId(block.id) + ' = w' + w + ';'
                );
              }
            }
          }
        }
      }

      content = content.concat(connections.localparam);
      content = content.concat(connections.wire);
      content = content.concat(connections.assign);

      // Wires Connections

      var numWires = graph.wires.length;
      var gwi, gwj;
      for (i = 1; i < numWires; i++) {
        for (j = 0; j < i; j++) {
          gwi = graph.wires[i];
          gwj = graph.wires[j];
          if (
            gwi.source.block === gwj.source.block &&
            gwi.source.port === gwj.source.port &&
            gwi.source.port !== 'constant-out' &&
            gwi.source.port !== 'memory-out'
          ) {
            content.push('assign w' + i + ' = w' + j + ';');
          }
        }
      }

      // Block instances

      content = content.concat(getInstances(name, project.design.graph));

      // Restore original graph
      // delete temporal wires
      //

      wtemp = [];
      var wn = 0;
      for (wi = 0, wn = graph.wiresVirtual.length; wi < wn; wi++) {
        if (
          typeof graph.wiresVirtual[wi].tcTodelete !== 'undefined' &&
          graph.wiresVirtual[wi].tcTodelete === true
        ) {
          //Nothing for now, only remove
        } else {
          wtemp.push(graph.wiresVirtual[wi]);
        }
      }

      for (wi = 0, wn = graph.wires.length; wi < wn; wi++) {
        if (
          typeof graph.wires[wi].tcTodelete !== 'undefined' &&
          graph.wires[wi].tcTodelete === true
        ) {
          //Nothing for now, only remove
        } else {
          wtemp.push(graph.wires[wi]);
        }
      }

      graph.wires = wtemp;

      delete graph.wiresVirtual;
      //END ONWORK

      return content.join('\n');
    }

    function getInstances(name, graph) {
      var w, wire;
      var instances = [];
      var blocks = graph.blocks;

      for (var b in blocks) {
        var block = blocks[b];

        if (
          block.type !== 'basic.input' &&
          block.type !== 'basic.output' &&
          block.type !== 'basic.constant' &&
          block.type !== 'basic.memory' &&
          block.type !== 'basic.info' &&
          block.type !== 'basic.inputLabel' &&
          block.type !== 'basic.outputLabel'
        ) {
          // Header
          var instance;
          if (block.type === 'basic.code') {
            instance = name + '_' + utils.digestId(block.id);
          } else {
            instance = utils.digestId(block.type);
          }

          //-- Parameters

          var params = [];
          for (w in graph.wires) {
            wire = graph.wires[w];
            if (
              block.id === wire.target.block &&
              (wire.source.port === 'constant-out' ||
                wire.source.port === 'memory-out')
            ) {
              var paramName = wire.target.port;
              if (block.type !== 'basic.code') {
                paramName = utils.digestId(paramName);
              }
              var param = '';
              param += ' .' + paramName;
              param += '(p' + w + ')';
              params.push(param);
            }
          }

          if (params.length > 0) {
            instance += ' #(\n' + params.join(',\n') + '\n)';
          }

          //-- Instance name

          instance += ' ' + utils.digestId(block.id);

          //-- Ports

          var ports = [];
          var portsNames = [];
          for (w in graph.wires) {
            wire = graph.wires[w];
            if (block.id === wire.source.block) {
              connectPort(wire.source.port, portsNames, ports, block);
            }
            if (block.id === wire.target.block) {
              if (
                wire.source.port !== 'constant-out' &&
                wire.source.port !== 'memory-out'
              ) {
                connectPort(wire.target.port, portsNames, ports, block);
              }
            }
          }

          instance += ' (\n' + ports.join(',\n') + '\n);';

          if (instance) {
            instances.push(instance);
          }
        }
      }

      function connectPort(portName, portsNames, ports, block) {
        if (portName) {
          if (block.type !== 'basic.code') {
            portName = utils.digestId(portName);
          }
          if (portsNames.indexOf(portName) === -1) {
            portsNames.push(portName);
            var port = '';
            port += ' .' + portName;
            port += '(w' + w + ')';
            ports.push(port);
          }
        }
      }

      return instances;
    }

    function findBlock(id, graph) {
      for (var b in graph.blocks) {
        if (graph.blocks[b].id === id) {
          return graph.blocks[b];
        }
      }
      return null;
    }

    this.getInitPorts = getInitPorts;

    function getInitPorts(project) {
      // Find not connected input wire ports to initialize

      var i, j;
      var initPorts = [];
      var blocks = project.design.graph.blocks;

      // Find all not connected input ports:
      // - Code blocks
      // - Generic blocks
      for (i in blocks) {
        var block = blocks[i];
        if (block) {
          if (block.type === 'basic.code' || !block.type.startsWith('basic.')) {
            // Code block or Generic block
            for (j in block.data.ports.in) {
              var inPort = block.data.ports.in[j];
              if (inPort.default && inPort.default.apply) {
                initPorts.push({
                  block: block.id,
                  port: inPort.name,
                  name: inPort.default.port,
                  pin: inPort.default.pin,
                });
              }
            }
          }
        }
      }

      return initPorts;
    }

    this.getInitPins = getInitPins;

    function getInitPins(project) {
      // Find not used output pins to initialize

      var i;
      var initPins = [];
      var usedPins = [];
      var blocks = project.design.graph.blocks;

      // Find all set output pins
      for (i in blocks) {
        var block = blocks[i];
        if (block.type === 'basic.output') {
          for (var p in block.data.pins) {
            usedPins.push(block.data.virtual ? '' : block.data.pins[p].value);
          }
        }
      }

      // Filter pins defined in rules
      var allInitPins = common.selectedBoard.rules.output;
      for (i in allInitPins) {
        if (usedPins.indexOf(allInitPins[i].pin) === -1) {
          initPins.push(allInitPins[i]);
        }
      }

      return initPins;
    }

    function verilogCompiler(name, project, opt) {
      var i,
        data,
        block,
        code = '';
      opt = opt || {};

      if (project && project.design && project.design.graph) {
        var blocks = project.design.graph.blocks;
        var dependencies = project.dependencies;

        // Main module

        if (name) {
          // Initialize input ports

          if (name === 'main' && opt.boardRules) {
            var initPorts = opt.initPorts || getInitPorts(project);
            for (i in initPorts) {
              var initPort = initPorts[i];

              // Find existing input block with the initPort value
              var found = false;
              var source = {
                block: initPort.name,
                port: 'out',
              };
              for (i in blocks) {
                block = blocks[i];
                if (
                  block.type === 'basic.input' &&
                  !block.data.range &&
                  !block.data.virtual &&
                  initPort.pin === block.data.pins[0].value
                ) {
                  found = true;
                  source.block = block.id;
                  break;
                }
              }

              if (!found) {
                // Add imaginary input block with the initPort value
                project.design.graph.blocks.push({
                  id: initPort.name,
                  type: 'basic.input',
                  data: {
                    name: initPort.name,
                    pins: [
                      {
                        index: '0',
                        value: initPort.pin,
                      },
                    ],
                    virtual: false,
                  },
                });
              }

              // Add imaginary wire between the input block and the initPort
              project.design.graph.wires.push({
                source: {
                  block: source.block,
                  port: source.port,
                },
                target: {
                  block: initPort.block,
                  port: initPort.port,
                },
              });
            }
          }

          var params = getParams(project);

          //-- Future improvement: After reading the ports, get the names defined in the .pcf or .lpf file
          //-- these are better names to use in the top module, instead of the current not-for-humans pin names
          var ports = getPorts(project);

          //-- Debug
          //console.log(ports)

          var content = getContent(name, project);

          // Initialize output pins

          if (name === 'main' && opt.boardRules) {
            var initPins = opt.initPins || getInitPins(project);
            var n = initPins.length;

            if (n > 0) {
              // Declare m port
              ports.out.push({
                name: 'vinit',
                range: '[0:' + (n - 1) + ']',
              });
              // Generate port value
              var value = n.toString() + "'b";
              for (i in initPins) {
                value += initPins[i].bit;
              }
              // Assign m port
              content += '\nassign vinit = ' + value + ';';
            }
          }

          data = {
            name: name,
            params: params,
            ports: ports,
            content: content,
          };

          code += '//---- Top entity';
          code += module(data);
        }

        code += '\n';

        // Dependencies modules
        if (typeof project.package !== 'undefined') {
          code += '//---------------------------------------------------\n';
          code += '//-- ' + project.package.name + '\n';
          code += '//-- - - - - - - - - - - - - - - - - - - - - - - - --\n';
          code += '//-- ' + project.package.description + '\n';
          code += '//---------------------------------------------------\n';
        }
        for (var d in dependencies) {
          code += verilogCompiler(utils.digestId(d), dependencies[d]);
        }

        // Code modules

        for (i in blocks) {
          block = blocks[i];
          if (block) {
            if (block.type === 'basic.code') {
              data = {
                name: name + '_' + utils.digestId(block.id),
                params: block.data.params,
                ports: block.data.ports,
                content: block.data.code, //.replace(/\n+/g, '\n').replace(/\n$/g, '')
              };
              code += module(data);
            }
          }
        }
      }

      return code;
    }

    function pcfCompiler(project, opt) {
      var i,
        j,
        block,
        pin,
        value,
        code = '';
      var blocks = project.design.graph.blocks;
      opt = opt || {};

      for (i in blocks) {
        block = blocks[i];
        if (block.type === 'basic.input' || block.type === 'basic.output') {
          if (block.data.pins.length > 1) {
            for (var p in block.data.pins) {
              pin = block.data.pins[p];
              value = block.data.virtual ? '' : pin.value;
              code += 'set_io ';
              code += utils.digestId(block.id);
              code += '[' + pin.index + '] ';
              code += value;
              code += '\n';
            }
          } else if (block.data.pins.length > 0) {
            pin = block.data.pins[0];
            value = block.data.virtual ? '' : pin.value;
            code += 'set_io ';
            code += utils.digestId(block.id);
            code += ' ';
            code += value;
            code += '\n';
          }
        }
      }

      if (opt.boardRules) {
        // Declare init input ports

        var used = [];
        var initPorts = opt.initPorts || getInitPorts(project);
        for (i in initPorts) {
          var initPort = initPorts[i];
          if (used.indexOf(initPort.pin) !== -1) {
            break;
          }
          used.push(initPort.pin);

          // Find existing input block with the initPort value
          var found = false;
          for (j in blocks) {
            block = blocks[j];
            if (
              block.type === 'basic.input' &&
              !block.data.range &&
              !block.data.virtual &&
              initPort.pin === block.data.pins[0].value
            ) {
              found = true;
              used.push(initPort.pin);
              break;
            }
          }

          if (!found) {
            code += 'set_io v';
            code += initPorts[i].name;
            code += ' ';
            code += initPorts[i].pin;
            code += '\n';
          }
        }

        // Declare init output pins

        var initPins = opt.initPins || getInitPins(project);
        if (initPins.length > 1) {
          for (i in initPins) {
            code += 'set_io vinit[' + i + '] ';
            code += initPins[i].pin;
            code += '\n';
          }
        } else if (initPins.length > 0) {
          code += 'set_io vinit ';
          code += initPins[0].pin;
          code += '\n';
        }
      }

      return code;
    }

    function lpfCompiler(project) {
      var i,
        block,
        pin,
        value,
        code = '';
      var blocks = project.design.graph.blocks;

      code += '# -- Board: ';
      code += common.selectedBoard.name;
      code += '\n\n';

      for (i in blocks) {
        block = blocks[i];
        if (block.type === 'basic.input' || block.type === 'basic.output') {
          //-- Future improvement: Both cases: 1-pin or multiple pins in an array
          //-- could be refactorized instead of repeating code
          //-- (i usually name this as plural and singular cases)

          if (block.data.pins.length > 1) {
            for (var p in block.data.pins) {
              pin = block.data.pins[p];
              value = block.data.virtual ? '' : pin.value;
              code += 'LOCATE COMP "';
              code += utils.digestId(block.id); //-- Future improvement: use pin.name. It should also be changed in the main module
              code += '[' + pin.index + ']" SITE "';
              code += value;
              code += '";\n';

              code += 'IOBUF  PORT "';
              code += utils.digestId(block.id);
              code += '[' + pin.index + ']" ';

              //-- Get the pullmode property of the physical pin (its id is pin.value)
              let pullmode = common.selectedBoard.pinout.find(
                (x) => x.value == value
              ).pullmode;
              pullmode = typeof pullmode == 'undefined' ? 'NONE' : pullmode;

              code += 'PULLMODE=' + pullmode;
              code += ' IO_TYPE=LVCMOS33 DRIVE=4;\n\n';
            }
          } else if (block.data.pins.length > 0) {
            pin = block.data.pins[0];
            value = block.data.virtual ? '' : pin.value;
            code += 'LOCATE COMP "';
            code += utils.digestId(block.id); //-- Future improvement: use pin.name. It should also be changed in the main module
            code += '" SITE "';
            code += value;
            code += '";\n';

            code += 'IOBUF  PORT "';
            code += utils.digestId(block.id);
            code += '" ';

            //-- Get the pullmode property of the physical pin (its id is pin.value)
            let pullmode = common.selectedBoard.pinout.find(
              (x) => x.value == value
            ).pullmode;
            pullmode = typeof pullmode == 'undefined' ? 'NONE' : pullmode;

            code += 'PULLMODE=' + pullmode;
            code += ' IO_TYPE=LVCMOS33 DRIVE=4;\n\n';
          }
        }
      }

      return code;
    }

    function listCompiler(project) {
      var i;
      var listFiles = [];

      if (project && project.design && project.design.graph) {
        var blocks = project.design.graph.blocks;
        var dependencies = project.dependencies;

        // Find in blocks

        for (i in blocks) {
          var block = blocks[i];
          if (block.type === 'basic.memory') {
            listFiles.push({
              name: utils.digestId(block.id) + '.list',
              content: block.data.list,
            });
          }
        }

        // Find in dependencies

        for (i in dependencies) {
          var dependency = dependencies[i];
          listFiles = listFiles.concat(listCompiler(dependency));
        }
      }

      return listFiles;
    }

    function testbenchCompiler(project) {
      var i, o, p;
      var code = '';

      code += '// Testbench template\n\n';

      code += '`default_nettype none\n';
      code += '`define DUMPSTR(x) `"x.vcd`"\n';
      code += '`timescale 10 ns / 1 ns\n\n';

      var ports = {
        in: [],
        out: [],
      };
      var content = '\n';

      content += '// Simulation time: 100ns (10 * 10ns)\n';
      content += 'parameter DURATION = 10;\n';

      // Parameters
      var _params = [];
      var params = mainParams(project);
      if (params.length > 0) {
        content += '\n// TODO: edit the module parameters here\n';
        content += '// e.g. localparam constant_value = 1;\n';
        for (p in params) {
          content +=
            'localparam ' + params[p].name + ' = ' + params[p].value + ';\n';
          _params.push(' .' + params[p].id + '(' + params[p].name + ')');
        }
      }

      // Input/Output
      var io = mainIO(project);
      var input = io.input;
      var output = io.output;
      content += '\n// Input/Output\n';
      var _ports = [];
      for (i in input) {
        content +=
          'reg ' +
          (input[i].range ? input[i].range + ' ' : '') +
          input[i].name +
          ';\n';
        _ports.push(' .' + input[i].id + '(' + input[i].name + ')');
      }
      for (o in output) {
        content +=
          'wire ' +
          (output[o].range ? output[o].range + ' ' : '') +
          output[o].name +
          ';\n';
        _ports.push(' .' + output[o].id + '(' + output[o].name + ')');
      }

      // Module instance
      content += '\n// Module instance\n';
      content += 'main';

      //-- Parameters
      if (_params.length > 0) {
        content += ' #(\n';
        content += _params.join(',\n');
        content += '\n)';
      }

      content += ' MAIN';

      //-- Ports
      if (_ports.length > 0) {
        content += ' (\n';
        content += _ports.join(',\n');
        content += '\n)';
      }

      content += ';\n';

      // Clock signal
      var hasClk = false;
      for (i in input) {
        if (input[i].name.toLowerCase() === 'clk') {
          hasClk = true;
          break;
        }
      }
      if (hasClk) {
        content += '\n// Clock signal\n';
        content += 'always #0.5 clk = ~clk;\n';
      }

      content += '\ninitial begin\n';
      content += ' // File were to store the simulation results\n';
      content += ' $dumpfile(`DUMPSTR(`VCD_OUTPUT));\n';
      content += ' $dumpvars(0, main_tb);\n\n';
      content += ' // TODO: initialize the registers here\n';
      content += ' // e.g. value = 1;\n';
      content += ' // e.g. #2 value = 0;\n';
      for (i in input) {
        content += ' ' + input[i].name + ' = 0;\n';
      }
      content += '\n';
      content += ' #(DURATION) $display("End of simulation");\n';
      content += ' $finish;\n';
      content += 'end\n';

      var data = {
        name: 'main_tb',
        ports: ports,
        content: content,
      };
      code += module(data);

      return code;
    }

    function gtkwaveCompiler(project) {
      var code = '';

      var io = mainIO(project);
      var input = io.input;
      var output = io.output;

      for (var i in input) {
        code +=
          'main_tb.' +
          input[i].name +
          (input[i].range ? input[i].range : '') +
          '\n';
      }
      for (var o in output) {
        code +=
          'main_tb.' +
          output[o].name +
          (output[o].range ? output[o].range : '') +
          '\n';
      }

      return code;
    }

    function mainIO(project) {
      var input = [];
      var output = [];
      var inputUnnamed = 0;
      var outputUnnamed = 0;
      var graph = project.design.graph;
      for (var i in graph.blocks) {
        var block = graph.blocks[i];
        if (block.type === 'basic.input') {
          if (block.data.name) {
            input.push({
              id: utils.digestId(block.id),
              name: block.data.name.replace(/ /g, '_'),
              range: block.data.range,
            });
          } else {
            input.push({
              id: utils.digestId(block.id),
              name: inputUnnamed.toString(),
            });
            inputUnnamed += 1;
          }
        } else if (block.type === 'basic.output') {
          if (block.data.name) {
            output.push({
              id: utils.digestId(block.id),
              name: block.data.name.replace(/ /g, '_'),
              range: block.data.range,
            });
          } else {
            output.push({
              id: utils.digestId(block.id),
              name: outputUnnamed.toString(),
            });
            outputUnnamed += 1;
          }
        }
      }

      return {
        input: input,
        output: output,
      };
    }

    function mainParams(project) {
      var params = [];
      var paramsUnnamed = 0;
      var graph = project.design.graph;
      for (var i in graph.blocks) {
        var block = graph.blocks[i];
        if (block.type === 'basic.constant') {
          if (!block.data.local) {
            if (block.data.name) {
              params.push({
                id: utils.digestId(block.id),
                name: 'constant_' + block.data.name.replace(/ /g, '_'),
                value: block.data.value,
              });
            } else {
              params.push({
                id: utils.digestId(block.id),
                name: 'constant_' + paramsUnnamed.toString(),
                value: block.data.value,
              });
              paramsUnnamed += 1;
            }
          }
        }
      }

      return params;
    }
  });