diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99852a0..ebc2006 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,48 +7,32 @@ on: pull_request: jobs: + test: + name: Test/Nix + runs-on: macos-14 + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + - uses: DeterminateSystems/nix-installer-action@main + with: + extra-conf: | + extra-substituters = https://openlane.cachix.org + extra-trusted-public-keys = openlane.cachix.org-1:qqdwh+QMNGmZAuyeQJTH9ErW57OWSvdtuwfBKdS254E= + - uses: DeterminateSystems/magic-nix-cache-action@main + - run: nix build push_to_pypi: - name: Build/Publish Docker - runs-on: ubuntu-20.04 + name: Build + needs: [test] + runs-on: ubuntu-24.04 steps: - name: Check out Git repository - uses: actions/checkout@v2 - - name: Export Repo URL - run: echo "REPO_URL=https://github.com/${{ github.repository }}" >> $GITHUB_ENV - - name: Export Branch Name - run: echo "BRANCH_NAME=${GITHUB_REF##*/}" >> $GITHUB_ENV + uses: actions/checkout@v3 - name: Set default for env.NEW_TAG run: echo "NEW_TAG=NO_NEW_TAG" >> $GITHUB_ENV - - name: Write Hash - run: | - echo "GIT_COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV - - name: Build Image - run: | - docker build --target runner -t aucohl/fault:latest . - name: Check for new version if: ${{ env.BRANCH_NAME == 'main' }} run: | python3 .github/scripts/generate_tag.py - - name: Log in to the Container registry - if: ${{ env.BRANCH_NAME == 'main' }} - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Push to GHCR (Commit) - if: ${{ env.BRANCH_NAME == 'main' }} - run: | - docker image tag aucohl/fault:latest ghcr.io/aucohl/fault:${{ env.GIT_COMMIT_HASH }} - docker push ghcr.io/aucohl/fault:${{ env.GIT_COMMIT_HASH }} - - name: Push to GHCR (Tag) - if: ${{ env.NEW_TAG != 'NO_NEW_TAG' }} - run: | - docker image tag aucohl/fault:latest ghcr.io/aucohl/fault:latest - docker image tag aucohl/fault:latest ghcr.io/aucohl/fault:$NEW_TAG - docker push ghcr.io/aucohl/fault:$NEW_TAG - docker push ghcr.io/aucohl/fault:latest - # Last, because this triggers the AppImage CI - name: Tag Commit if: ${{ env.NEW_TAG != 'NO_NEW_TAG' }} uses: tvdias/github-tagger@v0.0.1 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c28b01a..0000000 --- a/Dockerfile +++ /dev/null @@ -1,106 +0,0 @@ -FROM efabless/openlane-tools:yosys-cfe940a98b08f1a5d08fb44427db155ba1f18b62-centos-7 AS yosys -# --- - -FROM swift:5.8-centos7 AS builder - -# Setup Build Environment -RUN yum install -y --setopt=skip_missing_names_on_install=False https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm centos-release-scl -RUN yum install -y --setopt=skip_missing_names_on_install=False git gettext curl devtoolset-8 devtoolset-8-libatomic-devel - -ENV CC=/opt/rh/devtoolset-8/root/usr/bin/gcc \ - CPP=/opt/rh/devtoolset-8/root/usr/bin/cpp \ - CXX=/opt/rh/devtoolset-8/root/usr/bin/g++ \ - PATH=/opt/rh/devtoolset-8/root/usr/bin:$PATH \ - LD_LIBRARY_PATH=/opt/rh/devtoolset-8/root/usr/lib64:/opt/rh/devtoolset-8/root/usr/lib:/opt/rh/devtoolset-8/root/usr/lib64/dyninst:/opt/rh/devtoolset-8/root/usr/lib/dyninst:/opt/rh/devtoolset-8/root/usr/lib64:/opt/rh/devtoolset-8/root/usr/lib:$LD_LIBRARY_PATH - -# Install Python3 -WORKDIR /python3 -RUN curl -L https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tgz | tar --strip-components=1 -xzC . \ - && ./configure --enable-shared --prefix=/build \ - && make -j$(nproc) \ - && make install \ - && rm -rf /python3 - -# Set environment -ENV PATH=/build/bin:$PATH -ENV PYTHON_LIBRARY /build/lib/libpython3.6m.so -ENV PYTHONPATH /build/lib/pythonpath - -# Install Other Dependencies -RUN yum install -y flex bison autoconf libtool gperf tcl-devel libcurl-devel openssl-devel zlib-devel - -# --- - -# Install Yosys -COPY --from=yosys /build /build - -# Install Git (Build-only dependency) -WORKDIR /git -RUN curl -L https://github.com/git/git/tarball/e9d7761bb94f20acc98824275e317fa82436c25d/ |\ - tar -xzC . --strip-components=1 &&\ - make configure &&\ - ./configure --prefix=/usr &&\ - make -j$(nproc) &&\ - make install &&\ - rm -rf * - -# Install IcarusVerilog 11 -WORKDIR /iverilog -RUN curl -L https://github.com/steveicarus/iverilog/archive/refs/tags/v11_0.tar.gz |\ - tar --strip-components=1 -xzC . &&\ - aclocal &&\ - autoconf &&\ - ./configure --prefix=/build &&\ - make -j$(nproc) &&\ - make install &&\ - rm -rf * - -# Python Setup -WORKDIR / -COPY requirements.txt /requirements.txt -RUN python3 -m pip install --target /build/lib/pythonpath --upgrade -r ./requirements.txt - -RUN cp /lib64/libtinfo.so.5 /build/lib -RUN cp /lib64/libffi.so.6 /build/lib -RUN cp /lib64/libz.so.1 /build/lib -RUN cp /lib64/libreadline.so.6 /build/lib -RUN cp /lib64/libtcl8.5.so /build/lib - -# Fault Setup -WORKDIR /fault -COPY . . -ENV CC=clang -ENV CXX=clang++ -RUN swift build --static-swift-stdlib -c release -RUN cp /fault/.build/x86_64-unknown-linux-gnu/release/Fault /build/bin/fault - -## Tests -ENV PATH=/build/bin:$PATH\ - PYTHON_LIBRARY=/build/lib/libpython3.6m.so\ - PYTHONPATH=/build/lib/pythonpath\ - LD_LIBRARY_PATH=/build/lib\ - FAULT_IVL_BASE=/build/lib/ivl\ - FAULT_IVERILOG=/build/bin/iverilog\ - FAULT_VVP=/build/bin/vvp -RUN swift test - -WORKDIR / -# --- - -FROM centos:centos7 AS runner -COPY --from=builder /build /build -WORKDIR /test -COPY ./Tech/osu035 ./osu035 - -WORKDIR / - -# Set environment -ENV PATH=/build/bin:$PATH\ - PYTHON_LIBRARY=/build/lib/libpython3.6m.so\ - PYTHONPATH=/build/lib/pythonpath\ - LD_LIBRARY_PATH=/build/lib\ - FAULT_IVL_BASE=/build/lib/ivl\ - FAULT_IVERILOG=/build/bin/iverilog\ - FAULT_VVP=/build/bin/vvp - -CMD [ "/bin/bash" ] diff --git a/Package.swift b/Package.swift index 8717c78..831261c 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "Fault", platforms: [ - .macOS(.v11), // executableURL and a bunch of other things are not available before High Sierra + .macOS(.v13), // executableURL and a bunch of other things are not available before High Sierra ], dependencies: [ // Dependencies declare other packages that this package depends on. @@ -21,13 +21,9 @@ let package = Package( // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .executableTarget( - name: "Fault", + name: "fault", dependencies: ["PythonKit", .product(name: "ArgumentParser", package: "swift-argument-parser"), "Defile", .product(name: "Collections", package: "swift-collections"), "BigInt", "Yams"], path: "Sources" - ), - .testTarget( - name: "FaultTests", - dependencies: ["Fault"] - ), + ) ] ) diff --git a/Sources/Fault/BenchCircuit.swift b/Sources/Fault/BenchCircuit.swift deleted file mode 100644 index e83f58a..0000000 --- a/Sources/Fault/BenchCircuit.swift +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (C) 2019 The American University in Cairo -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Foundation -import PythonKit - -struct BenchCircuit: Codable { - var cells: [BenchCell] - - init(cells: [BenchCell]) { - self.cells = cells - } - - static func represent(_ item: PythonObject) -> String { - return Python.type(item).__name__ == "Pointer" ? - "\(item.var)[\(item.ptr)]" : - "\(item)" - } - - static func extract(definitions: PythonObject) throws -> [BenchCell] { - var cells: [BenchCell] = [] - for definition in definitions { - let type = Python.type(definition).__name__ - - if type == "ModuleDef" { - let (_, inputs, outputs) = try Port.extract(from: definition) - let cellName = definition.name - - var cellStatements: [String] = [] - - var cellOutput = "" - for output in outputs { - cellOutput = String(describing: output.name) - } - - var cellInputs: [String] = [] - for input in inputs { - cellInputs.append(String(describing: input.name)) - } - - for item in definition.items { - let type = Python.type(item).__name__ - - if type == "InstanceList" { - let instance = item.instances[0] - - let outArgname = represent(instance.portlist[0].argname) - let output = (outArgname == cellOutput) ? outArgname : "__\(outArgname)___" - - var benchStatement = "(" - for hook in instance.portlist[1...] { - let argname = represent(hook.argname) - if cellInputs.contains(argname) { - benchStatement += "\(hook.argname), " - } else { - benchStatement += "__\(hook.argname)___, " - } - } - - benchStatement = String(benchStatement.dropLast(2)) - benchStatement += ")" - switch instance.module { - case "and": - cellStatements.append("\(output) = AND" + benchStatement) - case "or": - cellStatements.append("\(output) = OR" + benchStatement) - case "xor": - let inputA = instance.portlist[1].argname - let inputB = instance.portlist[2].argname - cellStatements.append(contentsOf: [ - "__or_out___ = OR(\(inputA), \(inputB))", - "__nand_out___ = NAND(\(inputA), \(inputB))", - "\(output) = AND(__or_out___, __nand_out___)", - ]) - case "buf": - cellStatements.append("\(output) = BUFF" + benchStatement) - case "not": - cellStatements.append("\(output) = NOT" + benchStatement) - default: - print("[Warning]: can't expand \(instance.module) in \(cellName) to primitive cells") - } - } - } - - let cell = BenchCell( - name: String(cellName)!, - inputs: cellInputs, - output: cellOutput, - statements: cellStatements - ) - cells.append(cell) - } - } - - return cells - } -} - -struct BenchCell: Codable { - var name: String - var inputs: [String] - var output: String - var statements: [String] - - init( - name: String, - inputs: [String], - output: String, - statements: [String] - ) { - self.name = name - self.inputs = inputs - self.output = output - self.statements = statements - } - - func extract(name: String, inputs: [String: String], output: [String]) throws -> String { - do { - let regexOutput = try NSRegularExpression(pattern: "\(self.output) = ") - let regexWires = try NSRegularExpression(pattern: "___") - let outputName = (output[0].hasPrefix("\\")) ? "\\\(output[0])" : "\(output[0])" - - var benchStatements = statements - for (index, _) in statements.enumerated() { - var range = NSRange(benchStatements[index].startIndex..., in: benchStatements[index]) - benchStatements[index] = regexOutput.stringByReplacingMatches( - in: benchStatements[index], - options: [], - range: range, - withTemplate: "\(outputName) = " - ) - - range = NSRange(benchStatements[index].startIndex..., in: benchStatements[index]) - benchStatements[index] = regexWires.stringByReplacingMatches( - in: benchStatements[index], - options: [], - range: range, - withTemplate: "__\(name)" - ) - - for input in self.inputs { - let regexInput = try NSRegularExpression(pattern: "(?<=\\(|,)\\s*\(input)(?=\\s*,|\\s*\\))") - let name = (inputs[input]!.hasPrefix("\\")) ? "\\\(inputs[input]!)" : "\(inputs[input]!)" - - range = NSRange(benchStatements[index].startIndex..., in: benchStatements[index]) - benchStatements[index] = regexInput.stringByReplacingMatches( - in: benchStatements[index], - options: [], - range: NSRange(benchStatements[index].startIndex..., in: benchStatements[index]), - withTemplate: name - ) - } - } - - var cellDefinition = "" - for statement in benchStatements { - cellDefinition += "\(statement) \n" - } - cellDefinition = String(cellDefinition.dropLast(1)) - - return cellDefinition - } catch { - print("Invalid regex: \(error.localizedDescription)") - return "" - } - } -} diff --git a/Sources/Fault/Compaction.swift b/Sources/Fault/Compaction.swift index 12f72de..33621fc 100644 --- a/Sources/Fault/Compaction.swift +++ b/Sources/Fault/Compaction.swift @@ -123,7 +123,7 @@ enum Compactor { if sa0 == sa0Final, sa1 == sa1Final { let ratio = (1 - (Float(filtered.count) / Float(tvCount))) * 100 print("Initial TV Count: \(tvCount). Compacted TV Count: \(filtered.count). ") - print("Compaction is successfuly concluded with a reduction percentage of : \(String(format: "%.2f", ratio))% .\n") + print("Successfully compacted test vectors by a ratio of \(String(format: "%.2f", ratio))%.") } else { print("Error: All faults aren't covered after compaction .\n") } diff --git a/Sources/Fault/Entries/asm.swift b/Sources/Fault/Entries/asm.swift index bcfc708..070bd40 100644 --- a/Sources/Fault/Entries/asm.swift +++ b/Sources/Fault/Entries/asm.swift @@ -26,10 +26,10 @@ extension Fault { abstract: "Assemble test vectors and golden outputs from JSON and Verilog files." ) - @Option(name: [.customShort("o"), .long], help: "Path to the output vector file. (Default: + .vec.bin)") + @Option(name: [.customShort("o"), .long], help: "Path to the output vector file.") var output: String? - @Option(name: [.customShort("O"), .long], help: "Path to the golden output file. (Default: + .out.bin)") + @Option(name: [.customShort("O"), .long], help: "Path to the golden output file.") var goldenOutput: String? @Argument(help: "JSON file (.json).") @@ -47,11 +47,10 @@ extension Fault { throw ValidationError("JSON file '\(json)' not found.") } - let vectorOutput = output ?? "\(json).vec.bin" - let goldenOutput = goldenOutput ?? "\(json).out.bin" + let vectorOutput = output ?? json.replacingExtension(".json", with: ".bin") + let goldenOutput = goldenOutput ?? json.replacingExtension(".tv.json", with: ".au.bin") print("Loading JSON data…") - let start = DispatchTime.now() guard let data = try? Data(contentsOf: URL(fileURLWithPath: json)) else { throw ValidationError("Failed to open test vector JSON file.") } @@ -60,10 +59,6 @@ extension Fault { guard let tvinfo = try? decoder.decode(TVInfo.self, from: data) else { throw ValidationError("Test vector JSON file is invalid.") } - let end = DispatchTime.now() - let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds - let timeInterval = Double(nanoTime) / 1_000_000_000 - print("Loaded JSON data in \(timeInterval)s.") // Extract chain metadata let (chain, _, _) = ChainMetadata.extract(file: verilog) @@ -80,7 +75,7 @@ extension Fault { // Check input order let chainOrder = order.filter { $0.kind != .bypassInput } guard chainOrder.count == jsInputOrder.count else { - throw ValidationError("Number of inputs in the JSON (\(jsInputOrder.count)) does not match scan-chain registers (\(chainOrder.count)).") + throw ValidationError("Number of inputs in the test-vector JSON file (\(jsInputOrder.count)) does not match scan-chain registers (\(chainOrder.count)): Found \(Set(chainOrder.map { $0.name }).symmetricDifference(jsInputOrder.map { $0.name })).") } for (i, input) in jsInputOrder.enumerated() { @@ -93,7 +88,7 @@ extension Fault { for (i, output) in jsOutputOrder.enumerated() { var name = output.name.hasPrefix("\\") ? String(output.name.dropFirst()) : output.name - name = name.hasSuffix(".q") ? String(name.dropLast(2)) : name + name = name.hasSuffix(".d") ? String(name.dropLast(2)) : name outputMap[name] = i } diff --git a/Sources/Fault/Entries/atpg.swift b/Sources/Fault/Entries/atpg.swift index 7ec28f1..51bc6e2 100644 --- a/Sources/Fault/Entries/atpg.swift +++ b/Sources/Fault/Entries/atpg.swift @@ -66,18 +66,12 @@ extension Fault { @Option(name: [.short, .long], help: "Netlist in bench format. (Required iff generator is set to Atalanta or PODEM.)") var bench: String? - - @Option(name: [.short, .long], help: "Inputs to ignore. May be specified multiple times.") - var ignoring: [String] = [] - - @Flag(help: "Hold ignored inputs in the simulation to low instead of high.") - var holdLow: Bool = false - + @Flag(help: "Generate only one testbench for inspection, and do not delete it.") var sampleRun: Bool = false - @Option(help: "Clock name to add to --ignoring") - var clock: String + @OptionGroup + var bypass: BypassOptions @Option(help: "If provided, this JSON file's test vectors are simulated and no generation is attempted.") var externalTVSet: String? @@ -115,17 +109,9 @@ extension Fault { ) } - let jsonOutput = output ?? "\(file).tv.json" - let svfOutput = outputSvf ?? "\(file).tv.svf" + let jsonOutput = output ?? file.replacingExtension(".cut.v", with: ".tv.json") + let svfOutput = outputSvf ?? file.replacingExtension(".cut.v", with: ".tv.svf") - ignoring.append(clock) - - let behavior - = [Simulator.Behavior]( - repeating: holdLow ? .holdLow : .holdHigh, - count: ignoring.count - ) - // MARK: Importing Python and Pyverilog let parse = Python.import("pyverilog.vparser.parser").parse @@ -211,20 +197,28 @@ extension Fault { var faultPoints: Set = [] var gateCount = 0 - var inputsMinusIgnored: [Port] = [] - if etvSetVectors.count == 0 { - inputsMinusIgnored = inputs.filter { - !ignoring.contains($0.name) - } - } else { - etvSetInputs.sort { $0.ordinal < $1.ordinal } - inputsMinusIgnored = etvSetInputs.filter { - !ignoring.contains($0.name) + var inputsMinusIgnored: [Port] = inputs.filter { + !bypass.bypassedInputs.contains($0.name) + } + if etvSetVectors.count > 0 { + var evtInputsMinusIgnored: [Port] = [] + var offset = 0 + for (i, input) in etvSetInputs.enumerated() { + if bypass.bypassedInputs.contains(input.name) { + for (j, _) in etvSetVectors.enumerated() { + etvSetVectors[j].remove(at: i - offset) + } + offset += 1 + } else { + evtInputsMinusIgnored.append(input) + } } + assert(inputsMinusIgnored.count == evtInputsMinusIgnored.count); + inputsMinusIgnored = evtInputsMinusIgnored } for (_, port) in ports { - if ignoring.contains(port.name) { + if bypass.bypassedInputs.contains(port.name) { continue } if port.width == 1 { @@ -285,8 +279,7 @@ extension Fault { with: models, ports: ports, inputs: inputsMinusIgnored, - ignoring: Set(ignoring), - behavior: behavior, + bypassingWithBehavior: bypass.simulationValues, outputs: outputs, initialVectorCount: tvCount, incrementingBy: increment, @@ -297,7 +290,7 @@ extension Fault { initialTVInfo: initialTVInfo, externalTestVectors: etvSetVectors, sampleRun: sampleRun, - clock: clock, + clock: bypass.clock, defines: Set(defines), using: iverilogExecutable, with: vvpExecutable @@ -311,36 +304,28 @@ extension Fault { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted - let tvInfo = TVInfo( + let rawTVInfo = TVInfo( inputs: inputsMinusIgnored, outputs: outputs, coverageList: result.coverageList ) + let jsonRawOutput = jsonOutput.replacingExtension(".tv.json", with: ".raw_tv.json") + + print("Writing raw generated test vectors in Fault JSON format to \(jsonOutput)…") + try encoder.encode(rawTVInfo).write(to: URL(fileURLWithPath: jsonRawOutput)) + + let tvInfo = TVInfo( + inputs: inputsMinusIgnored, + outputs: outputs, + coverageList: Compactor.compact(coverageList: result.coverageList) + ) + print("Writing compacted generated test vectors in Fault JSON format to \(jsonOutput)…") + try encoder.encode(tvInfo).write(to: URL(fileURLWithPath: jsonOutput)) - let data = try encoder.encode(tvInfo) - - let svfString = try SerialVectorCreator.create(tvInfo: tvInfo) - - guard let string = String(data: data, encoding: .utf8) - else { - throw "Could not create utf8 string." - } - try File.open(jsonOutput, mode: .write) { - print("Writing generated test vectors in Fault JSON format to \(jsonOutput)…") - try $0.print(string) - } - - try File.open(svfOutput, mode: .write) { - print("Writing generated test vectors in SVF format to \(svfOutput)…") - try $0.print(svfString) - } - - if let faultPointOutput = outputFaultPoints { - print("Writing YAML file of fault points to \(faultPointOutput)…") - try File.open(faultPointOutput, mode: .write) { - try $0.write(string: YAMLEncoder().encode(faultPoints.sorted())) - } - } + // try File.open(svfOutput, mode: .write) { + // print("Writing generated test vectors in SVF format to \(svfOutput)…") + // try $0.print(try SerialVectorCreator.create(tvInfo: tvInfo)) + // } if let coverageMetaFilePath = outputCoverageMetadata { print("Writing YAML file of final coverage metadata to \(coverageMetaFilePath)…") diff --git a/Sources/Fault/Entries/bench.swift b/Sources/Fault/Entries/bench.swift deleted file mode 100644 index a591c7d..0000000 --- a/Sources/Fault/Entries/bench.swift +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (C) 2019-2024 The American University in Cairo -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import ArgumentParser -import CoreFoundation -import Defile -import Foundation -import PythonKit -import BigInt - -extension Fault { - struct Bench: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Generate a benchmark file from Verilog or JSON cell models." - ) - - @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .bench)") - var output: String? - - @Option(name: [.short, .long, .customLong("cellModel")], help: "Path to cell models file. (.v) files are converted to (.json). If .json is available, it could be supplied directly.") - var cellModel: String - - @Argument(help: "Verilog or JSON file.") - var file: String - - mutating func run() throws { - let fileManager = FileManager.default - - // Validate input files - guard fileManager.fileExists(atPath: file) else { - throw ValidationError("File '\(file)' not found.") - } - guard fileManager.fileExists(atPath: cellModel) else { - throw ValidationError("Cell model file '\(cellModel)' not found.") - } - - let output = self.output ?? "\(file).bench" - - var cellModelsFile = cellModel - - // Convert .v to .json if needed - if cellModel.hasSuffix(".v") || cellModel.hasSuffix(".sv") { - print("Creating JSON for the cell models…") - cellModelsFile = "\(cellModel).json" - - // Extract cell definitions from Verilog - let cellModels = "grep -E -- \"\\bmodule\\b|\\bendmodule\\b|and|xor|or|not(\\s+|\\()|buf|input.*;|output.*;\" \(cellModel)".shOutput() - let pattern = "(?s)(?:module).*?(?:endmodule)" - - var cellDefinitions = "" - if let range = cellModels.output.range(of: pattern, options: .regularExpression) { - cellDefinitions = String(cellModels.output[range]) - } - - do { - let regex = try NSRegularExpression(pattern: pattern) - let range = NSRange(cellModels.output.startIndex..., in: cellModels.output) - let results = regex.matches(in: cellModels.output, range: range) - let matches = results.map { String(cellModels.output[Range($0.range, in: cellModels.output)!]) } - - cellDefinitions = matches.joined(separator: "\n") - - let folderName = "\(NSTemporaryDirectory())/thr\(Unmanaged.passUnretained(Thread.current).toOpaque())" - try? FileManager.default.createDirectory(atPath: folderName, withIntermediateDirectories: true, attributes: nil) - defer { - try? FileManager.default.removeItem(atPath: folderName) - } - - let cellFile = "\(folderName)/cells.v" - - try File.open(cellFile, mode: .write) { - try $0.print(cellDefinitions) - } - - // Parse using Pyverilog - let parse = Python.import("pyverilog.vparser.parser").parse - let ast = parse([cellFile])[0] - let description = ast[dynamicMember: "description"] - - let cells = try BenchCircuit.extract(definitions: description.definitions) - let circuit = BenchCircuit(cells: cells) - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let data = try encoder.encode(circuit) - - guard let string = String(data: data, encoding: .utf8) else { - throw ValidationError("Could not create UTF-8 string.") - } - - try File.open(cellModelsFile, mode: .write) { - try $0.print(string) - } - - } catch { - throw ValidationError("Internal error: \(error)") - } - } else if !cellModel.hasSuffix(".json") { - print("Warning: Cell model file provided does not end with .v or .sv or .json. It will be treated as a JSON file.") - } - - // Process library cells - let data = try Data(contentsOf: URL(fileURLWithPath: cellModelsFile), options: .mappedIfSafe) - guard let benchCells = try? JSONDecoder().decode(BenchCircuit.self, from: data) else { - throw ValidationError("File '\(cellModel)' is invalid.") - } - - let cellsDict = benchCells.cells.reduce(into: [String: BenchCell]()) { $0[$1.name] = $1 } - - // Parse using Pyverilog - let parse = Python.import("pyverilog.vparser.parser").parse - let ast = parse([file])[0] - let description = ast[dynamicMember: "description"] - var moduleDef: PythonObject? - - for definition in description.definitions { - if Python.type(definition).__name__ == "ModuleDef" { - moduleDef = definition - break - } - } - - guard let definition = moduleDef else { - throw ValidationError("No module found.") - } - - let (_, inputs, outputs) = try Port.extract(from: definition) - - var inputNames: [String] = [] - var usedInputs: [String] = [] - var floatingOutputs: [String] = [] - var benchStatements = "" - - for input in inputs { - if input.width > 1 { - let range = (input.from > input.to) ? input.to ... input.from : input.from ... input.to - for index in range { - let name = "\(input.name)[\(index)]" - inputNames.append(name) - benchStatements += "INPUT(\(name)) \n" - } - } else { - let name = input.name - inputNames.append(name) - benchStatements += "INPUT(\(name)) \n" - } - } - - for item in definition.items { - let type = Python.type(item).__name__ - - if type == "InstanceList" { - let instance = item.instances[0] - let cellName = String(describing: instance.module) - let instanceName = String(describing: instance.name) - let cell = cellsDict[cellName]! - - var inputs: [String: String] = [:] - var outputs: [String] = [] - - for hook in instance.portlist { - let portname = String(describing: hook.portname) - let argname = BenchCircuit.represent(hook.argname) - - if portname == cell.output { - outputs.append(argname) - } else { - inputs[portname] = argname - } - } - - let statements = try cell.extract(name: instanceName, inputs: inputs, output: outputs) - benchStatements += "\(statements) \n" - - usedInputs.append(contentsOf: Array(inputs.values)) - - } else if type == "Assign" { - let right = BenchCircuit.represent(item.right.var) - let left = BenchCircuit.represent(item.left.var) - - if right == "1'b0" || right == "1'h0" { - print("[Warning]: Constants are not recognized by atalanta. Removing \(left) associated gates and nets..") - floatingOutputs.append(left) - } else { - let statement = "\(left) = BUFF(\(right)) \n" - benchStatements += statement - - usedInputs.append(right) - } - } - } - - let ignoredInputs = inputNames.filter { !usedInputs.contains($0) } - print("Found \(ignoredInputs.count) floating inputs.") - - let filteredOutputs = outputs.filter { !floatingOutputs.contains($0.name) } - for output in filteredOutputs { - if output.width > 1 { - let range = (output.from > output.to) ? output.to ... output.from : output.from ... output.to - for index in range { - benchStatements += "OUTPUT(\(output.name)[\(index)]) \n" - } - } else { - benchStatements += "OUTPUT(\(output.name)) \n" - } - } - - var floatingStatements = "" - for input in ignoredInputs { - floatingStatements += "OUTPUT(\(input)) \n" - } - - let boilerplate = """ - # Bench for \(definition.name) - # Automatically generated by Fault. - # Don't modify. \n - """ - - try File.open(output, mode: .write) { - try $0.print(boilerplate) - try $0.print(floatingStatements.dropLast()) - try $0.print(benchStatements) - } - - print("Benchmark file generated successfully at \(output).") - } - } -} diff --git a/Sources/Fault/Entries/chain.swift b/Sources/Fault/Entries/chain.swift index c1e437b..d36c45c 100644 --- a/Sources/Fault/Entries/chain.swift +++ b/Sources/Fault/Entries/chain.swift @@ -25,16 +25,13 @@ func chainInternal( module: Module, blackboxModules: OrderedDictionary, bsrCreator: BoundaryScanRegisterCreator, - clockName: String, - resetName _: String, - resetActive _: Simulator.Active, + bypass: inout BypassOptions, shiftName: String, inputName: String, outputName: String, testName: String, tckName: String, - invClockName: String?, - ignoredInputs: Set + invClockName: String? ) throws -> [ChainRegister] { // Modifies module definition in-place to create scan-chain. // Changes name to .original to differentate from new top level. @@ -102,7 +99,7 @@ func chainInternal( for hook in instance.portlist { let portnameStr = String(describing: hook.portname) if portnameStr == dffinfo.clk { - if String(describing: hook.argname) == clockName { + if String(describing: hook.argname) == bypass.clock { hook.argname = clkSourceId } else { warn = true @@ -145,7 +142,7 @@ func chainInternal( // Note that `hook.argname` is actually an expression let portInfo = blackboxModule.portsByName["\(hook.portname)"]! - if ignoredInputs.contains(portInfo.name) { + if bypass.bypassedInputs.contains(portInfo.name) { // Leave it alone continue } @@ -195,7 +192,7 @@ func chainInternal( if let invClock = invClockName, moduleName == invClock { for hook in instance.portlist { - if String(describing: hook.argname) == clockName { + if String(describing: hook.argname) == bypass.clock { invClkSourceId = Node.Identifier(invClkSourceName) hook.argname = invClkSourceId! } @@ -207,7 +204,7 @@ func chainInternal( } if warn { - print("[Warning]: Detected flip-flops with clock different from \(clockName).") + print("[Warning]: Detected flip-flops with clock different from \(bypass.clock).") } let finalAssignment = Node.Assign( @@ -219,7 +216,7 @@ func chainInternal( let clockCond = Node.Cond( Node.Identifier(testName), Node.Identifier(tckName), - Node.Identifier(clockName) + Node.Identifier(bypass.clock) ) let clkSourceAssignment = Node.Assign( Node.Lvalue(clkSourceId), @@ -231,7 +228,7 @@ func chainInternal( let invClockCond = Node.Cond( Node.Identifier(testName), Node.Unot(Node.Identifier(tckName)), - Node.Identifier(clockName) + Node.Identifier(bypass.clock) ) let invClockAssignment = Node.Assign( @@ -253,16 +250,13 @@ func chainTop( module: Module, blackboxModules _: OrderedDictionary, bsrCreator: BoundaryScanRegisterCreator, - clockName: String, - resetName: String, - resetActive _: Simulator.Active, + bypass: inout BypassOptions, shiftName: String, inputName: String, outputName: String, testName: String, tckName: String, invClockName _: String?, - ignoredInputs: Set, internalOrder: [ChainRegister] ) throws -> (supermodel: PythonObject, order: [ChainRegister]) { var order: [ChainRegister] = [] @@ -271,11 +265,11 @@ func chainTop( var statements: [PythonObject] = [] statements.append(Node.Input(inputName)) statements.append(Node.Output(outputName)) - statements.append(Node.Input(resetName)) + statements.append(Node.Input(bypass.reset.name)) statements.append(Node.Input(shiftName)) statements.append(Node.Input(tckName)) statements.append(Node.Input(testName)) - statements.append(Node.Input(clockName)) + statements.append(Node.Input(bypass.clock)) let portArguments = Python.list() @@ -298,10 +292,10 @@ func chainTop( for input in module.inputs { let inputStatement = Node.Input(input.name) - if input.name != clockName, input.name != resetName { + if input.name != bypass.clock, input.name != bypass.reset.name { statements.append(inputStatement) } - if ignoredInputs.contains(input.name) { + if bypass.bypassedInputs.contains(input.name) { portArguments.append(Node.PortArg( input.name, Node.Identifier(input.name) @@ -487,27 +481,18 @@ extension Fault { @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") var output: String? - @Option(name: [.short, .long], help: "Inputs to ignore on both the top level design and all black-boxed macros. Comma-delimited.") - var ignoring: String? - @Option(name: [.short, .long, .customLong("cellModel")], help: "Verify scan chain using given cell model.") var cellModel: String? - @Option(name: [.long], help: "Clock signal to add to --ignoring and use in simulation. (Required.)") - var clock: String - @Option(name: [.long], help: "Inverted clk tree source cell name. (Default: none)") var invClock: String? - @Option(name: [.long], help: "Reset signal to add to --ignoring and use in simulation. (Required.)") - var reset: String - - @Flag(name: [.long, .customLong("activeLow")], help: "Reset signal is active low instead of active high.") - var activeLow: Bool = false - @Option(name: [.short, .long], help: "Liberty file. (Required.)") var liberty: String + @OptionGroup + var bypass: BypassOptions + @Option(name: [.short, .long, .customLong("sclConfig")], help: "Path for the YAML SCL config file. Recommended.") var sclConfig: String? @@ -520,8 +505,8 @@ extension Fault { @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") var blackboxModels: [String] = [] - @Option(name: .long, help: "define statements to include during simulations. Comma-delimited. (Default: none)") - var define: String? + @Option(name: [.customShort("D"), .customLong("define")], help: "define statements to include during simulations. Comma-delimited. (Default: none)") + var defines: [String] = [] @Option(name: .long, help: "Extra verilog models to include during simulations. Comma-delimited. (Default: none)") var inc: String? @@ -547,7 +532,7 @@ extension Fault { @Argument var file: String - func run() throws { + mutating func run() throws { let fileManager = FileManager() // Check if input file exists @@ -556,18 +541,6 @@ extension Fault { } // Required options validation - guard !clock.isEmpty else { - throw ValidationError("Option --clock is required.") - } - - guard !reset.isEmpty else { - throw ValidationError("Option --reset is required.") - } - - guard !liberty.isEmpty else { - throw ValidationError("Option --liberty is required.") - } - var sclConfig = SCLConfiguration(dffMatches: [DFFMatch(name: "DFFSR,DFFNEGX1,DFFPOSX1", clk: "CLK", d: "D", q: "Q")]) if let sclConfigPath = self.sclConfig { guard let sclConfigYML = File.read(sclConfigPath) else { @@ -589,14 +562,8 @@ extension Fault { print("Warning: Liberty file provided does not end with .lib.") } - let output = output ?? "\(file).chained.v" - let intermediate = output + ".intermediate.v" - - var ignoredInputs = Set(ignoring?.components(separatedBy: ",") ?? []) - let defines = Set(define?.components(separatedBy: ",") ?? []) - - ignoredInputs.insert(clock) - ignoredInputs.insert(reset) + let output = output ?? file.replacingExtension(".nl.v", with: ".chained.v") + let intermediate = output.replacingExtension(".chained.v", with: ".chain-intermediate.v") let includeFiles = Set(inc?.components(separatedBy: ",").filter { !$0.isEmpty } ?? []) @@ -618,8 +585,8 @@ extension Fault { let bsrCreator = BoundaryScanRegisterCreator( name: "BoundaryScanRegister", clock: tck, - reset: reset, - resetActive: activeLow ? .low : .high, + reset: bypass.reset.name, + resetActive: bypass.reset.active, testing: test, shift: shift, using: Node @@ -631,16 +598,13 @@ extension Fault { module: module, blackboxModules: blackboxModules, bsrCreator: bsrCreator, - clockName: clock, - resetName: reset, - resetActive: activeLow ? .low : .high, + bypass: &bypass, shiftName: shift, inputName: sin, outputName: sout, testName: test, tckName: tck, - invClockName: invClock, - ignoredInputs: ignoredInputs + invClockName: invClock ) let internalCount = internalOrder.reduce(0) { $0 + $1.width } @@ -656,16 +620,13 @@ extension Fault { module: module, blackboxModules: blackboxModules, bsrCreator: bsrCreator, - clockName: clock, - resetName: reset, - resetActive: activeLow ? .low : .high, + bypass: &bypass, shiftName: shift, inputName: sin, outputName: sout, testName: test, tckName: tck, invClockName: invClock, - ignoredInputs: ignoredInputs, internalOrder: internalOrder ) let finalCount = finalOrder.reduce(0) { $0 + $1.width } @@ -758,16 +719,16 @@ extension Fault { inputs: inputs, outputs: outputs, chainLength: finalOrder.reduce(0) { $0 + $1.width }, - clock: clock, + clock: bypass.clock, tck: tck, - reset: reset, + reset: bypass.reset.name, sin: sin, sout: sout, - resetActive: activeLow ? .low : .high, + resetActive: bypass.reset.active, shift: shift, test: test, output: netlist + ".tb.sv", - defines: defines, + defines: Set(defines), using: iverilogExecutable, with: vvpExecutable ) @@ -776,7 +737,7 @@ extension Fault { } else { print("Scan chain verification failed.") print("・Ensure that clock and reset signals, if they exist are passed as such to the program.") - if activeLow { + if !bypass.resetActiveLow { print("・Ensure that the reset is active high- pass --activeLow for activeLow.") } if internalOrder.count == 0 { diff --git a/Sources/Fault/Entries/common.swift b/Sources/Fault/Entries/common.swift new file mode 100644 index 0000000..5133516 --- /dev/null +++ b/Sources/Fault/Entries/common.swift @@ -0,0 +1,58 @@ +// Copyright (C) 2019-2024 The American University in Cairo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import ArgumentParser +import Collections + +struct Reset { + let name: String + let active: Simulator.Active +} + +struct BypassOptions: ParsableArguments { + @Option(name: [.long], help: "Inputs to bypass when performing operations. May be specified multiple times to bypass multiple inputs. Will be held high during simulations by default, unless =0 is appended to the option.") + var bypassing: [String] = [] + + @Option(help: "Clock name. In addition to being bypassed for certain manipulation operations, during simulations it will always be held high.") + var clock: String + + @Option(name: [.customLong("reset")], help: "Reset name. In addition to being bypassed for certain manipulation operations, during simulations it will always be held low.") + var resetName: String = "rst" + + @Flag(name: [.long, .customLong("activeLow")], help: "The reset signal is considered active-low insted, and will be held high during simulations.") + var resetActiveLow: Bool = false + + lazy var reset: Reset = { + return Reset(name: resetName, active: resetActiveLow ? .low : .high) + }() + + lazy var simulationValues: OrderedDictionary = { + var result: OrderedDictionary = [:] + result[clock] = .holdHigh + result[reset.name] = reset.active == .low ? .holdHigh : .holdLow + for bypassed in bypassing { + let split = bypassed.components(separatedBy: "=") + if split.count == 1 { + result[split[0]] = .holdHigh + } else if split.count == 2 { + result[split[0]] = split[1] == "0" ? .holdLow : .holdHigh + } + } + return result + }() + + lazy var bypassedInputs: Set = { + return Set(simulationValues.keys) + }() + +} diff --git a/Sources/Fault/Entries/compact.swift b/Sources/Fault/Entries/compact.swift deleted file mode 100644 index 3db3553..0000000 --- a/Sources/Fault/Entries/compact.swift +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (C) 2019 The American University in Cairo -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Defile -import Foundation -import PythonKit -import ArgumentParser -import Foundation - -extension Fault { - struct Compact: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Compact test vectors and optionally verify using given cell models and netlists." - ) - - @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .compacted.json)") - var output: String? - - @Option(name: [.short, .long, .customLong("cellModel")], help: "Verify compaction using given cell model.") - var cellModel: String? - - @Option(name: [.short, .long], help: "Verify compaction for the given netlist.") - var netlist: String? - - @Argument(help: "The file to process.") - var file: String - - func run() throws { - let fileManager = FileManager() - // Validate input file existence - guard fileManager.fileExists(atPath: file) else { - throw ValidationError("File '\(file)' not found.") - } - - // Validate cell model and netlist options - if let cellModel = cellModel { - guard fileManager.fileExists(atPath: cellModel) else { - throw ValidationError("Cell model file '\(cellModel)' not found.") - } - if !(cellModel.hasSuffix(".v") || cellModel.hasSuffix(".sv")) { - print("Warning: Cell model file provided does not end with .v or .sv.") - } - guard let _ = netlist else { - throw ValidationError("Error: The netlist must be provided to verify compaction.") - } - } - - if let netlist = netlist { - guard fileManager.fileExists(atPath: netlist) else { - throw ValidationError("Netlist file '\(netlist)' not found.") - } - guard let _ = cellModel else { - throw ValidationError("Error: The cell model must be provided to verify compaction.") - } - } - - let output = self.output ?? "\(file).compacted.json" - - // Parse JSON File - let data = try Data(contentsOf: URL(fileURLWithPath: file)) - let tvInfo = try JSONDecoder().decode(TVInfo.self, from: data) - - let compactedTV = Compactor.compact(coverageList: tvInfo.coverageList) - - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - - let jsonData = try encoder.encode( - TVInfo( - inputs: tvInfo.inputs, - outputs: tvInfo.outputs, - coverageList: compactedTV - ) - ) - - let jsonString = String(data: jsonData, encoding: .utf8) ?? "" - try File.open(output, mode: .write) { - try $0.print(jsonString) - } - - // // Verify compaction if cellModel is provided - // if let cellModel = cellModel { - // print("Running simulations using the compacted set…") - // let verifiedOutput = "\(output).verified.json" - // let mainArguments = [ - // CommandLine.arguments[0], - // "-c", cellModel, - // "-r", "10", - // "-v", "10", - // "-m", "100", - // "--tvSet", output, - // "-o", verifiedOutput, - // netlist - // ] - // } - } - } -} diff --git a/Sources/Fault/Entries/cut.swift b/Sources/Fault/Entries/cut.swift index 1ee6f28..00fea2c 100644 --- a/Sources/Fault/Entries/cut.swift +++ b/Sources/Fault/Entries/cut.swift @@ -40,8 +40,8 @@ extension Fault { @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") var blackboxModels: [String] = [] - @Option(name: [.short, .long], help: "Inputs to ignore on black-boxed macros. Comma-delimited.") - var ignoring: String? + @OptionGroup + var bypass: BypassOptions @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") var output: String? @@ -56,12 +56,7 @@ extension Fault { throw ValidationError("File '\(file)' not found.") } - let output = self.output ?? "\(file).cut.v" - - var ignoredInputs: Set = [] - if let ignoring = ignoring { - ignoredInputs = Set(ignoring.components(separatedBy: ",")) - } + let output = self.output ?? file.replacingExtension(".nl.v", with: ".cut.v") // MARK: Importing Python and Pyverilog @@ -124,7 +119,7 @@ extension Fault { if let dffinfo = getMatchingDFFInfo(from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch) { yank = true - let outputName = "\\" + instanceName + ".q" + let outputName = "\\" + instanceName + ".d" let inputIdentifier = Node.Identifier(instanceName) let outputIdentifier = Node.Identifier(outputName) var dArg: PythonObject? @@ -161,12 +156,12 @@ extension Fault { for hook in instance.portlist { let portName = String(describing: hook.portname) - if ignoredInputs.contains(portName) { + if bypass.simulationValues[portName] != nil { continue } let portInfo = blackboxModule.portsByName[portName]! - let ioName = "\\\(instanceName).\(portName)" + (portInfo.polarity == .input ? ".q" : "") + let ioName = "\\\(instanceName).\(portName)" + (portInfo.polarity == .input ? ".d" : "") let width = Node.Width(Node.IntConst(portInfo.from), Node.IntConst(portInfo.to)) let ioDeclaration = portInfo.polarity == .input ? Node.Output(ioName, width) : diff --git a/Sources/Fault/Entries/main.swift b/Sources/Fault/Entries/main.swift index 2fbf8db..bd6f6e7 100644 --- a/Sources/Fault/Entries/main.swift +++ b/Sources/Fault/Entries/main.swift @@ -36,6 +36,7 @@ _ = [ // Register all RNGs ] _ = [ // Register all TVGens Atalanta.registered, + Quaigh.registered, PODEM.registered, ] @@ -73,7 +74,7 @@ let pythonVersions = { struct Fault: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Open-source EDA's missing DFT Toolchain", - subcommands: [ATPG.self, Cut.self, Synth.self, Assemble.self, Compact.self, Tap.self, Bench.self, Chain.self], + subcommands: [ATPG.self, Cut.self, Synth.self, Assemble.self, Tap.self, Chain.self], defaultSubcommand: ATPG.self ) } diff --git a/Sources/Fault/Entries/tap.swift b/Sources/Fault/Entries/tap.swift index c9abc4d..7fd618f 100644 --- a/Sources/Fault/Entries/tap.swift +++ b/Sources/Fault/Entries/tap.swift @@ -32,14 +32,8 @@ extension Fault { @Option(name: [.short, .long, .customLong("cellModel")], help: "Verify JTAG port using given cell model.") var cellModel: String? - @Option(name: [.long], help: "Clock signal of core logic to use in simulation. (Required)") - var clock: String - - @Option(name: [.long], help: "Reset signal of core logic to use in simulation. (Required)") - var reset: String - - @Flag(name: [.long, .customLong("activeLow")], help: "Reset signal of core logic is active low instead of active high.") - var activeLow: Bool = false + @OptionGroup + var bypass: BypassOptions @Option(name: [.short, .long], help: "Liberty file. (Required.)") var liberty: String @@ -50,9 +44,6 @@ extension Fault { @Option(name: [.short, .long], help: ".bin file for golden output. Required iff testVectors is provided.") var goldenOutput: String? - @Option(name: [.short, .long], help: "Inputs to ignore. May be specified multiple times.") - var ignoring: [String] = [] - @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") var blackboxModels: [String] = [] @@ -106,16 +97,10 @@ extension Fault { } let (_, boundaryCount, internalCount) = ChainMetadata.extract(file: file) - - var ignoredInputs - = Set(ignoring) let defines = Set(defines) - - ignoredInputs.insert(clock) - ignoredInputs.insert(reset) - + if !fileManager.fileExists(atPath: liberty) { throw ValidationError("Liberty file '\(liberty)' not found.") } @@ -151,8 +136,8 @@ extension Fault { } } - let output = output ?? "\(file).jtag.v" - let intermediate = output + ".intermediate.v" + let output = output ?? file.replacingExtension(".chained.v", with: ".jtag.v") // "\(file).jtag.v" + let intermediate = output.replacingExtension(".jtag.v", with: ".jtag_intermediate.v") // MARK: Importing Python and Pyverilog @@ -426,9 +411,9 @@ extension Fault { inputs: myInputs, outputs: myOutputs, chainLength: boundaryCount + internalCount, - clock: clock, - reset: reset, - resetActive: activeLow ? .low : .high, + clock: bypass.clock, + reset: bypass.reset.name, + resetActive: bypass.reset.active, tms: tms, tdi: tdi, tck: tck, @@ -445,7 +430,7 @@ extension Fault { } else { print("Tap port verification failed.") print("・Ensure that clock and reset signals, if they exist are passed as such to the program.") - if !activeLow { + if bypass.reset.active == .high { print("・Ensure that the reset is active high- pass --activeLow for activeLow.") } print("・Ensure that there are no other asynchronous resets anywhere in the circuit.") @@ -455,11 +440,6 @@ extension Fault { if let tvFile = testVectors { print("Generating testbench for test vectors…") - let behavior - = [Simulator.Behavior]( - repeating: .holdHigh, - count: ignoredInputs.count - ) let (vectorCount, vectorLength) = binMetadata.extract(file: tvFile) let (_, outputLength) = binMetadata.extract(file: goldenOutput!) let testbecnh = netlist + ".tv" + ".tb.sv" @@ -469,12 +449,11 @@ extension Fault { with: models, ports: myPorts, inputs: myInputs, - ignoring: ignoredInputs, - behavior: behavior, + bypassingWithBehavior: bypass.simulationValues, outputs: myOutputs, - clock: clock, - reset: reset, - resetActive: activeLow ? .low : .high, + clock: bypass.clock, + reset: bypass.reset.name, + resetActive: bypass.reset.active, tms: tms, tdi: tdi, tck: tck, @@ -495,8 +474,8 @@ extension Fault { print("Test vectors verified successfully.") } else { print("Test vector simulation failed.") - if !activeLow { // default is ignored inputs are held high - print("・Ensure that ignored inputs in the simulation are held low. Pass --holdLow if reset is active high.") + if bypass.reset.active == .high {// default is ignored inputs are held high + print("・The reset is assumed active-high and thus held low. Pass --activeLow if reset is active low.") } Foundation.exit(EX_DATAERR) } diff --git a/Sources/Fault/Module.swift b/Sources/Fault/Module.swift index 5e0fe94..846bb2c 100644 --- a/Sources/Fault/Module.swift +++ b/Sources/Fault/Module.swift @@ -147,7 +147,7 @@ struct Port: Codable { extension Port: CustomStringConvertible { var description: String { - "Port(\(name): \(polarity ?? .unknown)[\(from)..\(to)])" + "Port@\(ordinal)(\(name): \(polarity ?? .unknown)[\(from)..\(to)])" } } diff --git a/Sources/Fault/Simulation.swift b/Sources/Fault/Simulation.swift index 442ef83..ab2254e 100644 --- a/Sources/Fault/Simulation.swift +++ b/Sources/Fault/Simulation.swift @@ -15,18 +15,16 @@ import BigInt import Defile import Foundation +import Collections import PythonKit -class CoverageMeta: Codable { +struct CoverageMeta: Codable { let ratio: Float + let faultPoints: [String] let sa0Covered: [String] let sa1Covered: [String] - - init(ratio: Float, sa0Covered: [String], sa1Covered: [String]) { - self.ratio = ratio - self.sa0Covered = sa0Covered - self.sa1Covered = sa1Covered - } + let sa0Uncovered: [String] + let sa1Uncovered: [String] } enum Simulator { @@ -43,8 +41,7 @@ enum Simulator { with models: [String], ports: [String: Port], inputs: [Port], - ignoring ignoredInputs: Set, - behavior: [Behavior], + bypassingWithBehavior: OrderedDictionary, outputs: [Port], stuckAt: Int, cleanUp: Bool, @@ -83,11 +80,16 @@ enum Simulator { inputList += "\(name) , " } - for (i, rawName) in ignoredInputs.enumerated() { + + for (rawName, behavior) in bypassingWithBehavior { + guard ports[rawName] != nil else { + continue // black-box module ignore probably + } + let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - inputAssignment += " \(name) = \(behavior[i].rawValue) ;\n" - inputAssignment += " \(name).gm = \(behavior[i].rawValue) ;\n" + inputAssignment += " \(name) = \(behavior.rawValue) ;\n" + inputAssignment += " \(name).gm = \(behavior.rawValue) ;\n" } fmtString = String(fmtString.dropLast(1)) @@ -224,8 +226,7 @@ enum Simulator { with models: [String], ports: [String: Port], inputs: [Port], - ignoring ignoredInputs: Set = [], - behavior: [Behavior] = [], + bypassingWithBehavior: OrderedDictionary, outputs: [Port], initialVectorCount: Int, incrementingBy increment: Int, @@ -318,8 +319,7 @@ enum Simulator { with: models, ports: ports, inputs: inputs, - ignoring: ignoredInputs, - behavior: behavior, + bypassingWithBehavior: bypassingWithBehavior, outputs: outputs, stuckAt: 0, cleanUp: !sampleRun, @@ -340,8 +340,7 @@ enum Simulator { with: models, ports: ports, inputs: inputs, - ignoring: ignoredInputs, - behavior: behavior, + bypassingWithBehavior: bypassingWithBehavior, outputs: outputs, stuckAt: 1, cleanUp: !sampleRun, @@ -396,8 +395,11 @@ enum Simulator { coverageList: coverageList, coverageMeta: CoverageMeta( ratio: coverage, + faultPoints: [String](faultPoints), sa0Covered: sa0Covered.sorted(), - sa1Covered: sa1Covered.sorted() + sa1Covered: sa1Covered.sorted(), + sa0Uncovered: faultPoints.filter { !sa0Covered.contains($0) }, + sa1Uncovered: faultPoints.filter { !sa0Covered.contains($0) } ) ) } @@ -672,8 +674,7 @@ enum Simulator { with models: [String], ports: [String: Port], inputs: [Port], - ignoring ignoredInputs: Set, - behavior: [Behavior], + bypassingWithBehavior: OrderedDictionary, outputs _: [Port], clock: String, reset: String, @@ -703,9 +704,12 @@ enum Simulator { } var inputAssignment = "" - for (i, rawName) in ignoredInputs.enumerated() { + for (rawName, behavior) in bypassingWithBehavior { + guard ports[rawName] != nil else { + continue // black-box module ignore probably + } let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - inputAssignment += " \(name) = \(behavior[i].rawValue) ;\n" + inputAssignment += " \(name) = \(behavior.rawValue) ;\n" } let tapPorts = [tck, trst, tdi] diff --git a/Sources/Fault/String.swift b/Sources/Fault/String.swift index 108d777..2ffea4e 100644 --- a/Sources/Fault/String.swift +++ b/Sources/Fault/String.swift @@ -61,6 +61,17 @@ extension String { return task.terminationStatus } + + func replacingExtension(_ before: String, with after: String) -> String { + var result = self + if result.hasSuffix(before) { + result = result.replacingOccurrences(of: before, with: after) + } + if !result.hasSuffix(after) { + result += after + } + return result + } } extension String: Error {} diff --git a/Sources/Fault/Synthesis.swift b/Sources/Fault/Synthesis.swift index 2490ec9..01d47b2 100644 --- a/Sources/Fault/Synthesis.swift +++ b/Sources/Fault/Synthesis.swift @@ -70,8 +70,9 @@ enum Synthesis { # names # autoname - write_verilog -noexpr \(output)+attrs - write_verilog -noexpr -noattr \(output) + write_verilog -noexpr -nohex -nodec -defparam \(output)+attrs + write_verilog -noexpr -noattr -noexpr -nohex -nodec -defparam \(output) + # write_blif -gates -unbuf DFFSR D Q \(output).blif """ } } diff --git a/Sources/Fault/TVGenerator.swift b/Sources/Fault/TVGenerator.swift index 48d41b4..b3def59 100644 --- a/Sources/Fault/TVGenerator.swift +++ b/Sources/Fault/TVGenerator.swift @@ -245,15 +245,7 @@ class Atalanta: ExternalTestVectorGenerator { required init() {} func generate(file: String, module: String) -> ([TestVector], [Port]) { - let tempDir = "\(NSTemporaryDirectory())" - - let folderName = "\(tempDir)thr\(Unmanaged.passUnretained(Thread.current).toOpaque())" - try? FileManager.default.createDirectory(atPath: folderName, withIntermediateDirectories: true, attributes: nil) - defer { - try? FileManager.default.removeItem(atPath: folderName) - } - - let output = "\(folderName)/\(module).test" + let output = file.replacingExtension(".bench", with: ".test") let atalanta = "atalanta -t \(output) \(file)".sh() if atalanta != EX_OK { @@ -261,7 +253,7 @@ class Atalanta: ExternalTestVectorGenerator { } do { - let (testvectors, inputs) = try TVSet.readFromTest(file: output) + let (testvectors, inputs) = try TVSet.readFromTest(output, withInputsFrom: file) return (vectors: testvectors, inputs: inputs) } catch { Stderr.print("Internal software error: \(error)") @@ -272,6 +264,29 @@ class Atalanta: ExternalTestVectorGenerator { static let registered = ETVGFactory.register(name: "Atalanta", type: Atalanta.self) } +class Quaigh: ExternalTestVectorGenerator { + required init() {} + + func generate(file: String, module: String) -> ([TestVector], [Port]) { + let output = file.replacingExtension(".bench", with: ".test") + let quaigh = "quaigh atpg \(file) -o \(output)".sh() + + if quaigh != EX_OK { + exit(quaigh) + } + + do { + let (testvectors, inputs) = try TVSet.readFromTest(output, withInputsFrom: file) + return (vectors: testvectors, inputs: inputs) + } catch { + Stderr.print("Internal software error: \(error)") + exit(EX_SOFTWARE) + } + } + + static let registered = ETVGFactory.register(name: "Quaigh", type: Quaigh.self) +} + class PODEM: ExternalTestVectorGenerator { required init() {} @@ -285,7 +300,7 @@ class PODEM: ExternalTestVectorGenerator { } let output = "\(folderName)/\(module).out" - let podem = "atpg -output \(output) \(file) > /dev/null 2>&1".sh() + let podem = "atpg-podem -output \(output) \(file) > /dev/null 2>&1".sh() if podem != EX_OK { exit(podem) diff --git a/Sources/Fault/TestVector.swift b/Sources/Fault/TestVector.swift index 630fbef..46fae12 100644 --- a/Sources/Fault/TestVector.swift +++ b/Sources/Fault/TestVector.swift @@ -15,6 +15,7 @@ import BigInt import Defile import Foundation +import Collections typealias TestVector = [BigUInt] @@ -123,92 +124,40 @@ enum TVSet { return (vectors: vectors, inputs: inputs) } - static func readFromTest(file: String) throws -> ([TestVector], [Port]) { - var vectors: [TestVector] = [] - var inputs: [Port] = [] - do { - let string = try String(contentsOf: URL(fileURLWithPath: file), encoding: .utf8) - - let inputPattern = "(?s)(?<=\\* Primary inputs :).*?(?=\\* Primary outputs:)" - let tvPattern = "(?s)(?<=: ).*?(?= [0-1]*)" - let multibitPattern = "(?.*).{1}(?<=\\[)(?[0-9]+)(?=\\])" - - var inputResult = "" - if let range = string.range(of: inputPattern, options: .regularExpression) { - inputResult = String(string[range]) - inputResult = inputResult.trimmingCharacters(in: .whitespacesAndNewlines) + static func readFromTest(_ file: String, withInputsFrom bench: String) throws -> ([TestVector], [Port]) { + var inputDict: OrderedDictionary = [:] + let benchStr = File.read(bench)! + let inputRx = #/INPUT\(([^\(\)]+?)(\[\d+\])?\)/# + var ordinal = -1 + for line in benchStr.components(separatedBy: "\n") { + if let match = try? inputRx.firstMatch(in: line) { + let name = String(match.1) + inputDict[name] = inputDict[name] ?? Port(name: name, polarity: .input, from: 0, to: -1, at: { ordinal += 1; return ordinal }()) + inputDict[name]!.to += 1 } - - let ports = inputResult.components(separatedBy: " ") - let multiBitRegex = try NSRegularExpression(pattern: multibitPattern) - - var multiBitPorts: [String: Port] = [:] - var portName = "", bitNumber = 0 - - var count = 0 - for port in ports { - if !port.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - if let match = multiBitRegex.firstMatch(in: port, options: [], range: NSRange(location: 0, length: port.utf16.count)) { - if let nameRange = Range(match.range(at: 1), in: port) { - portName = String(port[nameRange]) - let exists = multiBitPorts[portName] != nil - if !exists { - multiBitPorts[portName] = Port(name: portName, at: count) - multiBitPorts[portName]!.from = 0 - } - } - if let bitRange = Range(match.range(at: 2), in: port) { - bitNumber = Int(port[bitRange])! - multiBitPorts[portName]!.to = bitNumber - } - } else { - inputs.append(Port(name: port, at: count)) - } - count += 1 + } + + let inputs: [Port] = inputDict.values.sorted { $0.ordinal < $1.ordinal } + var vectors: [TestVector] = [] + let testStr = File.read(file)! + let tvRx = #/(\d+):\s*([01]+)/# + for line in testStr.components(separatedBy: "\n") { + if let match = try? tvRx.firstMatch(in: line) { + let vectorStr = match.2 + guard var vectorCat = BigUInt(String(vectorStr.reversed()), radix: 2) else { + Stderr.print("Failed to parse test vector in .test file: \(vectorStr)") + exit(EX_DATAERR) } - } - - var vectorSlices: [Range] = [] - for port in multiBitPorts.values { - inputs.append(port) - vectorSlices.append(port.ordinal ..< (port.to + port.ordinal) + 1) - } - vectorSlices.sort { $0.lowerBound < $1.lowerBound } - - let vectorRegex = try NSRegularExpression(pattern: tvPattern) - let range = NSRange(string.startIndex..., in: string) - let results = vectorRegex.matches(in: string, range: range) - let matches = results.map { String(string[Range($0.range, in: string)!]) } - - inputs.sort { $0.ordinal < $1.ordinal } - - for match in matches { - let vector = Array(match) - if vector.count != 0 { - var testVector: TestVector = [] - var start = 0 - for slice in vectorSlices { - let lowerVec = vector[start ..< slice.lowerBound].map { BigUInt(String($0), radix: 2)! } - if lowerVec.count != 0 { - testVector.append(contentsOf: lowerVec) - } - let middleVec = BigUInt(String(vector[slice]), radix: 2)! - testVector.append(middleVec) - - start = slice.upperBound - } - - if start < vector.count { - let remVector = vector[start...].map { BigUInt(String($0), radix: 2)! } - testVector.append(contentsOf: remVector) - } - vectors.append(testVector) + let tv: TestVector = inputs.map { + input in + let value = vectorCat & ((1 << input.width) - 1) + vectorCat >>= input.width + return value } + vectors.append(tv) } - } catch { - exit(EX_DATAERR) } - return (vectos: vectors, inputs: inputs) + return (vectors: vectors, inputs: inputs) } } diff --git a/Tech/osu035/osu035_stdcells.v b/Tech/osu035/osu035_stdcells.v index 60ecad9..7222bd9 100755 --- a/Tech/osu035/osu035_stdcells.v +++ b/Tech/osu035/osu035_stdcells.v @@ -876,38 +876,6 @@ output DI ; endmodule `endcelldefine -`timescale 1ns/10ps -`celldefine -module PADINOUT (DO, OEN, DI, YPAD); -input DO ; -input OEN ; -output DI ; -inout YPAD ; - - bufif1 (YPAD, DO, OEN); - buf (DI, YPAD); - - specify - // delay parameters - specparam - tpllh$DO$YPAD = 0.59:0.59:0.59, - tphhl$DO$YPAD = 0.59:0.59:0.59, - tpzh$OEN$YPAD = 1.1:1.1:1.1, - tpzl$OEN$YPAD = 0.65:0.65:0.65, - tplz$OEN$YPAD = 0.82:0.82:0.82, - tphz$OEN$YPAD = 1.5:1.5:1.5, - tpllh$YPAD$DI = 0.15:0.15:0.15, - tphhl$YPAD$DI = 0.17:0.17:0.17; - - // path delays - (DO *> YPAD) = (tpllh$DO$YPAD, tphhl$DO$YPAD); - (OEN *> YPAD) = (0, 0, tplz$OEN$YPAD, tpzh$OEN$YPAD, tphz$OEN$YPAD, tpzl$OEN$YPAD); - (YPAD *> DI) = (tpllh$YPAD$DI, tphhl$YPAD$DI); - - endspecify - -endmodule -`endcelldefine `timescale 1ns/10ps `celldefine diff --git a/Tests/FaultTests/FaultTests.swift b/Tests/FaultTests/FaultTests.swift deleted file mode 100644 index 15bd614..0000000 --- a/Tests/FaultTests/FaultTests.swift +++ /dev/null @@ -1,162 +0,0 @@ -import class Foundation.Bundle -import XCTest - -var env = ProcessInfo.processInfo.environment - -extension Process { - func startAndBlock() throws { - log("$ \(executableURL!.path) \((arguments ?? []).joined(separator: " "))") - launch() - waitUntilExit() - print("Exited with: \(terminationStatus)") - } -} - -extension String { - func shOutput() -> (terminationStatus: Int32, output: String) { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/env") - task.arguments = ["sh", "-c", self] - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = pipe - - do { - try task.run() - } catch { - print("Could not launch task `\(self)': \(error)") - exit(EX_UNAVAILABLE) - } - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - task.waitUntilExit() - let output = String(data: data, encoding: .utf8) - - return (terminationStatus: task.terminationStatus, output: output!) - } -} - -func log(_ string: String) { - print(string) - fflush(stdout) -} - -final class FaultTests: XCTestCase { - func run(scl: String, steps: [[String]]) throws { - let binary = productsDirectory.appendingPathComponent("Fault") - for (i, step) in steps.enumerated() { - log("\(scl): \(i)/6") - let process = Process() - process.executableURL = binary - process.arguments = step - try process.startAndBlock() - - XCTAssertEqual(process.terminationStatus, 0) - if process.terminationStatus != 0 { - return - } - } - } - - func testSPM() throws { - /* - This test runs the default SPM design, which is an all-digital - active-low design. - */ - - // Fault Tests - for (scl, liberty, models, config) in [ - ("osu035", "Tech/osu035/osu035_stdcells.lib", "Tech/osu035/osu035_stdcells.v", "Tech/osu035/config.yml"), - // ("sky130_fd_sc_hd", "Tech/sky130_fd_sc_hd/sky130_fd_sc_hd__trimmed.lib", "Tech/sky130_fd_sc_hd/sky130_fd_sc_hd.v", "Tech/sky130_fd_sc_hd/config.yml"), - ] { - let fileName = "Tests/RTL/spm/spm.v" - let topModule = "spm" - let clock = "clk" - let reset = "rst" - let ignoredInputs = "\(reset)" - - let base = "Netlists/" + NSString(string: fileName).deletingPathExtension - let fileSynth = base + ".nl.v" - let fileCut = base + ".cut.v" - let fileJson = base + ".tv.json" - let fileChained = base + ".chained.v" - let fileAsmVec = fileJson + ".vec.bin" - let fileAsmOut = fileJson + ".out.bin" - - let fileManager = FileManager() - for file in [fileSynth, fileCut, fileJson, fileChained, fileAsmVec, fileAsmOut] { - try? fileManager.removeItem(atPath: file) - } - - try run(scl: scl, steps: [ - ["synth", "-l", liberty, "-t", topModule, "-o", fileSynth, fileName], - ["cut", "-o", fileCut, "--sclConfig", config, fileSynth], - ["-c", models, "-i", ignoredInputs, "--clock", clock, "-o", fileJson, fileCut], - ["chain", "-c", models, "-l", liberty, "-o", fileChained, "--clock", clock, "--reset", reset, "--activeLow", "-i", ignoredInputs, "--sclConfig", config, fileSynth], - ["asm", fileJson, fileChained], - ["compact", "-o", "/dev/null", fileJson], - ["tap", fileChained, "-c", models, "--clock", clock, "--reset", reset, "--activeLow", "-l", liberty, "-t", fileAsmVec, "-g", fileAsmOut, "-i", ignoredInputs], - ]) - - } - } - - func testIntegration() throws { - /* - This test runs the TripleDelay design, which has blackboxed macros. - */ - - // Fault Tests - let liberty = "Tech/osu035/osu035_stdcells.lib" - let models = "Tech/osu035/osu035_stdcells.v" - - let fileName = "Tests/RTL/integration/triple_delay.v" - let topModule = "TripleDelay" - let clock = "clk" - let reset = "rst" - let ignoredInputs = "\(reset),rstn" - - let base = "Netlists/" + NSString(string: fileName).deletingPathExtension - let fileSynth = base + ".nl.v" - let fileCut = base + ".cut.v" - let fileJson = base + ".tv.json" - let faultPointsYML = base + ".fault_points.yml" - let coverageYml = base + ".coverage_meta.yml" - let fileChained = base + ".chained.v" - let fileAsmVec = fileJson + ".vec.bin" - let fileAsmOut = fileJson + ".out.bin" - - let fileManager = FileManager() - for file in [fileSynth, fileCut, fileJson, fileChained, fileAsmVec, fileAsmOut] { - try? fileManager.removeItem(atPath: file) - } - - try run(scl: "osu035", steps: [ - ["synth", "-l", liberty, "-t", topModule, "-o", fileSynth, "--blackboxModel", "Tests/RTL/integration/buffered_inverter.v", fileName], - ["cut", "-o", fileCut, "--blackbox", "BufferedInverter", "--blackboxModel", "Tests/RTL/integration/buffered_inverter.v", "--ignoring", "clk,rst,rstn", fileSynth], - ["-c", models, "-i", reset, "--clock", clock, "-o", fileJson, "--output-faultPoints", faultPointsYML, "--output-covered", coverageYml, fileCut], - ["chain", "-c", models, "-l", liberty, "-o", fileChained, "--clock", clock, "--reset", reset, "--activeLow", "-i", ignoredInputs, fileSynth, "--blackbox", "BufferedInverter", "--blackboxModel", "Tests/RTL/integration/buffered_inverter.v"], - ["asm", fileJson, fileChained], - ["compact", "-o", "/dev/null", fileJson], - ["tap", fileChained, "-c", models, "--clock", clock, "--reset", reset, "--active-low", "-l", liberty, "-t", fileAsmVec, "-g", fileAsmOut, "--blackboxModel", "Tests/RTL/integration/buffered_inverter.v"], - ]) - } - - /// Returns path to the built products directory. - var productsDirectory: URL { - #if os(macOS) - for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { - return bundle.bundleURL.deletingLastPathComponent() - } - fatalError("couldn't find the products directory") - #else - return Bundle.main.bundleURL - #endif - } - - static var allTests = [ - ("testSPM", testSPM), - ("testIntegration", testIntegration), - ] -} diff --git a/Tests/FaultTests/XCTestManifests.swift b/Tests/FaultTests/XCTestManifests.swift deleted file mode 100644 index a2a0a47..0000000 --- a/Tests/FaultTests/XCTestManifests.swift +++ /dev/null @@ -1,9 +0,0 @@ -import XCTest - -#if !canImport(ObjectiveC) - public func allTests() -> [XCTestCaseEntry] { - [ - testCase(FaultTests.allTests), - ] - } -#endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 54e4d87..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,7 +0,0 @@ -import XCTest - -import FaultTests - -var tests = [XCTestCaseEntry]() -tests += FaultTests.allTests() -XCTMain(tests) diff --git a/Tests/RTL/spm/spm.v b/Tests/RTL/spm/spm.v index be02ce3..acd221a 100644 --- a/Tests/RTL/spm/spm.v +++ b/Tests/RTL/spm/spm.v @@ -22,7 +22,6 @@ module spm #(parameter bits=32) ( input x, input[bits-1: 0] a, output y, - output always_high, ); wire[bits: 0] y_chain; assign y_chain[0] = 0; @@ -43,8 +42,6 @@ module spm #(parameter bits=32) ( .y_in(y_chain[bits-1:0]), .y_out(y_chain[bits:1]) ); - - assign always_high = 1'b1; endmodule diff --git a/Tests/conftest.py b/Tests/conftest.py new file mode 100644 index 0000000..5298d55 --- /dev/null +++ b/Tests/conftest.py @@ -0,0 +1,7 @@ +import os +import pytest + + +def pytest_configure(): + __dir__ = os.path.dirname(os.path.abspath(__file__)) + pytest.root = os.path.dirname(__dir__) diff --git a/Tests/test_fault.py b/Tests/test_fault.py new file mode 100644 index 0000000..a0eccfc --- /dev/null +++ b/Tests/test_fault.py @@ -0,0 +1,270 @@ +import os +import shlex +import pytest +import subprocess + + +def run(label, steps): + for i, step in enumerate(steps): + print(f"\n=== {label}: Step {i + 1}/{len(steps)} ===\n") + if step[0] == "nl2bench": + cmd = step + else: + cmd = shlex.split(os.getenv("PYTEST_FAULT_BIN", "swift run fault")) + step + print(f"$ {shlex.join(cmd)}") + subprocess.check_call(cmd) + + +@pytest.mark.parametrize( + "liberty,models,config", + [ + pytest.param( + "Tech/osu035/osu035_stdcells.lib", + "Tech/osu035/osu035_stdcells.v", + "Tech/osu035/config.yml", + id="osu035", + ), + # ( + # "Tech/sky130_fd_sc_hd/sky130_fd_sc_hd__trimmed.lib", + # "Tech/sky130_fd_sc_hd/sky130_fd_sc_hd.v", + # "Tech/sky130_fd_sc_hd/config.yml", + # id="sky130_fd_sc_hd", + # ), + ], +) +@pytest.mark.parametrize("atpg", ["PRNG", "Quaigh"]) +@pytest.mark.parametrize( + "fileName,topModule,clock,reset,other_bypassed,activeLow", + [ + pytest.param( + "Tests/RTL/spm/spm.v", + "spm", + "clk", + "rst", + [], + True, + id="spm", + ), + pytest.param( + "Benchmarks/ISCAS_89/s27.v", + "s27", + "CK", + "reset", + ["VDD=1", "GND=0"], + False, + id="s27", + ), + # pytest.param( + # "Benchmarks/ISCAS_89/s344.v", + # "s344", + # "CK", + # None, + # ["VDD=1", "GND=0"], + # False, + # id="s344", + # ), + ], +) +def test_flat( + request, + liberty, + models, + config, + fileName, + topModule, + clock, + reset, + other_bypassed, + activeLow, + atpg, +): + base = os.path.splitext("Netlists/" + fileName)[0] + fileSynth = base + ".nl.v" + fileCut = base + ".cut.v" + fileJson = base + ".tv.json" + fileBench = base + ".bench" + fileChained = base + ".chained.v" + fileAsmVec = base + ".tv.bin" + fileAsmOut = base + ".au.bin" + + bypassOptions = ["--clock", clock] + (["--activeLow"] if activeLow else []) + if reset is not None: + bypassOptions.append("--reset") + bypassOptions.append(reset) + for bypassed in other_bypassed: + bypassOptions.append("--bypassing") + bypassOptions.append(bypassed) + + for file in [fileSynth, fileCut, fileJson, fileChained, fileAsmVec, fileAsmOut]: + try: + os.remove(file) + except OSError: + pass + + atpg_options = [] + if atpg != "PRNG": + atpg_options = ["-g", atpg, "-b", fileBench] + + run( + request.node.name, + [ + ["synth", "-l", liberty, "-t", topModule, "-o", fileSynth, fileName], + ["cut", "-o", fileCut, "--sclConfig", config, fileSynth] + bypassOptions, + [ + "nl2bench", + "-o", + fileBench, + "-l", + liberty, + fileCut, + ], + [ + "atpg", + "-c", + models, + "-o", + fileJson, + fileCut, + "--output-coverage-metadata", + base + f".{atpg}.coverage.yml", + ] + + bypassOptions + + atpg_options, + [ + "chain", + "-c", + models, + "-l", + liberty, + "-o", + fileChained, + "--sclConfig", + config, + fileSynth, + ] + + bypassOptions, + ["asm", fileJson, fileChained], + [ + "tap", + fileChained, + "-c", + models, + "-l", + liberty, + "-t", + fileAsmVec, + "-g", + fileAsmOut, + ] + + bypassOptions, + ], + ) + + +def test_integration(): + liberty = "Tech/osu035/osu035_stdcells.lib" + models = "Tech/osu035/osu035_stdcells.v" + + fileName = "Tests/RTL/integration/triple_delay.v" + topModule = "TripleDelay" + clock = "clk" + reset = "rst" + + base = os.path.splitext("Netlists/" + fileName)[0] + fileSynth = base + ".nl.v" + fileCut = base + ".cut.v" + fileJson = base + ".tv.json" + faultPointsYML = base + ".fault_points.yml" + coverageYml = base + ".coverage_meta.yml" + fileChained = base + ".chained.v" + fileAsmVec = base + ".tv.bin" + fileAsmOut = base + ".au.bin" + + for file in [fileSynth, fileCut, fileJson, fileChained, fileAsmVec, fileAsmOut]: + try: + os.remove(file) + except OSError: + pass + + bypassOptions = [ + "--clock", + clock, + "--reset", + reset, + "--bypassing", + "rstn", + "--activeLow", + ] + + run( + "osu035", + [ + [ + "synth", + "-l", + liberty, + "-t", + topModule, + "-o", + fileSynth, + "--blackboxModel", + "Tests/RTL/integration/buffered_inverter.v", + fileName, + ], + [ + "cut", + "-o", + fileCut, + "--blackbox", + "BufferedInverter", + "--blackboxModel", + "Tests/RTL/integration/buffered_inverter.v", + fileSynth, + ] + + bypassOptions, + [ + "atpg", + "-c", + models, + "-o", + fileJson, + "--output-faultPoints", + faultPointsYML, + "--output-covered", + coverageYml, + fileCut, + ] + + bypassOptions, + [ + "chain", + "-c", + models, + "-l", + liberty, + "-o", + fileChained, + fileSynth, + "--blackbox", + "BufferedInverter", + "--blackboxModel", + "Tests/RTL/integration/buffered_inverter.v", + ] + + bypassOptions, + ["asm", fileJson, fileChained], + [ + "tap", + fileChained, + "-c", + models, + "-l", + liberty, + "-t", + fileAsmVec, + "-g", + fileAsmOut, + "--blackboxModel", + "Tests/RTL/integration/buffered_inverter.v", + ] + + bypassOptions, + ], + ) diff --git a/atalanta_podem_build.swift b/atalanta_podem_build.swift deleted file mode 100755 index b855e50..0000000 --- a/atalanta_podem_build.swift +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env swift -import Foundation - -extension String { - func sh() -> Int32 { - let task = Process() - task.launchPath = "/usr/bin/env" - task.arguments = ["sh", "-c", self] - task.launch() - task.waitUntilExit() - return task.terminationStatus - } -} - -func main() -> Int32 { - let apt = "which apt-get".sh() - if apt == 0 { - let _ = "sudo apt-get install -y make flex bison libreadline-dev libncurses5-dev libncursesw5-dev ".sh() - } - - let env = ProcessInfo.processInfo.environment - - let execPrefix = env["EXEC_PREFIX"] ?? "/usr/local/bin" - - let previousCWD = env["PWD"]! - - let atalanta = installAtalanta(execPrefix: execPrefix, previousCWD: previousCWD) - if atalanta != 0 { - let _ = "echo Failed to install Atalanta".sh() - return atalanta - } - - let podem = installPODEM(execPrefix: execPrefix, previousCWD: previousCWD) - - if podem != 0 { - let _ = "echo Failed to install PODEM".sh() - return podem - } - - return EX_OK -} - -func installAtalanta(execPrefix: String, previousCWD: String) -> Int32 { - defer { - chdir(previousCWD) - } - - let downloadAndExtract = "curl -sL https://github.com/hsluoyz/Atalanta/archive/master.tar.gz | tar -xzf -".sh() - if downloadAndExtract != 0 { - return downloadAndExtract - } - - chdir("Atalanta-master") - - let make = "make".sh() - if make != 0 { - return make - } - - let copy = "sudo cp atalanta \(execPrefix)".sh() - if copy != 0 { - return copy - } - return EX_OK -} - -func installPODEM(execPrefix: String, previousCWD: String) -> Int32 { - defer { - chdir(previousCWD) - } - - let downloadAndExtract = "curl -sL http://tiger.ee.nctu.edu.tw/course/Testing2018/assignments/hw0/podem.tgz | tar -xzf -".sh() - if downloadAndExtract != 0 { - return downloadAndExtract - } - - chdir("podem") - - let make = "make".sh() - if make != 0 { - return make - } - - let copy = "sudo cp atpg \(execPrefix)".sh() - if copy != 0 { - return copy - } - return EX_OK -} - -exit(main()) diff --git a/default.nix b/default.nix index fd3c7e8..1f0997a 100644 --- a/default.nix +++ b/default.nix @@ -9,6 +9,7 @@ yosys, verilog, quaigh, + nl2bench, ncurses, makeBinaryWrapper, }: @@ -22,7 +23,6 @@ stdenv.mkDerivation (finalAttrs: { src = nix-gitignore.gitignoreSourcePure ./.gitignore ./.; - nativeBuildInputs = [ swift swiftpm makeBinaryWrapper ]; swiftpmFlags = [ "--verbose" ]; @@ -38,25 +38,31 @@ stdenv.mkDerivation (finalAttrs: { # "-Xswiftc" # "x86_64-apple-macosx11" # ]; + nativeBuildInputs = [ swift swiftpm makeBinaryWrapper ]; + + buildInputs = with swiftPackages; [ + Foundation + XCTest + ] ++ lib.lists.optional (!stdenv.isDarwin) [Dispatch]; propagatedBuildInputs = [ pyenv yosys verilog quaigh + nl2bench ]; - buildInputs = with swiftPackages; [ - Foundation - XCTest - ] ++ lib.lists.optional (!stdenv.isDarwin) [Dispatch]; + nativeCheckInputs = with python3.pkgs; [ + pytest + ]; configurePhase = generated.configure; installPhase = '' binPath="$(swiftpmBinPath)" mkdir -p $out/bin - cp $binPath/Fault $out/bin/fault + cp $binPath/fault $out/bin/fault ''; # This doesn't work on Linux otherwise and I don't know why. @@ -64,9 +70,9 @@ stdenv.mkDerivation (finalAttrs: { export LD_LIBRARY_PATH=${swiftPackages.Dispatch}/lib:$LD_LIBRARY_PATH ''; - doCheck = !swiftPackages.stdenv.isDarwin; + doCheck = true; - preCheck = '' + faultEnv = '' export PYTHONPATH=${pyenv}/${pyenv.sitePackages} export PATH=${verilog}/bin:$PATH export PATH=${yosys}/bin:$PATH @@ -75,9 +81,8 @@ stdenv.mkDerivation (finalAttrs: { ''; checkPhase = '' - ${finalAttrs.preCheck} - echo $PATH - swift test + ${finalAttrs.faultEnv} + PYTEST_FAULT_BIN="$(swiftpmBinPath)/fault" pytest ''; fixupPhase = '' @@ -96,5 +101,5 @@ stdenv.mkDerivation (finalAttrs: { platforms = platforms.linux ++ platforms.darwin; }; - shellHook = finalAttrs.preCheck + finalAttrs.preBuild; + shellHook = finalAttrs.faultEnv + finalAttrs.preBuild; }) diff --git a/docs/Source/index.md b/docs/Source/index.md index 0e3ec00..cd64154 100644 --- a/docs/Source/index.md +++ b/docs/Source/index.md @@ -24,13 +24,7 @@ be skipped if the user wants to run their own synthesis script. Cut removes the flip flops from the flatten netlist and converts it into a pure combinational design. -## 3. Bench - -Bench converts a flatten netlist to the ISCAS bench format by mapping the -netlist cells to primitive gates. This option is needed only, if atalanta is set -as the test vector generator in the Main option. - -## 4. ATPG +## 3. ATPG ATPG is the main event. It runs Fault simulations for a generated test vector set using the stuck-at fault model. The test vector set could be supplied to the @@ -40,18 +34,16 @@ number generator or by using [Atalanta](https://github.com/hsluoyz/Atalanta). Fault supports two random number generators Swift system default generator and a linear feedback shift register (LFSR). -### 5. Compact - -Compact performs static test vector compaction on the generated test vector set. +ATPG also optimizes the test vector set by eliminating redundant vectors. -### 6. Chain +### 4. Chain Chain performs scan-chain stitching. Using [Pyverilog](https://github.com/PyHDI/Pyverilog), a boundary scan chain is constructed through a netlist's input and output ports. An internal register chain is also constructed through the netlist's D-flip-flops. -### 7. Tap +### 5. Tap Tap adds the [JTAG interface](https://opencores.org/websvn/listing?repname=adv_debug_sys&path=%2Fadv_debug_sys%2Ftrunk%2FHardware%2Fjtag%2Ftap%2Fdoc%2F#path_adv_debug_sys_trunk_Hardware_jtag_tap_doc_) diff --git a/docs/Source/installation.md b/docs/Source/installation.md index c4de9c2..532aab0 100644 --- a/docs/Source/installation.md +++ b/docs/Source/installation.md @@ -6,8 +6,6 @@ In order to use Fault, you have three options: - Using the Docker Image (Windows, macOS, Linux) - Bring-your-own-dependencies (macOS, Linux) -> Docker images for Fault are only available on x86-64 devices. - ## Using Nix Nix is a declarative utility that takes a unique approach to package management @@ -22,74 +20,9 @@ the `fault` command available in path, you can ## Docker -Docker is software working at the OS level that allows small environments called -"containers" to be run at-will. - -It works on Windows, macOS and Linux, where for the first two, a Linux virtual -machine is used. - -For instructions on how to install Docker, check -[Docker's website](https://docs.docker.com/install/). - -### Getting the Fault Docker image - -After installing Docker, run the following command in your terminal of choice: - -```sh -docker pull ghcr.io/aucohl/fault:latest -``` - -You can then run Fault commands using that image. For example, to run -`fault -V`: - -```sh -docker run -ti --rm ghcr.io/aucohl/fault:latest fault -V -``` - -This should print something like -`Fault X.X.X. ©The American University in Cairo 2019-2022. All rights reserved.`. -If you see that, you have successfully set the Fault environment up on your -machine. - -To use the current folder inside the Docker container, you need to add these -options to the command: - -```sh --v :/mount -w /mount -``` - -Obviously, replacing `` with your current path. For example, if -your current folder is `/Users/me/Code`, your options would be -`-v /Users/me/Code:/mount -w /mount`. - -This makes the final command: - -```sh -docker run -ti -v :/mount -w /mount --rm ghcr.io/cloud-v/fault:latest fault -V -``` - -### Tip on Unix-based systems - -You can set what is known a shell alias so you can avoid typing the long command -repeatedly. - -```sh -alias fault='docker run -tiv `pwd`:`pwd` -w `pwd` --rm ghcr.io/aucohl/fault:latest fault' -``` - -Then, you can invoke fault's options, by directly typing `Fault`. An equivalent -to the command above for example would be: - -``` -fault -V -``` - -Note that this command mounts the existing folder with an identical path, and -not to `/mount`. - -If you're on Windows, this section will not work as paths on that operating -system are incompatible with the Linux virtual machine. Not to mention, no -default Windows shells support bash aliases. +We no longer provide Docker images ourselves. We intend to work with +[IIC-OSIC-TOOLS](https://github.com/iic-jku/IIC-OSIC-TOOLS) to make Fault +available via Docker and will update this document when we do. ## Bring-your-own-dependencies @@ -119,26 +52,7 @@ are some pointers nevertheless. - You will need to set the environment variable `PYTHON_LIBARY` to point to the `.so`/`.dylib` file for Python. - [Pyverilog](https://github.com/pyverilog/pyverilog) -- Atalanta (Optional) -- PODEM (Optional) - -### OS-Specific Instructions - -#### macOS -macOS 12 or higher is required. - -Install the latest Xcode from the Mac App Store. - -Use [Homebrew](https://brew.sh). - -`brew install python yosys icarus-verilog` - -#### Ubuntu GNU/Linux - -Ubuntu 20.04 or higher is required. - -Using apt: - -`sudo apt-get install git clang python3 python3-dev python3-pip python3-venv yosys iverilog` - -Then install the Swift programming language: instructions are on [swift.org](https://swift.org/download/#using-downloads). + - [nl2bench](https://github.com/donn/nl2bench) (Required if using Quaigh or Atalanta) +- [Quaigh](https://github.com/coloquinte/quaigh) (Optional but really recommended) +- [Atalanta](https://github.com/hsluoyz/atalanta) (Optional) +- [NTU EE PODEM](https://github.com/donn/VLSI-Testing) (Optional) diff --git a/docs/Source/usage.md b/docs/Source/usage.md index 05fe58d..0f9474f 100644 --- a/docs/Source/usage.md +++ b/docs/Source/usage.md @@ -8,16 +8,19 @@ We will use the `s27` design which could be found in ## Assumptions -We also assume you've cloned the current repository and you are using it as your +We assume you've cloned the current repository and you are using it as your current working directory. +If you're using Nix without installing to `PATH`, replace `fault` with +`nix run .#fault --`. + ## Synthesis The first step is to generate a synthesized netlist for the `s27` module and we will use the `synth` command with the following options: ```bash - fault synth -l -t -o +fault synth -l -t [-o ]. ``` - `-l` : specifies the path to the liberty file of the standard cell library to @@ -31,11 +34,15 @@ will use the `synth` command with the following options: To generate the synthesized netlist, run : ```bash - fault synth -t s27 -l Tech/osu035/osu035_stdcells.lib Benchmarks/ISCAS_89/s27.v +fault synth\ + -t s27 \ + -l Tech/osu035/osu035_stdcells.lib \ + -o Netlists/s27.nl.v \ + Benchmarks/ISCAS_89/s27.v ``` This will run a yosys-based synthesis script and it will generate a flattened -netlist in the default path at `Netlists/s27.nl.v`. +netlist at `Netlists/s27.nl.v`. - For combinational circuits, add dummy clock and reset ports to the design. The clock port doesn't have to be connected to anything, necessarily, but will be @@ -48,19 +55,41 @@ is necessary for the fault simulations. (This step is not required for purely combinational designs.) ```bash - fault cut -o +fault cut [-o ] [--sclConfig ] + +``` + +```{note} Bypass Options +You will notice a new option called "bypass options." + +Bypass options are shared across multiple steps. They list signals that are +to be bypassed by the scan-chain insertion process. This includes but is not +limited to: +* Clocks +* Resets +* VDD +* GND + +The flags are as follows: +* `--clock`: The name of the clock signal +* `--reset`: The name of the active-high reset signal +* `--activeLow`: Sets the reset to active-low instgead +* `--bypassing NAME[=0|1]`: Additional signals to bypass. Bypassed signals are + held low during simulations, but by adding `=1`, they will be held high + instead. + ``` To generate the cut netlist, run: ```bash - fault cut Netlists/s27.nl.v +fault cut Netlists/s27.nl.v --clock CK --reset reset --bypassing VDD=1 --bypassing GND=0 ``` This will remove all the netlist flip flops converting it into a pure combinational netlist. The removed flip-flops will be exposed as input and output ports. The generated comb-only (only combinational logic) netlist default -path is: `Netlists/s27.nl.v.cut.v ` +path is: `Netlists/s27.cut.v` The comb-only netlist is then used for performing fault simulations in the next step. @@ -77,7 +106,7 @@ is increased if sufficient coverage isn't met. This is done by the following options: ```bash -fault -v -r -m --ceiling -c --clock --reset [--ignoring [--ignoring ] ] [ --activeLow] +fault [-v ] [-r ] [-m ] [--ceiling ] [-c ] ``` - `-v`: Number of the initially generated test vectors. @@ -96,14 +125,6 @@ fault -v -r -m --ceiling -o +nl2bench -o -l [-l [-l …]] ``` -- `-c`: Path of the cell models library. Fault converts the cell library to json - representation for easier cell extraction. So, if .json file is available from - previous runs, the file could be passed directly. +- `-l`: Path to the lib files. At least one must be specified, but + you may specify multiple. - `-o`: Path of the output bench netlist. Default is - ` + .bench` + ` + .bench -To generate bench netlist, invoke the following: +To generate a .bench netlist, invoke the following: -``` - fault bench -c Tech/osu035/osu035_stdcells.v Netlists/s27.nl.v.cut.v +```bash +nl2bench -o Netlists/s27.bench -l Tech/osu035/osu035_stdcells.lib Netlists/s27.cut.v ``` -This will generate the json representation for the osu035 cell library at: -`Tech/osu035/osu035_stdcells.v.json` which could be used for subsequent runs. - -The bench netlist will be generated at ` Netlists/s27.nl.v.cut.v.bench` - -After the bench netlist is created, we can generate test vectors using atalanta +After the bench netlist is created, we can generate test vectors and run fault simulations by setting the following options: -- `-g`: Type of the test vector generator. Set to `Atalanta` +- `-g`: Type of the test vector generator. - `-c`: Cell models file path. - `-b`: Path to the bench netlist. ```bash - fault -g Atalanta -c Tech/osu035/osu035_stdcells.v -b Netlists/s27.nl.v.cut.v.bench Netlists/s27.nl.v.cut.v +fault atpg -g [Atalanta|Quaigh] -c Tech/osu035/osu035_stdcells.v -b Netlists/s27.bench Netlists/s27.cut.v --clock CK --reset reset --bypassing VDD=1 --bypassing GND=0 ``` This will run the simulations with the default options for the initial TV count, increment, and ceiling. TV coverage will be generated at the default path -`Netlists/s27.nl.v.cut.v.tv.json` - -## Compaction - -`Compact` option is used to reduce the size of the test vector generated in the -previous step while maintaining the same coverage. - -```bash - fault compact -o -``` - -To run compact, invoke: - -```bash - fault compact Netlists/s27.nl.v.cut.v.tv.json -``` - -This will generate the compacted test vector set which is output in the default -path at: `fault compact Netlists/s27.nl.v.cut.v.tv.json.compacted.json` +`Netlists/s27.tv.json` ## Scan Chain Insertion @@ -187,15 +187,9 @@ path at: `fault compact Netlists/s27.nl.v.cut.v.tv.json.compacted.json` It has the following options: ```bash - fault chain -i --clock --reset -l -c -o +fault chain -l -c -o ``` -- `-i`: Specifies the inputs to ignore (if any) -- `--clock`: Clock signal name which is automatically added to the ignored - inputs. -- `--reset`: **Asynchronous** Reset signal name which is also automaticallyadded - to the ignored inputs. - - `--activeLow`: If your reset is active low, also include this flag. - `-l`: specifies the path to the liberty file of the standard cell library. - `-c`: cell models file to verify the scan chain integrity. - `-o`: path of the chained netlist. @@ -203,7 +197,11 @@ It has the following options: The chained netlist could be generated by running: ```bash - fault chain -l Tech/osu035/osu035_stdcells.lib -c Tech/osu035/osu035_stdcells.v --clock CK --reset reset Netlists/s27.nl.v +fault chain\ + --clock CK --reset reset --bypassing VDD=1 --bypassing GND=0\ + -l Tech/osu035/osu035_stdcells.lib\ + -c Tech/osu035/osu035_stdcells.v\ + Netlists/s27.nl.v ``` This will generate the chained netlist at the default path: @@ -215,16 +213,16 @@ In this part, we will add the JTAG port to the chained netlist. To run tap, we set the following options: - `-o`: Path to the output file. (Default: input + .jtag.v) -- `--clock`: Clock signal of core logic to use in simulation -- `--reset`: Reset signal of core logic to use in simulation. -- `--ignoring`: Other signals to ignore -- `--activeLow`: Ignored signals (including reset) signal of core logic are held - low instead of high. - `-c`: Cell models file to verify JTAG port using given cell model. - `-l`: Path to the liberty file for resynthesis. +- Bypass options To run tap option, invoke the following ```bash - fault tap -l Tech/osu035/osu035_stdcells.lib -c Tech/osu035/osu035_stdcells.v -l Tech/osu035/osu035_stdcells.lib -c Tech/osu035/osu035_stdcells.v --clock CK --reset reset Netlists/s27.nl.v.chained.v +fault tap\ + --clock CK --reset reset --bypassing VDD --bypassing GND\ + -l Tech/osu035/osu035_stdcells.lib\ + -c Tech/osu035/osu035_stdcells.v\ + Netlists/s27.chained.v ``` diff --git a/flake.lock b/flake.lock index ef20829..e44be17 100644 --- a/flake.lock +++ b/flake.lock @@ -59,6 +59,28 @@ "type": "github" } }, + "libparse": { + "inputs": { + "nixpkgs": [ + "nl2bench", + "nix-eda", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1713178934, + "narHash": "sha256-1w6HBBE2bWAD0GM98O8WZRmZDW9+EzD0KFvnnH2ho/k=", + "owner": "efabless", + "repo": "libparse-python", + "rev": "cec8b6dfd3d1c97bc5ccd1d0a41c44e51bc9c1eb", + "type": "github" + }, + "original": { + "owner": "efabless", + "repo": "libparse-python", + "type": "github" + } + }, "nix-eda": { "inputs": { "nixpkgs": "nixpkgs" @@ -93,6 +115,27 @@ "type": "github" } }, + "nl2bench": { + "inputs": { + "libparse": "libparse", + "nix-eda": [ + "nix-eda" + ] + }, + "locked": { + "lastModified": 1721923472, + "narHash": "sha256-Rvjd/0WYNTVg1hF1ODK7KagXrs4eLP/mTZROA+psWag=", + "owner": "donn", + "repo": "nl2bench", + "rev": "4fc66fb534bff125d437f7807d1dd660a08ed03c", + "type": "github" + }, + "original": { + "owner": "donn", + "repo": "nl2bench", + "type": "github" + } + }, "quaigh": { "inputs": { "cargo2nix": "cargo2nix", @@ -102,15 +145,16 @@ ] }, "locked": { - "lastModified": 1719607439, - "narHash": "sha256-ZRWbrI+HCpTZAbgTjqf4IWMuUspk++DI4DGeVTBNumQ=", + "lastModified": 1722015420, + "narHash": "sha256-WILtL6WKXs5pB5Jujx9HIT2w1jiVTZymXC7DTuqLPEM=", "owner": "donn", "repo": "quaigh", - "rev": "2253ddcfb8ba62683f1da54a54d06734126fb612", + "rev": "39b14e2d3e45b5e04338cf22adeede35dddc3dd6", "type": "github" }, "original": { "owner": "donn", + "ref": "fix_nix_linux", "repo": "quaigh", "type": "github" } @@ -118,6 +162,7 @@ "root": { "inputs": { "nix-eda": "nix-eda", + "nl2bench": "nl2bench", "quaigh": "quaigh" } }, diff --git a/flake.nix b/flake.nix index ac52c92..099b2a6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,17 +1,43 @@ { inputs = { nix-eda.url = github:efabless/nix-eda; + nl2bench = { + url = github:donn/nl2bench; + inputs.nix-eda.follows = "nix-eda"; + }; quaigh = { - url = github:donn/quaigh; + url = github:donn/quaigh/fix_nix_linux; inputs.nixpkgs.follows = "nix-eda/nixpkgs"; }; }; - outputs = {self, nix-eda, quaigh, ...}: { - packages = nix-eda.forAllSystems { current = self; withInputs = [nix-eda quaigh]; } (util: with util; rec{ + outputs = {self, nix-eda, quaigh, nl2bench, ...}: { + packages = nix-eda.forAllSystems { current = self; withInputs = [nix-eda quaigh nl2bench]; } (util: with util; rec{ atalanta = callPackage ./nix/atalanta.nix {}; + podem = callPackage ./nix/podem.nix {}; fault = callPackage ./default.nix {}; default = fault; }); + + devShells = nix-eda.forAllSystems { withInputs = [nix-eda quaigh nl2bench self]; } (util: with util; rec { + mac-testing = pkgs.stdenvNoCC.mkDerivation (with pkgs; let + pyenv = (python3.withPackages(ps: with ps; [pyverilog pyyaml pytest])); + in { + # Use the host's Clang and Swift + name = "shell"; + buildInputs = [ + yosys + verilog + pkgs.quaigh + pkgs.nl2bench + pyenv + gtkwave + ]; + + PYTHON_LIBRARY="${pyenv}/lib/lib${python3.libPrefix}${stdenvNoCC.hostPlatform.extensions.sharedLibrary}"; + PYTHONPATH="${pyenv}/${pyenv.sitePackages}"; + FAULT_IVL_BASE="${verilog}/lib/ivl"; + }); + }); }; } diff --git a/mac-testing.nix b/mac-testing.nix deleted file mode 100644 index a1396eb..0000000 --- a/mac-testing.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - pkgs? import {} -}: -with pkgs; stdenvNoCC.mkDerivation { - # Use the host's Clang and Swift - name = "shell"; - buildInputs = [ - yosys - verilog - (python3.withPackages(ps: with ps; [pyverilog pyyaml])) - gtkwave - ]; - - PYTHON_LIBRARY="${python3}/lib/lib${python3.libPrefix}${stdenvNoCC.hostPlatform.extensions.sharedLibrary}"; - FAULT_IVL_BASE="${verilog}/lib/ivl"; -} diff --git a/nix/podem.nix b/nix/podem.nix new file mode 100644 index 0000000..745eab9 --- /dev/null +++ b/nix/podem.nix @@ -0,0 +1,37 @@ +{ + lib, + gccStdenv, + fetchFromGitHub, +}: +gccStdenv.mkDerivation { + name = "nctu-ee-podem"; + version = "0.1.0"; + + src = fetchFromGitHub { + owner = "donn"; + repo = "VLSI-Testing"; + rev = "ff82db776521b294d79d54acc00b7b6eaaa5846d"; + sha256 = "sha256-Nj8hQb9XlRjIIrfht8VNEfORmwtb+WWrP6UVlWgo81A="; + }; + + postPatch = '' + sed -i 's/^LIBS.*/LIBS = /' podem/Makefile + ''; + + buildPhase = '' + make -C podem + ''; + + installPhase = '' + mkdir -p $out/bin + cp podem/atpg $out/bin/atpg-podem + ''; + + meta = with lib; { + description = "A C++ implementation of PODEM used in the testing course of the NCTU EE program"; + homepage = "https://github.com/cylinbao/VLSI-Testing"; + license = licenses.unfree; + mainProgram = "atpg-podem"; + platforms = platforms.linux ++ platforms.darwin; + }; +} diff --git a/requirements.txt b/requirements.txt index bf4fe73..c994428 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -https://github.com/PyHDI/Pyverilog/archive/refs/tags/1.3.0.zip -find_libpython \ No newline at end of file +pyverilog +nl2bench