mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-09-27 08:54:48 +00:00
312 lines
12 KiB
TypeScript
312 lines
12 KiB
TypeScript
|
|
// TODO: must be a better way to do all this
|
|
|
|
import { WorkerError, CodeListingMap, SourceLocation } from "../../common/workertypes";
|
|
import { Assembler } from "../assembler";
|
|
import * as vxmlparser from '../../common/hdl/vxmlparser';
|
|
import { EmscriptenModule, emglobal, execMain, getWASMMemory, loadNative, moduleInstFn, print_fn, setupFS } from "../wasmutils";
|
|
import { getWorkFileAsString, BuildStep, BuildStepResult, gatherFiles, staleFiles, populateFiles, starttime, endtime, putWorkFile, anyTargetChanged, populateExtraFiles } from "../builder";
|
|
import { makeErrorMatcher } from "../listingutils";
|
|
|
|
function detectModuleName(code: string) {
|
|
var m = /^\s*module\s+(\w+_top)\b/m.exec(code)
|
|
|| /^\s*module\s+(top|t)\b/m.exec(code)
|
|
|| /^\s*module\s+(\w+)\b/m.exec(code);
|
|
return m ? m[1] : null;
|
|
}
|
|
|
|
function detectTopModuleName(code: string) {
|
|
var topmod = detectModuleName(code) || "top";
|
|
var m = /^\s*module\s+(\w+?_top)/m.exec(code);
|
|
if (m && m[1]) topmod = m[1];
|
|
return topmod;
|
|
}
|
|
|
|
// cached stuff (TODO)
|
|
var jsasm_module_top;
|
|
var jsasm_module_output;
|
|
var jsasm_module_key;
|
|
|
|
function compileJSASM(asmcode: string, platform, options, is_inline) {
|
|
var asm = new Assembler(null);
|
|
var includes = [];
|
|
asm.loadJSON = (filename: string) => {
|
|
var jsontext = getWorkFileAsString(filename);
|
|
if (!jsontext) throw Error("could not load " + filename);
|
|
return JSON.parse(jsontext);
|
|
};
|
|
asm.loadInclude = (filename) => {
|
|
if (!filename.startsWith('"') || !filename.endsWith('"'))
|
|
return 'Expected filename in "double quotes"';
|
|
filename = filename.substr(1, filename.length - 2);
|
|
includes.push(filename);
|
|
};
|
|
var loaded_module = false;
|
|
asm.loadModule = (top_module: string) => {
|
|
// compile last file in list
|
|
loaded_module = true;
|
|
var key = top_module + '/' + includes;
|
|
if (jsasm_module_key != key) {
|
|
jsasm_module_key = key;
|
|
jsasm_module_output = null;
|
|
}
|
|
jsasm_module_top = top_module;
|
|
var main_filename = includes[includes.length - 1];
|
|
// TODO: take out .asm dependency
|
|
var voutput = compileVerilator({ platform: platform, files: includes, path: main_filename, tool: 'verilator' });
|
|
if (voutput)
|
|
jsasm_module_output = voutput;
|
|
return null; // no error
|
|
}
|
|
var result = asm.assembleFile(asmcode);
|
|
if (loaded_module && jsasm_module_output) {
|
|
// errors? return them
|
|
if (jsasm_module_output.errors && jsasm_module_output.errors.length)
|
|
return jsasm_module_output;
|
|
// return program ROM array
|
|
var asmout = result.output;
|
|
// TODO: unify
|
|
result.output = jsasm_module_output.output;
|
|
// TODO: typecheck this garbage
|
|
(result as any).output.program_rom = asmout;
|
|
// TODO: not cpu_platform__DOT__program_rom anymore, make const
|
|
(result as any).output.program_rom_variable = jsasm_module_top + "$program_rom";
|
|
(result as any).listings = {};
|
|
(result as any).listings[options.path] = { lines: result.lines };
|
|
return result;
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export function compileJSASMStep(step: BuildStep): BuildStepResult {
|
|
gatherFiles(step);
|
|
var code = getWorkFileAsString(step.path);
|
|
var platform = step.platform || 'verilog';
|
|
return compileJSASM(code, platform, step, false);
|
|
}
|
|
|
|
function compileInlineASM(code: string, platform, options, errors, asmlines) {
|
|
code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function (s, asmcode, index) {
|
|
var firstline = code.substr(0, index).match(/\n/g).length;
|
|
var asmout = compileJSASM(asmcode, platform, options, true);
|
|
if (asmout.errors && asmout.errors.length) {
|
|
for (var i = 0; i < asmout.errors.length; i++) {
|
|
asmout.errors[i].line += firstline;
|
|
errors.push(asmout.errors[i]);
|
|
}
|
|
return "";
|
|
} else if (asmout.output) {
|
|
let s = "";
|
|
var out = asmout.output;
|
|
for (var i = 0; i < out.length; i++) {
|
|
if (i > 0) {
|
|
s += ",";
|
|
if ((i & 0xff) == 0) s += "\n";
|
|
}
|
|
s += 0 | out[i];
|
|
}
|
|
if (asmlines) {
|
|
var al = asmout.lines;
|
|
for (var i = 0; i < al.length; i++) {
|
|
al[i].line += firstline;
|
|
asmlines.push(al[i]);
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
});
|
|
return code;
|
|
}
|
|
|
|
export function compileVerilator(step: BuildStep): BuildStepResult {
|
|
loadNative("verilator_bin");
|
|
var platform = step.platform || 'verilog';
|
|
var errors: WorkerError[] = [];
|
|
gatherFiles(step);
|
|
// compile verilog if files are stale
|
|
if (staleFiles(step, [xmlPath])) {
|
|
// TODO: %Error: Specified --top-module 'ALU' isn't at the top level, it's under another cell 'cpu'
|
|
// TODO: ... Use "/* verilator lint_off BLKSEQ */" and lint_on around source to disable this message.
|
|
var match_fn = makeErrorMatcher(errors, /%(.+?): (.+?):(\d+)?[:]?\s*(.+)/i, 3, 4, step.path, 2);
|
|
var verilator_mod: EmscriptenModule = emglobal.verilator_bin({
|
|
instantiateWasm: moduleInstFn('verilator_bin'),
|
|
noInitialRun: true,
|
|
noExitRuntime: true,
|
|
print: print_fn,
|
|
printErr: match_fn,
|
|
wasmMemory: getWASMMemory(), // reuse memory
|
|
//INITIAL_MEMORY:256*1024*1024,
|
|
});
|
|
var code = getWorkFileAsString(step.path);
|
|
var topmod = detectTopModuleName(code);
|
|
var FS = verilator_mod.FS;
|
|
var listings: CodeListingMap = {};
|
|
// process inline assembly, add listings where found
|
|
populateFiles(step, FS, {
|
|
mainFilePath: step.path,
|
|
processFn: (path, code) => {
|
|
if (typeof code === 'string') {
|
|
let asmlines = [];
|
|
code = compileInlineASM(code, platform, step, errors, asmlines);
|
|
if (asmlines.length) {
|
|
listings[path] = { lines: asmlines };
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
});
|
|
starttime();
|
|
var xmlPath = `obj_dir/V${topmod}.xml`;
|
|
try {
|
|
var args = ["--cc", "-O3",
|
|
"-DEXT_INLINE_ASM", "-DTOPMOD__" + topmod, "-D__8BITWORKSHOP__",
|
|
"-Wall",
|
|
"-Wno-DECLFILENAME", "-Wno-UNUSED", "-Wno-EOFNEWLINE", "-Wno-PROCASSWIRE",
|
|
"--x-assign", "fast", "--noassert", "--pins-sc-biguint",
|
|
"--debug-check", // for XML output
|
|
"--top-module", topmod, step.path]
|
|
execMain(step, verilator_mod, args);
|
|
} catch (e) {
|
|
console.log(e);
|
|
errors.push({ line: 0, msg: "Compiler internal error: " + e });
|
|
}
|
|
endtime("compile");
|
|
// remove boring errors
|
|
errors = errors.filter(function (e) { return !/Exiting due to \d+/.exec(e.msg); }, errors);
|
|
errors = errors.filter(function (e) { return !/Use ["][/][*]/.exec(e.msg); }, errors);
|
|
if (errors.length) {
|
|
return { errors: errors };
|
|
}
|
|
starttime();
|
|
var xmlParser = new vxmlparser.VerilogXMLParser();
|
|
try {
|
|
var xmlContent = FS.readFile(xmlPath, { encoding: 'utf8' });
|
|
var xmlScrubbed = xmlContent.replace(/ fl=".+?" loc=".+?"/g, '');
|
|
// TODO: this squelches the .asm listing
|
|
//listings[step.prefix + '.xml'] = {lines:[],text:xmlContent};
|
|
putWorkFile(xmlPath, xmlScrubbed); // don't detect changes in source position
|
|
if (!anyTargetChanged(step, [xmlPath]))
|
|
return;
|
|
xmlParser.parse(xmlContent);
|
|
} catch (e) {
|
|
console.log(e, e.stack);
|
|
if (e.$loc != null) {
|
|
let $loc = e.$loc as SourceLocation;
|
|
errors.push({ msg: "" + e, path: $loc.path, line: $loc.line });
|
|
} else {
|
|
errors.push({ line: 0, msg: "" + e });
|
|
}
|
|
return { errors: errors, listings: listings };
|
|
} finally {
|
|
endtime("parse");
|
|
}
|
|
return {
|
|
output: xmlParser,
|
|
errors: errors,
|
|
listings: listings,
|
|
};
|
|
}
|
|
}
|
|
|
|
// TODO: test
|
|
export function compileYosys(step: BuildStep): BuildStepResult {
|
|
loadNative("yosys");
|
|
var code = step.code;
|
|
var errors = [];
|
|
var match_fn = makeErrorMatcher(errors, /ERROR: (.+?) in line (.+?[.]v):(\d+)[: ]+(.+)/i, 3, 4, step.path);
|
|
starttime();
|
|
var yosys_mod: EmscriptenModule = emglobal.yosys({
|
|
instantiateWasm: moduleInstFn('yosys'),
|
|
noInitialRun: true,
|
|
print: print_fn,
|
|
printErr: match_fn,
|
|
});
|
|
endtime("create module");
|
|
var topmod = detectTopModuleName(code);
|
|
var FS = yosys_mod.FS;
|
|
FS.writeFile(topmod + ".v", code);
|
|
starttime();
|
|
try {
|
|
execMain(step, yosys_mod, ["-q", "-o", topmod + ".json", "-S", topmod + ".v"]);
|
|
} catch (e) {
|
|
console.log(e);
|
|
endtime("compile");
|
|
return { errors: errors };
|
|
}
|
|
endtime("compile");
|
|
//TODO: filename in errors
|
|
if (errors.length) return { errors: errors };
|
|
try {
|
|
var json_file = FS.readFile(topmod + ".json", { encoding: 'utf8' });
|
|
var json = JSON.parse(json_file);
|
|
console.log(json);
|
|
return { output: json, errors: errors }; // TODO
|
|
} catch (e) {
|
|
console.log(e);
|
|
return { errors: errors };
|
|
}
|
|
}
|
|
|
|
export function compileSilice(step: BuildStep): BuildStepResult {
|
|
loadNative("silice");
|
|
var params = step.params;
|
|
gatherFiles(step, { mainFilePath: "main.ice" });
|
|
var destpath = step.prefix + '.v';
|
|
var errors: WorkerError[] = [];
|
|
var errfile: string;
|
|
var errline: number;
|
|
if (staleFiles(step, [destpath])) {
|
|
//[preprocessor] 97] attempt to concatenate a nil value (global 'addrW')
|
|
var match_fn = (s: string) => {
|
|
s = (s as any).replaceAll(/\033\[\d+\w/g, '');
|
|
var mf = /file:\s*(\w+)/.exec(s);
|
|
var ml = /line:\s+(\d+)/.exec(s);
|
|
var preproc = /\[preprocessor\] (\d+)\] (.+)/.exec(s);
|
|
if (mf) errfile = mf[1];
|
|
else if (ml) errline = parseInt(ml[1]);
|
|
else if (preproc) {
|
|
errors.push({ path: step.path, line: parseInt(preproc[1]), msg: preproc[2] });
|
|
}
|
|
else if (errfile && errline && s.length > 1) {
|
|
if (s.length > 2) {
|
|
errors.push({ path: errfile + ".ice", line: errline, msg: s });
|
|
} else {
|
|
errfile = null;
|
|
errline = null;
|
|
}
|
|
}
|
|
else console.log(s);
|
|
}
|
|
var silice: EmscriptenModule = emglobal.silice({
|
|
instantiateWasm: moduleInstFn('silice'),
|
|
noInitialRun: true,
|
|
print: match_fn,
|
|
printErr: match_fn,
|
|
});
|
|
var FS = silice.FS;
|
|
setupFS(FS, 'Silice');
|
|
populateFiles(step, FS);
|
|
populateExtraFiles(step, FS, params.extra_compile_files);
|
|
const FWDIR = '/share/frameworks';
|
|
var args = [
|
|
'-D', 'NTSC=1',
|
|
'--frameworks_dir', FWDIR,
|
|
'-f', `/8bitworkshop.v`,
|
|
'-o', destpath,
|
|
step.path];
|
|
execMain(step, silice, args);
|
|
if (errors.length)
|
|
return { errors: errors };
|
|
var vout = FS.readFile(destpath, { encoding: 'utf8' });
|
|
putWorkFile(destpath, vout);
|
|
}
|
|
return {
|
|
nexttool: "verilator",
|
|
path: destpath,
|
|
args: [destpath],
|
|
files: [destpath],
|
|
};
|
|
}
|
|
|