UNPKG

xilt

Version:

Xilinx Command Line ToolKit

860 lines (718 loc) 23 kB
#!/usr/bin/env node let child_process = require('child_process'); let path = require('path'); let fs = require('fs'); let os = require('os'); let parseArgs = require('./parseArgs'); let scanDeps = require('./scanDeps'); let parseMessage = require('./parseMessage'); let misc = require('./misc'); // Work out current folder let cwd = process.cwd(); let folderName = path.parse(cwd).name; // ------------ Platform/Environment Checks -------------- // Check supported platform if (os.platform() != "linux") { console.log("xilt is only supported on Linux"); process.exit(7); } // Find Xilinx tools let xilinxDir = "/opt/Xilinx/14.7/"; if (!fs.existsSync(xilinxDir)) { console.log(`Xilinx tools not found at ${xilinxDir}`); process.exit(7); } let xilinxBin = path.join(xilinxDir, "ISE_DS/ISE/bin/lin64"); let startTime = Date.now(); function elapsed() { let ms = Date.now() - startTime; let seconds = parseInt(ms / 1000); let minutes = parseInt(seconds / 60); let hours = parseInt(minutes / 60); return `${hours.toString().padStart(2, '0')}:${(minutes % 60).toString().padStart(2, '0')}:${(seconds % 60).toString().padStart(2, 0)}`; } // ------------ Settings -------------- let action = ""; let debug = false; let verbose = false; let intStyle = "ise"; let filterRules = []; let settings = { projectName: null, intDir: null, outDir: null, device: null, topModule: null, device: null, hdlLanguage: null, ucfFile: null, filterWarnings: true, infoMessages: true, msgFormat: "ise", depPath: [], xstFlags: [], ngdBuildFlags: [], mapFlags: [], parFlags: [], bitGenFlags: [], sourceFiles: [], }; // ------------ Main -------------- async function Main() { if (process.argv.length > 2 && process.argv[2] == "ghdl-filter") { return await (require('./ghdlFilter'))(process.argv.slice(3)); } try { // Process settings processCommandLine(process.argv.slice(2)); resolveDefaultSettings(); // Invoke selected action switch (action) { case "help": showHelp(); break; case "settings": console.log(settings); break; case "build": await build(); break; case "buildsim": await build_sim(); break; case "scandeps": let filespecs = settings.sourceFiles.slice(); if (settings.ucfFile) filespecs.push(settings.ucfFile); settings.debug = debug; let files = scanDeps(cwd, filespecs, settings); for (let i =0; i<files.length; i++) { console.log(files[i]); } break; case "coregen": launchXilinxTool("coregen"); break; case "ise": launchXilinxTool("ise"); break; case "xlcm": launchXilinxTool("xlcm"); break; default: throw new Error(`Unknown action: '${action}'`); } } catch (err) { console.error(`${err.message}`); process.exit(7); } } // Invoke main Main(); // ------------ Build Actions ------------ function createDirectories() { // Ensure folders exist misc.mkdirp(settings.intDir); misc.mkdirp(settings.outDir); } async function build() { // Ensure folder exist createDirectories(); // Create XST files createXstProjectFile(); createXstCommandFile(false); // Run the build... scanForFilterRules() await runXst(); await runNgdBuild(); await runMap(); await runPar(); await runBitGen(); console.log(`\n[${elapsed()}]: Finished!\n`); } async function build_sim() { createDirectories(); createXstProjectFile(); console.log(`[${elapsed()}]: Building Simulator...`); // Run it await run(`${xilinxBin}/fuse`, [ "-intstyle", intStyle, "-prj", `${settings.projectName}.prj`, "-o", `${settings.topModule}-sim`, `${settings.topModule}` ], { cwd: settings.intDir, }, verbose ? null : xilinx_filter ); // Create a shell script to launch it let sb = "#!/bin/bash\n"; sb += `source ${xilinxDir}ISE_DS/settings64.sh ${xilinxDir}ISE_DS > /dev/null\n`; sb += `./${settings.topModule}-sim $*\n`; let scriptFile = misc.smartJoin(settings.intDir, settings.topModule +"-isim"); fs.writeFileSync(scriptFile, sb); fs.chmodSync(scriptFile, 0775); // Done console.log(`\n[${elapsed()}]: Finished!\n`); } function launchXilinxTool(name) { let cp = child_process.spawn("bash", ['-c', `source ${xilinxDir}/ISE_DS/settings64.sh; ${name}`], {detached: true, stdio: 'ignore', shell: false}); cp.unref(); console.log(`Launched ${name}`); } // ------------ Xilinx Build Actions ------------ function is_filtered(message) { // For filtering purposes replace all whitespace with a single space message = message.replace(/\s+/gm, " "); for (let r=0; r<filterRules.length; r++) { if (filterRules[r] instanceof RegExp) filtered = !!message.match(filterRules[r]); else filtered = message.indexOf(filterRules[r]) >= 0; if (filtered) return true; } return false; } function xilinx_filter_2(line) { // Parse the message let msg = parseMessage(line, settings.sourceFiles); if (msg) { // Filter info messages? if (msg.severity == 'info' && !settings.infoMessages) return; // Filtered? if (msg.severity != 'error' && is_filtered(line)) return; // Reformat message? if (msg.file) { switch (settings.messageFormat) { case "ms": case "mscompile": line =`${msg.file}(${msg.line},1) : ${msg.severity} ${msg.code} : ${msg.message}` if (msg.severity != 'error' && is_filtered(line)) return; break; case "gcc": line = `${msg.file}:${msg.line}:1 ${msg.severity}: ${msg.message}`; if (msg.severity != 'error' && is_filtered(line)) return; break; } } // Output it console.log(line); } } let reIsMessage = /^(ERROR|WARNING|INFO)\:(.*?)\:(\d+) - (.*)/i let rebuiltLine; function xilinx_filter(line) { if (line.startsWith("INFO:")) debugger; // Is it a message? let m = line.match(reIsMessage); if (m) { // Flush last line if (rebuiltLine) { xilinx_filter_2(rebuiltLine); } // We don't need to handle multiline messages for these if (m[2] == "HDLCompiler" || m[2] == "Xst") { xilinx_filter_2(line); return; } // Start building new line rebuiltLine = line; return; } // Currently rebuilding a multiline message? if (!rebuiltLine) { // Nope, process this line immediately xilinx_filter_2(line); return; } // Is it a continued line? if (line.length == 0 || line.startsWith(' ') || line.startsWith('\t')) { // Continue building the line rebuiltLine += "\n" + line; } else { // Process it xilinx_filter_2(rebuiltLine); rebuiltLine = null; } } function scanForFilterRules() { if (verbose) return; for (let i=0; i<settings.sourceFiles.length; i++) { let ext = path.extname(settings.sourceFiles[i]).toLowerCase(); if (ext == ".vhdl" || ext == ".vhd") { let src = fs.readFileSync(settings.sourceFiles[i], "utf8"); let ruleFinder = /--xilt:nowarn:(.*)/g; while (match = ruleFinder.exec(src)) { if (match[1].startsWith("~")) { filterRules.push(new RegExp(match[1].substring(1))); } else { filterRules.push(match[1]); } } } } } async function runXst() { console.log(`[${elapsed()}]: Synthesize...`); // Run it await run(`${xilinxBin}/xst`, [ "-intstyle", intStyle, "-ifn", `${settings.projectName}.xst`, "-ofn", `${settings.projectName}.syr`, ], { cwd: settings.intDir, }, verbose ? null : xilinx_filter ); } async function runNgdBuild() { console.log(`[${elapsed()}]: NGD Build...`); let flags = settings.ngdBuildFlags.concat([ "-intstyle", intStyle, '-uc', path.resolve(settings.ucfFile), '-dd', '.', '-sd', 'ipcore_dir', '-p', settings.device, `${settings.projectName}.ngc`, `${settings.projectName}.ngd` ]); // Run it await run(`${xilinxBin}/ngdbuild`, flags, { cwd: settings.intDir, }, verbose ? null : xilinx_filter ); } async function runMap() { console.log(`[${elapsed()}]: Map...`); let flags = settings.mapFlags.concat([ "-intstyle", intStyle, '-p', settings.device, '-o', settings.projectName + "_map.ncd", `${settings.projectName}.ngd`, `${settings.projectName}.pcf`, ]); // Run it await run(`${xilinxBin}/map`, flags, { cwd: settings.intDir, }, verbose ? null : xilinx_filter ); } async function runPar() { console.log(`[${elapsed()}]: Place and Route...`); let flags = settings.parFlags.concat([ "-intstyle", intStyle, `${settings.projectName}_map.ncd`, `${settings.projectName}.ncd`, `${settings.projectName}.pcf`, ]); // Run it await run(`${xilinxBin}/par`, flags, { cwd: settings.intDir, }, verbose ? null : xilinx_filter ); } async function runBitGen() { console.log(`[${elapsed()}]: BitGen...`); let outputFile = misc.smartJoin(settings.outDir, settings.projectName + ".bit") let flags = settings.bitGenFlags.concat([ "-intstyle", intStyle, `${settings.projectName}.ncd`, `${path.resolve(outputFile)}`, `${settings.projectName}.pcf`, ]); // Run it await run(`${xilinxBin}/bitgen`, flags, { cwd: settings.intDir, }, verbose ? null : xilinx_filter ); } // ------------ XST -------------- function createXstProjectFile() { let sb = ""; for (let i=0; i<settings.sourceFiles.length; i++) { let file = path.resolve(settings.sourceFiles[i]); let ext = path.extname(settings.sourceFiles[i]); switch (ext.toLowerCase()) { case ".vhdl": case ".vhd": sb += "vhdl work \"" + file + "\"\n"; break; case ".v": sb += "verilog work \"" + file + "\"\n"; break; default: throw new Error(`Internal error unknown source file type: ${file}`); } } fs.writeFileSync(misc.smartJoin(settings.intDir, settings.projectName + ".prj"), sb); } function createXstCommandFile(elaborate) { let sb = ""; sb += `set -tmpdir .\n`; sb += `set -xsthdpdir "xst"\n`; sb += elaborate ? `elaborate\n` : `run\n`; sb += `-ifn "${settings.projectName}.prj"\n`; sb += `-ifmt mixed\n`; if (!elaborate) { sb += `-ofn "${settings.projectName}"\n`; sb += `-ofmt NGC\n` sb += `-top ${settings.topModule}\n`; sb += `-p ${settings.device}\n`; sb += `-opt_mode Speed\n`; sb += `-opt_level 1\n`; } fs.writeFileSync(misc.smartJoin(settings.intDir, settings.projectName + ".xst"), sb); } // ------------ Command Line Parse -------------- function processCommandLine(argv) { for (let i=0; i<argv.length; i++) { let a = argv[i]; // Response file? if (a.startsWith("@")) { let responseFile = a.substring(1); if (fs.existsSync(responseFile)) { let content = fs.readFileSync(responseFile, 'UTF8'); processCommandLine(parseArgs(content)); } else { throw new Error(`Response file ${responseFile} not found`); } continue; } let isSwitch = false; if (a.startsWith("--")) { isSwitch = true; a = a.substring(2); } if (isSwitch) { let parts = a.split(':'); if (parts.length > 2) { parts = [parts[0], parts.slice(1).join(":")] } if (parts.length == 2) { if (parts[1]=='false' || parts[1]=='no') parts[1] = false; if (parts[1]=='true' || parts[1]=='yes') parts[1] = true; } parts[0] = parts[0].toLowerCase(); function pushParts(tool) { let underPos = parts[0].indexOf('_'); settings[tool].push("-" + parts[0].substring(underPos+1)); if (parts.length > 1) settings[tool].push(parts[1]); } if (parts[0].startsWith("xst_")) { pushParts("xstFlags"); continue; } if (parts[0].startsWith("ngd_") || parts[0].startsWith("ngdbuild_")) { pushParts("ngdBuildFlags"); continue; } if (parts[0].startsWith("map_")) { pushParts("mapFlags"); continue; } if (parts[0].startsWith("par_")) { pushParts("parFlags"); continue; } if (parts[0].startsWith("bitgen_")) { pushParts("bitGenFlags"); continue; } switch (parts[0]) { case "debug": debug = true; break; case "verbose": verbose = true; break; case "projectname": if (settings.projectName) throw new Error("Duplicate projectName setting"); if (parts.length < 2) throw new Error("projectName argument missing"); settings.projectName = parts[1]; break; case "device": if (settings.device) throw new Error("Duplicate device setting"); if (parts.length < 2) throw new Error("device argument missing"); settings.device = parts[1]; break; case "topmodule": if (settings.topModule) throw new Error("Duplicate topModule setting"); if (parts.length < 2) throw new Error("topModule argument missing"); settings.topModule = parts[1]; break; case "intdir": if (parts.length < 2) throw new Error("intDir argument missing"); settings.intDir = parts[1]; break; case "outdir": if (parts.length < 2) throw new Error("outDir argument missing"); settings.outDir = parts[1]; break; case "deppath": if (parts.length < 2) throw new Error("depPath argument missing"); settings.depPath.push(parts[1]); break; case "nofilter": settings.filterWarnings = false; break; case "noinfo": settings.infoMessages = false; break; case "messageformat": if (parts[1].toLowerCase() != "ise" && parts[1].toLowerCase() != "gcc" && parts[1].toLowerCase() != "mscompile" && parts[1].toLowerCase() != "ms") throw new Error(`unknown message format '${parts[1]}'`); settings.messageFormat = parts[1].toLowerCase(); break; case "help": showHelp(); process.exit(0); break; case "version": showVersion(); process.exit(0); default: throw new Error(`Unrecognized switch: --${parts[0]}`) } } else { switch (path.extname(a).toLowerCase()) { case ".vhdl": case ".vhd": case ".v": settings.sourceFiles.push(a); break; case ".ucf": if (settings.ucfFile) throw new Error("Duplicate UCF file specified"); settings.ucfFile = a; break; case "": if (!action) { action = a.toLowerCase(); } else { throw new Error(`Duplicate action specified: '${action}' or '${a.toLowerCase()}'?`); } break; default: throw new Error(`Unknown file type: ${a}`); } } } } function resolveDefaultSettings() { if (!settings.projectName) settings.projectName = folderName; if (!settings.topModule) settings.topModule = settings.projectName; if (!settings.ucfFile && action != "scandeps") settings.ucfFile = settings.projectName + ".ucf"; if (!settings.hdlLanguage) settings.hdlLanguage = "VHDL"; if (!action) action = "help"; if (!settings.intDir) settings.intDir = "./build"; if (!settings.outDir) settings.outDir = settings.intDir; } // ------------ Help -------------- function showVersion() { let pkg = require('../package.json'); console.log(`xilt v${pkg.version} - Xilinx Command Line Tools`); console.log("Copyright (C) 2019 Topten Software. All Rights Reserved"); } function showHelp() { showVersion(); console.log(); console.log("USAGE: xilt action [Options] [SourceFiles] [UCFFile]"); console.log(); console.log("Actions:"); console.log(" build build the project"); console.log(" buildsim build the project as an ISim simulation"); console.log(" settings show all resolved build settings"); console.log(" scandeps scan for dependencies"); console.log(" ghdl-filter filters ghdl output to $msCompile message format"); console.log(" ise launch Xilinx ISE"); console.log(" coregen launch Xilinx Core Generator"); console.log(" xlcm launch Xilinx license manager"); console.log(" help show this help"); console.log(); console.log("Options:"); console.log(" --debug show file dependency checks"); console.log(" --device:val set the Xilinx device name (required)"); console.log(" --help show this help"); console.log(" --version show current version number"); console.log(" --depPath:val set a folder to search for dependencies"), console.log(" --intDir:val set the intermediate build directory (default: ./build"); console.log(" --outDir:val set the output folder for .bit file (default: intermediate directory)"); console.log(" --projectName:val set the name of the project (default: folder name)"); console.log(" --topModule:val set the name of the top module (default: project name)"); console.log(" --nofilter disable filtering of warnings"); console.log(" --messageFormat:val reformat errors and warnings to 'mscompile' or 'gcc' format"); console.log(" --verbose show verbose output"); console.log(); console.log("Xilinx Tools Passthrough:") console.log(" --xst_<name>[:val] sets additional arguments to pass to xst"); console.log(" --ngcbuild_<name>[:val] sets additional arguments to pass to ngdbuild"); console.log(" --map_<name>[:val] sets additional arguments to pass to map"); console.log(" --par_<name>[:val] sets additional arguments to pass to par"); console.log(" --bitgen_<name>[:val] sets additional arguments to pass to bitgen"); console.log(""); console.log("eg: '--bitgen_g:StartupClk:CCLK' will invoke bitgen with '-g StartupClk:CCLK'"); } // ------------ Utility Functions -------------- /* function escapeArg(x) { if (os.platform() == "win32") return x.indexOf(' ') >= 0 ? `"${x}"` : x; else return x.replace(/ /g, '\\ '); } function run(cmd, args) { if (os.platform() == "win32") cmd += ".exe"; if (options.verbose) { console.log(cmd, args.map(escapeArg).join(" ")); } return child_process.spawnSync(cmd, args, { stdio: 'inherit' }); } function getEnv(name, defVal) { if (process.env[name]) return process.env[name]; else return defVal; } function pushOneOrArray(target, arg, value) { if (Array.isArray(value)) { for (let i=0; i<value.length; i++) { target.push(arg); target.push(value[i]); } } else { target.push(arg); target.push(value); } } */ // Run a command async function run(cmd, args, opts, stdioCallback) { if (verbose) { console.log(`>${cmd} ${args.join(' ')}`); } return misc.run(cmd, args, opts, stdioCallback); }