UNPKG

edacation

Version:

Library and CLI for interacting with Yosys and nextpnr.

372 lines 14.1 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Project = exports.ProjectOutputFile = exports.ProjectInputFile = void 0; const util_js_1 = require("../util.js"); const configuration_js_1 = require("./configuration.js"); class ProjectInputFile { constructor(_path, _type) { this._path = _path; this._type = _type; } get path() { return this._path; } get type() { return this._type; } set type(type) { this._type = type; } serialize() { return { path: this.path, type: this.type }; } static deserialize(data, ..._args) { // Older versions of this module (<= 0.3.9) stored input files as an array of paths instead, // so we need to migrate if data is a string (single output file). if (typeof data === 'string') { data = { path: data, type: 'design' }; } return new ProjectInputFile(data.path, data.type); } copy() { return ProjectInputFile.deserialize(this.serialize()); } } exports.ProjectInputFile = ProjectInputFile; class ProjectOutputFile { constructor(_project, _path, _targetId = null, _stale = false) { this._project = _project; this._path = _path; this._targetId = _targetId; this._stale = _stale; } get path() { return this._path; } get targetId() { return this._targetId; } set targetId(id) { if (id !== null && this._project.getTarget(id) === null) { throw new Error(`Invalid target id: ${id}`); } this._targetId = id; } get target() { if (!this._targetId) return null; return this._project.getTarget(this._targetId); } get stale() { return this._stale; } set stale(isStale) { this._stale = isStale; } serialize() { return { path: this.path, targetId: this.targetId, stale: this.stale }; } static deserialize(project, data, ..._args) { // Older versions of this module (<= 0.3.12) stored output files as an array of paths instead, // so we need to migrate if data is a string (single output file). if (typeof data === 'string') { data = { path: data, targetId: null, stale: false }; } return new ProjectOutputFile(project, data.path, data.targetId, data.stale); } copy(project) { return ProjectOutputFile.deserialize(project, this.serialize()); } } exports.ProjectOutputFile = ProjectOutputFile; class Project { constructor(name, inputFiles = [], outputFiles = [], configuration = configuration_js_1.DEFAULT_CONFIGURATION, eventCallback) { this.batchedEvents = new Set(); this.batchCounter = 0; this.name = name; this.inputFiles = inputFiles.map((file) => ProjectInputFile.deserialize(file)); this.outputFiles = outputFiles.map((file) => ProjectOutputFile.deserialize(this, file)); const config = configuration_js_1.schemaProjectConfiguration.safeParse(configuration); if (config.success) { this.configuration = config.data; } else { throw new Error(`Failed to parse project configuration: ${config.error.toString()}`); } // Trigger a config 'update' to deploy any modifications it might want to make this.updateConfiguration({}); // Set event callback LAST to prevent firing events in constructor this.eventCallback = eventCallback; } getName() { return this.name; } getInputFiles() { return this.inputFiles; } hasInputFile(filePath) { return this.getInputFile(filePath) !== null; } getInputFile(filePath) { return this.inputFiles.find((file) => file.path === filePath) ?? null; } addInputFiles(files) { for (const file of files) { if (!this.hasInputFile(file.path)) { const inputFile = new ProjectInputFile(file.path, file.type ?? 'design'); this.inputFiles.push(inputFile); } } this.inputFiles.sort((a, b) => { return a < b ? -1 : 1; }); this.expireOutputFiles(); } removeInputFiles(filePaths) { this.inputFiles = this.inputFiles.filter((file) => !filePaths.includes(file.path)); this.expireOutputFiles(); } getOutputFiles() { return this.outputFiles; } hasOutputFile(filePath) { return this.getOutputFile(filePath) !== null; } getOutputFile(filePath) { return this.outputFiles.find((file) => file.path === filePath) ?? null; } addOutputFiles(files) { for (const file of files) { const existingOutFile = this.getOutputFile(file.path); if (existingOutFile) { // File already exists, so we don't want to add it again. // But, we should make sure the target ID gets updated and set `stale` to false. existingOutFile.targetId = file.targetId; existingOutFile.stale = false; continue; } const outputFile = new ProjectOutputFile(this, file.path, file.targetId); if (outputFile.target === null) throw new Error(`Invalid target ID: ${file.targetId}`); this.outputFiles.push(outputFile); } this.outputFiles.sort((a, b) => { return a < b ? -1 : 1; }); } removeOutputFiles(filePaths) { this.outputFiles = this.outputFiles.filter((file) => !filePaths.includes(file.path)); } expireOutputFiles() { if (!this.outputFiles.length) return; let didUpdate = false; for (const file of this.outputFiles) { if (!file.stale) { file.stale = true; didUpdate = true; } } if (didUpdate) this.emitEvents('outputFiles'); } setTopLevelModule(targetId, module) { const target = this.getTarget(targetId); if (!target) throw new Error(`Target "${targetId}" does not exist!`); // Ensure the config tree exists // We don't care about setting missing defaults, as this is target-level configuration, // so any missing properties will fallback to project-level config. if (!target.yosys) target.yosys = {}; if (!target.yosys.options) target.yosys.options = {}; target.yosys.options.topLevelModule = module; } setTestbenchPath(targetId, testbenchPath) { const testbenchFiles = this.getInputFiles() .filter((file) => file.type === 'testbench') .map((file) => file.path); if (testbenchPath && !testbenchFiles.includes(testbenchPath)) throw new Error(`Testbench ${testbenchPath} is not marked as such!`); const target = this.getTarget(targetId); if (!target) throw new Error(`Target "${targetId}" does not exist!`); // Ensure the config tree exists // We don't care about setting missing defaults, as this is target-level configuration, // so any missing properties will fallback to project-level config. if (!target.iverilog) target.iverilog = {}; if (!target.iverilog.options) target.iverilog.options = {}; target.iverilog.options.testbenchFile = testbenchPath; } setInputFileType(filePath, type) { const file = this.getInputFile(filePath); if (!file) { console.warn(`Tried to set file type of missing input file: ${filePath}`); return; } file.type = type; } getTarget(id) { const targets = this.configuration.targets; return targets.find((target) => target.id === id) ?? null; } getConfiguration() { return this.configuration; } updateConfiguration(configuration) { this.configuration = { ...this.configuration, ...configuration }; // Unset 'lingering' output file target IDs for (const outFile of this.outputFiles) { if (!outFile.target) outFile.targetId = null; } } importFromProject(other, doTriggerEvent = true) { this.inputFiles = other.getInputFiles().map((file) => file.copy()); this.outputFiles = other.getOutputFiles().map((file) => file.copy(this)); this.configuration = structuredClone(other.getConfiguration()); if (doTriggerEvent) this.emitEvents('inputFiles', 'outputFiles', 'configuration'); } emitEvents(...events) { for (const event of events) this.batchedEvents.add(event); // Do not emit when empty if (!this.batchedEvents.size) return; // Do not emit events when batching if (this.batchCounter > 0) return; // Emit new + batched events if (this.eventCallback) this.eventCallback(this, Array.from(this.batchedEvents)); this.batchedEvents.clear(); } batchEvents(func, ...events) { this.batchCounter += 1; const res = func(); this.batchCounter -= 1; this.emitEvents(...events); return res; } static emitsEvents(...events) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function decorator(_target, _propertyKey, descriptor) { const originalMethod = descriptor.value; if (!originalMethod) throw new Error('No original method!'); descriptor.value = function (...args) { return this.batchEvents(() => originalMethod.apply(this, args), ...events); }; return descriptor; }; } static serialize(project) { return { name: project.name, inputFiles: project.inputFiles.map((file) => file.serialize()), outputFiles: project.outputFiles.map((file) => file.serialize()), configuration: project.configuration }; } static deserialize(data, ..._args) { const name = data.name; const inputFiles = data.inputFiles ?? []; const outputFiles = data.outputFiles ?? []; const configuration = data.configuration ?? {}; return new Project(name, inputFiles, outputFiles, configuration); } static loadFromData(rawData) { const data = (0, util_js_1.decodeJSON)(rawData); const project = Project.deserialize(data); return project; } static storeToData(project) { const data = Project.serialize(project); return (0, util_js_1.encodeJSON)(data, true); } } exports.Project = Project; __decorate([ Project.emitsEvents('inputFiles'), __metadata("design:type", Function), __metadata("design:paramtypes", [Array]), __metadata("design:returntype", void 0) ], Project.prototype, "addInputFiles", null); __decorate([ Project.emitsEvents('inputFiles'), __metadata("design:type", Function), __metadata("design:paramtypes", [Array]), __metadata("design:returntype", void 0) ], Project.prototype, "removeInputFiles", null); __decorate([ Project.emitsEvents('outputFiles'), __metadata("design:type", Function), __metadata("design:paramtypes", [Array]), __metadata("design:returntype", void 0) ], Project.prototype, "addOutputFiles", null); __decorate([ Project.emitsEvents('outputFiles'), __metadata("design:type", Function), __metadata("design:paramtypes", [Array]), __metadata("design:returntype", void 0) ], Project.prototype, "removeOutputFiles", null); __decorate([ Project.emitsEvents(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], Project.prototype, "expireOutputFiles", null); __decorate([ Project.emitsEvents('configuration'), __metadata("design:type", Function), __metadata("design:paramtypes", [String, String]), __metadata("design:returntype", void 0) ], Project.prototype, "setTopLevelModule", null); __decorate([ Project.emitsEvents('configuration'), __metadata("design:type", Function), __metadata("design:paramtypes", [String, String]), __metadata("design:returntype", void 0) ], Project.prototype, "setTestbenchPath", null); __decorate([ Project.emitsEvents('inputFiles'), __metadata("design:type", Function), __metadata("design:paramtypes", [String, Object]), __metadata("design:returntype", void 0) ], Project.prototype, "setInputFileType", null); __decorate([ Project.emitsEvents('configuration'), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], Project.prototype, "updateConfiguration", null); __decorate([ Project.emitsEvents(), __metadata("design:type", Function), __metadata("design:paramtypes", [Project, Object]), __metadata("design:returntype", void 0) ], Project.prototype, "importFromProject", null); //# sourceMappingURL=project.js.map