diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..308d70d --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + JAVA_VERSION: 21 + +jobs: + test: + strategy: + fail-fast: false + matrix: + node-version: ['22.x'] + os: [ubuntu-latest, windows-2022, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + registry-url: 'https://registry.npmjs.org/' + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: npm install + + - name: Build the project + run: npm run build + + - name: Run tests + run: npm run test diff --git a/package.json b/package.json index 773b58f..3688177 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,10 @@ "build": "tsc", "build:watch": "tsc --watch", "lint": "eslint .", - "docs": "typedoc" + "docs": "typedoc", + "test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --detectOpenHandles --forceExit src", + "test:cov": "npm run test -- --coverage", + "test:watch": "npm run test -- --watch" }, "exports": { "./VitisHlsConfig": "./dist/src/VitisHlsConfig.js", diff --git a/src/VitisHlsConfig.test.ts b/src/VitisHlsConfig.test.ts new file mode 100644 index 0000000..6f3a895 --- /dev/null +++ b/src/VitisHlsConfig.test.ts @@ -0,0 +1,69 @@ +import { + AmdPlatform, + ClockUnit, + FlowTarget, + OutputFormat, + UncertaintyUnit, + VitisHlsConfig, +} from "./VitisHlsConfig.js"; + +describe("VitisHlsConfig", () => { + it("uses the documented defaults", () => { + const config = new VitisHlsConfig("kernel"); + + expect(config.getTopFunction()).toBe("kernel"); + expect(config.getPlatform()).toBe(AmdPlatform.ZCU102); + expect(config.getClock()).toEqual({ value: 10, unit: ClockUnit.NANOSECOND }); + expect(config.getUncertainty()).toEqual({ + value: 2.5, + unit: UncertaintyUnit.NANOSECOND, + }); + expect(config.getFlowTarget()).toBe(FlowTarget.VITIS); + expect(config.getOutputFormat()).toBe(OutputFormat.VITIS_XO); + expect(config.isPackagingEnabled()).toBe(false); + expect(config.getSources()).toEqual([]); + }); + + it("supports fluent configuration", () => { + const config = new VitisHlsConfig("oldKernel"); + + const result = config + .setTopFunction("newKernel") + .setPlatform("custom-part") + .setClock({ value: 250, unit: ClockUnit.MEGAHERTZ }) + .setUncertainty({ value: 10, unit: UncertaintyUnit.PERCENTAGE }) + .setFlowTarget(FlowTarget.VIVADO) + .setOutputFormat(OutputFormat.RTL) + .setEnablePackaging(true); + + expect(result).toBe(config); + expect(config.getTopFunction()).toBe("newKernel"); + expect(config.getPlatform()).toBe("custom-part"); + expect(config.getClock()).toEqual({ value: 250, unit: ClockUnit.MEGAHERTZ }); + expect(config.getUncertainty()).toEqual({ + value: 10, + unit: UncertaintyUnit.PERCENTAGE, + }); + expect(config.getFlowTarget()).toBe(FlowTarget.VIVADO); + expect(config.getOutputFormat()).toBe(OutputFormat.RTL); + expect(config.isPackagingEnabled()).toBe(true); + }); + + it("generates a Vitis configuration and de-duplicates sources", () => { + type Source = Parameters[0]; + const sourceA = { filename: "kernel.cpp", path: "/src/kernel.cpp" } as Source; + const sourceB = { filename: "helper.cpp", path: "/src/helper.cpp" } as Source; + const config = new VitisHlsConfig("kernel") + .addSource(sourceA) + .addSources([sourceA, sourceB]); + + const generated = config.generateConfigFile(); + + expect(config.getSources()).toEqual([sourceA, sourceB]); + expect(generated).toContain(`part=${AmdPlatform.ZCU102}`); + expect(generated).toContain("syn.top=kernel"); + expect(generated).toContain("clock=10ns"); + expect(generated.match(/syn\.file=kernel\.cpp/g)).toHaveLength(1); + expect(generated).toContain("syn.file=helper.cpp"); + }); +}); diff --git a/src/VitisReportParsers.test.ts b/src/VitisReportParsers.test.ts new file mode 100644 index 0000000..ee007fe --- /dev/null +++ b/src/VitisReportParsers.test.ts @@ -0,0 +1,101 @@ +import { ClockUnit, UncertaintyUnit } from "./VitisHlsConfig.js"; +import { VitisImplReportParser } from "./VitisImplReportParser.js"; +import { TimeUnit } from "./VitisReports.js"; +import { VitisSynReportParser } from "./VitisSynReportParser.js"; + +describe("Vitis report parsers", () => { + it("parses a synthesis report", () => { + const report = new VitisSynReportParser().parseReport( + "inputs/report-syn/csynth.xml", + ); + + expect(report).toMatchObject({ + valid: true, + errors: [], + vitisVersion: 2024.1, + platform: "xczu9eg-ffvb1156-2-e", + topFunction: "foo", + clockTarget: { value: 10, unit: ClockUnit.NANOSECOND }, + clockTargetUncertainty: { value: 2, unit: UncertaintyUnit.NANOSECOND }, + clockEstim: { value: 8, unit: ClockUnit.NANOSECOND }, + frequencyMaxMHz: 125, + latencyWorst: 6_021_801, + latencyAvg: 6_018_201, + latencyBest: 6_014_601, + hasFixedLatency: false, + execTimeWorst: { value: 60.218, unit: TimeUnit.MILLISECOND }, + execTimeAvg: { value: 60.182, unit: TimeUnit.MILLISECOND }, + execTimeBest: { value: 60.146, unit: TimeUnit.MILLISECOND }, + FF: 4096, + LUT: 5425, + BRAM: 30, + DSP: 0, + availFF: 548160, + availLUT: 274080, + availBRAM: 1824, + availDSP: 2520, + }); + expect(report.perFF).toBeCloseTo(4096 / 548160); + expect(report.perLUT).toBeCloseTo(5425 / 274080); + expect(report.perBRAM).toBeCloseTo(30 / 1824); + expect(report.perDSP).toBe(0); + expect(Date.parse(report.timestamp)).not.toBeNaN(); + }); + + it("parses an implementation report", () => { + const report = new VitisImplReportParser().parseReport( + "inputs/report-impl/export_impl.xml", + ); + + expect(report).toMatchObject({ + valid: true, + errors: [], + vivadoVersion: "v.2024.2", + clockTarget: { value: 6.667, unit: ClockUnit.NANOSECOND }, + clockAchieved: { value: 3.311, unit: ClockUnit.NANOSECOND }, + FF: 4246, + LUT: 3719, + BRAM: 3, + DSP: 0, + availFF: 548160, + availLUT: 274080, + availBRAM: 1824, + availDSP: 2520, + }); + expect(report.perFF).toBeCloseTo(4246 / 548160); + expect(report.perLUT).toBeCloseTo(3719 / 274080); + expect(report.perBRAM).toBeCloseTo(3 / 1824); + expect(report.perDSP).toBe(0); + expect(Date.parse(report.timestamp)).not.toBeNaN(); + }); + + it("creates an invalid empty synthesis report", () => { + const report = VitisSynReportParser.emptyReport(); + + expect(report).toMatchObject({ + valid: false, + errors: [], + platform: "", + topFunction: "", + vitisVersion: "", + latencyWorst: -1, + execTimeWorst: { value: -1, unit: TimeUnit.MICROSECOND }, + }); + }); + + it("creates an invalid empty implementation report", () => { + const report = VitisImplReportParser.emptyReport(); + + expect(report).toMatchObject({ + valid: false, + errors: [], + vivadoVersion: "", + clockTarget: { value: 0, unit: ClockUnit.NANOSECOND }, + clockAchieved: { value: 0, unit: ClockUnit.NANOSECOND }, + FF: -1, + LUT: -1, + BRAM: -1, + DSP: -1, + }); + }); +}); diff --git a/src/VitisReports.test.ts b/src/VitisReports.test.ts new file mode 100644 index 0000000..2613eda --- /dev/null +++ b/src/VitisReports.test.ts @@ -0,0 +1,17 @@ +import { convertTimeUnit, TimeUnit } from "./VitisReports.js"; + +describe("convertTimeUnit", () => { + it("converts between supported time units", () => { + expect(convertTimeUnit(1, TimeUnit.SECOND, TimeUnit.MILLISECOND)).toBeCloseTo(1_000); + expect(convertTimeUnit(1, TimeUnit.MILLISECOND, TimeUnit.MICROSECOND)).toBeCloseTo(1_000); + expect(convertTimeUnit(1, TimeUnit.MICROSECOND, TimeUnit.NANOSECOND)).toBeCloseTo(1_000); + }); + + it("converts smaller units to larger units", () => { + expect(convertTimeUnit(2_500_000, TimeUnit.NANOSECOND, TimeUnit.MILLISECOND)).toBeCloseTo(2.5); + }); + + it("leaves a value unchanged when the units match", () => { + expect(convertTimeUnit(42, TimeUnit.MICROSECOND, TimeUnit.MICROSECOND)).toBe(42); + }); +});