From 2d6cbf6b350c116bd73bb883ee52886dea5a3d92 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Sat, 2 Nov 2024 16:50:46 +0200 Subject: [PATCH 1/3] initial: known good --- Package.swift | 9 +- Sources/Fault/Entries/asm.swift | 63 +-- Sources/Fault/Entries/atpg.swift | 634 ++++++++++++------------- Sources/Fault/Entries/chain.swift | 203 +++++--- Sources/Fault/Entries/common.swift | 46 +- Sources/Fault/Entries/cut.swift | 133 +++--- Sources/Fault/Entries/main.swift | 10 +- Sources/Fault/Entries/synth.swift | 52 +- Sources/Fault/Entries/tap.swift | 152 +++--- Sources/Fault/Future.swift | 9 +- Sources/Fault/JTAG.swift | 4 +- Sources/Fault/Metadata.swift | 10 +- Sources/Fault/Module.swift | 34 +- Sources/Fault/Mux.swift | 20 +- Sources/Fault/RNG.swift | 98 ++-- Sources/Fault/SerialVectorFormat.swift | 3 +- Sources/Fault/Simulation.swift | 50 +- Sources/Fault/String.swift | 4 +- Sources/Fault/Synthesis.swift | 72 +-- Sources/Fault/TVGenerator.swift | 55 +-- Sources/Fault/TestVector.swift | 18 +- flake.lock | 18 +- flake.nix | 3 +- 23 files changed, 939 insertions(+), 761 deletions(-) diff --git a/Package.swift b/Package.swift index c3d0edc..f0b8da9 100644 --- a/Package.swift +++ b/Package.swift @@ -6,12 +6,13 @@ import PackageDescription let package = Package( name: "Fault", platforms: [ - .macOS(.v13) // Regex features only available in Ventura+ + .macOS(.v13), // Regex features only available in Ventura+ ], dependencies: [ // Dependencies declare other packages that this package depends on. .package( - url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.0")), + url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.0") + ), .package(url: "https://github.com/pvieito/PythonKit", from: "0.5.0"), .package(url: "https://github.com/donn/Defile.git", from: "5.2.1"), .package(url: "https://github.com/attaswift/BigInt.git", from: "5.2.1"), @@ -36,10 +37,10 @@ let package = Package( dependencies: [], path: "Sources/CThreadPool", sources: [ - "thpool.c" + "thpool.c", ], cSettings: [ - .headerSearchPath("Sources/CThreadPool/include") + .headerSearchPath("Sources/CThreadPool/include"), ] ), ] diff --git a/Sources/Fault/Entries/asm.swift b/Sources/Fault/Entries/asm.swift index 070bd40..e187bca 100644 --- a/Sources/Fault/Entries/asm.swift +++ b/Sources/Fault/Entries/asm.swift @@ -13,11 +13,11 @@ // limitations under the License. import ArgumentParser +import BigInt import CoreFoundation import Defile import Foundation import PythonKit -import BigInt extension Fault { struct Assemble: ParsableCommand { @@ -25,19 +25,19 @@ extension Fault { commandName: "asm", abstract: "Assemble test vectors and golden outputs from JSON and Verilog files." ) - + @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.") var goldenOutput: String? - + @Argument(help: "JSON file (.json).") var json: String - + @Argument(help: "Verilog file (.v)") var verilog: String - + mutating func run() throws { // Validate input files guard FileManager.default.fileExists(atPath: verilog) else { @@ -46,52 +46,56 @@ extension Fault { guard FileManager.default.fileExists(atPath: json) else { throw ValidationError("JSON file '\(json)' not found.") } - + let vectorOutput = output ?? json.replacingExtension(".json", with: ".bin") let goldenOutput = goldenOutput ?? json.replacingExtension(".tv.json", with: ".au.bin") - + print("Loading JSON data…") guard let data = try? Data(contentsOf: URL(fileURLWithPath: json)) else { throw ValidationError("Failed to open test vector JSON file.") } - + let decoder = JSONDecoder() guard let tvinfo = try? decoder.decode(TVInfo.self, from: data) else { throw ValidationError("Test vector JSON file is invalid.") } - + // Extract chain metadata let (chain, _, _) = ChainMetadata.extract(file: verilog) - + let order = chain.filter { $0.kind != .output }.sorted { $0.ordinal < $1.ordinal } let outputOrder = chain.filter { $0.kind != .input }.sorted { $0.ordinal < $1.ordinal } - + let jsInputOrder = tvinfo.inputs let jsOutputOrder = tvinfo.outputs - + var inputMap: [String: Int] = [:] var outputMap: [String: Int] = [:] - + // Check input order let chainOrder = order.filter { $0.kind != .bypassInput } guard chainOrder.count == jsInputOrder.count else { - 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 })).") + 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(\.name)).symmetricDifference(jsInputOrder.map(\.name)))." + ) } - + for (i, input) in jsInputOrder.enumerated() { let name = input.name.hasPrefix("\\") ? String(input.name.dropFirst()) : input.name inputMap[name] = i guard chainOrder[i].name == name else { - throw ValidationError("Ordinal mismatch between TV input \(name) and scan-chain register \(chainOrder[i].name).") + throw ValidationError( + "Ordinal mismatch between TV input \(name) and scan-chain register \(chainOrder[i].name)." + ) } } - + for (i, output) in jsOutputOrder.enumerated() { var name = output.name.hasPrefix("\\") ? String(output.name.dropFirst()) : output.name name = name.hasSuffix(".d") ? String(name.dropLast(2)) : name outputMap[name] = i } - + var outputDecimal: [[BigUInt]] = [] for tvcPair in tvinfo.coverageList { guard let hex = BigUInt(tvcPair.goldenOutput, radix: 16) else { @@ -105,13 +109,13 @@ extension Fault { for output in jsOutputOrder { let start = outputBinary.index(outputBinary.startIndex, offsetBy: pointer) let end = outputBinary.index(start, offsetBy: output.width) - let value = String(outputBinary[start...size)-byte value to use as an RNG seed for test vector generators, provided as a hexadecimal string (without 0x)." + ) + var rngSeed: String = "DEADCAFEDEADF00D" + + @Option( + name: [.customShort("g"), .long], + help: "Use an external TV Generator: Atalanta or PODEM.") + var etvGen: String? + + @Option( + name: [.short, .long], + help: "Netlist in bench format. (Required iff generator is set to Atalanta or PODEM.)") + var bench: String? + + @Flag(help: "Generate only one testbench for inspection, and do not delete it.") + var sampleRun: Bool = false + + @OptionGroup + var bypass: BypassOptions + + @Option( + help: + "If provided, this JSON file's test vector are used as the initial set of test vectors, with iterations taking place with them in mind." + ) + var iteratingUpon: String? + + @Option( + name: [.customShort("D"), .customLong("define")], + help: "Define statements to include during simulations.") + var defines: [String] = [] + + @Option( + name: [.customShort("I"), .customLong("include")], + help: "Extra verilog models to include during simulations.") + var includes: [String] = [] + + @Argument(help: "The cutaway netlist to generate patterns for.") + var file: String + + mutating func run() throws { + + if !TVGeneratorFactory.validNames.contains(tvGen) { + throw ValidationError("Invalid test-vector generator \(tvGen).") + } + + let fileManager = FileManager() + 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.") + } + + if !cellModel.hasSuffix(".v"), !cellModel.hasSuffix(".sv") { + Stderr.print( + "Warning: Cell model file provided does not end with .v or .sv." ) + } - @Option( - name: [.short, .long], help: "Path to the output JSON file. (Default: input + .tv.json)" - ) - var output: String? + let jsonOutput = output ?? file.replacingExtension(".cut.v", with: ".tv.json") + let svfOutput = outputSvf ?? file.replacingExtension(".cut.v", with: ".tv.svf") - @Option(help: "Path to the output SVF file. (Default: input + .tv.svf)") - var outputSvf: String? + // MARK: Importing Python and Pyverilog - @Option( - name: [.long, .customLong("output-faultPoints")], - help: "Path to the output yml file listing all generated fault points. (Default: nil)") - var outputFaultPoints: String? + let parse = Python.import("pyverilog.vparser.parser").parse - @Option( - name: [.long, .customLong("output-covered")], - help: - "Path to the output yml file listing coverage metadata, i.e., ratio and fault points covered. (Default: nil)" - ) - var outputCoverageMetadata: String? - - @Option( - name: [.short, .long, .customLong("cellModel")], - help: "A Verilog model with which standard cells can be simulated.") - var cellModel: String - - @Option( - name: [.customShort("v"), .long], - help: "Number of test vectors to generate in the first batch.") - var tvCount: Int = 100 - - @Option( - name: [.customShort("r"), .long], - help: - "Increment in test vector count in subsequent batches should sufficient coverage not be reached." - ) - var increment: Int = 50 + // MARK: Parsing and Processing - @Option( - name: [.short, .long], - help: - "The minimum coverage to reach before ceasing increments. If set to 0, only the initial batch is run." - ) - var minCoverage: Float = 80 + let parseResult = parse([file]) + let ast = parseResult[0] + let description = ast[dynamicMember: "description"] + var definitionOptional: PythonObject? - @Option( - help: - "Ceiling for Test Vector increments: if this number is reached, no more increments will occur regardless the coverage." - ) - var ceiling: Int? + for definition in description.definitions { + let type = Python.type(definition).__name__ + if type == "ModuleDef" { + definitionOptional = definition + break + } + } - @Option(help: "Type of the pseudo-random internal test-vector-generator.") - var tvGen: String = "swift" + guard let definition = definitionOptional else { + Stderr.print("No module found.") + Foundation.exit(EX_DATAERR) + } - @Option( - help: - "A \(MemoryLayout.size)-byte value to use as an RNG seed for test vector generators, provided as a hexadecimal string (without 0x)." - ) - var rngSeed: String = "DEADCAFEDEADF00D" + print("Processing module \(definition.name)…") - @Option( - name: [.customShort("g"), .long], - help: "Use an external TV Generator: Atalanta or PODEM.") - var etvGen: String? + // MARK: TV Generation Mode Selection - @Option( - name: [.short, .long], - help: "Netlist in bench format. (Required iff generator is set to Atalanta or PODEM.)") - var bench: String? + var etvSetVectors: [TestVector] = [] + var etvSetInputs: [Port] = [] - @Flag(help: "Generate only one testbench for inspection, and do not delete it.") - var sampleRun: Bool = false + if let tvGenerator = etvGen { + guard let etvgen = ETVGFactory.get(name: tvGenerator) else { + Stderr.print("Unknown external test vector generator '\(tvGenerator)'.") + Foundation.exit(EX_USAGE) + } - @OptionGroup - var bypass: BypassOptions + let benchUnwrapped = bench! // Program exits if etvGen.value isn't nil and bench.value is or vice versa - @Option( - help: - "If provided, this JSON file's test vector are used as the initial set of test vectors, with iterations taking place with them in mind." - ) - var iteratingUpon: String? + if !fileManager.fileExists(atPath: benchUnwrapped) { + throw ValidationError("Bench file '\(benchUnwrapped)' not found.") + } + (etvSetVectors, etvSetInputs) = etvgen.generate( + file: benchUnwrapped, module: "\(definition.name)") + + if etvSetVectors.count == 0 { + Stderr.print( + "Bench netlist appears invalid (no vectors generated). Are you sure there are no floating nets/outputs?" + ) + Foundation.exit(EX_DATAERR) + } else { + print( + "Generated \(etvSetVectors.count) test vectors using external utilties to verify." + ) + } + } - @Option( - name: [.customShort("D"), .customLong("define")], - help: "Define statements to include during simulations.") - var defines: [String] = [] + let tvMinimumCoverage = minCoverage / 100 + let finalTvCeiling: Int = + ceiling ?? (etvSetVectors.count == 0 ? 1000 : etvSetVectors.count) - @Option( - name: [.customShort("I"), .customLong("include")], - help: "Extra verilog models to include during simulations.") - var includes: [String] = [] + let finalRNGSeed = UInt(rngSeed, radix: 16)! - @Argument(help: "The cutaway netlist to generate patterns for.") - var file: String + do { + let (ports, inputs, outputs) = try Port.extract(from: definition) - mutating func run() throws { + if inputs.count == 0 { + print("Module has no inputs.") + Foundation.exit(EX_OK) + } + if outputs.count == 0 { + print("Module has no outputs.") + Foundation.exit(EX_OK) + } - if !TVGeneratorFactory.validNames.contains(tvGen) { - throw ValidationError("Invalid test-vector generator \(tvGen).") - } + // MARK: Discover fault points - let fileManager = FileManager() - guard fileManager.fileExists(atPath: file) else { - throw ValidationError("File '\(file)' not found.") + var faultPoints: Set = [] + var gateCount = 0 + var inputsMinusIgnored: [Port] = inputs.filter { + !bypass.bypassedIOs.contains($0.name) + } + if etvSetVectors.count > 0 { + var evtInputsMinusIgnored: [Port] = [] + var offset = 0 + for (i, input) in etvSetInputs.enumerated() { + if bypass.bypassedIOs.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 + } - guard fileManager.fileExists(atPath: cellModel) else { - throw ValidationError("Cell model file '\(cellModel)' not found.") + for (_, port) in ports { + if bypass.bypassedIOs.contains(port.name) { + continue + } + if port.width == 1 { + faultPoints.insert(port.name) + } else { + let minimum = min(port.from, port.to) + let maximum = max(port.from, port.to) + for i in minimum...maximum { + faultPoints.insert("\(port.name) [\(i)]") } + } + } + + var warnAboutDFF = false - if !cellModel.hasSuffix(".v"), !cellModel.hasSuffix(".sv") { - Stderr.print( - "Warning: Cell model file provided does not end with .v or .sv." - ) + for itemDeclaration in definition.items { + let type = Python.type(itemDeclaration).__name__ + + // Process gates + if type == "InstanceList" { + gateCount += 1 + let instance = itemDeclaration.instances[0] + if String(describing: instance.module).starts(with: "DFF") { + warnAboutDFF = true + } + for hook in instance.portlist { + faultPoints.insert("\(instance.name).\(hook.portname)") } + } + } - let jsonOutput = output ?? file.replacingExtension(".cut.v", with: ".tv.json") - let svfOutput = outputSvf ?? file.replacingExtension(".cut.v", with: ".tv.svf") + if warnAboutDFF { + print( + "Warning: D-flipflops were found in this netlist. Are you sure you ran it through 'fault cut'?" + ) + } - // MARK: Importing Python and Pyverilog + print( + "Found \(faultPoints.count) fault sites in \(gateCount) gates and \(ports.count) ports." + ) - let parse = Python.import("pyverilog.vparser.parser").parse + // MARK: Load Initial Set - // MARK: Parsing and Processing + var initialTVInfo: TVInfo? = nil + if let startingTVSet = iteratingUpon { + let loadedInitialTVInfo = try TVInfo.fromJSON(file: startingTVSet) + print("Loaded \(loadedInitialTVInfo.coverageList.count) initial test vectors.") + initialTVInfo = loadedInitialTVInfo + } - let parseResult = parse([file]) - let ast = parseResult[0] - let description = ast[dynamicMember: "description"] - var definitionOptional: PythonObject? + // MARK: Simulation + + let startTime = CFAbsoluteTimeGetCurrent() + + let models = [cellModel] + includes + + print("Performing simulations…") + let result = try Simulator.simulate( + for: faultPoints, + in: file, + module: "\(definition.name)", + with: models, + ports: ports, + inputs: inputsMinusIgnored, + bypassingWithBehavior: bypass.simulationValues, + outputs: outputs, + initialVectorCount: tvCount, + incrementingBy: increment, + minimumCoverage: tvMinimumCoverage, + ceiling: finalTvCeiling, + tvGenerator: TVGeneratorFactory.get(name: tvGen)!, + rngSeed: finalRNGSeed, + initialTVInfo: initialTVInfo, + externalTestVectors: etvSetVectors, + sampleRun: sampleRun, + clock: bypass.clock, + defines: Set(defines), + using: iverilogExecutable, + with: vvpExecutable + ) - for definition in description.definitions { - let type = Python.type(definition).__name__ - if type == "ModuleDef" { - definitionOptional = definition - break - } - } + let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime + print("Time elapsed: \(String(format: "%.2f", timeElapsed))s.") - guard let definition = definitionOptional else { - Stderr.print("No module found.") - Foundation.exit(EX_DATAERR) - } + print("Simulations concluded: Coverage \(result.coverageMeta.ratio * 100)%") - print("Processing module \(definition.name)…") - - // MARK: TV Generation Mode Selection - - var etvSetVectors: [TestVector] = [] - var etvSetInputs: [Port] = [] - - if let tvGenerator = etvGen { - guard let etvgen = ETVGFactory.get(name: tvGenerator) else { - Stderr.print("Unknown external test vector generator '\(tvGenerator)'.") - Foundation.exit(EX_USAGE) - } - - let benchUnwrapped = bench! // Program exits if etvGen.value isn't nil and bench.value is or vice versa - - if !fileManager.fileExists(atPath: benchUnwrapped) { - throw ValidationError("Bench file '\(benchUnwrapped)' not found.") - } - (etvSetVectors, etvSetInputs) = etvgen.generate( - file: benchUnwrapped, module: "\(definition.name)") - - if etvSetVectors.count == 0 { - Stderr.print( - "Bench netlist appears invalid (no vectors generated). Are you sure there are no floating nets/outputs?" - ) - Foundation.exit(EX_DATAERR) - } else { - print( - "Generated \(etvSetVectors.count) test vectors using external utilties to verify." - ) - } - } + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted - let tvMinimumCoverage = minCoverage / 100 - let finalTvCeiling: Int = - ceiling ?? (etvSetVectors.count == 0 ? 1000 : etvSetVectors.count) - - let finalRNGSeed = UInt(rngSeed, radix: 16)! - - do { - let (ports, inputs, outputs) = try Port.extract(from: definition) - - if inputs.count == 0 { - print("Module has no inputs.") - Foundation.exit(EX_OK) - } - if outputs.count == 0 { - print("Module has no outputs.") - Foundation.exit(EX_OK) - } - - // MARK: Discover fault points - - var faultPoints: Set = [] - var gateCount = 0 - 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 bypass.bypassedInputs.contains(port.name) { - continue - } - if port.width == 1 { - faultPoints.insert(port.name) - } else { - let minimum = min(port.from, port.to) - let maximum = max(port.from, port.to) - for i in minimum...maximum { - faultPoints.insert("\(port.name) [\(i)]") - } - } - } - - var warnAboutDFF = false - - for itemDeclaration in definition.items { - let type = Python.type(itemDeclaration).__name__ - - // Process gates - if type == "InstanceList" { - gateCount += 1 - let instance = itemDeclaration.instances[0] - if String(describing: instance.module).starts(with: "DFF") { - warnAboutDFF = true - } - for hook in instance.portlist { - faultPoints.insert("\(instance.name).\(hook.portname)") - } - } - } - - if warnAboutDFF { - print( - "Warning: D-flipflops were found in this netlist. Are you sure you ran it through 'fault cut'?" - ) - } - - print( - "Found \(faultPoints.count) fault sites in \(gateCount) gates and \(ports.count) ports." - ) - - // MARK: Load Initial Set - - var initialTVInfo: TVInfo? = nil - if let startingTVSet = iteratingUpon { - let loadedInitialTVInfo = try TVInfo.fromJSON(file: startingTVSet) - print("Loaded \(loadedInitialTVInfo.coverageList.count) initial test vectors.") - initialTVInfo = loadedInitialTVInfo - } - - // MARK: Simulation - - let startTime = CFAbsoluteTimeGetCurrent() - - let models = [cellModel] + includes - - print("Performing simulations…") - let result = try Simulator.simulate( - for: faultPoints, - in: file, - module: "\(definition.name)", - with: models, - ports: ports, - inputs: inputsMinusIgnored, - bypassingWithBehavior: bypass.simulationValues, - outputs: outputs, - initialVectorCount: tvCount, - incrementingBy: increment, - minimumCoverage: tvMinimumCoverage, - ceiling: finalTvCeiling, - tvGenerator: TVGeneratorFactory.get(name: tvGen)!, - rngSeed: finalRNGSeed, - initialTVInfo: initialTVInfo, - externalTestVectors: etvSetVectors, - sampleRun: sampleRun, - clock: bypass.clock, - defines: Set(defines), - using: iverilogExecutable, - with: vvpExecutable - ) - - let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime - print("Time elapsed: \(String(format: "%.2f", timeElapsed))s.") - - print("Simulations concluded: Coverage \(result.coverageMeta.ratio * 100)%") - - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - - 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)) - - // 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)…") - try File.open(coverageMetaFilePath, mode: .write) { - try $0.write(string: YAMLEncoder().encode(result.coverageMeta)) - } - } - - } catch { - Stderr.print("Internal error: \(error)") - Foundation.exit(EX_SOFTWARE) - } + 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)) + + // 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)…") + try File.open(coverageMetaFilePath, mode: .write) { + try $0.write(string: YAMLEncoder().encode(result.coverageMeta)) + } } + + } catch { + Stderr.print("Internal error: \(error)") + Foundation.exit(EX_SOFTWARE) + } } + } } diff --git a/Sources/Fault/Entries/chain.swift b/Sources/Fault/Entries/chain.swift index d36c45c..d0b8a79 100644 --- a/Sources/Fault/Entries/chain.swift +++ b/Sources/Fault/Entries/chain.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import Collections import ArgumentParser +import Collections import Defile import Foundation import PythonKit @@ -95,7 +95,9 @@ func chainInternal( let instance = itemDeclaration.instances[0] let moduleName = String(describing: instance.module) let instanceName = String(describing: instance.name) - if let dffinfo = getMatchingDFFInfo(from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch) { + if let dffinfo = getMatchingDFFInfo( + from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch + ) { for hook in instance.portlist { let portnameStr = String(describing: hook.portname) if portnameStr == dffinfo.clk { @@ -107,7 +109,9 @@ func chainInternal( } if portnameStr == dffinfo.d { if let mc = muxCreator { - let (muxCellDecls, muxWireDecls, muxOut) = mc.create(for: instanceName, selection: shiftIdentifier, a: previousOutput, b: hook.argname) + let (muxCellDecls, muxWireDecls, muxOut) = mc.create( + for: instanceName, selection: shiftIdentifier, a: previousOutput, b: hook.argname + ) hook.argname = muxOut statements += muxCellDecls statements += muxWireDecls @@ -142,7 +146,7 @@ func chainInternal( // Note that `hook.argname` is actually an expression let portInfo = blackboxModule.portsByName["\(hook.portname)"]! - if bypass.bypassedInputs.contains(portInfo.name) { + if bypass.bypassedIOs.contains(portInfo.name) { // Leave it alone continue } @@ -150,7 +154,9 @@ func chainInternal( let wireNameOriginal = "\\\(instanceName).\(portInfo.name).original" let wireNameMultiplexed = "\\\(instanceName).\(portInfo.name).multiplexed" let width = Node.Width(Node.IntConst(portInfo.from), Node.IntConst(portInfo.to)) - let wiresDecl = Node.Decl([Node.Wire(wireNameOriginal, width: width), Node.Wire(wireNameMultiplexed, width: width)]) + let wiresDecl = Node.Decl([ + Node.Wire(wireNameOriginal, width: width), Node.Wire(wireNameMultiplexed, width: width), + ]) statements.append(wiresDecl) var kind: ChainRegister.Kind @@ -167,7 +173,9 @@ func chainInternal( for bit in portInfo.bits { let originalBit = Node.Pointer(Node.Identifier(wireNameOriginal), Node.IntConst(bit)) - let multiplexedBit = Node.Pointer(Node.Identifier(wireNameMultiplexed), Node.IntConst(bit)) + let multiplexedBit = Node.Pointer( + Node.Identifier(wireNameMultiplexed), Node.IntConst(bit) + ) let nextOutput = newShiftWire() let decl = bsrCreator.create( group: instanceName, @@ -295,11 +303,12 @@ func chainTop( if input.name != bypass.clock, input.name != bypass.reset.name { statements.append(inputStatement) } - if bypass.bypassedInputs.contains(input.name) { - portArguments.append(Node.PortArg( - input.name, - Node.Identifier(input.name) - )) + if bypass.bypassedIOs.contains(input.name) { + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(input.name) + )) continue } @@ -315,10 +324,11 @@ func chainTop( } statements.append(doutStatement) - portArguments.append(Node.PortArg( - input.name, - Node.Identifier(doutName) - )) + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(doutName) + )) if input.width == 1 { let nextOutput = newShiftWire() let decl = bsrCreator.create( @@ -357,29 +367,34 @@ func chainTop( ) } - portArguments.append(Node.PortArg( - shiftName, - Node.Identifier(shiftName) - )) - portArguments.append(Node.PortArg( - tckName, - Node.Identifier(tckName) - )) - portArguments.append(Node.PortArg( - testName, - Node.Identifier(testName) - )) - - portArguments.append(Node.PortArg( - inputName, - previousOutput - )) + portArguments.append( + Node.PortArg( + shiftName, + Node.Identifier(shiftName) + )) + portArguments.append( + Node.PortArg( + tckName, + Node.Identifier(tckName) + )) + portArguments.append( + Node.PortArg( + testName, + Node.Identifier(testName) + )) + + portArguments.append( + Node.PortArg( + inputName, + previousOutput + )) let nextOutput = newShiftWire() - portArguments.append(Node.PortArg( - outputName, - nextOutput - )) + portArguments.append( + Node.PortArg( + outputName, + nextOutput + )) order += internalOrder previousOutput = nextOutput @@ -399,10 +414,11 @@ func chainTop( statements.append(outputStatement) statements.append(dinStatement) - portArguments.append(Node.PortArg( - output.name, - Node.Identifier(dinName) - )) + portArguments.append( + Node.PortArg( + output.name, + Node.Identifier(dinName) + )) if output.width == 1 { let nextOutput = newShiftWire() @@ -448,11 +464,12 @@ func chainTop( Python.tuple() ) - statements.append(Node.InstanceList( - module.definition.name, - Python.tuple(), - Python.tuple([submoduleInstance]) - )) + statements.append( + Node.InstanceList( + module.definition.name, + Python.tuple(), + Python.tuple([submoduleInstance]) + )) let boundaryAssignment = Node.Assign( Node.Lvalue(Node.Identifier(outputName)), @@ -466,54 +483,78 @@ func chainTop( Node.Portlist(Python.tuple(ports)), Python.tuple(statements) ) - print("Boundary scan cells successfully chained. Length: ", order.reduce(0) { $0 + $1.width } - internalOrder.reduce(0) { $0 + $1.width }) + print( + "Boundary scan cells successfully chained. Length: ", + order.reduce(0) { $0 + $1.width } - internalOrder.reduce(0) { $0 + $1.width } + ) return (supermodel, order) } - extension Fault { struct Chain: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Manipulate a netlist to create a scan chain, and resynthesize." ) - + @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") var output: String? - - @Option(name: [.short, .long, .customLong("cellModel")], help: "Verify scan chain using given cell model.") + + @Option( + name: [.short, .long, .customLong("cellModel")], + help: "Verify scan chain using given cell model." + ) var cellModel: String? - + @Option(name: [.long], help: "Inverted clk tree source cell name. (Default: none)") var invClock: String? - + @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.") + + @Option( + name: [.short, .long, .customLong("sclConfig")], + help: "Path for the YAML SCL config file. Recommended." + ) var sclConfig: String? - - @Option(name: [.short, .long], help: "Optional override for the DFF names from the PDK config. Comma-delimited.") + + @Option( + name: [.short, .long], + help: "Optional override for the DFF names from the PDK config. Comma-delimited." + ) var dff: String? - - @Option(name: [.customShort("b"), .customLong("blackbox")], help: "Blackbox module names. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("b"), .customLong("blackbox")], + help: "Blackbox module names. Specify multiple times to specify multiple modules." + ) var blackbox: [String] = [] - - @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) var blackboxModels: [String] = [] - - @Option(name: [.customShort("D"), .customLong("define")], help: "define statements to include during simulations. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("D"), .customLong("define")], + help: + "`define statements to include during simulations. Specify multiple times to specify multiple defines." + ) var defines: [String] = [] - - @Option(name: .long, help: "Extra verilog models to include during simulations. Comma-delimited. (Default: none)") - var inc: String? - + + @Option( + name: [.customShort("I"), .customLong("inc"), .customLong("include")], + help: "Extra verilog models to include during simulations. (Default: none)" + ) + var includes: [String] = [] + @Flag(name: .long, help: "Skip Re-synthesizing the chained netlist. (Default: none)") var skipSynth: Bool = false - + @Option(name: .long, help: "Name for scan-chain serial data in signal.") var sin: String = "sin" @@ -528,10 +569,10 @@ extension Fault { @Option(name: .long, help: "Name for JTAG test clock signal.") var tck: String = "tck" - + @Argument var file: String - + mutating func run() throws { let fileManager = FileManager() @@ -541,7 +582,9 @@ extension Fault { } // Required options validation - var sclConfig = SCLConfiguration(dffMatches: [DFFMatch(name: "DFFSR,DFFNEGX1,DFFPOSX1", clk: "CLK", d: "D", q: "Q")]) + 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 { throw ValidationError("File not found: \(sclConfigPath)") @@ -565,8 +608,6 @@ extension Fault { 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 } ?? []) - // MARK: Importing Python and Pyverilog let parse = Python.import("pyverilog.vparser.parser").parse @@ -632,7 +673,10 @@ extension Fault { let finalCount = finalOrder.reduce(0) { $0 + $1.width } let finalAst = parse([bsrCreator.inputDefinition + bsrCreator.outputDefinition])[0] - let finalDefinitions = [PythonObject](finalAst[dynamicMember: "description"].definitions)! + [module.definition, supermodel] + let finalDefinitions = + [PythonObject](finalAst[dynamicMember: "description"].definitions)! + [ + module.definition, supermodel, + ] finalAst[dynamicMember: "description"].definitions = Python.tuple(finalDefinitions) try File.open(intermediate, mode: .write) { @@ -692,7 +736,7 @@ extension Fault { // MARK: Verification if let model = cellModel { - let models = [model] + Array(includeFiles) + Array(blackboxModels) + let models = [model] + Array(includes) + Array(blackboxModels) print("Verifying scan chain integrity…") let ast = parse([netlist])[0] @@ -736,19 +780,22 @@ extension Fault { print("Scan chain verified successfully.") } else { print("Scan chain verification failed.") - print("・Ensure that clock and reset signals, if they exist are passed as such to the program.") + print( + "・Ensure that clock and reset signals, if they exist are passed as such to the program." + ) if !bypass.resetActiveLow { print("・Ensure that the reset is active high- pass --activeLow for activeLow.") } if internalOrder.count == 0 { - print("・Ensure that D flip-flop cell names match those either in the defaults, the PDK config, or the overrides.") + print( + "・Ensure that D flip-flop cell names match those either in the defaults, the PDK config, or the overrides." + ) } print("・Ensure that there are no other asynchronous resets anywhere in the circuit.") Foundation.exit(EX_DATAERR) } } print("Done.") - } } } diff --git a/Sources/Fault/Entries/common.swift b/Sources/Fault/Entries/common.swift index 5133516..89250d0 100644 --- a/Sources/Fault/Entries/common.swift +++ b/Sources/Fault/Entries/common.swift @@ -20,23 +20,36 @@ struct Reset { } 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.") + @Option( + name: [.long], + help: + "I/Os to bypass when performing operations. May be specified multiple times to bypass multiple I/Os. 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.") + + @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.") + + @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.") + @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 = { + + lazy var reset: Reset = .init(name: resetName, active: resetActiveLow ? .low : .high) + + lazy var simulationValues: OrderedDictionary = { var result: OrderedDictionary = [:] result[clock] = .holdHigh result[reset.name] = reset.active == .low ? .holdHigh : .holdLow @@ -50,9 +63,6 @@ struct BypassOptions: ParsableArguments { } return result }() - - lazy var bypassedInputs: Set = { - return Set(simulationValues.keys) - }() - -} + + lazy var bypassedIOs: Set = .init(simulationValues.keys) +} diff --git a/Sources/Fault/Entries/cut.swift b/Sources/Fault/Entries/cut.swift index 00fea2c..b24679e 100644 --- a/Sources/Fault/Entries/cut.swift +++ b/Sources/Fault/Entries/cut.swift @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import ArgumentParser import BigInt import Collections -import ArgumentParser import CoreFoundation // Not automatically imported on Linux import Defile import Foundation @@ -23,55 +23,69 @@ import Yams extension Fault { struct Cut: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Cut away D-flipflops, converting them into inputs and outputs. This is a necessary precursor to the ATPG step." + abstract: + "Cut away D-flipflops, converting them into inputs and outputs. This is a necessary precursor to the ATPG step." + ) + + @Option( + name: [.short, .long], + help: "Override for flip-flop cell names. Comma-delimited. (Default: DFF)." ) - - @Option(name: [.short, .long], help: "Override for flip-flop cell names. Comma-delimited. (Default: DFF).") var dff: String? - - @Option(name: [.short, .long, .customLong("sclConfig")], help: "Path for the YAML SCL config file. Recommended.") + + @Option( + name: [.short, .long, .customLong("sclConfig")], + help: "Path for the YAML SCL config file. Recommended." + ) var sclConfig: String? - - @Option(name: [.short, .long], help: "Blackbox module names. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("b"), .customLong("blackbox")], + help: "Blackbox module names. Specify multiple times to specify multiple modules." + ) var blackbox: [String] = [] - - @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) var blackboxModels: [String] = [] - + @OptionGroup var bypass: BypassOptions - + @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") var output: String? - + @Argument(help: "The file to process.") var file: String - + mutating func run() throws { let fileManager = FileManager() - + guard fileManager.fileExists(atPath: file) else { throw ValidationError("File '\(file)' not found.") } - - let output = self.output ?? file.replacingExtension(".nl.v", with: ".cut.v") - + + let output = output ?? file.replacingExtension(".nl.v", with: ".cut.v") + // MARK: Importing Python and Pyverilog - + let parse = Python.import("pyverilog.vparser.parser").parse let Node = Python.import("pyverilog.vparser.ast") let Generator = Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() - + var blackboxModules: OrderedDictionary = [:] - + if blackboxModels.count != 0 { blackboxModules = try Module.getModules(in: blackboxModels, filter: Set(blackbox)) } - - var sclConfig = SCLConfiguration(dffMatches: [DFFMatch(name: "DFFSR,DFFNEGX1,DFFPOSX1", clk: "CLK", d: "D", q: "Q")]) - + + var sclConfig = SCLConfiguration(dffMatches: [ + DFFMatch(name: "DFFSR,DFFNEGX1,DFFPOSX1", clk: "CLK", d: "D", q: "Q"), + ]) + if let sclConfigPath = self.sclConfig { guard let sclConfigYML = try? String(contentsOfFile: sclConfigPath) else { throw ValidationError("File not found: \(sclConfigPath)") @@ -79,15 +93,15 @@ extension Fault { let decoder = YAMLDecoder() sclConfig = try decoder.decode(SCLConfiguration.self, from: sclConfigYML) } - + if let dffOverride = dff { sclConfig.dffMatches.last!.name = dffOverride } - + let ast = parse([file])[0] let description = ast[dynamicMember: "description"] var definitionOptional: PythonObject? - + for definition in description.definitions { let type = Python.type(definition).__name__ if type == "ModuleDef" { @@ -95,36 +109,38 @@ extension Fault { break } } - + guard let definition = definitionOptional else { throw ValidationError("No module found.") } - + let ports = Python.list(definition.portlist.ports) var declarations: [PythonObject] = [] var items: [PythonObject] = [] - + let fnmatch = Python.import("fnmatch") - + for item in definition.items { var yank = false - + let type = Python.type(item).__name__ // Process gates if type == "InstanceList" { let instance = item.instances[0] let moduleName = "\(instance.module)" let instanceName = "\(instance.name)" - - if let dffinfo = getMatchingDFFInfo(from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch) { + + if let dffinfo = getMatchingDFFInfo( + from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch + ) { yank = true - + let outputName = "\\" + instanceName + ".d" let inputIdentifier = Node.Identifier(instanceName) let outputIdentifier = Node.Identifier(outputName) var dArg: PythonObject? var qArg: PythonObject? - + for hook in instance.portlist { if String(describing: hook.portname) == dffinfo.d { dArg = hook.argname @@ -133,62 +149,63 @@ extension Fault { qArg = hook.argname } } - + guard let d = dArg, let q = qArg else { throw ValidationError("Cell \(instanceName) missing either a 'D' or 'Q' port.") } - + ports.append(Node.Port(instanceName, Python.None, Python.None, Python.None)) ports.append(Node.Port(outputName, Python.None, Python.None, Python.None)) - + declarations.append(Node.Input(instanceName)) declarations.append(Node.Output(outputName)) - + let inputAssignment = Node.Assign(Node.Lvalue(q), Node.Rvalue(inputIdentifier)) let outputAssignment = Node.Assign(Node.Lvalue(outputIdentifier), Node.Rvalue(d)) - + items.append(inputAssignment) items.append(outputAssignment) - + } else if let blackboxModule = blackboxModules[moduleName] { yank = true - + for hook in instance.portlist { let portName = String(describing: hook.portname) - + if bypass.simulationValues[portName] != nil { continue } - + let portInfo = blackboxModule.portsByName[portName]! - let ioName = "\\\(instanceName).\(portName)" + (portInfo.polarity == .input ? ".d" : "") + 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) : - Node.Input(ioName, width) - let assignStatement = portInfo.polarity == .input ? - Node.Assign(Node.Lvalue(Node.Identifier(ioName)), Node.Rvalue(hook.argname)) : - Node.Assign(Node.Rvalue(hook.argname), Node.Lvalue(Node.Identifier(ioName))) - + let ioDeclaration = + portInfo.polarity == .input ? Node.Output(ioName, width) : Node.Input(ioName, width) + let assignStatement = + portInfo.polarity == .input + ? Node.Assign(Node.Lvalue(Node.Identifier(ioName)), Node.Rvalue(hook.argname)) + : Node.Assign(Node.Rvalue(hook.argname), Node.Lvalue(Node.Identifier(ioName))) + items.append(assignStatement) declarations.append(ioDeclaration) ports.append(Node.Port(ioName, Python.None, Python.None, Python.None)) } } } - + if !yank { items.append(item) } } - + if declarations.isEmpty { print("[Warning]: Failed to detect any flip-flop cells.") } - + definition.portlist.ports = ports definition.items = Python.tuple(declarations + items) - + try File.open(output, mode: .write) { try $0.print(String.boilerplate) try $0.print(Generator.visit(definition)) diff --git a/Sources/Fault/Entries/main.swift b/Sources/Fault/Entries/main.swift index c44fad7..92ae7f5 100644 --- a/Sources/Fault/Entries/main.swift +++ b/Sources/Fault/Entries/main.swift @@ -15,13 +15,13 @@ import ArgumentParser import BigInt import Collections -import CoreFoundation // Not automatically imported on Linux +import CoreFoundation // Not automatically imported on Linux import Defile import Foundation import PythonKit import Yams -let VERSION = "0.8.0" +let VERSION = "0.9.0" var env = ProcessInfo.processInfo.environment let iverilogBase = env["FAULT_IVL_BASE"] ?? "/usr/local/lib/ivl" @@ -29,12 +29,12 @@ let iverilogExecutable = env["FAULT_IVERILOG"] ?? env["PYVERILOG_IVERILOG"] ?? " let vvpExecutable = env["FAULT_VVP"] ?? "vvp" let yosysExecutable = env["FAULT_YOSYS"] ?? "yosys" -_ = [ // Register all RNGs +_ = [ // Register all RNGs SwiftRNG.registered, LFSR.registered, PatternGenerator.registered, ] -_ = [ // Register all TVGens +_ = [ // Register all TVGens Atalanta.registered, Quaigh.registered, PODEM.registered, @@ -72,7 +72,7 @@ let pythonVersions = { Stderr.print("\(error)") exit(EX_UNAVAILABLE) } -}() // Just to check +}() // Just to check struct Fault: ParsableCommand { static let configuration = CommandConfiguration( diff --git a/Sources/Fault/Entries/synth.swift b/Sources/Fault/Entries/synth.swift index e488d8b..12ae9d5 100644 --- a/Sources/Fault/Entries/synth.swift +++ b/Sources/Fault/Entries/synth.swift @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import ArgumentParser import BigInt import Collections -import ArgumentParser import CoreFoundation // Not automatically imported on Linux import Defile import Foundation @@ -26,50 +26,60 @@ extension Fault { static let configuration = CommandConfiguration( abstract: "Synthesize Verilog designs using Yosys." ) - - @Option(name: [.customShort("o"), .long], help: "Path to the output netlist. (Default: Netlists/ + input + .netlist.v)") + + @Option( + name: [.customShort("o"), .long], + help: "Path to the output netlist. (Default: Netlists/ + input + .netlist.v)" + ) var output: String? - + @Option(name: [.customShort("l"), .long], help: "Liberty file. (Required.)") var liberty: String - + @Option(name: [.customShort("t"), .long], help: "Top module. (Required.)") var top: String - - @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) var blackboxModels: [String] = [] - + @Argument(help: "Verilog files to synthesize.") var files: [String] - + mutating func run() throws { let fileManager = FileManager.default - - + for file in files { guard fileManager.fileExists(atPath: file) else { throw ValidationError("File '\(file)' not found.") } } - + if !fileManager.fileExists(atPath: liberty) { print("Liberty file '\(liberty)' not found.") return } - + if !liberty.hasSuffix(".lib") { print("Warning: Liberty file provided does not end with .lib.") } - - let output = self.output ?? "Netlists/\(top).nl.v" - - let script = Synthesis.script(for: top, in: files, cutting: false, liberty: liberty, blackboxing: blackboxModels, output: output) - + + let output = output ?? "Netlists/\(top).nl.v" + + let script = Synthesis.script( + for: top, in: files, cutting: false, liberty: liberty, blackboxing: blackboxModels, + output: output + ) + let outputDirectory = URL(fileURLWithPath: output).deletingLastPathComponent().path - try fileManager.createDirectory(atPath: outputDirectory, withIntermediateDirectories: true, attributes: nil) - + try fileManager.createDirectory( + atPath: outputDirectory, withIntermediateDirectories: true, attributes: nil + ) + let result = "echo '\(script)' | '\(yosysExecutable)'".sh() - + if result != EX_OK { print("A yosys error has occurred.") throw ValidationError("Yosys error occurred.") diff --git a/Sources/Fault/Entries/tap.swift b/Sources/Fault/Entries/tap.swift index 7fd618f..6142cec 100644 --- a/Sources/Fault/Entries/tap.swift +++ b/Sources/Fault/Entries/tap.swift @@ -13,23 +13,25 @@ // limitations under the License. import ArgumentParser +import BigInt import CoreFoundation import Defile import Foundation import PythonKit -import BigInt - extension Fault { struct Tap: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Add a JTAG TAP port and controller to a netlist with a scan-chain." ) - + @Option(name: [.short, .long], help: "Path to the output file.") var output: String? - @Option(name: [.short, .long, .customLong("cellModel")], help: "Verify JTAG port using given cell model.") + @Option( + name: [.short, .long, .customLong("cellModel")], + help: "Verify JTAG port using given cell model." + ) var cellModel: String? @OptionGroup @@ -41,16 +43,35 @@ extension Fault { @Option(name: [.short, .long], help: ".bin file for test vectors.") var testVectors: String? - @Option(name: [.short, .long], help: ".bin file for golden output. Required iff testVectors is provided.") + @Option( + name: [.short, .long], + help: ".bin file for golden output. Required iff testVectors is provided." + ) var goldenOutput: String? - - @Option(name: [.customShort("B"), .long, .customLong("blackboxModel")], help: "Files containing definitions for blackbox models. Comma-delimited. (Default: none)") + + @Option( + name: [.customShort("b"), .customLong("blackbox")], + help: "Blackbox module names. Specify multiple times to specify multiple modules." + ) + var blackbox: [String] = [] + + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) var blackboxModels: [String] = [] - @Option(name: [.customShort("D"), .customLong("define")], help: "Define statements to include during simulations.") + @Option( + name: [.customShort("D"), .customLong("define")], + help: + "`define statements to include during simulations. Specify multiple times to specify multiple defines." + ) var defines: [String] = [] - - @Option(name: [.customShort("I"), .customLong("include")], help: "Extra verilog models to include during simulations.") + + @Option( + name: [.customShort("I"), .customLong("inc"), .customLong("include")], + help: "Extra verilog models to include during simulations. (Default: none)" + ) var includes: [String] = [] @Flag(name: [.long], help: "Skip re-synthesizing the chained netlist.") @@ -86,10 +107,10 @@ extension Fault { @Option(name: .long, help: "Name for JTAG test reset (active low) signal.") var trst: String = "trst" - + @Argument var file: String - + mutating func run() throws { let fileManager = FileManager() if !fileManager.fileExists(atPath: file) { @@ -98,9 +119,8 @@ extension Fault { let (_, boundaryCount, internalCount) = ChainMetadata.extract(file: file) - let defines - = Set(defines) - + let defines = Set(defines) + if !fileManager.fileExists(atPath: liberty) { throw ValidationError("Liberty file '\(liberty)' not found.") } @@ -131,7 +151,7 @@ extension Fault { "Warning: Test vectors file provided does not end with .bin." ) } - guard let _ = goldenOutput else { + guard goldenOutput != nil else { throw ValidationError("Using goldenOutput (-g) option is required '\(tvTest)'.") } } @@ -184,28 +204,35 @@ extension Fault { tck, test, ] - let topModulePorts = Python.list(ports.filter { - !chainPorts.contains(String($0.name)!) - }) - - topModulePorts.append(Node.Port( - tms, Python.None, Python.None, Python.None - )) - topModulePorts.append(Node.Port( - tck, Python.None, Python.None, Python.None - )) - topModulePorts.append(Node.Port( - tdi, Python.None, Python.None, Python.None - )) - topModulePorts.append(Node.Port( - tdo, Python.None, Python.None, Python.None - )) - topModulePorts.append(Node.Port( - trst, Python.None, Python.None, Python.None - )) - topModulePorts.append(Node.Port( - tdoEnable, Python.None, Python.None, Python.None - )) + let topModulePorts = Python.list( + ports.filter { + !chainPorts.contains(String($0.name)!) + }) + + topModulePorts.append( + Node.Port( + tms, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tck, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tdi, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tdo, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + trst, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tdoEnable, Python.None, Python.None, Python.None + )) let statements = Python.list() statements.append(Node.Input(tms)) @@ -219,10 +246,11 @@ extension Fault { for input in inputs { if !chainPorts.contains(input.name) { let inputStatement = Node.Input(input.name) - portArguments.append(Node.PortArg( - input.name, - Node.Identifier(input.name) - )) + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(input.name) + )) if input.width > 1 { let width = Node.Width( Node.Constant(input.from), @@ -233,10 +261,11 @@ extension Fault { statements.append(inputStatement) } else { let portIdentifier = input.name - portArguments.append(Node.PortArg( - input.name, - Node.Identifier(portIdentifier) - )) + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(portIdentifier) + )) } } @@ -252,10 +281,11 @@ extension Fault { } statements.append(outputStatement) } - portArguments.append(Node.PortArg( - output.name, - Node.Identifier(output.name) - )) + portArguments.append( + Node.PortArg( + output.name, + Node.Identifier(output.name) + )) } // MARK: tap module @@ -291,11 +321,12 @@ extension Fault { Python.tuple() ) - statements.append(Node.InstanceList( - alteredName, - Python.tuple(), - Python.tuple([submoduleInstance]) - )) + statements.append( + Node.InstanceList( + alteredName, + Python.tuple(), + Python.tuple([submoduleInstance]) + )) let supermodel = Node.ModuleDef( definitionName, @@ -386,6 +417,7 @@ extension Fault { } // MARK: Verification + let models = [model] + includes + blackboxModels print("Verifying tap port integrity…") @@ -429,7 +461,9 @@ extension Fault { print("Tap port verified successfully.") } else { print("Tap port verification failed.") - print("・Ensure that clock and reset signals, if they exist are passed as such to the program.") + print( + "・Ensure that clock and reset signals, if they exist are passed as such to the program." + ) if bypass.reset.active == .high { print("・Ensure that the reset is active high- pass --activeLow for activeLow.") } @@ -474,8 +508,10 @@ extension Fault { print("Test vectors verified successfully.") } else { print("Test vector simulation failed.") - 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.") + 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/Future.swift b/Sources/Fault/Future.swift index ed66449..77fc68e 100644 --- a/Sources/Fault/Future.swift +++ b/Sources/Fault/Future.swift @@ -25,7 +25,7 @@ public class Future { private var executor: () -> Any init(executor: @escaping () -> Any) { - self.semaphore = DispatchSemaphore(value: 0) + semaphore = DispatchSemaphore(value: 0) self.executor = executor if Future.pool == nil { @@ -35,18 +35,19 @@ public class Future { ) ?? CInt(clamping: ProcessInfo.processInfo.processorCount)) } - let _ = thpool_add_work( + _ = thpool_add_work( Future.pool!, { pointer in let this = Unmanaged.fromOpaque(pointer!).takeUnretainedValue() this.store = this.executor() this.semaphore.signal() - }, Unmanaged.passUnretained(self).toOpaque()) + }, Unmanaged.passUnretained(self).toOpaque() + ) } public var value: Any { - self.semaphore.wait() + semaphore.wait() let value = store! return value } diff --git a/Sources/Fault/JTAG.swift b/Sources/Fault/JTAG.swift index 7dc4c79..9844007 100644 --- a/Sources/Fault/JTAG.swift +++ b/Sources/Fault/JTAG.swift @@ -174,5 +174,7 @@ struct TapInfo: Codable { self.chain = chain } - static let `default`: TapInfo = try! JSONDecoder().decode(TapInfo.self, from: Data(TapCreator.info.utf8)) + static let `default`: TapInfo = try! JSONDecoder().decode( + TapInfo.self, from: Data(TapCreator.info.utf8) + ) } diff --git a/Sources/Fault/Metadata.swift b/Sources/Fault/Metadata.swift index 97ea554..da526b6 100644 --- a/Sources/Fault/Metadata.swift +++ b/Sources/Fault/Metadata.swift @@ -76,7 +76,11 @@ struct ChainMetadata: Codable { } let decoder = JSONDecoder() let metadataString = slice.components(separatedBy: "' END FAULT METADATA */")[0] - guard let metadata = try? decoder.decode(ChainMetadata.self, from: metadataString.data(using: .utf8)!) else { + guard + let metadata = try? decoder.decode( + ChainMetadata.self, from: metadataString.data(using: .utf8)! + ) + else { Stderr.print("Metadata json is invalid.") exit(EX_DATAERR) } @@ -113,7 +117,9 @@ struct binMetadata: Codable { let decoder = JSONDecoder() let metadataString = slice.components(separatedBy: "' END FAULT METADATA */")[0] - guard let metadata = try? decoder.decode(binMetadata.self, from: metadataString.data(using: .utf8)!) else { + guard + let metadata = try? decoder.decode(binMetadata.self, from: metadataString.data(using: .utf8)!) + else { Stderr.print("Metadata json is invalid.") exit(EX_DATAERR) } diff --git a/Sources/Fault/Module.swift b/Sources/Fault/Module.swift index 846bb2c..bc44608 100644 --- a/Sources/Fault/Module.swift +++ b/Sources/Fault/Module.swift @@ -17,7 +17,7 @@ import Defile import Foundation import PythonKit -struct Port: Codable { +struct Port: Codable, Hashable { enum Polarity: String, Codable { case input case output @@ -46,7 +46,9 @@ struct Port: Codable { self.ordinal = ordinal } - static func extract(from definition: PythonObject) throws -> (ports: [String: Port], inputs: [Port], outputs: [Port]) { + static func extract(from definition: PythonObject) throws -> ( + ports: [String: Port], inputs: [Port], outputs: [Port] + ) { var ports: [String: Port] = [:] var paramaters: [String: Int] = [:] @@ -108,10 +110,15 @@ struct Port: Codable { } } - let inputs: [Port] = ports.values.filter { $0.polarity == .input }.sorted(by: { $0.ordinal < $1.ordinal }) - let outputs: [Port] = ports.values.filter { $0.polarity == .output }.sorted(by: { $0.ordinal < $1.ordinal }) + let inputs: [Port] = ports.values.filter { $0.polarity == .input }.sorted(by: { + $0.ordinal < $1.ordinal + }) + let outputs: [Port] = ports.values.filter { $0.polarity == .output }.sorted(by: { + $0.ordinal < $1.ordinal + }) if ports.count != inputs.count + outputs.count { - throw RuntimeError("Some ports in \(definition.name) are not properly declared as an input or output.") + throw RuntimeError( + "Some ports in \(definition.name) are not properly declared as an input or output.") } return (ports: ports, inputs: inputs, outputs: outputs) @@ -132,7 +139,8 @@ struct Port: Codable { case "Identifier": value = params["\(expr.name)"]! default: - Stderr.print("Got unknown expression type \(type) while evaluating port expression \(expr)") + Stderr.print( + "Got unknown expression type \(type) while evaluating port expression \(expr)") exit(EX_DATAERR) } return value @@ -147,7 +155,11 @@ struct Port: Codable { extension Port: CustomStringConvertible { var description: String { - "Port@\(ordinal)(\(name): \(polarity ?? .unknown)[\(from)..\(to)])" + var pfx = "" + if from != to || from != 0 { + pfx = "[\(from)..\(to)]" + } + return "Port@\(ordinal)(\(name)\(pfx): \(polarity ?? .unknown))" } } @@ -180,7 +192,9 @@ struct Module { portsByName = ports.reduce(into: [String: Port]()) { $0[$1.name] = $1 } } - static func getModules(in files: [String], filter filterOpt: Set? = nil) throws -> OrderedDictionary { + static func getModules(in files: [String], filter filterOpt: Set? = nil) throws + -> OrderedDictionary + { let parse = Python.import("pyverilog.vparser.parser").parse var result: OrderedDictionary = [:] @@ -202,7 +216,9 @@ struct Module { } } let (_, inputs, outputs) = try Port.extract(from: definition) - result[name] = Module(name: name, inputs: inputs, outputs: outputs, definition: definition) + result[name] = Module( + name: name, inputs: inputs, outputs: outputs, definition: definition + ) } } diff --git a/Sources/Fault/Mux.swift b/Sources/Fault/Mux.swift index dff291f..c4d8b9e 100644 --- a/Sources/Fault/Mux.swift +++ b/Sources/Fault/Mux.swift @@ -28,7 +28,10 @@ class MuxCreator { selection: PythonObject, a: PythonObject, b: PythonObject - ) -> (cellDeclarations: [PythonObject], wireDeclarations: [PythonObject], replacementHook: PythonObject) { + ) -> ( + cellDeclarations: [PythonObject], wireDeclarations: [PythonObject], + replacementHook: PythonObject + ) { let muxName = instance + "__scanchain_mux" let outputWireName = "\(muxName)_\(muxInfo.y)" let outputWireDecl = Node.Wire(outputWireName) @@ -63,11 +66,14 @@ class MuxCreator { if muxInfo.invertedOutput { hook = Node.Unot(hook) } - return ([ - pragma, - instanceDecl, - ], [ - outputWireDecl, - ], hook) + return ( + [ + pragma, + instanceDecl, + ], + [ + outputWireDecl, + ], hook + ) } } diff --git a/Sources/Fault/RNG.swift b/Sources/Fault/RNG.swift index 5158c29..a6cd9c9 100644 --- a/Sources/Fault/RNG.swift +++ b/Sources/Fault/RNG.swift @@ -17,9 +17,9 @@ //===----------------------------------------------------------------------===// #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) -import Darwin + import Darwin #else -import Glibc + import Glibc #endif /// A type that provides seedable deterministic pseudo-random data. @@ -37,18 +37,18 @@ import Glibc /// must form a deterministic sequence that depends only on the seed provided /// upon initialization. public protocol SeedableRandomNumberGenerator: RandomNumberGenerator { - init(seed: [UInt8]) - init(seed: T) + init(seed: [UInt8]) + init(seed: T) } -extension SeedableRandomNumberGenerator { - public init(seed: T) { - var newSeed: [UInt8] = [] - for i in 0..> (UInt8.bitWidth * i))) +public extension SeedableRandomNumberGenerator { + init(seed: some BinaryInteger) { + var newSeed: [UInt8] = [] + for i in 0 ..< seed.bitWidth / UInt8.bitWidth { + newSeed.append(UInt8(truncatingIfNeeded: seed >> (UInt8.bitWidth * i))) + } + self.init(seed: newSeed) } - self.init(seed: newSeed) - } } /// An implementation of `SeedableRandomNumberGenerator` using ARC4. @@ -64,49 +64,49 @@ extension SeedableRandomNumberGenerator { /// suitable for cryptographic applications. @frozen public struct ARC4RandomNumberGenerator: SeedableRandomNumberGenerator { - public static var global = ARC4RandomNumberGenerator(seed: UInt32(time(nil))) - var state: [UInt8] = Array(0...255) - var iPos: UInt8 = 0 - var jPos: UInt8 = 0 + public static var global = ARC4RandomNumberGenerator(seed: UInt32(time(nil))) + var state: [UInt8] = Array(0 ... 255) + var iPos: UInt8 = 0 + var jPos: UInt8 = 0 - /// Initialize ARC4RandomNumberGenerator using an array of UInt8. The array - /// must have length between 1 and 256 inclusive. - public init(seed: [UInt8]) { - precondition(seed.count > 0, "Length of seed must be positive") - precondition(seed.count <= 256, "Length of seed must be at most 256") - var j: UInt8 = 0 - for i: UInt8 in 0...255 { - j &+= S(i) &+ seed[Int(i) % seed.count] - swapAt(i, j) + /// Initialize ARC4RandomNumberGenerator using an array of UInt8. The array + /// must have length between 1 and 256 inclusive. + public init(seed: [UInt8]) { + precondition(seed.count > 0, "Length of seed must be positive") + precondition(seed.count <= 256, "Length of seed must be at most 256") + var j: UInt8 = 0 + for i: UInt8 in 0 ... 255 { + j &+= S(i) &+ seed[Int(i) % seed.count] + swapAt(i, j) + } } - } - // Produce the next random UInt64 from the stream, and advance the internal - // state. - public mutating func next() -> UInt64 { - var result: UInt64 = 0 - for _ in 0.. UInt64 { + var result: UInt64 = 0 + for _ in 0 ..< UInt64.bitWidth / UInt8.bitWidth { + result <<= UInt8.bitWidth + result += UInt64(nextByte()) + } + return result } - return result - } - // Helper to access the state. - private func S(_ index: UInt8) -> UInt8 { - return state[Int(index)] - } + // Helper to access the state. + private func S(_ index: UInt8) -> UInt8 { + state[Int(index)] + } - // Helper to swap elements of the state. - private mutating func swapAt(_ i: UInt8, _ j: UInt8) { - state.swapAt(Int(i), Int(j)) - } + // Helper to swap elements of the state. + private mutating func swapAt(_ i: UInt8, _ j: UInt8) { + state.swapAt(Int(i), Int(j)) + } - // Generates the next byte in the keystream. - private mutating func nextByte() -> UInt8 { - iPos &+= 1 - jPos &+= S(iPos) - swapAt(iPos, jPos) - return S(S(iPos) &+ S(jPos)) - } + // Generates the next byte in the keystream. + private mutating func nextByte() -> UInt8 { + iPos &+= 1 + jPos &+= S(iPos) + swapAt(iPos, jPos) + return S(S(iPos) &+ S(jPos)) + } } diff --git a/Sources/Fault/SerialVectorFormat.swift b/Sources/Fault/SerialVectorFormat.swift index 63fd702..d2509fd 100644 --- a/Sources/Fault/SerialVectorFormat.swift +++ b/Sources/Fault/SerialVectorFormat.swift @@ -43,7 +43,8 @@ enum SerialVectorCreator { let mask = String(repeating: "f", count: tdiHex.count) if let output = BigUInt(tvcPair.goldenOutput, radix: 16) { let hexOutput = String(output, radix: 16) - scanStatements += "SDR \(chainLength) TDI (\(tdiHex)) MASK (\(mask)) TDO (\(hexOutput)); \n" + scanStatements += + "SDR \(chainLength) TDI (\(tdiHex)) MASK (\(mask)) TDO (\(hexOutput)); \n" } else { print("TV golden output \(tvcPair.goldenOutput) is invalid.") } diff --git a/Sources/Fault/Simulation.swift b/Sources/Fault/Simulation.swift index ab2254e..426d18f 100644 --- a/Sources/Fault/Simulation.swift +++ b/Sources/Fault/Simulation.swift @@ -13,9 +13,9 @@ // limitations under the License. import BigInt +import Collections import Defile import Foundation -import Collections import PythonKit struct CoverageMeta: Codable { @@ -57,14 +57,18 @@ enum Simulator { var portHooksGM = "" for (rawName, port) in ports { let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" - portWires += " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name).gm ;\n" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name).gm ;\n" portHooks += ".\(name) ( \(name) ) , " portHooksGM += ".\(name) ( \(name).gm ) , " } let folderName = "\(filePrefix)/thr\(Unmanaged.passUnretained(Thread.current).toOpaque())" - try FileManager.default.createDirectory(atPath: folderName, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.createDirectory( + atPath: folderName, withIntermediateDirectories: true, attributes: nil + ) var inputAssignment = "" var fmtString = "" @@ -80,12 +84,11 @@ enum Simulator { inputList += "\(name) , " } - 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.rawValue) ;\n" @@ -108,7 +111,8 @@ enum Simulator { outputComparison += " ( \(name) != \(name).gm ) || " if output.width > 1 { for i in 0 ..< output.width { - outputAssignment += " assign goldenOutput[\(outputCount)] = gm.\(output.name) [\(i)] ; \n" + outputAssignment += + " assign goldenOutput[\(outputCount)] = gm.\(output.name) [\(i)] ; \n" outputCount += 1 } } else { @@ -185,7 +189,8 @@ enum Simulator { let vvpExecutable = env["FAULT_VVP"] ?? "vvp" let iverilogResult = - "'\(iverilogExecutable)' -B '\(iverilogBase)' -Ttyp \(defineStatements) -o \(aoutName) \(tbName) 2>&1 > /dev/null".sh() + "'\(iverilogExecutable)' -B '\(iverilogBase)' -Ttyp \(defineStatements) -o \(aoutName) \(tbName) 2>&1 > /dev/null" + .sh() if iverilogResult != EX_OK { exit(Int32(iverilogResult)) } @@ -258,9 +263,8 @@ enum Simulator { } } - var coverage: Float = - Float(sa0Covered.count + sa1Covered.count) / - Float(2 * faultPoints.count) + var coverage = + Float(sa0Covered.count + sa1Covered.count) / Float(2 * faultPoints.count) print("Initial coverage: \(coverage * 100)%") var totalTVAttempts = 0 @@ -275,7 +279,9 @@ enum Simulator { if sampleRun { break } - print("Minimum coverage not met (\(coverage * 100)%/\(minimumCoverage * 100)%,) incrementing to \(totalTVAttempts + tvAttempts)…") + print( + "Minimum coverage not met (\(coverage * 100)%/\(minimumCoverage * 100)%,) incrementing to \(totalTVAttempts + tvAttempts)…" + ) } var futureList: [Future] = [] @@ -379,8 +385,7 @@ enum Simulator { } coverage = - Float(sa0Covered.count + sa1Covered.count) / - Float(2 * faultPoints.count) + Float(sa0Covered.count + sa1Covered.count) / Float(2 * faultPoints.count) totalTVAttempts += tvAttempts let remainingTV = ceiling - totalTVAttempts @@ -395,7 +400,7 @@ enum Simulator { coverageList: coverageList, coverageMeta: CoverageMeta( ratio: coverage, - faultPoints: [String](faultPoints), + faultPoints: [String](faultPoints), sa0Covered: sa0Covered.sorted(), sa1Covered: sa1Covered.sorted(), sa0Uncovered: faultPoints.filter { !sa0Covered.contains($0) }, @@ -434,7 +439,8 @@ enum Simulator { var portHooks = "" for (rawName, port) in ports { let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" portHooks += ".\(name) ( \(name) ) , " } @@ -550,7 +556,8 @@ enum Simulator { var portHooks = "" for (rawName, port) in ports { let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" portHooks += ".\(name) ( \(name) ) , " } @@ -699,7 +706,8 @@ enum Simulator { var portHooks = "" for (rawName, port) in ports { let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" portHooks += ".\(name) ( \(name) ) , " } @@ -892,11 +900,13 @@ enum Simulator { let _ = "rm \(aoutName)".sh() } } - let iverilogCmd = "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" + let iverilogCmd = + "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" try outputFile.write(string: "$ \(iverilogCmd)\n") let iverilogResult = - "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1".shOutput() + "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" + .shOutput() try outputFile.write(string: iverilogResult.output) if iverilogResult.terminationStatus != EX_OK { diff --git a/Sources/Fault/String.swift b/Sources/Fault/String.swift index 2ffea4e..0c7cd9d 100644 --- a/Sources/Fault/String.swift +++ b/Sources/Fault/String.swift @@ -44,7 +44,7 @@ extension String { let task = Process() task.executableURL = URL(fileURLWithPath: "/bin/sh") task.arguments = ["-c", self] - //print("$ \(self)") + // print("$ \(self)") if silent { task.standardOutput = FileHandle.nullDevice task.standardError = FileHandle.nullDevice @@ -61,7 +61,7 @@ extension String { return task.terminationStatus } - + func replacingExtension(_ before: String, with after: String) -> String { var result = self if result.hasSuffix(before) { diff --git a/Sources/Fault/Synthesis.swift b/Sources/Fault/Synthesis.swift index 10a6da2..be9272b 100644 --- a/Sources/Fault/Synthesis.swift +++ b/Sources/Fault/Synthesis.swift @@ -25,56 +25,56 @@ enum Synthesis { ) -> String { let opt = optimize ? "opt" : "" return """ - # read liberty - read_liberty -lib -ignore_miss_dir -setattr blackbox \(libertyFile) + # read liberty + read_liberty -lib -ignore_miss_dir -setattr blackbox \(libertyFile) - # read black boxes - read_verilog -sv -lib \(blackboxedModules.map { "'\($0)'" }.joined(separator: " ")) + # read black boxes + read_verilog -sv -lib \(blackboxedModules.map { "'\($0)'" }.joined(separator: " ")) - # read design - read_verilog -sv \(files.map { "'\($0)'" }.joined(separator: " ")) + # read design + read_verilog -sv \(files.map { "'\($0)'" }.joined(separator: " ")) - # check design hierarchy - hierarchy \(checkHierarchy ? "-check" : "") -top \(module) - flatten; + # check design hierarchy + hierarchy \(checkHierarchy ? "-check" : "") -top \(module) + flatten; - # translate processes (always blocks) - proc; \(opt) + # translate processes (always blocks) + proc; \(opt) - # detect and optimize FSM encodings - fsm; \(opt) + # detect and optimize FSM encodings + fsm; \(opt) - # implement memories (arrays) - memory; \(opt) + # implement memories (arrays) + memory; \(opt) - # convert to gate logic - techmap; \(opt) + # convert to gate logic + techmap; \(opt) - # flatten - flatten; \(opt) + # flatten + flatten; \(opt) - # mapping flip-flops to mycells.lib - dfflibmap -liberty \(libertyFile) + # mapping flip-flops to mycells.lib + dfflibmap -liberty \(libertyFile) - # expose dff - \(cutting ? "expose -cut -evert-dff; \(opt)" : "") + # expose dff + \(cutting ? "expose -cut -evert-dff; \(opt)" : "") - # mapping logic to mycells.lib - abc -liberty \(libertyFile) - splitnets + # mapping logic to mycells.lib + abc -liberty \(libertyFile) + splitnets - # print gate count - stat + # print gate count + stat - # cleanup - opt_clean -purge + # cleanup + opt_clean -purge - # names - # autoname + # names + # autoname - 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 - """ + 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 17e7432..c05ce1f 100644 --- a/Sources/Fault/TVGenerator.swift +++ b/Sources/Fault/TVGenerator.swift @@ -16,12 +16,11 @@ import BigInt import Defile import Foundation - protocol TVGenerator { var current: BigUInt { get set } - + init(allBits: Int, seed: UInt) - + func generate(count: Int) } @@ -37,7 +36,7 @@ extension TVGenerator { enum TVGeneratorFactory { private static var registry: [String: TVGenerator.Type] = [:] - static func register(name: String, type: T.Type) -> Bool { + static func register(name: String, type: (some TVGenerator).Type) -> Bool { registry[name] = type return true } @@ -58,15 +57,15 @@ class SwiftRNG: TVGenerator { var current: BigUInt var bits: Int var rng: ARC4RandomNumberGenerator - + required init(allBits bits: Int, seed: UInt) { - self.current = 0 + current = 0 self.bits = bits - self.rng = ARC4RandomNumberGenerator(seed: seed) + rng = ARC4RandomNumberGenerator(seed: seed) } - func generate(count: Int) { - self.current = BigUInt.randomInteger(withMaximumWidth: bits, using: &self.rng) + func generate(count _: Int) { + current = BigUInt.randomInteger(withMaximumWidth: bits, using: &rng) } static let registered = TVGeneratorFactory.register(name: "swift", type: SwiftRNG.self) @@ -75,7 +74,7 @@ class SwiftRNG: TVGenerator { class LFSR: TVGenerator { var current: BigUInt var bits: Int - + static let taps: [UInt: [UInt]] = [ // nbits : Feedback Polynomial 2: [2, 1], @@ -120,8 +119,8 @@ class LFSR: TVGenerator { self.seed = seed self.nbits = nbits self.bits = bits - self.current = 0 - + current = 0 + let polynomial = LFSR.taps[nbits]! polynomialHex = 0 @@ -151,7 +150,7 @@ class LFSR: TVGenerator { return seed } - func generate(count: Int) { + func generate(count _: Int) { var returnValue: BigUInt = 0 var generations = bits / Int(nbits) let leftover = bits % Int(nbits) @@ -173,16 +172,16 @@ class LFSR: TVGenerator { class PatternGenerator: TVGenerator { var current: BigUInt = 0 var bits: Int = 0 - + required init(allBits bits: Int, seed _: UInt) { self.bits = bits - self.current = 0 + current = 0 } func generate(count: Int) { let selector = count / 2 let complement = (count % 2) == 1 - self.current = 0 + current = 0 if selector == 0 { // Nothing, it's already all zeroes } else if selector == 1 { @@ -201,20 +200,18 @@ class PatternGenerator: TVGenerator { let window = (BigUInt(1) << windowSize) - 1 current |= (window << windowShift) } - + let mask = (BigUInt(1) << bits) - 1 current &= mask - - if (complement) { + + if complement { current ^= mask } } - + static let registered = TVGeneratorFactory.register(name: "pattern", type: PatternGenerator.self) } - - // TODO: Unify external and internal TV generators protocol ExternalTestVectorGenerator { init() @@ -224,7 +221,7 @@ protocol ExternalTestVectorGenerator { enum ETVGFactory { private static var registry: [String: ExternalTestVectorGenerator.Type] = [:] - static func register(name: String, type: T.Type) -> Bool { + static func register(name: String, type: (some ExternalTestVectorGenerator).Type) -> Bool { registry[name] = type return true } @@ -244,7 +241,7 @@ enum ETVGFactory { class Atalanta: ExternalTestVectorGenerator { required init() {} - func generate(file: String, module: String) -> ([TestVector], [Port]) { + func generate(file: String, module _: String) -> ([TestVector], [Port]) { let output = file.replacingExtension(".bench", with: ".test") let atalanta = "atalanta -t \(output) \(file)".sh() @@ -267,7 +264,7 @@ class Atalanta: ExternalTestVectorGenerator { class Quaigh: ExternalTestVectorGenerator { required init() {} - func generate(file: String, module: String) -> ([TestVector], [Port]) { + func generate(file: String, module _: String) -> ([TestVector], [Port]) { let output = file.replacingExtension(".bench", with: ".test") let quaigh = "quaigh atpg \(file) -o \(output)".sh() @@ -294,7 +291,9 @@ class PODEM: ExternalTestVectorGenerator { let tempDir = "\(NSTemporaryDirectory())" let folderName = "\(tempDir)thr\(Unmanaged.passUnretained(Thread.current).toOpaque())" - try? FileManager.default.createDirectory(atPath: folderName, withIntermediateDirectories: true, attributes: nil) + try? FileManager.default.createDirectory( + atPath: folderName, withIntermediateDirectories: true, attributes: nil + ) defer { try? FileManager.default.removeItem(atPath: folderName) } @@ -317,12 +316,10 @@ class PODEM: ExternalTestVectorGenerator { static let registered = ETVGFactory.register(name: "PODEM", type: PODEM.self) } - class PodemQuest: ExternalTestVectorGenerator { - required init() {} - func generate(file: String, module: String) -> ([TestVector], [Port]) { + func generate(file: String, module _: String) -> ([TestVector], [Port]) { let output = file.replacingExtension(".bench", with: ".test") let podemQuest = "podemquest -i\(file) -o \(output)".sh() diff --git a/Sources/Fault/TestVector.swift b/Sources/Fault/TestVector.swift index 46fae12..8a8f558 100644 --- a/Sources/Fault/TestVector.swift +++ b/Sources/Fault/TestVector.swift @@ -13,9 +13,9 @@ // limitations under the License. import BigInt +import Collections import Defile import Foundation -import Collections typealias TestVector = [BigUInt] @@ -124,7 +124,9 @@ enum TVSet { return (vectors: vectors, inputs: inputs) } - static func readFromTest(_ file: String, withInputsFrom bench: String) throws -> ([TestVector], [Port]) { + static func readFromTest(_ file: String, withInputsFrom bench: String) throws -> ( + [TestVector], [Port] + ) { var inputDict: OrderedDictionary = [:] let benchStr = File.read(bench)! let inputRx = #/INPUT\(([^\(\)]+?)(\[\d+\])?\)/# @@ -132,11 +134,19 @@ enum TVSet { 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] = + inputDict[name] + ?? Port( + name: name, polarity: .input, from: 0, to: -1, + at: { + ordinal += 1 + return ordinal + }() + ) inputDict[name]!.to += 1 } } - + let inputs: [Port] = inputDict.values.sorted { $0.ordinal < $1.ordinal } var vectors: [TestVector] = [] let testStr = File.read(file)! diff --git a/flake.lock b/flake.lock index 9d57cda..b9a9942 100644 --- a/flake.lock +++ b/flake.lock @@ -86,11 +86,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1728905391, - "narHash": "sha256-iox9yGNG4MwSKhQuwegLcDW6wVGzfdBPrh8SrhSLA8c=", + "lastModified": 1729690989, + "narHash": "sha256-vH6nNmYWX5IV8+C1eorCjA/8YyYhNlKnPCruaiTlW6U=", "owner": "efabless", "repo": "nix-eda", - "rev": "0814aa6c1c7d556aa08212cc875063cff62cb9b0", + "rev": "c54cac502fc8e828244063a0e0fd1dfbf706cb00", "type": "github" }, "original": { @@ -126,11 +126,11 @@ ] }, "locked": { - "lastModified": 1729429745, - "narHash": "sha256-dQgCxAcoEv1NTqSRpNGEE4NhNufc76Cyc3HDInqL2+0=", + "lastModified": 1730557369, + "narHash": "sha256-GWoKlyvzHYXdTny8p7gP2norJnYoNl8gbitHYP9IAKo=", "owner": "donn", "repo": "nl2bench", - "rev": "23d4e0954d56ec15a12bb27064501e93566ee8e7", + "rev": "bfbf43214a279103c79bb955bc50cab878ca647c", "type": "github" }, "original": { @@ -148,11 +148,11 @@ ] }, "locked": { - "lastModified": 1722162293, - "narHash": "sha256-WILtL6WKXs5pB5Jujx9HIT2w1jiVTZymXC7DTuqLPEM=", + "lastModified": 1729433159, + "narHash": "sha256-/zUDNezyZaV7tGIXK9LcdWH5xGHI5CRKALh0vNDf6HE=", "owner": "coloquinte", "repo": "quaigh", - "rev": "2fec998178d4e48c5379dd0a2025f8688797f99a", + "rev": "c9a17d28bfb3ff73315262d4f2b345e8877b395a", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f71a029..4383115 100644 --- a/flake.nix +++ b/flake.nix @@ -55,7 +55,7 @@ callPackage = lib.callPackageWith pkgs; in { mac-testing = pkgs.stdenvNoCC.mkDerivation (with pkgs; let - pyenv = python3.withPackages (ps: with ps; [pyverilog pyyaml pytest nl2bench]); + pyenv = python3.withPackages (ps: [ps.pyverilog ps.pyyaml ps.pytest ps.nl2bench]); in { # Use the host's Clang and Swift name = "shell"; @@ -68,6 +68,7 @@ ]; PYTHON_LIBRARY = "${pyenv}/lib/lib${python3.libPrefix}${stdenvNoCC.hostPlatform.extensions.sharedLibrary}"; + NIX_PYTHONPATH = "${pyenv}/${pyenv.sitePackages}"; PYTHONPATH = "${pyenv}/${pyenv.sitePackages}"; FAULT_IVL_BASE = "${verilog}/lib/ivl"; }); From 791e71446ee0adce164b970b152c7bc3edd4903a Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Sat, 2 Nov 2024 17:13:51 +0200 Subject: [PATCH 2/3] allow ignoring outputs --- Sources/Fault/Entries/atpg.swift | 76 ++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/Sources/Fault/Entries/atpg.swift b/Sources/Fault/Entries/atpg.swift index 4ae1102..b304e4b 100644 --- a/Sources/Fault/Entries/atpg.swift +++ b/Sources/Fault/Entries/atpg.swift @@ -38,7 +38,8 @@ extension Fault { @Option( name: [.long, .customLong("output-faultPoints")], - help: "Path to the output yml file listing all generated fault points. (Default: nil)") + help: "Path to the output yml file listing all generated fault points. (Default: nil)" + ) var outputFaultPoints: String? @Option( @@ -50,12 +51,14 @@ extension Fault { @Option( name: [.short, .long, .customLong("cellModel")], - help: "A Verilog model with which standard cells can be simulated.") + help: "A Verilog model with which standard cells can be simulated." + ) var cellModel: String @Option( name: [.customShort("v"), .long], - help: "Number of test vectors to generate in the first batch.") + help: "Number of test vectors to generate in the first batch." + ) var tvCount: Int = 100 @Option( @@ -89,14 +92,24 @@ extension Fault { @Option( name: [.customShort("g"), .long], - help: "Use an external TV Generator: Atalanta or PODEM.") + help: "Use an external TV Generator: Atalanta or PODEM." + ) var etvGen: String? @Option( name: [.short, .long], - help: "Netlist in bench format. (Required iff generator is set to Atalanta or PODEM.)") + help: + "Netlist in bench format. (Required if generator is set to Atalanta or PODEM and a liberty file is not passed.)" + ) var bench: String? + @Option( + name: [.customShort("l"), .long], + help: + "Liberty file. (Required if generator is set to Atalanta or PODEM and a bench file is not passed.)" + ) + var liberty: String? + @Flag(help: "Generate only one testbench for inspection, and do not delete it.") var sampleRun: Bool = false @@ -111,19 +124,20 @@ extension Fault { @Option( name: [.customShort("D"), .customLong("define")], - help: "Define statements to include during simulations.") + help: "Define statements to include during simulations." + ) var defines: [String] = [] @Option( name: [.customShort("I"), .customLong("include")], - help: "Extra verilog models to include during simulations.") + help: "Extra verilog models to include during simulations." + ) var includes: [String] = [] @Argument(help: "The cutaway netlist to generate patterns for.") var file: String mutating func run() throws { - if !TVGeneratorFactory.validNames.contains(tvGen) { throw ValidationError("Invalid test-vector generator \(tvGen).") } @@ -182,14 +196,36 @@ extension Fault { Stderr.print("Unknown external test vector generator '\(tvGenerator)'.") Foundation.exit(EX_USAGE) } + if bench == nil, liberty == nil { + Stderr.print( + "Either --bench or --liberty must be passed when using an external test vector generator." + ) + Foundation.exit(EX_USAGE) + } - let benchUnwrapped = bench! // Program exits if etvGen.value isn't nil and bench.value is or vice versa + let benchUnwrapped = + bench + ?? { + let nl2bench = Python.import("nl2bench") + let pyPath = Python.import("pathlib").Path + let benchPath = file.replacingExtension(".v", with: ".bench") + let benchPathF = Python.open(benchPath, "w", encoding: "utf8") + nl2bench.nl2bench.verilog_netlist_to_bench( + pyPath(file), + [ + liberty! + ], + benchPathF, Array(bypass.bypassedIOs) + ) + return benchPath + }() if !fileManager.fileExists(atPath: benchUnwrapped) { throw ValidationError("Bench file '\(benchUnwrapped)' not found.") } (etvSetVectors, etvSetInputs) = etvgen.generate( - file: benchUnwrapped, module: "\(definition.name)") + file: benchUnwrapped, module: "\(definition.name)" + ) if etvSetVectors.count == 0 { Stderr.print( @@ -245,10 +281,22 @@ extension Fault { inputsMinusIgnored = evtInputsMinusIgnored } + var inputSimulationValues: OrderedDictionary = [:] + var portsMinusBypassedOutputs: [String: Port] = ports + var outputsMinusBypassed: [Port] = [] for (_, port) in ports { if bypass.bypassedIOs.contains(port.name) { + print("Bypassing \(port.name)…") + if port.polarity == .input { + inputSimulationValues[port.name] = bypass.simulationValues[port.name] + } else { + portsMinusBypassedOutputs.removeValue(forKey: port.name) + } continue } + if port.polarity == .output { + outputsMinusBypassed.append(port) + } if port.width == 1 { faultPoints.insert(port.name) } else { @@ -311,8 +359,8 @@ extension Fault { with: models, ports: ports, inputs: inputsMinusIgnored, - bypassingWithBehavior: bypass.simulationValues, - outputs: outputs, + bypassingWithBehavior: inputSimulationValues, + outputs: outputsMinusBypassed, initialVectorCount: tvCount, incrementingBy: increment, minimumCoverage: tvMinimumCoverage, @@ -338,7 +386,7 @@ extension Fault { let rawTVInfo = TVInfo( inputs: inputsMinusIgnored, - outputs: outputs, + outputs: outputsMinusBypassed, coverageList: result.coverageList ) let jsonRawOutput = jsonOutput.replacingExtension(".tv.json", with: ".raw_tv.json") @@ -348,7 +396,7 @@ extension Fault { let tvInfo = TVInfo( inputs: inputsMinusIgnored, - outputs: outputs, + outputs: outputsMinusBypassed, coverageList: Compactor.compact(coverageList: result.coverageList) ) print( From d9954d8391481d12f5ea4013f5663a3153c18663 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Sat, 2 Nov 2024 17:14:42 +0200 Subject: [PATCH 3/3] use apple swift-format --- Package.swift | 80 +- Sources/Fault/BoundaryScanRegister.swift | 294 ++-- Sources/Fault/Compaction.swift | 338 ++--- Sources/Fault/Entries/asm.swift | 308 ++-- Sources/Fault/Entries/chain.swift | 1434 +++++++++--------- Sources/Fault/Entries/common.swift | 82 +- Sources/Fault/Entries/cut.swift | 342 ++--- Sources/Fault/Entries/main.swift | 82 +- Sources/Fault/Entries/synth.swift | 126 +- Sources/Fault/Entries/tap.swift | 916 +++++------ Sources/Fault/Errors.swift | 14 +- Sources/Fault/Future.swift | 62 +- Sources/Fault/JTAG.swift | 280 ++-- Sources/Fault/JTAGStrings.swift | 6 +- Sources/Fault/Metadata.swift | 192 +-- Sources/Fault/Module.swift | 366 ++--- Sources/Fault/Mux.swift | 112 +- Sources/Fault/RNG.swift | 98 +- Sources/Fault/SCLConfiguration.swift | 83 +- Sources/Fault/SerialVectorFormat.swift | 106 +- Sources/Fault/Simulation.swift | 1756 +++++++++++----------- Sources/Fault/String.swift | 136 +- Sources/Fault/Synthesis.swift | 128 +- Sources/Fault/TVGenerator.swift | 514 +++---- Sources/Fault/TestVector.swift | 248 +-- 25 files changed, 4052 insertions(+), 4051 deletions(-) diff --git a/Package.swift b/Package.swift index f0b8da9..f821ea3 100644 --- a/Package.swift +++ b/Package.swift @@ -4,44 +4,44 @@ import PackageDescription let package = Package( - name: "Fault", - platforms: [ - .macOS(.v13), // Regex features only available in Ventura+ - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - .package( - url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.0") - ), - .package(url: "https://github.com/pvieito/PythonKit", from: "0.5.0"), - .package(url: "https://github.com/donn/Defile.git", from: "5.2.1"), - .package(url: "https://github.com/attaswift/BigInt.git", from: "5.2.1"), - .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.6"), - .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), - ], - targets: [ - // 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", - dependencies: [ - "PythonKit", .product(name: "ArgumentParser", package: "swift-argument-parser"), - "Defile", .product(name: "Collections", package: "swift-collections"), "BigInt", - "Yams", - "CThreadPool", - ], - path: "Sources/Fault" - ), - .target( - name: "CThreadPool", - dependencies: [], - path: "Sources/CThreadPool", - sources: [ - "thpool.c", - ], - cSettings: [ - .headerSearchPath("Sources/CThreadPool/include"), - ] - ), - ] + name: "Fault", + platforms: [ + .macOS(.v13) // Regex features only available in Ventura+ + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package( + url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.0") + ), + .package(url: "https://github.com/pvieito/PythonKit", from: "0.5.0"), + .package(url: "https://github.com/donn/Defile.git", from: "5.2.1"), + .package(url: "https://github.com/attaswift/BigInt.git", from: "5.2.1"), + .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.6"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), + ], + targets: [ + // 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", + dependencies: [ + "PythonKit", .product(name: "ArgumentParser", package: "swift-argument-parser"), + "Defile", .product(name: "Collections", package: "swift-collections"), "BigInt", + "Yams", + "CThreadPool", + ], + path: "Sources/Fault" + ), + .target( + name: "CThreadPool", + dependencies: [], + path: "Sources/CThreadPool", + sources: [ + "thpool.c" + ], + cSettings: [ + .headerSearchPath("Sources/CThreadPool/include") + ] + ), + ] ) diff --git a/Sources/Fault/BoundaryScanRegister.swift b/Sources/Fault/BoundaryScanRegister.swift index ced3f81..52130dd 100644 --- a/Sources/Fault/BoundaryScanRegister.swift +++ b/Sources/Fault/BoundaryScanRegister.swift @@ -16,154 +16,154 @@ import Foundation import PythonKit class BoundaryScanRegisterCreator { - var name: String - private var inputName: String - private var outputName: String - var counter: Int = 0 - - var clock: String - var reset: String - var resetActive: Simulator.Active - var testing: String - var shift: String - - private var clockIdentifier: PythonObject - private var resetIdentifier: PythonObject - private var testingIdentifier: PythonObject - private var shiftIdentifier: PythonObject - - private var Node: PythonObject - - init( - name: String, - clock: String, - reset: String, - resetActive: Simulator.Active, - testing: String, - shift: String, - using Node: PythonObject - ) { - self.name = name - inputName = "\(name)_input" - outputName = "\(name)_output" - - self.clock = clock - clockIdentifier = Node.Identifier(clock) - - self.reset = reset - resetIdentifier = Node.Identifier(reset) - - self.resetActive = resetActive - - self.testing = testing - testingIdentifier = Node.Identifier(testing) - - self.shift = shift - shiftIdentifier = Node.Identifier(shift) - - self.Node = Node - } - - func create( - group: String, - din: PythonObject, - dout: PythonObject, - sin: String, - sout: String, - input: Bool - ) -> PythonObject { - let sinIdentifier = Node.Identifier(sin) - let soutIdentifier = Node.Identifier(sout) - - let name = input ? inputName : outputName - - let portArguments = [ - Node.PortArg("din", din), - Node.PortArg("dout", dout), - Node.PortArg("sin", sinIdentifier), - Node.PortArg("sout", soutIdentifier), - Node.PortArg("clock", clockIdentifier), - Node.PortArg("reset", resetIdentifier), - Node.PortArg("testing", testingIdentifier), - Node.PortArg("shift", shiftIdentifier), - ] - - let instanceName = "__\(name)_\(group)_\(counter)__" - - let submoduleInstance = Node.Instance( - name, - instanceName, - Python.tuple(portArguments), - Python.tuple() - ) - - counter += 1 - - return Node.InstanceList( - name, - Python.tuple(), - Python.tuple([submoduleInstance]) - ) - } - - var inputDefinition: String { - """ - module \(inputName) ( - din, - dout, - sin, - sout, - clock, - reset, - testing, - shift - ); - input din; output dout; - input sin; output sout; - input clock, reset, testing, shift; - - reg store; - always @ (posedge clock or \(resetActive == .high ? "posedge" : "negedge") reset) begin - if (\(resetActive == .high ? "" : "~") reset) begin - store <= 1'b0; - end else begin - store <= shift ? sin: dout; - end + var name: String + private var inputName: String + private var outputName: String + var counter: Int = 0 + + var clock: String + var reset: String + var resetActive: Simulator.Active + var testing: String + var shift: String + + private var clockIdentifier: PythonObject + private var resetIdentifier: PythonObject + private var testingIdentifier: PythonObject + private var shiftIdentifier: PythonObject + + private var Node: PythonObject + + init( + name: String, + clock: String, + reset: String, + resetActive: Simulator.Active, + testing: String, + shift: String, + using Node: PythonObject + ) { + self.name = name + inputName = "\(name)_input" + outputName = "\(name)_output" + + self.clock = clock + clockIdentifier = Node.Identifier(clock) + + self.reset = reset + resetIdentifier = Node.Identifier(reset) + + self.resetActive = resetActive + + self.testing = testing + testingIdentifier = Node.Identifier(testing) + + self.shift = shift + shiftIdentifier = Node.Identifier(shift) + + self.Node = Node + } + + func create( + group: String, + din: PythonObject, + dout: PythonObject, + sin: String, + sout: String, + input: Bool + ) -> PythonObject { + let sinIdentifier = Node.Identifier(sin) + let soutIdentifier = Node.Identifier(sout) + + let name = input ? inputName : outputName + + let portArguments = [ + Node.PortArg("din", din), + Node.PortArg("dout", dout), + Node.PortArg("sin", sinIdentifier), + Node.PortArg("sout", soutIdentifier), + Node.PortArg("clock", clockIdentifier), + Node.PortArg("reset", resetIdentifier), + Node.PortArg("testing", testingIdentifier), + Node.PortArg("shift", shiftIdentifier), + ] + + let instanceName = "__\(name)_\(group)_\(counter)__" + + let submoduleInstance = Node.Instance( + name, + instanceName, + Python.tuple(portArguments), + Python.tuple() + ) + + counter += 1 + + return Node.InstanceList( + name, + Python.tuple(), + Python.tuple([submoduleInstance]) + ) + } + + var inputDefinition: String { + """ + module \(inputName) ( + din, + dout, + sin, + sout, + clock, + reset, + testing, + shift + ); + input din; output dout; + input sin; output sout; + input clock, reset, testing, shift; + + reg store; + always @ (posedge clock or \(resetActive == .high ? "posedge" : "negedge") reset) begin + if (\(resetActive == .high ? "" : "~") reset) begin + store <= 1'b0; + end else begin + store <= shift ? sin: dout; end - assign sout = store; - assign dout = testing ? store : din; - endmodule - - """ - } - - var outputDefinition: String { - """ - module \(outputName) ( - din, - dout, - sin, - sout, - clock, - reset, - testing, - shift - ); - input din; output dout; - input sin; output sout; - input clock, reset, testing, shift; - reg store; - always @ (posedge clock or \(resetActive == .high ? "posedge" : "negedge") reset) begin - if (\(resetActive == .high ? "" : "~") reset) begin - store <= 1'b0; - end else begin - store <= shift ? sin: dout; - end + end + assign sout = store; + assign dout = testing ? store : din; + endmodule + + """ + } + + var outputDefinition: String { + """ + module \(outputName) ( + din, + dout, + sin, + sout, + clock, + reset, + testing, + shift + ); + input din; output dout; + input sin; output sout; + input clock, reset, testing, shift; + reg store; + always @ (posedge clock or \(resetActive == .high ? "posedge" : "negedge") reset) begin + if (\(resetActive == .high ? "" : "~") reset) begin + store <= 1'b0; + end else begin + store <= shift ? sin: dout; end - assign sout = store; - assign dout = din; - endmodule + end + assign sout = store; + assign dout = din; + endmodule - """ - } + """ + } } diff --git a/Sources/Fault/Compaction.swift b/Sources/Fault/Compaction.swift index 33621fc..b58018e 100644 --- a/Sources/Fault/Compaction.swift +++ b/Sources/Fault/Compaction.swift @@ -13,198 +13,198 @@ // limitations under the License. enum Compactor { - static func compact( - coverageList: [TVCPair] - ) -> [TVCPair] { - var sa0 = Set() - var sa1 = Set() + static func compact( + coverageList: [TVCPair] + ) -> [TVCPair] { + var sa0 = Set() + var sa1 = Set() - var sa0Covered = Set() - var sa1Covered = Set() + var sa0Covered = Set() + var sa1Covered = Set() - let tvCount = coverageList.count + let tvCount = coverageList.count - // Construct Set of all Faults - for tvPair in coverageList { - sa0.formUnion(tvPair.coverage.sa0) - sa1.formUnion(tvPair.coverage.sa1) - } + // Construct Set of all Faults + for tvPair in coverageList { + sa0.formUnion(tvPair.coverage.sa0) + sa1.formUnion(tvPair.coverage.sa1) + } - // Find Essential TVs - print("Finding essential test vectors…") - let result = - Compactor.findEssentials(coverageList: coverageList, sa0: sa0, sa1: sa1) + // Find Essential TVs + print("Finding essential test vectors…") + let result = + Compactor.findEssentials(coverageList: coverageList, sa0: sa0, sa1: sa1) - print("Found \(result.vectors.count) essential test vectors.") + print("Found \(result.vectors.count) essential test vectors.") - // Essential TV columns - for fault in result.faultSA0 { - sa0Covered.insert(fault) - } + // Essential TV columns + for fault in result.faultSA0 { + sa0Covered.insert(fault) + } - for fault in result.faultSA1 { - sa1Covered.insert(fault) - } + for fault in result.faultSA1 { + sa1Covered.insert(fault) + } - var rowCount = [TestVector: UInt]() + var rowCount = [TestVector: UInt]() - for tvPair in coverageList { - rowCount[tvPair.vector] = - UInt(tvPair.coverage.sa0.count + tvPair.coverage.sa1.count) - } + for tvPair in coverageList { + rowCount[tvPair.vector] = + UInt(tvPair.coverage.sa0.count + tvPair.coverage.sa1.count) + } - var vectors = result.vectors - - func exec( - tvPair: TVCPair, - sa0Covered: Set, - sa1Covered: Set - ) -> (Int, TestVector) { - var sa0: [String] = [] - var sa1: [String] = [] - - if tvPair.coverage.sa0.count != 0 { - sa0 = tvPair.coverage.sa0.filter { - !sa0Covered.contains($0) - } - } - if tvPair.coverage.sa1.count != 0 { - sa1 = tvPair.coverage.sa1.filter { - !sa1Covered.contains($0) - } - } - return (covered: sa0.count + sa1.count, vector: tvPair.vector) - } + var vectors = result.vectors + + func exec( + tvPair: TVCPair, + sa0Covered: Set, + sa1Covered: Set + ) -> (Int, TestVector) { + var sa0: [String] = [] + var sa1: [String] = [] - print("Performing compaction…") - - repeat { - let sortedCount = rowCount.sorted { $0.1 > $1.1 } - let tvPairDominant = coverageList.filter { $0.vector == sortedCount[0].key }[0] - - for fault in tvPairDominant.coverage.sa0 { - sa0Covered.insert(fault) - } - for fault in tvPairDominant.coverage.sa1 { - sa1Covered.insert(fault) - } - - var FutureList: [Future] = [] - // Update Row Count - for tvPair in coverageList { - let future = Future { - exec( - tvPair: tvPair, - sa0Covered: sa0Covered, - sa1Covered: sa1Covered - ) - } - FutureList.append(future) - } - - for future in FutureList { - let (count, vector) = future.value as! (Int, TestVector) - rowCount[vector] = UInt(count) - } - - vectors.insert(sortedCount[0].key) - } while (sa0Covered.count != sa0.count) || (sa1Covered.count != sa1.count) - - let filtered = coverageList.filter { vectors.contains($0.vector) } - - // Verify that Compaction didn't reduce the coverage - var sa0Final = Set() - var sa1Final = Set() - - for tvPair in filtered { - sa0Final.formUnion(tvPair.coverage.sa0) - sa1Final.formUnion(tvPair.coverage.sa1) + if tvPair.coverage.sa0.count != 0 { + sa0 = tvPair.coverage.sa0.filter { + !sa0Covered.contains($0) } - 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("Successfully compacted test vectors by a ratio of \(String(format: "%.2f", ratio))%.") - } else { - print("Error: All faults aren't covered after compaction .\n") + } + if tvPair.coverage.sa1.count != 0 { + sa1 = tvPair.coverage.sa1.filter { + !sa1Covered.contains($0) } - return filtered + } + return (covered: sa0.count + sa1.count, vector: tvPair.vector) } - private static func findEssentials( - coverageList: [TVCPair], - sa0: Set, - sa1: Set - ) -> (vectors: Set, faultSA0: [String], faultSA1: [String]) { - var vectors = Set() - var faultSA0: [String] = [] - var faultSA1: [String] = [] - - func sa0Exec( - fault: String, - coverageList: [TVCPair] - ) -> (fault: String, count: Int, tvRow: TestVector) { - var count = 0 - var tvRow: TestVector = [] - for tvPair in coverageList { - if tvPair.coverage.sa0.contains(fault) { - count = count + 1 - tvRow = tvPair.vector - } - } - return (fault: fault, count: count, tvRow: tvRow) + print("Performing compaction…") + + repeat { + let sortedCount = rowCount.sorted { $0.1 > $1.1 } + let tvPairDominant = coverageList.filter { $0.vector == sortedCount[0].key }[0] + + for fault in tvPairDominant.coverage.sa0 { + sa0Covered.insert(fault) + } + for fault in tvPairDominant.coverage.sa1 { + sa1Covered.insert(fault) + } + + var FutureList: [Future] = [] + // Update Row Count + for tvPair in coverageList { + let future = Future { + exec( + tvPair: tvPair, + sa0Covered: sa0Covered, + sa1Covered: sa1Covered + ) } + FutureList.append(future) + } - func sa1Exec( - fault: String, - coverageList: [TVCPair] - ) -> (fault: String, count: Int, tvRow: TestVector) { - var count = 0 - var tvRow: TestVector = [] - for tvPair in coverageList { - if tvPair.coverage.sa1.contains(fault) { - count = count + 1 - tvRow = tvPair.vector - } - } - return (fault: fault, count: count, tvRow: tvRow) - } + for future in FutureList { + let (count, vector) = future.value as! (Int, TestVector) + rowCount[vector] = UInt(count) + } - var sa0Futures: [Future] = [] - for fault in sa0 { - let future = Future { - sa0Exec(fault: fault, coverageList: coverageList) - } - sa0Futures.append(future) - } + vectors.insert(sortedCount[0].key) + } while (sa0Covered.count != sa0.count) || (sa1Covered.count != sa1.count) - var sa1Futures: [Future] = [] - for fault in sa1 { - let future = Future { - sa1Exec(fault: fault, coverageList: coverageList) - } - sa1Futures.append(future) - } + let filtered = coverageList.filter { vectors.contains($0.vector) } - for future in sa0Futures { - let (fault, count, tvRow) = future.value as! (String, Int, TestVector) - if count == 1 { - faultSA0.append(fault) - vectors.insert(tvRow) - } + // Verify that Compaction didn't reduce the coverage + var sa0Final = Set() + var sa1Final = Set() + + for tvPair in filtered { + sa0Final.formUnion(tvPair.coverage.sa0) + sa1Final.formUnion(tvPair.coverage.sa1) + } + 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("Successfully compacted test vectors by a ratio of \(String(format: "%.2f", ratio))%.") + } else { + print("Error: All faults aren't covered after compaction .\n") + } + return filtered + } + + private static func findEssentials( + coverageList: [TVCPair], + sa0: Set, + sa1: Set + ) -> (vectors: Set, faultSA0: [String], faultSA1: [String]) { + var vectors = Set() + var faultSA0: [String] = [] + var faultSA1: [String] = [] + + func sa0Exec( + fault: String, + coverageList: [TVCPair] + ) -> (fault: String, count: Int, tvRow: TestVector) { + var count = 0 + var tvRow: TestVector = [] + for tvPair in coverageList { + if tvPair.coverage.sa0.contains(fault) { + count = count + 1 + tvRow = tvPair.vector } + } + return (fault: fault, count: count, tvRow: tvRow) + } - for future in sa1Futures { - let (fault, count, tvRow) = future.value as! (String, Int, TestVector) - if count == 1 { - faultSA1.append(fault) - vectors.insert(tvRow) - } + func sa1Exec( + fault: String, + coverageList: [TVCPair] + ) -> (fault: String, count: Int, tvRow: TestVector) { + var count = 0 + var tvRow: TestVector = [] + for tvPair in coverageList { + if tvPair.coverage.sa1.contains(fault) { + count = count + 1 + tvRow = tvPair.vector } + } + return (fault: fault, count: count, tvRow: tvRow) + } + + var sa0Futures: [Future] = [] + for fault in sa0 { + let future = Future { + sa0Exec(fault: fault, coverageList: coverageList) + } + sa0Futures.append(future) + } + + var sa1Futures: [Future] = [] + for fault in sa1 { + let future = Future { + sa1Exec(fault: fault, coverageList: coverageList) + } + sa1Futures.append(future) + } - return ( - vectors: vectors, - faultSA0: faultSA0, - faultSA1: faultSA1 - ) + for future in sa0Futures { + let (fault, count, tvRow) = future.value as! (String, Int, TestVector) + if count == 1 { + faultSA0.append(fault) + vectors.insert(tvRow) + } } + + for future in sa1Futures { + let (fault, count, tvRow) = future.value as! (String, Int, TestVector) + if count == 1 { + faultSA1.append(fault) + vectors.insert(tvRow) + } + } + + return ( + vectors: vectors, + faultSA0: faultSA0, + faultSA1: faultSA1 + ) + } } diff --git a/Sources/Fault/Entries/asm.swift b/Sources/Fault/Entries/asm.swift index e187bca..c650cad 100644 --- a/Sources/Fault/Entries/asm.swift +++ b/Sources/Fault/Entries/asm.swift @@ -20,161 +20,161 @@ import Foundation import PythonKit extension Fault { - struct Assemble: ParsableCommand { - static let configuration = CommandConfiguration( - commandName: "asm", - abstract: "Assemble test vectors and golden outputs from JSON and Verilog files." + struct Assemble: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "asm", + abstract: "Assemble test vectors and golden outputs from JSON and Verilog files." + ) + + @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.") + var goldenOutput: String? + + @Argument(help: "JSON file (.json).") + var json: String + + @Argument(help: "Verilog file (.v)") + var verilog: String + + mutating func run() throws { + // Validate input files + guard FileManager.default.fileExists(atPath: verilog) else { + throw ValidationError("Verilog file '\(verilog)' not found.") + } + guard FileManager.default.fileExists(atPath: json) else { + throw ValidationError("JSON file '\(json)' not found.") + } + + let vectorOutput = output ?? json.replacingExtension(".json", with: ".bin") + let goldenOutput = goldenOutput ?? json.replacingExtension(".tv.json", with: ".au.bin") + + print("Loading JSON data…") + guard let data = try? Data(contentsOf: URL(fileURLWithPath: json)) else { + throw ValidationError("Failed to open test vector JSON file.") + } + + let decoder = JSONDecoder() + guard let tvinfo = try? decoder.decode(TVInfo.self, from: data) else { + throw ValidationError("Test vector JSON file is invalid.") + } + + // Extract chain metadata + let (chain, _, _) = ChainMetadata.extract(file: verilog) + + let order = chain.filter { $0.kind != .output }.sorted { $0.ordinal < $1.ordinal } + let outputOrder = chain.filter { $0.kind != .input }.sorted { $0.ordinal < $1.ordinal } + + let jsInputOrder = tvinfo.inputs + let jsOutputOrder = tvinfo.outputs + + var inputMap: [String: Int] = [:] + var outputMap: [String: Int] = [:] + + // Check input order + let chainOrder = order.filter { $0.kind != .bypassInput } + guard chainOrder.count == jsInputOrder.count else { + 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(\.name)).symmetricDifference(jsInputOrder.map(\.name)))." ) - - @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.") - var goldenOutput: String? - - @Argument(help: "JSON file (.json).") - var json: String - - @Argument(help: "Verilog file (.v)") - var verilog: String - - mutating func run() throws { - // Validate input files - guard FileManager.default.fileExists(atPath: verilog) else { - throw ValidationError("Verilog file '\(verilog)' not found.") - } - guard FileManager.default.fileExists(atPath: json) else { - throw ValidationError("JSON file '\(json)' not found.") - } - - let vectorOutput = output ?? json.replacingExtension(".json", with: ".bin") - let goldenOutput = goldenOutput ?? json.replacingExtension(".tv.json", with: ".au.bin") - - print("Loading JSON data…") - guard let data = try? Data(contentsOf: URL(fileURLWithPath: json)) else { - throw ValidationError("Failed to open test vector JSON file.") - } - - let decoder = JSONDecoder() - guard let tvinfo = try? decoder.decode(TVInfo.self, from: data) else { - throw ValidationError("Test vector JSON file is invalid.") - } - - // Extract chain metadata - let (chain, _, _) = ChainMetadata.extract(file: verilog) - - let order = chain.filter { $0.kind != .output }.sorted { $0.ordinal < $1.ordinal } - let outputOrder = chain.filter { $0.kind != .input }.sorted { $0.ordinal < $1.ordinal } - - let jsInputOrder = tvinfo.inputs - let jsOutputOrder = tvinfo.outputs - - var inputMap: [String: Int] = [:] - var outputMap: [String: Int] = [:] - - // Check input order - let chainOrder = order.filter { $0.kind != .bypassInput } - guard chainOrder.count == jsInputOrder.count else { - 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(\.name)).symmetricDifference(jsInputOrder.map(\.name)))." - ) - } - - for (i, input) in jsInputOrder.enumerated() { - let name = input.name.hasPrefix("\\") ? String(input.name.dropFirst()) : input.name - inputMap[name] = i - guard chainOrder[i].name == name else { - throw ValidationError( - "Ordinal mismatch between TV input \(name) and scan-chain register \(chainOrder[i].name)." - ) - } - } - - for (i, output) in jsOutputOrder.enumerated() { - var name = output.name.hasPrefix("\\") ? String(output.name.dropFirst()) : output.name - name = name.hasSuffix(".d") ? String(name.dropLast(2)) : name - outputMap[name] = i - } - - var outputDecimal: [[BigUInt]] = [] - for tvcPair in tvinfo.coverageList { - guard let hex = BigUInt(tvcPair.goldenOutput, radix: 16) else { - throw ValidationError("Invalid JSON. Golden output must be in hex format.") - } - var pointer = 0 - var list: [BigUInt] = [] - let binFromHex = String(hex, radix: 2) - let padLength = jsOutputOrder.reduce(0) { $0 + $1.width } - binFromHex.count - let outputBinary = (String(repeating: "0", count: padLength) + binFromHex).reversed() - for output in jsOutputOrder { - let start = outputBinary.index(outputBinary.startIndex, offsetBy: pointer) - let end = outputBinary.index(start, offsetBy: output.width) - let value = String(outputBinary[start ..< end]) - list.append(BigUInt(value, radix: 2)!) - pointer += output.width - } - outputDecimal.append(list) - } - - var binFileVec = "// test-vector \n" - var binFileOut = "// fault-free-response \n" - for (i, tvcPair) in tvinfo.coverageList.enumerated() { - var binaryString = "" - for element in order { - var value: BigUInt = 0 - if let locus = inputMap[element.name] { - value = tvcPair.vector[locus] - } else if element.kind == .bypassInput { - value = 0 - } else { - throw ValidationError("Chain register \(element.name) not found in the TVs.") - } - binaryString += value.pad(digits: element.width, radix: 2).reversed() - } - var outputBinary = "" - for element in outputOrder { - var value: BigUInt = 0 - if let locus = outputMap[element.name] { - value = outputDecimal[i][locus] - outputBinary += value.pad(digits: element.width, radix: 2) - } else if element.kind == .bypassOutput { - outputBinary += String(repeating: "x", count: element.width) - } else { - throw ValidationError( - "Mismatch between output port \(element.name) and chained netlist.") - } - } - binFileVec += binaryString + "\n" - binFileOut += outputBinary + " \n" - } - - let vectorCount = tvinfo.coverageList.count - let vectorLength = order.reduce(0) { $0 + $1.width } - - let vecMetadata = binMetadata(count: vectorCount, length: vectorLength) - let outMetadata = binMetadata( - count: vectorCount, length: outputOrder.reduce(0) { $0 + $1.width } - ) - - guard let vecMetadataString = vecMetadata.toJSON() else { - throw ValidationError("Could not generate metadata string.") - } - guard let outMetadataString = outMetadata.toJSON() else { - throw ValidationError("Could not generate metadata string.") - } - - try File.open(vectorOutput, mode: .write) { - try $0.print(String.boilerplate) - try $0.print("/* FAULT METADATA: '\(vecMetadataString)' END FAULT METADATA */") - try $0.print(binFileVec, terminator: "") - } - try File.open(goldenOutput, mode: .write) { - try $0.print(String.boilerplate) - try $0.print("/* FAULT METADATA: '\(outMetadataString)' END FAULT METADATA */") - try $0.print(binFileOut, terminator: "") - } - - print("Test vectors and golden outputs assembled successfully.") + } + + for (i, input) in jsInputOrder.enumerated() { + let name = input.name.hasPrefix("\\") ? String(input.name.dropFirst()) : input.name + inputMap[name] = i + guard chainOrder[i].name == name else { + throw ValidationError( + "Ordinal mismatch between TV input \(name) and scan-chain register \(chainOrder[i].name)." + ) + } + } + + for (i, output) in jsOutputOrder.enumerated() { + var name = output.name.hasPrefix("\\") ? String(output.name.dropFirst()) : output.name + name = name.hasSuffix(".d") ? String(name.dropLast(2)) : name + outputMap[name] = i + } + + var outputDecimal: [[BigUInt]] = [] + for tvcPair in tvinfo.coverageList { + guard let hex = BigUInt(tvcPair.goldenOutput, radix: 16) else { + throw ValidationError("Invalid JSON. Golden output must be in hex format.") + } + var pointer = 0 + var list: [BigUInt] = [] + let binFromHex = String(hex, radix: 2) + let padLength = jsOutputOrder.reduce(0) { $0 + $1.width } - binFromHex.count + let outputBinary = (String(repeating: "0", count: padLength) + binFromHex).reversed() + for output in jsOutputOrder { + let start = outputBinary.index(outputBinary.startIndex, offsetBy: pointer) + let end = outputBinary.index(start, offsetBy: output.width) + let value = String(outputBinary[start.., - bsrCreator: BoundaryScanRegisterCreator, - bypass: inout BypassOptions, - shiftName: String, - inputName: String, - outputName: String, - testName: String, - tckName: String, - invClockName: String? + Node: PythonObject, + sclConfig: SCLConfiguration, + module: Module, + blackboxModules: OrderedDictionary, + bsrCreator: BoundaryScanRegisterCreator, + bypass: inout BypassOptions, + shiftName: String, + inputName: String, + outputName: String, + testName: String, + tckName: String, + invClockName: String? ) throws -> [ChainRegister] { - // Modifies module definition in-place to create scan-chain. - // Changes name to .original to differentate from new top level. - print("Chaining internal flip-flops…") - let alteredName = "\\\(module.name).original" - - var internalOrder: [ChainRegister] = [] - - let shiftIdentifier = Node.Identifier(shiftName) - let inputIdentifier = Node.Identifier(inputName) - let outputIdentifier = Node.Identifier(outputName) - - let clkSourceName = "__clk_source__" - let clkSourceId = Node.Identifier(clkSourceName) - let invClkSourceName = "__clk_source_n__" - var invClkSourceId: PythonObject? - - var statements: [PythonObject] = [] - statements.append(Node.Input(inputName)) - statements.append(Node.Output(outputName)) - statements.append(Node.Input(shiftName)) - statements.append(Node.Input(tckName)) - statements.append(Node.Input(testName)) - statements.append(Node.Wire(clkSourceName)) - - if invClockName != nil { - statements.append(Node.Wire(invClkSourceName)) - } - - var ports = [PythonObject](module.definition.portlist.ports)! - ports.append(Node.Port(inputName, Python.None, Python.None, Python.None)) - ports.append(Node.Port(shiftName, Python.None, Python.None, Python.None)) - ports.append(Node.Port(outputName, Python.None, Python.None, Python.None)) - ports.append(Node.Port(tckName, Python.None, Python.None, Python.None)) - ports.append(Node.Port(testName, Python.None, Python.None, Python.None)) - module.definition.portlist.ports = Python.tuple(ports) - - var counter = 0 - let newShiftWire: () -> PythonObject = { // Type annotation required in Swift 5.4 - () in - let name = "__chain_\(counter)__" - counter += 1 - statements.append(Node.Decl([Node.Wire(name)])) - return Node.Identifier(name) - } - var previousOutput = newShiftWire() - - statements.append(Node.Assign(previousOutput, inputIdentifier)) - - let fnmatch = Python.import("fnmatch") - - var muxCreator: MuxCreator? - if let muxInfo = sclConfig.muxInfo { - muxCreator = MuxCreator(using: Node, muxInfo: muxInfo) - } - var warn = false - for itemDeclaration in module.definition.items { - let type = Python.type(itemDeclaration).__name__ - // Process gates - if type == "InstanceList" { - let instance = itemDeclaration.instances[0] - let moduleName = String(describing: instance.module) - let instanceName = String(describing: instance.name) - if let dffinfo = getMatchingDFFInfo( - from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch - ) { - for hook in instance.portlist { - let portnameStr = String(describing: hook.portname) - if portnameStr == dffinfo.clk { - if String(describing: hook.argname) == bypass.clock { - hook.argname = clkSourceId - } else { - warn = true - } - } - if portnameStr == dffinfo.d { - if let mc = muxCreator { - let (muxCellDecls, muxWireDecls, muxOut) = mc.create( - for: instanceName, selection: shiftIdentifier, a: previousOutput, b: hook.argname - ) - hook.argname = muxOut - statements += muxCellDecls - statements += muxWireDecls - - } else { - let ternary = Node.Cond( - shiftIdentifier, - previousOutput, - hook.argname - ) - hook.argname = ternary - } - } - - if portnameStr == dffinfo.q { - previousOutput = hook.argname - } - } - - internalOrder.append( - ChainRegister( - name: String(describing: instance.name), - kind: .dff - ) - ) - - } else if let blackboxModule = blackboxModules[moduleName] { - // MARK: Isolating hard blocks - - print("Chaining blackbox module '\(blackboxModule.name)'…") - for hook in instance.portlist { - // Note that `hook.argname` is actually an expression - let portInfo = blackboxModule.portsByName["\(hook.portname)"]! - - if bypass.bypassedIOs.contains(portInfo.name) { - // Leave it alone - continue - } - - let wireNameOriginal = "\\\(instanceName).\(portInfo.name).original" - let wireNameMultiplexed = "\\\(instanceName).\(portInfo.name).multiplexed" - let width = Node.Width(Node.IntConst(portInfo.from), Node.IntConst(portInfo.to)) - let wiresDecl = Node.Decl([ - Node.Wire(wireNameOriginal, width: width), Node.Wire(wireNameMultiplexed, width: width), - ]) - statements.append(wiresDecl) - - var kind: ChainRegister.Kind - if portInfo.polarity == .input { - statements.append(Node.Assign(Node.Identifier(wireNameOriginal), hook.argname)) - kind = .bypassInput - } else if portInfo.polarity == .output { - statements.append(Node.Assign(hook.argname, Node.Identifier(wireNameMultiplexed))) - hook.argname = Node.Identifier(wireNameOriginal) - kind = .bypassOutput - } else { - throw RuntimeError("Unknown polarity for \(instanceName)'s \(portInfo.name)") - } - - for bit in portInfo.bits { - let originalBit = Node.Pointer(Node.Identifier(wireNameOriginal), Node.IntConst(bit)) - let multiplexedBit = Node.Pointer( - Node.Identifier(wireNameMultiplexed), Node.IntConst(bit) - ) - let nextOutput = newShiftWire() - let decl = bsrCreator.create( - group: instanceName, - din: originalBit, - dout: multiplexedBit, - sin: "\(previousOutput)", - sout: "\(nextOutput)", - input: portInfo.polarity != .input // If it's an input to the macro, it's an output of the circuit we're stitching a scan-chain for - ) - previousOutput = nextOutput - statements.append(decl) - } - internalOrder.append( - ChainRegister( - name: "\(instanceName).\(portInfo.name)", - kind: kind, - width: portInfo.width - ) - ) - } + // Modifies module definition in-place to create scan-chain. + // Changes name to .original to differentate from new top level. + print("Chaining internal flip-flops…") + let alteredName = "\\\(module.name).original" + + var internalOrder: [ChainRegister] = [] + + let shiftIdentifier = Node.Identifier(shiftName) + let inputIdentifier = Node.Identifier(inputName) + let outputIdentifier = Node.Identifier(outputName) + + let clkSourceName = "__clk_source__" + let clkSourceId = Node.Identifier(clkSourceName) + let invClkSourceName = "__clk_source_n__" + var invClkSourceId: PythonObject? + + var statements: [PythonObject] = [] + statements.append(Node.Input(inputName)) + statements.append(Node.Output(outputName)) + statements.append(Node.Input(shiftName)) + statements.append(Node.Input(tckName)) + statements.append(Node.Input(testName)) + statements.append(Node.Wire(clkSourceName)) + + if invClockName != nil { + statements.append(Node.Wire(invClkSourceName)) + } + + var ports = [PythonObject](module.definition.portlist.ports)! + ports.append(Node.Port(inputName, Python.None, Python.None, Python.None)) + ports.append(Node.Port(shiftName, Python.None, Python.None, Python.None)) + ports.append(Node.Port(outputName, Python.None, Python.None, Python.None)) + ports.append(Node.Port(tckName, Python.None, Python.None, Python.None)) + ports.append(Node.Port(testName, Python.None, Python.None, Python.None)) + module.definition.portlist.ports = Python.tuple(ports) + + var counter = 0 + let newShiftWire: () -> PythonObject = { // Type annotation required in Swift 5.4 + () in + let name = "__chain_\(counter)__" + counter += 1 + statements.append(Node.Decl([Node.Wire(name)])) + return Node.Identifier(name) + } + var previousOutput = newShiftWire() + + statements.append(Node.Assign(previousOutput, inputIdentifier)) + + let fnmatch = Python.import("fnmatch") + + var muxCreator: MuxCreator? + if let muxInfo = sclConfig.muxInfo { + muxCreator = MuxCreator(using: Node, muxInfo: muxInfo) + } + var warn = false + for itemDeclaration in module.definition.items { + let type = Python.type(itemDeclaration).__name__ + // Process gates + if type == "InstanceList" { + let instance = itemDeclaration.instances[0] + let moduleName = String(describing: instance.module) + let instanceName = String(describing: instance.name) + if let dffinfo = getMatchingDFFInfo( + from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch + ) { + for hook in instance.portlist { + let portnameStr = String(describing: hook.portname) + if portnameStr == dffinfo.clk { + if String(describing: hook.argname) == bypass.clock { + hook.argname = clkSourceId + } else { + warn = true } + } + if portnameStr == dffinfo.d { + if let mc = muxCreator { + let (muxCellDecls, muxWireDecls, muxOut) = mc.create( + for: instanceName, selection: shiftIdentifier, a: previousOutput, b: hook.argname + ) + hook.argname = muxOut + statements += muxCellDecls + statements += muxWireDecls - if let invClock = invClockName, moduleName == invClock { - for hook in instance.portlist { - if String(describing: hook.argname) == bypass.clock { - invClkSourceId = Node.Identifier(invClkSourceName) - hook.argname = invClkSourceId! - } - } + } else { + let ternary = Node.Cond( + shiftIdentifier, + previousOutput, + hook.argname + ) + hook.argname = ternary } - } - - statements.append(itemDeclaration) - } - - if warn { - print("[Warning]: Detected flip-flops with clock different from \(bypass.clock).") - } - - let finalAssignment = Node.Assign( - Node.Lvalue(outputIdentifier), - Node.Rvalue(previousOutput) - ) - statements.append(finalAssignment) - - let clockCond = Node.Cond( - Node.Identifier(testName), - Node.Identifier(tckName), - Node.Identifier(bypass.clock) - ) - let clkSourceAssignment = Node.Assign( - Node.Lvalue(clkSourceId), - Node.Rvalue(clockCond) - ) - statements.append(clkSourceAssignment) + } - if let invClkId = invClkSourceId { - let invClockCond = Node.Cond( - Node.Identifier(testName), - Node.Unot(Node.Identifier(tckName)), - Node.Identifier(bypass.clock) - ) + if portnameStr == dffinfo.q { + previousOutput = hook.argname + } + } - let invClockAssignment = Node.Assign( - Node.Lvalue(invClkId), - Node.Rvalue(invClockCond) + internalOrder.append( + ChainRegister( + name: String(describing: instance.name), + kind: .dff + ) ) - statements.append(invClockAssignment) - } - - module.definition.items = Python.tuple(statements) - module.definition.name = Python.str(alteredName) - - return internalOrder -} - -func chainTop( - Node: PythonObject, - sclConfig _: SCLConfiguration, - module: Module, - blackboxModules _: OrderedDictionary, - bsrCreator: BoundaryScanRegisterCreator, - bypass: inout BypassOptions, - shiftName: String, - inputName: String, - outputName: String, - testName: String, - tckName: String, - invClockName _: String?, - internalOrder: [ChainRegister] -) throws -> (supermodel: PythonObject, order: [ChainRegister]) { - var order: [ChainRegister] = [] - let ports = Python.list(module.definition.portlist.ports) - - var statements: [PythonObject] = [] - statements.append(Node.Input(inputName)) - statements.append(Node.Output(outputName)) - 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(bypass.clock)) - - let portArguments = Python.list() - - var counter = 0 - let newShiftWire: () -> PythonObject = { // Type annotation required in Swift 5.4 - () in - let name = "__chain_\(counter)__" - counter += 1 - statements.append(Node.Decl([Node.Wire(name)])) - return Node.Identifier(name) - } - var previousOutput = newShiftWire() - let initialAssignment = Node.Assign( - Node.Lvalue(previousOutput), - Node.Rvalue(Node.Identifier(inputName)) - ) - statements.append(initialAssignment) + } else if let blackboxModule = blackboxModules[moduleName] { + // MARK: Isolating hard blocks - for input in module.inputs { - let inputStatement = Node.Input(input.name) + print("Chaining blackbox module '\(blackboxModule.name)'…") + for hook in instance.portlist { + // Note that `hook.argname` is actually an expression + let portInfo = blackboxModule.portsByName["\(hook.portname)"]! - if input.name != bypass.clock, input.name != bypass.reset.name { - statements.append(inputStatement) - } - if bypass.bypassedIOs.contains(input.name) { - portArguments.append( - Node.PortArg( - input.name, - Node.Identifier(input.name) - )) + if bypass.bypassedIOs.contains(portInfo.name) { + // Leave it alone continue - } - - let doutName = String(describing: input.name) + "__dout" - let doutStatement = Node.Wire(doutName) - if input.width > 1 { - let width = Node.Width( - Node.Constant(input.from), - Node.Constant(input.to) + } + + let wireNameOriginal = "\\\(instanceName).\(portInfo.name).original" + let wireNameMultiplexed = "\\\(instanceName).\(portInfo.name).multiplexed" + let width = Node.Width(Node.IntConst(portInfo.from), Node.IntConst(portInfo.to)) + let wiresDecl = Node.Decl([ + Node.Wire(wireNameOriginal, width: width), Node.Wire(wireNameMultiplexed, width: width), + ]) + statements.append(wiresDecl) + + var kind: ChainRegister.Kind + if portInfo.polarity == .input { + statements.append(Node.Assign(Node.Identifier(wireNameOriginal), hook.argname)) + kind = .bypassInput + } else if portInfo.polarity == .output { + statements.append(Node.Assign(hook.argname, Node.Identifier(wireNameMultiplexed))) + hook.argname = Node.Identifier(wireNameOriginal) + kind = .bypassOutput + } else { + throw RuntimeError("Unknown polarity for \(instanceName)'s \(portInfo.name)") + } + + for bit in portInfo.bits { + let originalBit = Node.Pointer(Node.Identifier(wireNameOriginal), Node.IntConst(bit)) + let multiplexedBit = Node.Pointer( + Node.Identifier(wireNameMultiplexed), Node.IntConst(bit) ) - inputStatement.width = width - doutStatement.width = width - } - statements.append(doutStatement) - - portArguments.append( - Node.PortArg( - input.name, - Node.Identifier(doutName) - )) - if input.width == 1 { let nextOutput = newShiftWire() let decl = bsrCreator.create( - group: "", - din: Node.Identifier(input.name), - dout: Node.Identifier(doutName), - sin: "\(previousOutput)", - sout: "\(nextOutput)", - input: true + group: instanceName, + din: originalBit, + dout: multiplexedBit, + sin: "\(previousOutput)", + sout: "\(nextOutput)", + input: portInfo.polarity != .input // If it's an input to the macro, it's an output of the circuit we're stitching a scan-chain for ) - statements.append(decl) previousOutput = nextOutput - - } else { - for bit in input.bits { - let nextOutput = newShiftWire() - let decl = bsrCreator.create( - group: "", - din: Node.Pointer(Node.Identifier(input.name), Node.IntConst(bit)), - dout: Node.Pointer(Node.Identifier(doutName), Node.IntConst(bit)), - sin: "\(previousOutput)", - sout: "\(nextOutput)", - input: true - ) - statements.append(decl) - previousOutput = nextOutput - } - } - - order.append( + statements.append(decl) + } + internalOrder.append( ChainRegister( - name: String(describing: input.name), - kind: .input, - width: input.width + name: "\(instanceName).\(portInfo.name)", + kind: kind, + width: portInfo.width ) - ) - } - - portArguments.append( - Node.PortArg( - shiftName, - Node.Identifier(shiftName) - )) - portArguments.append( - Node.PortArg( - tckName, - Node.Identifier(tckName) - )) - portArguments.append( - Node.PortArg( - testName, - Node.Identifier(testName) - )) - - portArguments.append( - Node.PortArg( - inputName, - previousOutput - )) - - let nextOutput = newShiftWire() - portArguments.append( - Node.PortArg( - outputName, - nextOutput - )) - - order += internalOrder - previousOutput = nextOutput - - for output in module.outputs { - let outputStatement = Node.Output(output.name) - let dinName = String(describing: output.name) + "_din" - let dinStatement = Node.Wire(dinName) - if output.width > 1 { - let width = Node.Width( - Node.Constant(output.from), - Node.Constant(output.to) - ) - outputStatement.width = width - dinStatement.width = width + ) } - statements.append(outputStatement) - statements.append(dinStatement) - - portArguments.append( - Node.PortArg( - output.name, - Node.Identifier(dinName) - )) - - if output.width == 1 { - let nextOutput = newShiftWire() - let decl = bsrCreator.create( - group: "", - din: Node.Identifier(dinName), - dout: Node.Identifier(output.name), - sin: "\(previousOutput)", - sout: "\(nextOutput)", - input: false - ) - statements.append(decl) - previousOutput = nextOutput - } else { - for bit in output.bits { - let nextOutput = newShiftWire() - let decl = bsrCreator.create( - group: "", - din: Node.Pointer(Node.Identifier(dinName), Node.IntConst(bit)), - dout: Node.Pointer(Node.Identifier(output.name), Node.IntConst(bit)), - sin: "\(previousOutput)", - sout: "\(nextOutput)", - input: false - ) - statements.append(decl) - previousOutput = nextOutput - } + } + + if let invClock = invClockName, moduleName == invClock { + for hook in instance.portlist { + if String(describing: hook.argname) == bypass.clock { + invClkSourceId = Node.Identifier(invClkSourceName) + hook.argname = invClkSourceId! + } } - - order.append( - ChainRegister( - name: String(describing: output.name), - kind: .output, - width: output.width - ) - ) + } } - let submoduleInstance = Node.Instance( - module.definition.name, - "__uuf__", - Python.tuple(portArguments), - Python.tuple() + statements.append(itemDeclaration) + } + + if warn { + print("[Warning]: Detected flip-flops with clock different from \(bypass.clock).") + } + + let finalAssignment = Node.Assign( + Node.Lvalue(outputIdentifier), + Node.Rvalue(previousOutput) + ) + statements.append(finalAssignment) + + let clockCond = Node.Cond( + Node.Identifier(testName), + Node.Identifier(tckName), + Node.Identifier(bypass.clock) + ) + let clkSourceAssignment = Node.Assign( + Node.Lvalue(clkSourceId), + Node.Rvalue(clockCond) + ) + statements.append(clkSourceAssignment) + + if let invClkId = invClkSourceId { + let invClockCond = Node.Cond( + Node.Identifier(testName), + Node.Unot(Node.Identifier(tckName)), + Node.Identifier(bypass.clock) ) - statements.append( - Node.InstanceList( - module.definition.name, - Python.tuple(), - Python.tuple([submoduleInstance]) - )) - - let boundaryAssignment = Node.Assign( - Node.Lvalue(Node.Identifier(outputName)), - Node.Rvalue(previousOutput) + let invClockAssignment = Node.Assign( + Node.Lvalue(invClkId), + Node.Rvalue(invClockCond) ) - statements.append(boundaryAssignment) + statements.append(invClockAssignment) + } - let supermodel = Node.ModuleDef( - module.name, - Python.None, - Node.Portlist(Python.tuple(ports)), - Python.tuple(statements) - ) - print( - "Boundary scan cells successfully chained. Length: ", - order.reduce(0) { $0 + $1.width } - internalOrder.reduce(0) { $0 + $1.width } - ) + module.definition.items = Python.tuple(statements) + module.definition.name = Python.str(alteredName) - return (supermodel, order) + return internalOrder } -extension Fault { - struct Chain: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Manipulate a netlist to create a scan chain, and resynthesize." - ) - - @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") - var output: String? - - @Option( - name: [.short, .long, .customLong("cellModel")], - help: "Verify scan chain using given cell model." - ) - var cellModel: String? - - @Option(name: [.long], help: "Inverted clk tree source cell name. (Default: none)") - var invClock: String? - - @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? - - @Option( - name: [.short, .long], - help: "Optional override for the DFF names from the PDK config. Comma-delimited." - ) - var dff: String? +func chainTop( + Node: PythonObject, + sclConfig _: SCLConfiguration, + module: Module, + blackboxModules _: OrderedDictionary, + bsrCreator: BoundaryScanRegisterCreator, + bypass: inout BypassOptions, + shiftName: String, + inputName: String, + outputName: String, + testName: String, + tckName: String, + invClockName _: String?, + internalOrder: [ChainRegister] +) throws -> (supermodel: PythonObject, order: [ChainRegister]) { + var order: [ChainRegister] = [] + let ports = Python.list(module.definition.portlist.ports) + + var statements: [PythonObject] = [] + statements.append(Node.Input(inputName)) + statements.append(Node.Output(outputName)) + 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(bypass.clock)) + + let portArguments = Python.list() + + var counter = 0 + let newShiftWire: () -> PythonObject = { // Type annotation required in Swift 5.4 + () in + let name = "__chain_\(counter)__" + counter += 1 + statements.append(Node.Decl([Node.Wire(name)])) + return Node.Identifier(name) + } + var previousOutput = newShiftWire() + + let initialAssignment = Node.Assign( + Node.Lvalue(previousOutput), + Node.Rvalue(Node.Identifier(inputName)) + ) + statements.append(initialAssignment) + + for input in module.inputs { + let inputStatement = Node.Input(input.name) + + if input.name != bypass.clock, input.name != bypass.reset.name { + statements.append(inputStatement) + } + if bypass.bypassedIOs.contains(input.name) { + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(input.name) + )) + continue + } - @Option( - name: [.customShort("b"), .customLong("blackbox")], - help: "Blackbox module names. Specify multiple times to specify multiple modules." - ) - var blackbox: [String] = [] + let doutName = String(describing: input.name) + "__dout" + let doutStatement = Node.Wire(doutName) + if input.width > 1 { + let width = Node.Width( + Node.Constant(input.from), + Node.Constant(input.to) + ) + inputStatement.width = width + doutStatement.width = width + } + statements.append(doutStatement) - @Option( - name: [.customShort("B"), .long, .customLong("blackboxModel")], - help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(doutName) + )) + if input.width == 1 { + let nextOutput = newShiftWire() + let decl = bsrCreator.create( + group: "", + din: Node.Identifier(input.name), + dout: Node.Identifier(doutName), + sin: "\(previousOutput)", + sout: "\(nextOutput)", + input: true + ) + statements.append(decl) + previousOutput = nextOutput + + } else { + for bit in input.bits { + let nextOutput = newShiftWire() + let decl = bsrCreator.create( + group: "", + din: Node.Pointer(Node.Identifier(input.name), Node.IntConst(bit)), + dout: Node.Pointer(Node.Identifier(doutName), Node.IntConst(bit)), + sin: "\(previousOutput)", + sout: "\(nextOutput)", + input: true ) - var blackboxModels: [String] = [] + statements.append(decl) + previousOutput = nextOutput + } + } - @Option( - name: [.customShort("D"), .customLong("define")], - help: - "`define statements to include during simulations. Specify multiple times to specify multiple defines." - ) - var defines: [String] = [] + order.append( + ChainRegister( + name: String(describing: input.name), + kind: .input, + width: input.width + ) + ) + } + + portArguments.append( + Node.PortArg( + shiftName, + Node.Identifier(shiftName) + )) + portArguments.append( + Node.PortArg( + tckName, + Node.Identifier(tckName) + )) + portArguments.append( + Node.PortArg( + testName, + Node.Identifier(testName) + )) + + portArguments.append( + Node.PortArg( + inputName, + previousOutput + )) + + let nextOutput = newShiftWire() + portArguments.append( + Node.PortArg( + outputName, + nextOutput + )) + + order += internalOrder + previousOutput = nextOutput + + for output in module.outputs { + let outputStatement = Node.Output(output.name) + let dinName = String(describing: output.name) + "_din" + let dinStatement = Node.Wire(dinName) + if output.width > 1 { + let width = Node.Width( + Node.Constant(output.from), + Node.Constant(output.to) + ) + outputStatement.width = width + dinStatement.width = width + } + statements.append(outputStatement) + statements.append(dinStatement) - @Option( - name: [.customShort("I"), .customLong("inc"), .customLong("include")], - help: "Extra verilog models to include during simulations. (Default: none)" + portArguments.append( + Node.PortArg( + output.name, + Node.Identifier(dinName) + )) + + if output.width == 1 { + let nextOutput = newShiftWire() + let decl = bsrCreator.create( + group: "", + din: Node.Identifier(dinName), + dout: Node.Identifier(output.name), + sin: "\(previousOutput)", + sout: "\(nextOutput)", + input: false + ) + statements.append(decl) + previousOutput = nextOutput + } else { + for bit in output.bits { + let nextOutput = newShiftWire() + let decl = bsrCreator.create( + group: "", + din: Node.Pointer(Node.Identifier(dinName), Node.IntConst(bit)), + dout: Node.Pointer(Node.Identifier(output.name), Node.IntConst(bit)), + sin: "\(previousOutput)", + sout: "\(nextOutput)", + input: false ) - var includes: [String] = [] - - @Flag(name: .long, help: "Skip Re-synthesizing the chained netlist. (Default: none)") - var skipSynth: Bool = false - - @Option(name: .long, help: "Name for scan-chain serial data in signal.") - var sin: String = "sin" - - @Option(name: .long, help: "Name for scan-chain serial data out signal.") - var sout: String = "sout" - - @Option(name: .long, help: "Name for scan-chain shift enable signal.") - var shift: String = "shift" + statements.append(decl) + previousOutput = nextOutput + } + } - @Option(name: .long, help: "Name for scan-chain test enable signal.") - var test: String = "test" + order.append( + ChainRegister( + name: String(describing: output.name), + kind: .output, + width: output.width + ) + ) + } + + let submoduleInstance = Node.Instance( + module.definition.name, + "__uuf__", + Python.tuple(portArguments), + Python.tuple() + ) + + statements.append( + Node.InstanceList( + module.definition.name, + Python.tuple(), + Python.tuple([submoduleInstance]) + )) + + let boundaryAssignment = Node.Assign( + Node.Lvalue(Node.Identifier(outputName)), + Node.Rvalue(previousOutput) + ) + statements.append(boundaryAssignment) + + let supermodel = Node.ModuleDef( + module.name, + Python.None, + Node.Portlist(Python.tuple(ports)), + Python.tuple(statements) + ) + print( + "Boundary scan cells successfully chained. Length: ", + order.reduce(0) { $0 + $1.width } - internalOrder.reduce(0) { $0 + $1.width } + ) + + return (supermodel, order) +} - @Option(name: .long, help: "Name for JTAG test clock signal.") - var tck: String = "tck" +extension Fault { + struct Chain: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Manipulate a netlist to create a scan chain, and resynthesize." + ) - @Argument - var file: String + @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") + var output: String? - mutating func run() throws { - let fileManager = FileManager() + @Option( + name: [.short, .long, .customLong("cellModel")], + help: "Verify scan chain using given cell model." + ) + var cellModel: String? - // Check if input file exists - guard fileManager.fileExists(atPath: file) else { - throw ValidationError("File '\(file)' not found.") - } + @Option(name: [.long], help: "Inverted clk tree source cell name. (Default: none)") + var invClock: String? - // Required options validation - 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 { - throw ValidationError("File not found: \(sclConfigPath)") - } - let decoder = YAMLDecoder() - sclConfig = try decoder.decode(SCLConfiguration.self, from: sclConfigYML) - } + @Option(name: [.short, .long], help: "Liberty file. (Required.)") + var liberty: String - if let dffOverride = dff { - sclConfig.dffMatches.last!.name = dffOverride - } + @OptionGroup + var bypass: BypassOptions - if !fileManager.fileExists(atPath: liberty) { - throw ValidationError("Liberty file '\(liberty)' not found.") - } + @Option( + name: [.short, .long, .customLong("sclConfig")], + help: "Path for the YAML SCL config file. Recommended." + ) + var sclConfig: String? - if !liberty.hasSuffix(".lib") { - print("Warning: Liberty file provided does not end with .lib.") - } + @Option( + name: [.short, .long], + help: "Optional override for the DFF names from the PDK config. Comma-delimited." + ) + var dff: String? - let output = output ?? file.replacingExtension(".nl.v", with: ".chained.v") - let intermediate = output.replacingExtension(".chained.v", with: ".chain-intermediate.v") + @Option( + name: [.customShort("b"), .customLong("blackbox")], + help: "Blackbox module names. Specify multiple times to specify multiple modules." + ) + var blackbox: [String] = [] - // MARK: Importing Python and Pyverilog + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) + var blackboxModels: [String] = [] - let parse = Python.import("pyverilog.vparser.parser").parse - let Node = Python.import("pyverilog.vparser.ast") - let Generator = Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() + @Option( + name: [.customShort("D"), .customLong("define")], + help: + "`define statements to include during simulations. Specify multiple times to specify multiple defines." + ) + var defines: [String] = [] - // MARK: Parse + @Option( + name: [.customShort("I"), .customLong("inc"), .customLong("include")], + help: "Extra verilog models to include during simulations. (Default: none)" + ) + var includes: [String] = [] - let modules = try! Module.getModules(in: [file]) - guard let module = modules.values.first else { - throw ValidationError("No modules found in file.") - } + @Flag(name: .long, help: "Skip Re-synthesizing the chained netlist. (Default: none)") + var skipSynth: Bool = false - let blackboxModules = try! Module.getModules(in: blackboxModels, filter: Set(blackbox)) + @Option(name: .long, help: "Name for scan-chain serial data in signal.") + var sin: String = "sin" - let bsrCreator = BoundaryScanRegisterCreator( - name: "BoundaryScanRegister", - clock: tck, - reset: bypass.reset.name, - resetActive: bypass.reset.active, - testing: test, - shift: shift, - using: Node - ) + @Option(name: .long, help: "Name for scan-chain serial data out signal.") + var sout: String = "sout" - let internalOrder = try chainInternal( - Node: Node, - sclConfig: sclConfig, - module: module, - blackboxModules: blackboxModules, - bsrCreator: bsrCreator, - bypass: &bypass, - shiftName: shift, - inputName: sin, - outputName: sout, - testName: test, - tckName: tck, - invClockName: invClock - ) + @Option(name: .long, help: "Name for scan-chain shift enable signal.") + var shift: String = "shift" - let internalCount = internalOrder.reduce(0) { $0 + $1.width } - if internalCount == 0 { - print("Warning: No internal scan elements found. Are your DFFs configured properly?") - } else { - print("Internal scan chain successfully constructed. Length: \(internalCount)") - } + @Option(name: .long, help: "Name for scan-chain test enable signal.") + var test: String = "test" - let (supermodel, finalOrder) = try chainTop( - Node: Node, - sclConfig: sclConfig, - module: module, - blackboxModules: blackboxModules, - bsrCreator: bsrCreator, - bypass: &bypass, - shiftName: shift, - inputName: sin, - outputName: sout, - testName: test, - tckName: tck, - invClockName: invClock, - internalOrder: internalOrder - ) - let finalCount = finalOrder.reduce(0) { $0 + $1.width } + @Option(name: .long, help: "Name for JTAG test clock signal.") + var tck: String = "tck" - let finalAst = parse([bsrCreator.inputDefinition + bsrCreator.outputDefinition])[0] - let finalDefinitions = - [PythonObject](finalAst[dynamicMember: "description"].definitions)! + [ - module.definition, supermodel, - ] - finalAst[dynamicMember: "description"].definitions = Python.tuple(finalDefinitions) + @Argument + var file: String - try File.open(intermediate, mode: .write) { - try $0.print(Generator.visit(finalAst)) - } + mutating func run() throws { + let fileManager = FileManager() - print("Total scan-chain length: ", finalCount) + // Check if input file exists + guard fileManager.fileExists(atPath: file) else { + throw ValidationError("File '\(file)' not found.") + } - let metadata = ChainMetadata( - boundaryCount: finalCount - internalCount, - internalCount: internalCount, - order: finalOrder, - shift: shift, - sin: sin, - sout: sout + // Required options validation + 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 { + throw ValidationError("File not found: \(sclConfigPath)") + } + let decoder = YAMLDecoder() + sclConfig = try decoder.decode(SCLConfiguration.self, from: sclConfigYML) + } + + if let dffOverride = dff { + sclConfig.dffMatches.last!.name = dffOverride + } + + if !fileManager.fileExists(atPath: liberty) { + throw ValidationError("Liberty file '\(liberty)' not found.") + } + + if !liberty.hasSuffix(".lib") { + print("Warning: Liberty file provided does not end with .lib.") + } + + let output = output ?? file.replacingExtension(".nl.v", with: ".chained.v") + let intermediate = output.replacingExtension(".chained.v", with: ".chain-intermediate.v") + + // MARK: Importing Python and Pyverilog + + let parse = Python.import("pyverilog.vparser.parser").parse + let Node = Python.import("pyverilog.vparser.ast") + let Generator = Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() + + // MARK: Parse + + let modules = try! Module.getModules(in: [file]) + guard let module = modules.values.first else { + throw ValidationError("No modules found in file.") + } + + let blackboxModules = try! Module.getModules(in: blackboxModels, filter: Set(blackbox)) + + let bsrCreator = BoundaryScanRegisterCreator( + name: "BoundaryScanRegister", + clock: tck, + reset: bypass.reset.name, + resetActive: bypass.reset.active, + testing: test, + shift: shift, + using: Node + ) + + let internalOrder = try chainInternal( + Node: Node, + sclConfig: sclConfig, + module: module, + blackboxModules: blackboxModules, + bsrCreator: bsrCreator, + bypass: &bypass, + shiftName: shift, + inputName: sin, + outputName: sout, + testName: test, + tckName: tck, + invClockName: invClock + ) + + let internalCount = internalOrder.reduce(0) { $0 + $1.width } + if internalCount == 0 { + print("Warning: No internal scan elements found. Are your DFFs configured properly?") + } else { + print("Internal scan chain successfully constructed. Length: \(internalCount)") + } + + let (supermodel, finalOrder) = try chainTop( + Node: Node, + sclConfig: sclConfig, + module: module, + blackboxModules: blackboxModules, + bsrCreator: bsrCreator, + bypass: &bypass, + shiftName: shift, + inputName: sin, + outputName: sout, + testName: test, + tckName: tck, + invClockName: invClock, + internalOrder: internalOrder + ) + let finalCount = finalOrder.reduce(0) { $0 + $1.width } + + let finalAst = parse([bsrCreator.inputDefinition + bsrCreator.outputDefinition])[0] + let finalDefinitions = + [PythonObject](finalAst[dynamicMember: "description"].definitions)! + [ + module.definition, supermodel, + ] + finalAst[dynamicMember: "description"].definitions = Python.tuple(finalDefinitions) + + try File.open(intermediate, mode: .write) { + try $0.print(Generator.visit(finalAst)) + } + + print("Total scan-chain length: ", finalCount) + + let metadata = ChainMetadata( + boundaryCount: finalCount - internalCount, + internalCount: internalCount, + order: finalOrder, + shift: shift, + sin: sin, + sout: sout + ) + guard let metadataString = metadata.toJSON() else { + Stderr.print("Could not generate metadata string.") + Foundation.exit(EX_SOFTWARE) + } + + let netlist: String = { + if !skipSynth { + let script = Synthesis.script( + for: module.name, + in: [intermediate], + liberty: liberty, + blackboxing: blackboxModels, + output: output + ) + + // MARK: Yosys + + print("Resynthesizing with yosys…") + let result = "echo '\(script)' | '\(yosysExecutable)' > /dev/null".sh() + + if result != EX_OK { + Stderr.print("A yosys error has occurred.") + Foundation.exit(EX_DATAERR) + } + return output + } else { + return intermediate + } + }() + + guard let content = File.read(netlist) else { + throw "Could not re-read created file." + } + + try File.open(netlist, mode: .write) { + try $0.print(String.boilerplate) + try $0.print("/* FAULT METADATA: '\(metadataString)' END FAULT METADATA */") + try $0.print(content) + } + + // MARK: Verification + + if let model = cellModel { + let models = [model] + Array(includes) + Array(blackboxModels) + + print("Verifying scan chain integrity…") + let ast = parse([netlist])[0] + let description = ast[dynamicMember: "description"] + var definitionOptional: PythonObject? + for definition in description.definitions { + let type = Python.type(definition).__name__ + if type == "ModuleDef" { + definitionOptional = definition + break + } + } + guard let definition = definitionOptional else { + Stderr.print("No module found.") + Foundation.exit(EX_DATAERR) + } + let (ports, inputs, outputs) = try Port.extract(from: definition) + + let verified = try Simulator.simulate( + verifying: module.name, + in: netlist, + with: models, + ports: ports, + inputs: inputs, + outputs: outputs, + chainLength: finalOrder.reduce(0) { $0 + $1.width }, + clock: bypass.clock, + tck: tck, + reset: bypass.reset.name, + sin: sin, + sout: sout, + resetActive: bypass.reset.active, + shift: shift, + test: test, + output: netlist + ".tb.sv", + defines: Set(defines), + using: iverilogExecutable, + with: vvpExecutable + ) + if verified { + print("Scan chain verified successfully.") + } else { + print("Scan chain verification failed.") + print( + "・Ensure that clock and reset signals, if they exist are passed as such to the program." + ) + if !bypass.resetActiveLow { + print("・Ensure that the reset is active high- pass --activeLow for activeLow.") + } + if internalOrder.count == 0 { + print( + "・Ensure that D flip-flop cell names match those either in the defaults, the PDK config, or the overrides." ) - guard let metadataString = metadata.toJSON() else { - Stderr.print("Could not generate metadata string.") - Foundation.exit(EX_SOFTWARE) - } - - let netlist: String = { - if !skipSynth { - let script = Synthesis.script( - for: module.name, - in: [intermediate], - liberty: liberty, - blackboxing: blackboxModels, - output: output - ) - - // MARK: Yosys - - print("Resynthesizing with yosys…") - let result = "echo '\(script)' | '\(yosysExecutable)' > /dev/null".sh() - - if result != EX_OK { - Stderr.print("A yosys error has occurred.") - Foundation.exit(EX_DATAERR) - } - return output - } else { - return intermediate - } - }() - - guard let content = File.read(netlist) else { - throw "Could not re-read created file." - } - - try File.open(netlist, mode: .write) { - try $0.print(String.boilerplate) - try $0.print("/* FAULT METADATA: '\(metadataString)' END FAULT METADATA */") - try $0.print(content) - } - - // MARK: Verification - - if let model = cellModel { - let models = [model] + Array(includes) + Array(blackboxModels) - - print("Verifying scan chain integrity…") - let ast = parse([netlist])[0] - let description = ast[dynamicMember: "description"] - var definitionOptional: PythonObject? - for definition in description.definitions { - let type = Python.type(definition).__name__ - if type == "ModuleDef" { - definitionOptional = definition - break - } - } - guard let definition = definitionOptional else { - Stderr.print("No module found.") - Foundation.exit(EX_DATAERR) - } - let (ports, inputs, outputs) = try Port.extract(from: definition) - - let verified = try Simulator.simulate( - verifying: module.name, - in: netlist, - with: models, - ports: ports, - inputs: inputs, - outputs: outputs, - chainLength: finalOrder.reduce(0) { $0 + $1.width }, - clock: bypass.clock, - tck: tck, - reset: bypass.reset.name, - sin: sin, - sout: sout, - resetActive: bypass.reset.active, - shift: shift, - test: test, - output: netlist + ".tb.sv", - defines: Set(defines), - using: iverilogExecutable, - with: vvpExecutable - ) - if verified { - print("Scan chain verified successfully.") - } else { - print("Scan chain verification failed.") - print( - "・Ensure that clock and reset signals, if they exist are passed as such to the program." - ) - if !bypass.resetActiveLow { - print("・Ensure that the reset is active high- pass --activeLow for activeLow.") - } - if internalOrder.count == 0 { - print( - "・Ensure that D flip-flop cell names match those either in the defaults, the PDK config, or the overrides." - ) - } - print("・Ensure that there are no other asynchronous resets anywhere in the circuit.") - Foundation.exit(EX_DATAERR) - } - } - print("Done.") + } + print("・Ensure that there are no other asynchronous resets anywhere in the circuit.") + Foundation.exit(EX_DATAERR) } + } + print("Done.") } + } } diff --git a/Sources/Fault/Entries/common.swift b/Sources/Fault/Entries/common.swift index 89250d0..901301a 100644 --- a/Sources/Fault/Entries/common.swift +++ b/Sources/Fault/Entries/common.swift @@ -15,54 +15,54 @@ import ArgumentParser import Collections struct Reset { - let name: String - let active: Simulator.Active + let name: String + let active: Simulator.Active } struct BypassOptions: ParsableArguments { - @Option( - name: [.long], - help: - "I/Os to bypass when performing operations. May be specified multiple times to bypass multiple I/Os. Inputs will be held high during simulations by default, unless =0 is appended to the option." - ) - var bypassing: [String] = [] + @Option( + name: [.long], + help: + "I/Os to bypass when performing operations. May be specified multiple times to bypass multiple I/Os. 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( + 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" + @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 + @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 = .init(name: resetName, active: resetActiveLow ? .low : .high) + lazy var reset: Reset = .init(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 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 bypassedIOs: Set = .init(simulationValues.keys) + lazy var bypassedIOs: Set = .init(simulationValues.keys) } diff --git a/Sources/Fault/Entries/cut.swift b/Sources/Fault/Entries/cut.swift index b24679e..dcf68c9 100644 --- a/Sources/Fault/Entries/cut.swift +++ b/Sources/Fault/Entries/cut.swift @@ -15,201 +15,201 @@ import ArgumentParser import BigInt import Collections -import CoreFoundation // Not automatically imported on Linux +import CoreFoundation // Not automatically imported on Linux import Defile import Foundation import PythonKit import Yams extension Fault { - struct Cut: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: - "Cut away D-flipflops, converting them into inputs and outputs. This is a necessary precursor to the ATPG step." - ) - - @Option( - name: [.short, .long], - help: "Override for flip-flop cell names. Comma-delimited. (Default: DFF)." - ) - var dff: String? - - @Option( - name: [.short, .long, .customLong("sclConfig")], - help: "Path for the YAML SCL config file. Recommended." - ) - var sclConfig: String? - - @Option( - name: [.customShort("b"), .customLong("blackbox")], - help: "Blackbox module names. Specify multiple times to specify multiple modules." - ) - var blackbox: [String] = [] - - @Option( - name: [.customShort("B"), .long, .customLong("blackboxModel")], - help: "Blackbox model verilog files. Specify multiple times to specify multiple models." - ) - var blackboxModels: [String] = [] - - @OptionGroup - var bypass: BypassOptions - - @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") - var output: String? - - @Argument(help: "The file to process.") - var file: String - - mutating func run() throws { - let fileManager = FileManager() - - guard fileManager.fileExists(atPath: file) else { - throw ValidationError("File '\(file)' not found.") - } + struct Cut: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: + "Cut away D-flipflops, converting them into inputs and outputs. This is a necessary precursor to the ATPG step." + ) - let output = output ?? file.replacingExtension(".nl.v", with: ".cut.v") + @Option( + name: [.short, .long], + help: "Override for flip-flop cell names. Comma-delimited. (Default: DFF)." + ) + var dff: String? - // MARK: Importing Python and Pyverilog + @Option( + name: [.short, .long, .customLong("sclConfig")], + help: "Path for the YAML SCL config file. Recommended." + ) + var sclConfig: String? - let parse = Python.import("pyverilog.vparser.parser").parse - let Node = Python.import("pyverilog.vparser.ast") - let Generator = Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() + @Option( + name: [.customShort("b"), .customLong("blackbox")], + help: "Blackbox module names. Specify multiple times to specify multiple modules." + ) + var blackbox: [String] = [] - var blackboxModules: OrderedDictionary = [:] + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) + var blackboxModels: [String] = [] - if blackboxModels.count != 0 { - blackboxModules = try Module.getModules(in: blackboxModels, filter: Set(blackbox)) - } + @OptionGroup + var bypass: BypassOptions - var sclConfig = SCLConfiguration(dffMatches: [ - DFFMatch(name: "DFFSR,DFFNEGX1,DFFPOSX1", clk: "CLK", d: "D", q: "Q"), - ]) + @Option(name: [.short, .long], help: "Path to the output file. (Default: input + .chained.v)") + var output: String? - if let sclConfigPath = self.sclConfig { - guard let sclConfigYML = try? String(contentsOfFile: sclConfigPath) else { - throw ValidationError("File not found: \(sclConfigPath)") - } - let decoder = YAMLDecoder() - sclConfig = try decoder.decode(SCLConfiguration.self, from: sclConfigYML) - } + @Argument(help: "The file to process.") + var file: String - if let dffOverride = dff { - sclConfig.dffMatches.last!.name = dffOverride - } + mutating func run() throws { + let fileManager = FileManager() - let ast = parse([file])[0] - let description = ast[dynamicMember: "description"] - var definitionOptional: PythonObject? + guard fileManager.fileExists(atPath: file) else { + throw ValidationError("File '\(file)' not found.") + } - for definition in description.definitions { - let type = Python.type(definition).__name__ - if type == "ModuleDef" { - definitionOptional = definition - break - } - } + let output = output ?? file.replacingExtension(".nl.v", with: ".cut.v") - guard let definition = definitionOptional else { - throw ValidationError("No module found.") - } + // MARK: Importing Python and Pyverilog - let ports = Python.list(definition.portlist.ports) - var declarations: [PythonObject] = [] - var items: [PythonObject] = [] - - let fnmatch = Python.import("fnmatch") - - for item in definition.items { - var yank = false - - let type = Python.type(item).__name__ - // Process gates - if type == "InstanceList" { - let instance = item.instances[0] - let moduleName = "\(instance.module)" - let instanceName = "\(instance.name)" - - if let dffinfo = getMatchingDFFInfo( - from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch - ) { - yank = true - - let outputName = "\\" + instanceName + ".d" - let inputIdentifier = Node.Identifier(instanceName) - let outputIdentifier = Node.Identifier(outputName) - var dArg: PythonObject? - var qArg: PythonObject? - - for hook in instance.portlist { - if String(describing: hook.portname) == dffinfo.d { - dArg = hook.argname - } - if String(describing: hook.portname) == dffinfo.q { - qArg = hook.argname - } - } - - guard let d = dArg, let q = qArg else { - throw ValidationError("Cell \(instanceName) missing either a 'D' or 'Q' port.") - } - - ports.append(Node.Port(instanceName, Python.None, Python.None, Python.None)) - ports.append(Node.Port(outputName, Python.None, Python.None, Python.None)) - - declarations.append(Node.Input(instanceName)) - declarations.append(Node.Output(outputName)) - - let inputAssignment = Node.Assign(Node.Lvalue(q), Node.Rvalue(inputIdentifier)) - let outputAssignment = Node.Assign(Node.Lvalue(outputIdentifier), Node.Rvalue(d)) - - items.append(inputAssignment) - items.append(outputAssignment) - - } else if let blackboxModule = blackboxModules[moduleName] { - yank = true - - for hook in instance.portlist { - let portName = String(describing: hook.portname) - - if bypass.simulationValues[portName] != nil { - continue - } - - let portInfo = blackboxModule.portsByName[portName]! - 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) : Node.Input(ioName, width) - let assignStatement = - portInfo.polarity == .input - ? Node.Assign(Node.Lvalue(Node.Identifier(ioName)), Node.Rvalue(hook.argname)) - : Node.Assign(Node.Rvalue(hook.argname), Node.Lvalue(Node.Identifier(ioName))) - - items.append(assignStatement) - declarations.append(ioDeclaration) - ports.append(Node.Port(ioName, Python.None, Python.None, Python.None)) - } - } - } - - if !yank { - items.append(item) - } + let parse = Python.import("pyverilog.vparser.parser").parse + let Node = Python.import("pyverilog.vparser.ast") + let Generator = Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() + + var blackboxModules: OrderedDictionary = [:] + + if blackboxModels.count != 0 { + blackboxModules = try Module.getModules(in: blackboxModels, filter: Set(blackbox)) + } + + var sclConfig = SCLConfiguration(dffMatches: [ + DFFMatch(name: "DFFSR,DFFNEGX1,DFFPOSX1", clk: "CLK", d: "D", q: "Q") + ]) + + if let sclConfigPath = self.sclConfig { + guard let sclConfigYML = try? String(contentsOfFile: sclConfigPath) else { + throw ValidationError("File not found: \(sclConfigPath)") + } + let decoder = YAMLDecoder() + sclConfig = try decoder.decode(SCLConfiguration.self, from: sclConfigYML) + } + + if let dffOverride = dff { + sclConfig.dffMatches.last!.name = dffOverride + } + + let ast = parse([file])[0] + let description = ast[dynamicMember: "description"] + var definitionOptional: PythonObject? + + for definition in description.definitions { + let type = Python.type(definition).__name__ + if type == "ModuleDef" { + definitionOptional = definition + break + } + } + + guard let definition = definitionOptional else { + throw ValidationError("No module found.") + } + + let ports = Python.list(definition.portlist.ports) + var declarations: [PythonObject] = [] + var items: [PythonObject] = [] + + let fnmatch = Python.import("fnmatch") + + for item in definition.items { + var yank = false + + let type = Python.type(item).__name__ + // Process gates + if type == "InstanceList" { + let instance = item.instances[0] + let moduleName = "\(instance.module)" + let instanceName = "\(instance.name)" + + if let dffinfo = getMatchingDFFInfo( + from: sclConfig.dffMatches, for: moduleName, fnmatch: fnmatch + ) { + yank = true + + let outputName = "\\" + instanceName + ".d" + let inputIdentifier = Node.Identifier(instanceName) + let outputIdentifier = Node.Identifier(outputName) + var dArg: PythonObject? + var qArg: PythonObject? + + for hook in instance.portlist { + if String(describing: hook.portname) == dffinfo.d { + dArg = hook.argname + } + if String(describing: hook.portname) == dffinfo.q { + qArg = hook.argname + } } - if declarations.isEmpty { - print("[Warning]: Failed to detect any flip-flop cells.") + guard let d = dArg, let q = qArg else { + throw ValidationError("Cell \(instanceName) missing either a 'D' or 'Q' port.") } - definition.portlist.ports = ports - definition.items = Python.tuple(declarations + items) + ports.append(Node.Port(instanceName, Python.None, Python.None, Python.None)) + ports.append(Node.Port(outputName, Python.None, Python.None, Python.None)) - try File.open(output, mode: .write) { - try $0.print(String.boilerplate) - try $0.print(Generator.visit(definition)) + declarations.append(Node.Input(instanceName)) + declarations.append(Node.Output(outputName)) + + let inputAssignment = Node.Assign(Node.Lvalue(q), Node.Rvalue(inputIdentifier)) + let outputAssignment = Node.Assign(Node.Lvalue(outputIdentifier), Node.Rvalue(d)) + + items.append(inputAssignment) + items.append(outputAssignment) + + } else if let blackboxModule = blackboxModules[moduleName] { + yank = true + + for hook in instance.portlist { + let portName = String(describing: hook.portname) + + if bypass.simulationValues[portName] != nil { + continue + } + + let portInfo = blackboxModule.portsByName[portName]! + 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) : Node.Input(ioName, width) + let assignStatement = + portInfo.polarity == .input + ? Node.Assign(Node.Lvalue(Node.Identifier(ioName)), Node.Rvalue(hook.argname)) + : Node.Assign(Node.Rvalue(hook.argname), Node.Lvalue(Node.Identifier(ioName))) + + items.append(assignStatement) + declarations.append(ioDeclaration) + ports.append(Node.Port(ioName, Python.None, Python.None, Python.None)) } + } + } + + if !yank { + items.append(item) } + } + + if declarations.isEmpty { + print("[Warning]: Failed to detect any flip-flop cells.") + } + + definition.portlist.ports = ports + definition.items = Python.tuple(declarations + items) + + try File.open(output, mode: .write) { + try $0.print(String.boilerplate) + try $0.print(Generator.visit(definition)) + } } + } } diff --git a/Sources/Fault/Entries/main.swift b/Sources/Fault/Entries/main.swift index 92ae7f5..435d81d 100644 --- a/Sources/Fault/Entries/main.swift +++ b/Sources/Fault/Entries/main.swift @@ -15,7 +15,7 @@ import ArgumentParser import BigInt import Collections -import CoreFoundation // Not automatically imported on Linux +import CoreFoundation // Not automatically imported on Linux import Defile import Foundation import PythonKit @@ -29,57 +29,57 @@ let iverilogExecutable = env["FAULT_IVERILOG"] ?? env["PYVERILOG_IVERILOG"] ?? " let vvpExecutable = env["FAULT_VVP"] ?? "vvp" let yosysExecutable = env["FAULT_YOSYS"] ?? "yosys" -_ = [ // Register all RNGs - SwiftRNG.registered, - LFSR.registered, - PatternGenerator.registered, +_ = [ // Register all RNGs + SwiftRNG.registered, + LFSR.registered, + PatternGenerator.registered, ] -_ = [ // Register all TVGens - Atalanta.registered, - Quaigh.registered, - PODEM.registered, - PodemQuest.registered, +_ = [ // Register all TVGens + Atalanta.registered, + Quaigh.registered, + PODEM.registered, + PodemQuest.registered, ] let yosysTest = "'\(yosysExecutable)' -V".sh(silent: true) if yosysTest != EX_OK { - Stderr.print( - "Yosys must be installed to PATH on your computer for Fault to work. Fault will now quit.") - exit(EX_UNAVAILABLE) + Stderr.print( + "Yosys must be installed to PATH on your computer for Fault to work. Fault will now quit.") + exit(EX_UNAVAILABLE) } let pythonVersions = { - // Test Yosys, Python - () -> (python: String, pyverilog: String) in - do { - let pythonVersion = try Python.attemptImport("platform").python_version() - let sys = Python.import("sys") - if let pythonPath = env["PYTHONPATH"] { - sys.path.append(pythonPath) - } else { - let pythonPathProcess = "python3 -c \"import sys; print(':'.join(sys.path), end='')\"" - .shOutput() - let pythonPath = pythonPathProcess.output - let pythonPathComponents = pythonPath.components(separatedBy: ":") - for component in pythonPathComponents { - sys.path.append(component) - } - } - - let pyverilogVersion = try Python.attemptImport("pyverilog").__version__ - return (python: "\(pythonVersion)", pyverilog: "\(pyverilogVersion)") - } catch { - Stderr.print("\(error)") - exit(EX_UNAVAILABLE) + // Test Yosys, Python + () -> (python: String, pyverilog: String) in + do { + let pythonVersion = try Python.attemptImport("platform").python_version() + let sys = Python.import("sys") + if let pythonPath = env["PYTHONPATH"] { + sys.path.append(pythonPath) + } else { + let pythonPathProcess = "python3 -c \"import sys; print(':'.join(sys.path), end='')\"" + .shOutput() + let pythonPath = pythonPathProcess.output + let pythonPathComponents = pythonPath.components(separatedBy: ":") + for component in pythonPathComponents { + sys.path.append(component) + } } -}() // Just to check + + let pyverilogVersion = try Python.attemptImport("pyverilog").__version__ + return (python: "\(pythonVersion)", pyverilog: "\(pyverilogVersion)") + } catch { + Stderr.print("\(error)") + exit(EX_UNAVAILABLE) + } +}() // Just to check struct Fault: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Open-source EDA's missing DFT Toolchain", - subcommands: [ATPG.self, Cut.self, Synth.self, Assemble.self, Tap.self, Chain.self], - defaultSubcommand: ATPG.self - ) + static let configuration = CommandConfiguration( + abstract: "Open-source EDA's missing DFT Toolchain", + subcommands: [ATPG.self, Cut.self, Synth.self, Assemble.self, Tap.self, Chain.self], + defaultSubcommand: ATPG.self + ) } Fault.main() diff --git a/Sources/Fault/Entries/synth.swift b/Sources/Fault/Entries/synth.swift index 12ae9d5..722307f 100644 --- a/Sources/Fault/Entries/synth.swift +++ b/Sources/Fault/Entries/synth.swift @@ -15,75 +15,75 @@ import ArgumentParser import BigInt import Collections -import CoreFoundation // Not automatically imported on Linux +import CoreFoundation // Not automatically imported on Linux import Defile import Foundation import PythonKit import Yams extension Fault { - struct Synth: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Synthesize Verilog designs using Yosys." - ) - - @Option( - name: [.customShort("o"), .long], - help: "Path to the output netlist. (Default: Netlists/ + input + .netlist.v)" - ) - var output: String? - - @Option(name: [.customShort("l"), .long], help: "Liberty file. (Required.)") - var liberty: String - - @Option(name: [.customShort("t"), .long], help: "Top module. (Required.)") - var top: String - - @Option( - name: [.customShort("B"), .long, .customLong("blackboxModel")], - help: "Blackbox model verilog files. Specify multiple times to specify multiple models." - ) - var blackboxModels: [String] = [] - - @Argument(help: "Verilog files to synthesize.") - var files: [String] - - mutating func run() throws { - let fileManager = FileManager.default - - for file in files { - guard fileManager.fileExists(atPath: file) else { - throw ValidationError("File '\(file)' not found.") - } - } - - if !fileManager.fileExists(atPath: liberty) { - print("Liberty file '\(liberty)' not found.") - return - } - - if !liberty.hasSuffix(".lib") { - print("Warning: Liberty file provided does not end with .lib.") - } - - let output = output ?? "Netlists/\(top).nl.v" - - let script = Synthesis.script( - for: top, in: files, cutting: false, liberty: liberty, blackboxing: blackboxModels, - output: output - ) - - let outputDirectory = URL(fileURLWithPath: output).deletingLastPathComponent().path - try fileManager.createDirectory( - atPath: outputDirectory, withIntermediateDirectories: true, attributes: nil - ) - - let result = "echo '\(script)' | '\(yosysExecutable)'".sh() - - if result != EX_OK { - print("A yosys error has occurred.") - throw ValidationError("Yosys error occurred.") - } + struct Synth: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Synthesize Verilog designs using Yosys." + ) + + @Option( + name: [.customShort("o"), .long], + help: "Path to the output netlist. (Default: Netlists/ + input + .netlist.v)" + ) + var output: String? + + @Option(name: [.customShort("l"), .long], help: "Liberty file. (Required.)") + var liberty: String + + @Option(name: [.customShort("t"), .long], help: "Top module. (Required.)") + var top: String + + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) + var blackboxModels: [String] = [] + + @Argument(help: "Verilog files to synthesize.") + var files: [String] + + mutating func run() throws { + let fileManager = FileManager.default + + for file in files { + guard fileManager.fileExists(atPath: file) else { + throw ValidationError("File '\(file)' not found.") } + } + + if !fileManager.fileExists(atPath: liberty) { + print("Liberty file '\(liberty)' not found.") + return + } + + if !liberty.hasSuffix(".lib") { + print("Warning: Liberty file provided does not end with .lib.") + } + + let output = output ?? "Netlists/\(top).nl.v" + + let script = Synthesis.script( + for: top, in: files, cutting: false, liberty: liberty, blackboxing: blackboxModels, + output: output + ) + + let outputDirectory = URL(fileURLWithPath: output).deletingLastPathComponent().path + try fileManager.createDirectory( + atPath: outputDirectory, withIntermediateDirectories: true, attributes: nil + ) + + let result = "echo '\(script)' | '\(yosysExecutable)'".sh() + + if result != EX_OK { + print("A yosys error has occurred.") + throw ValidationError("Yosys error occurred.") + } } + } } diff --git a/Sources/Fault/Entries/tap.swift b/Sources/Fault/Entries/tap.swift index 6142cec..d0b885a 100644 --- a/Sources/Fault/Entries/tap.swift +++ b/Sources/Fault/Entries/tap.swift @@ -20,506 +20,506 @@ import Foundation import PythonKit extension Fault { - struct Tap: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Add a JTAG TAP port and controller to a netlist with a scan-chain." - ) + struct Tap: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Add a JTAG TAP port and controller to a netlist with a scan-chain." + ) - @Option(name: [.short, .long], help: "Path to the output file.") - var output: String? + @Option(name: [.short, .long], help: "Path to the output file.") + var output: String? - @Option( - name: [.short, .long, .customLong("cellModel")], - help: "Verify JTAG port using given cell model." - ) - var cellModel: String? + @Option( + name: [.short, .long, .customLong("cellModel")], + help: "Verify JTAG port using given cell model." + ) + var cellModel: String? - @OptionGroup - var bypass: BypassOptions + @OptionGroup + var bypass: BypassOptions - @Option(name: [.short, .long], help: "Liberty file. (Required.)") - var liberty: String + @Option(name: [.short, .long], help: "Liberty file. (Required.)") + var liberty: String - @Option(name: [.short, .long], help: ".bin file for test vectors.") - var testVectors: String? + @Option(name: [.short, .long], help: ".bin file for test vectors.") + var testVectors: String? - @Option( - name: [.short, .long], - help: ".bin file for golden output. Required iff testVectors is provided." - ) - var goldenOutput: String? + @Option( + name: [.short, .long], + help: ".bin file for golden output. Required iff testVectors is provided." + ) + var goldenOutput: String? - @Option( - name: [.customShort("b"), .customLong("blackbox")], - help: "Blackbox module names. Specify multiple times to specify multiple modules." - ) - var blackbox: [String] = [] + @Option( + name: [.customShort("b"), .customLong("blackbox")], + help: "Blackbox module names. Specify multiple times to specify multiple modules." + ) + var blackbox: [String] = [] - @Option( - name: [.customShort("B"), .long, .customLong("blackboxModel")], - help: "Blackbox model verilog files. Specify multiple times to specify multiple models." - ) - var blackboxModels: [String] = [] + @Option( + name: [.customShort("B"), .long, .customLong("blackboxModel")], + help: "Blackbox model verilog files. Specify multiple times to specify multiple models." + ) + var blackboxModels: [String] = [] - @Option( - name: [.customShort("D"), .customLong("define")], - help: - "`define statements to include during simulations. Specify multiple times to specify multiple defines." - ) - var defines: [String] = [] + @Option( + name: [.customShort("D"), .customLong("define")], + help: + "`define statements to include during simulations. Specify multiple times to specify multiple defines." + ) + var defines: [String] = [] - @Option( - name: [.customShort("I"), .customLong("inc"), .customLong("include")], - help: "Extra verilog models to include during simulations. (Default: none)" - ) - var includes: [String] = [] + @Option( + name: [.customShort("I"), .customLong("inc"), .customLong("include")], + help: "Extra verilog models to include during simulations. (Default: none)" + ) + var includes: [String] = [] - @Flag(name: [.long], help: "Skip re-synthesizing the chained netlist.") - var skipSynth: Bool = false + @Flag(name: [.long], help: "Skip re-synthesizing the chained netlist.") + var skipSynth: Bool = false - // Scan chain signals with default names - @Option(name: .long, help: "Name for scan-chain serial data in signal.") - var sin: String = "sin" + // Scan chain signals with default names + @Option(name: .long, help: "Name for scan-chain serial data in signal.") + var sin: String = "sin" - @Option(name: .long, help: "Name for scan-chain serial data out signal.") - var sout: String = "sout" + @Option(name: .long, help: "Name for scan-chain serial data out signal.") + var sout: String = "sout" - @Option(name: .long, help: "Name for scan-chain shift enable signal.") - var shift: String = "shift" + @Option(name: .long, help: "Name for scan-chain shift enable signal.") + var shift: String = "shift" - @Option(name: .long, help: "Name for scan-chain test enable signal.") - var test: String = "test" + @Option(name: .long, help: "Name for scan-chain test enable signal.") + var test: String = "test" - @Option(name: .long, help: "Name for JTAG test mode select signal.") - var tms: String = "tms" + @Option(name: .long, help: "Name for JTAG test mode select signal.") + var tms: String = "tms" - @Option(name: .long, help: "Name for JTAG test clock signal.") - var tck: String = "tck" + @Option(name: .long, help: "Name for JTAG test clock signal.") + var tck: String = "tck" - @Option(name: .long, help: "Name for JTAG test data input signal.") - var tdi: String = "tdi" + @Option(name: .long, help: "Name for JTAG test data input signal.") + var tdi: String = "tdi" - @Option(name: .long, help: "Name for JTAG test data output signal.") - var tdo: String = "tdo" + @Option(name: .long, help: "Name for JTAG test data output signal.") + var tdo: String = "tdo" - @Option(name: .long, help: "Name for TDO Enable pad (active low) signal.") - var tdoEnable: String = "tdo_paden_o" + @Option(name: .long, help: "Name for TDO Enable pad (active low) signal.") + var tdoEnable: String = "tdo_paden_o" - @Option(name: .long, help: "Name for JTAG test reset (active low) signal.") - var trst: String = "trst" + @Option(name: .long, help: "Name for JTAG test reset (active low) signal.") + var trst: String = "trst" - @Argument - var file: String + @Argument + var file: String - mutating func run() throws { - let fileManager = FileManager() - if !fileManager.fileExists(atPath: file) { - throw ValidationError("File '\(file)' not found.") - } + mutating func run() throws { + let fileManager = FileManager() + if !fileManager.fileExists(atPath: file) { + throw ValidationError("File '\(file)' not found.") + } - let (_, boundaryCount, internalCount) = ChainMetadata.extract(file: file) + let (_, boundaryCount, internalCount) = ChainMetadata.extract(file: file) - let defines = Set(defines) + let defines = Set(defines) - if !fileManager.fileExists(atPath: liberty) { - throw ValidationError("Liberty file '\(liberty)' not found.") - } + if !fileManager.fileExists(atPath: liberty) { + throw ValidationError("Liberty file '\(liberty)' not found.") + } - if !liberty.hasSuffix(".lib") { - Stderr.print( - "Warning: Liberty file provided does not end with .lib." - ) - } + if !liberty.hasSuffix(".lib") { + Stderr.print( + "Warning: Liberty file provided does not end with .lib." + ) + } - if let modelTest = cellModel { - if !fileManager.fileExists(atPath: modelTest) { - throw ValidationError("Cell model file '\(modelTest)' not found.") - } - if !modelTest.hasSuffix(".v"), !modelTest.hasSuffix(".sv") { - Stderr.print( - "Warning: Cell model file provided does not end with .v or .sv." - ) - } + if let modelTest = cellModel { + if !fileManager.fileExists(atPath: modelTest) { + throw ValidationError("Cell model file '\(modelTest)' not found.") + } + if !modelTest.hasSuffix(".v"), !modelTest.hasSuffix(".sv") { + Stderr.print( + "Warning: Cell model file provided does not end with .v or .sv." + ) + } + } + + if let tvTest = testVectors { + if !fileManager.fileExists(atPath: tvTest) { + throw ValidationError("Test vectors file '\(tvTest)' not found.") + } + if !tvTest.hasSuffix(".bin") { + Stderr.print( + "Warning: Test vectors file provided does not end with .bin." + ) + } + guard goldenOutput != nil else { + throw ValidationError("Using goldenOutput (-g) option is required '\(tvTest)'.") + } + } + + 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 + + let parse = Python.import("pyverilog.vparser.parser").parse + + let Node = Python.import("pyverilog.vparser.ast") + + let Generator = + Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() + + // MARK: Parse + + let ast = parse([file])[0] + let description = ast[dynamicMember: "description"] + var definitionOptional: PythonObject? + for definition in description.definitions { + let type = Python.type(definition).__name__ + if type == "ModuleDef" { + definitionOptional = definition + break + } + } + + guard let definition = definitionOptional else { + Stderr.print("No module found.") + Foundation.exit(EX_DATAERR) + } + + // MARK: Internal signals + + print("Creating top module…") + let definitionName = String(describing: definition.name) + let alteredName = "__DESIGN__UNDER__TEST__" + + do { + let (_, inputs, outputs) = try Port.extract(from: definition) + definition.name = Python.str(alteredName) + let ports = Python.list(definition.portlist.ports) + + let chainPorts: [String] = [ + sin, + sout, + shift, + tck, + test, + ] + let topModulePorts = Python.list( + ports.filter { + !chainPorts.contains(String($0.name)!) + }) + + topModulePorts.append( + Node.Port( + tms, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tck, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tdi, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tdo, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + trst, Python.None, Python.None, Python.None + )) + topModulePorts.append( + Node.Port( + tdoEnable, Python.None, Python.None, Python.None + )) + + let statements = Python.list() + statements.append(Node.Input(tms)) + statements.append(Node.Input(tck)) + statements.append(Node.Input(tdi)) + statements.append(Node.Output(tdo)) + statements.append(Node.Output(tdoEnable)) + statements.append(Node.Input(trst)) + + let portArguments = Python.list() + for input in inputs { + if !chainPorts.contains(input.name) { + let inputStatement = Node.Input(input.name) + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(input.name) + )) + if input.width > 1 { + let width = Node.Width( + Node.Constant(input.from), + Node.Constant(input.to) + ) + inputStatement.width = width } + statements.append(inputStatement) + } else { + let portIdentifier = input.name + portArguments.append( + Node.PortArg( + input.name, + Node.Identifier(portIdentifier) + )) + } + } - if let tvTest = testVectors { - if !fileManager.fileExists(atPath: tvTest) { - throw ValidationError("Test vectors file '\(tvTest)' not found.") - } - if !tvTest.hasSuffix(".bin") { - Stderr.print( - "Warning: Test vectors file provided does not end with .bin." - ) - } - guard goldenOutput != nil else { - throw ValidationError("Using goldenOutput (-g) option is required '\(tvTest)'.") - } + for output in outputs { + if !chainPorts.contains(output.name) { + let outputStatement = Node.Output(output.name) + if output.width > 1 { + let width = Node.Width( + Node.Constant(output.from), + Node.Constant(output.to) + ) + outputStatement.width = width } + statements.append(outputStatement) + } + portArguments.append( + Node.PortArg( + output.name, + Node.Identifier(output.name) + )) + } + + // MARK: tap module + + print("Stitching tap port…") + let tapInfo = TapInfo.default - let output = output ?? file.replacingExtension(".chained.v", with: ".jtag.v") // "\(file).jtag.v" - let intermediate = output.replacingExtension(".jtag.v", with: ".jtag_intermediate.v") + let tapCreator = TapCreator( + name: "tap_wrapper", + using: Node + ) + let tapModule = tapCreator.create( + tapInfo: tapInfo, + tms: tms, + tck: tck, + tdi: tdi, + tdo: tdo, + tdoEnable_n: tdoEnable, + trst: trst, + sin: sin, + sout: sout, + shift: shift, + test: test + ) + + statements.extend(tapModule.wires) + statements.append(tapModule.tapModule) + + let submoduleInstance = Node.Instance( + alteredName, + "__dut__", + Python.tuple(portArguments), + Python.tuple() + ) + + statements.append( + Node.InstanceList( + alteredName, + Python.tuple(), + Python.tuple([submoduleInstance]) + )) + + let supermodel = Node.ModuleDef( + definitionName, + Python.None, + Node.Portlist(Python.tuple(topModulePorts)), + Python.tuple(statements) + ) - // MARK: Importing Python and Pyverilog + let tempDir = "\(NSTemporaryDirectory())" - let parse = Python.import("pyverilog.vparser.parser").parse + let tapLocation = "\(tempDir)/top.v" + let wrapperLocation = "\(tempDir)/wrapper.v" - let Node = Python.import("pyverilog.vparser.ast") + do { + try File.open(tapLocation, mode: .write) { + try $0.print(TapCreator.top) + } + try File.open(wrapperLocation, mode: .write) { + try $0.print(TapCreator.wrapper) + } - let Generator = - Python.import("pyverilog.ast_code_generator.codegen").ASTCodeGenerator() + } catch {} - // MARK: Parse + let tapDefinition = + parse([tapLocation])[0][dynamicMember: "description"].definitions - let ast = parse([file])[0] - let description = ast[dynamicMember: "description"] - var definitionOptional: PythonObject? - for definition in description.definitions { - let type = Python.type(definition).__name__ - if type == "ModuleDef" { - definitionOptional = definition - break - } - } + let wrapperDefinition = + parse([wrapperLocation])[0][dynamicMember: "description"].definitions + + try? File.delete(tapLocation) + try? File.delete(wrapperLocation) + + let definitions = Python.list(description.definitions) + definitions.extend(tapDefinition) + definitions.extend(wrapperDefinition) + definitions.append(supermodel) + description.definitions = Python.tuple(definitions) + + try File.open(intermediate, mode: .write) { + try $0.print(Generator.visit(ast)) + } + + let _ = Synthesis.script( + for: definitionName, + in: [intermediate], + liberty: liberty, + blackboxing: blackboxModels, + output: output + ) + + let netlist: String = { + if !skipSynth { + let script = Synthesis.script( + for: definitionName, + in: [intermediate], + liberty: liberty, + blackboxing: blackboxModels, + output: output + ) - guard let definition = definitionOptional else { - Stderr.print("No module found.") - Foundation.exit(EX_DATAERR) + // MARK: Yosys + + print("Resynthesizing with yosys…") + let result = "echo '\(script)' | '\(yosysExecutable)' > /dev/null".sh() + + if result != EX_OK { + Stderr.print("A yosys error has occurred.") + Foundation.exit(EX_DATAERR) } + return output + } else { + return intermediate + } + }() + + guard let model = cellModel else { + print("Done.") + return + } + + guard let content = File.read(netlist) else { + throw "Could not re-read created file." + } + + try File.open(netlist, mode: .write) { + try $0.print(String.boilerplate) + try $0.print(content) + } + + // MARK: Verification + + let models = [model] + includes + blackboxModels + + print("Verifying tap port integrity…") + let ast = parse([netlist])[0] + let description = ast[dynamicMember: "description"] + var definitionOptional: PythonObject? + for definition in description.definitions { + let type = Python.type(definition).__name__ + if type == "ModuleDef" { + definitionOptional = definition + } + } + guard let definition = definitionOptional else { + Stderr.print("No module found.") + Foundation.exit(EX_DATAERR) + } + let (myPorts, myInputs, myOutputs) = try Port.extract(from: definition) + let verified = try Simulator.simulate( + verifying: definitionName, + in: netlist, // DEBUG + with: models, + ports: myPorts, + inputs: myInputs, + outputs: myOutputs, + chainLength: boundaryCount + internalCount, + clock: bypass.clock, + reset: bypass.reset.name, + resetActive: bypass.reset.active, + tms: tms, + tdi: tdi, + tck: tck, + tdo: tdo, + trst: trst, + output: netlist + ".tb.sv", + defines: defines, + using: iverilogExecutable, + with: vvpExecutable + ) + print("Done.") + if verified { + print("Tap port verified successfully.") + } else { + print("Tap port verification failed.") + print( + "・Ensure that clock and reset signals, if they exist are passed as such to the program." + ) + 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.") + } - // MARK: Internal signals - - print("Creating top module…") - let definitionName = String(describing: definition.name) - let alteredName = "__DESIGN__UNDER__TEST__" - - do { - let (_, inputs, outputs) = try Port.extract(from: definition) - definition.name = Python.str(alteredName) - let ports = Python.list(definition.portlist.ports) - - let chainPorts: [String] = [ - sin, - sout, - shift, - tck, - test, - ] - let topModulePorts = Python.list( - ports.filter { - !chainPorts.contains(String($0.name)!) - }) - - topModulePorts.append( - Node.Port( - tms, Python.None, Python.None, Python.None - )) - topModulePorts.append( - Node.Port( - tck, Python.None, Python.None, Python.None - )) - topModulePorts.append( - Node.Port( - tdi, Python.None, Python.None, Python.None - )) - topModulePorts.append( - Node.Port( - tdo, Python.None, Python.None, Python.None - )) - topModulePorts.append( - Node.Port( - trst, Python.None, Python.None, Python.None - )) - topModulePorts.append( - Node.Port( - tdoEnable, Python.None, Python.None, Python.None - )) - - let statements = Python.list() - statements.append(Node.Input(tms)) - statements.append(Node.Input(tck)) - statements.append(Node.Input(tdi)) - statements.append(Node.Output(tdo)) - statements.append(Node.Output(tdoEnable)) - statements.append(Node.Input(trst)) - - let portArguments = Python.list() - for input in inputs { - if !chainPorts.contains(input.name) { - let inputStatement = Node.Input(input.name) - portArguments.append( - Node.PortArg( - input.name, - Node.Identifier(input.name) - )) - if input.width > 1 { - let width = Node.Width( - Node.Constant(input.from), - Node.Constant(input.to) - ) - inputStatement.width = width - } - statements.append(inputStatement) - } else { - let portIdentifier = input.name - portArguments.append( - Node.PortArg( - input.name, - Node.Identifier(portIdentifier) - )) - } - } - - for output in outputs { - if !chainPorts.contains(output.name) { - let outputStatement = Node.Output(output.name) - if output.width > 1 { - let width = Node.Width( - Node.Constant(output.from), - Node.Constant(output.to) - ) - outputStatement.width = width - } - statements.append(outputStatement) - } - portArguments.append( - Node.PortArg( - output.name, - Node.Identifier(output.name) - )) - } - - // MARK: tap module - - print("Stitching tap port…") - let tapInfo = TapInfo.default - - let tapCreator = TapCreator( - name: "tap_wrapper", - using: Node - ) - let tapModule = tapCreator.create( - tapInfo: tapInfo, - tms: tms, - tck: tck, - tdi: tdi, - tdo: tdo, - tdoEnable_n: tdoEnable, - trst: trst, - sin: sin, - sout: sout, - shift: shift, - test: test - ) - - statements.extend(tapModule.wires) - statements.append(tapModule.tapModule) - - let submoduleInstance = Node.Instance( - alteredName, - "__dut__", - Python.tuple(portArguments), - Python.tuple() - ) - - statements.append( - Node.InstanceList( - alteredName, - Python.tuple(), - Python.tuple([submoduleInstance]) - )) - - let supermodel = Node.ModuleDef( - definitionName, - Python.None, - Node.Portlist(Python.tuple(topModulePorts)), - Python.tuple(statements) - ) - - let tempDir = "\(NSTemporaryDirectory())" - - let tapLocation = "\(tempDir)/top.v" - let wrapperLocation = "\(tempDir)/wrapper.v" - - do { - try File.open(tapLocation, mode: .write) { - try $0.print(TapCreator.top) - } - try File.open(wrapperLocation, mode: .write) { - try $0.print(TapCreator.wrapper) - } - - } catch {} - - let tapDefinition = - parse([tapLocation])[0][dynamicMember: "description"].definitions - - let wrapperDefinition = - parse([wrapperLocation])[0][dynamicMember: "description"].definitions - - try? File.delete(tapLocation) - try? File.delete(wrapperLocation) - - let definitions = Python.list(description.definitions) - definitions.extend(tapDefinition) - definitions.extend(wrapperDefinition) - definitions.append(supermodel) - description.definitions = Python.tuple(definitions) - - try File.open(intermediate, mode: .write) { - try $0.print(Generator.visit(ast)) - } - - let _ = Synthesis.script( - for: definitionName, - in: [intermediate], - liberty: liberty, - blackboxing: blackboxModels, - output: output - ) - - let netlist: String = { - if !skipSynth { - let script = Synthesis.script( - for: definitionName, - in: [intermediate], - liberty: liberty, - blackboxing: blackboxModels, - output: output - ) - - // MARK: Yosys - - print("Resynthesizing with yosys…") - let result = "echo '\(script)' | '\(yosysExecutable)' > /dev/null".sh() - - if result != EX_OK { - Stderr.print("A yosys error has occurred.") - Foundation.exit(EX_DATAERR) - } - return output - } else { - return intermediate - } - }() - - guard let model = cellModel else { - print("Done.") - return - } - - guard let content = File.read(netlist) else { - throw "Could not re-read created file." - } - - try File.open(netlist, mode: .write) { - try $0.print(String.boilerplate) - try $0.print(content) - } - - // MARK: Verification - - let models = [model] + includes + blackboxModels - - print("Verifying tap port integrity…") - let ast = parse([netlist])[0] - let description = ast[dynamicMember: "description"] - var definitionOptional: PythonObject? - for definition in description.definitions { - let type = Python.type(definition).__name__ - if type == "ModuleDef" { - definitionOptional = definition - } - } - guard let definition = definitionOptional else { - Stderr.print("No module found.") - Foundation.exit(EX_DATAERR) - } - let (myPorts, myInputs, myOutputs) = try Port.extract(from: definition) - let verified = try Simulator.simulate( - verifying: definitionName, - in: netlist, // DEBUG - with: models, - ports: myPorts, - inputs: myInputs, - outputs: myOutputs, - chainLength: boundaryCount + internalCount, - clock: bypass.clock, - reset: bypass.reset.name, - resetActive: bypass.reset.active, - tms: tms, - tdi: tdi, - tck: tck, - tdo: tdo, - trst: trst, - output: netlist + ".tb.sv", - defines: defines, - using: iverilogExecutable, - with: vvpExecutable - ) - print("Done.") - if verified { - print("Tap port verified successfully.") - } else { - print("Tap port verification failed.") - print( - "・Ensure that clock and reset signals, if they exist are passed as such to the program." - ) - 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.") - } - - // MARK: Test bench - - if let tvFile = testVectors { - print("Generating testbench for test vectors…") - let (vectorCount, vectorLength) = binMetadata.extract(file: tvFile) - let (_, outputLength) = binMetadata.extract(file: goldenOutput!) - let testbecnh = netlist + ".tv" + ".tb.sv" - let verified = try Simulator.simulate( - verifying: definitionName, - in: netlist, - with: models, - ports: myPorts, - inputs: myInputs, - bypassingWithBehavior: bypass.simulationValues, - outputs: myOutputs, - clock: bypass.clock, - reset: bypass.reset.name, - resetActive: bypass.reset.active, - tms: tms, - tdi: tdi, - tck: tck, - tdo: tdo, - trst: trst, - output: testbecnh, - chainLength: internalCount + boundaryCount, - vecbinFile: testVectors!, - outbinFile: goldenOutput!, - vectorCount: vectorCount, - vectorLength: vectorLength, - outputLength: outputLength, - defines: defines, - using: iverilogExecutable, - with: vvpExecutable - ) - if verified { - print("Test vectors verified successfully.") - } else { - print("Test vector simulation failed.") - 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) - } - } - } catch { - Stderr.print("Internal software error: \(error)") - Foundation.exit(EX_SOFTWARE) + // MARK: Test bench + + if let tvFile = testVectors { + print("Generating testbench for test vectors…") + let (vectorCount, vectorLength) = binMetadata.extract(file: tvFile) + let (_, outputLength) = binMetadata.extract(file: goldenOutput!) + let testbecnh = netlist + ".tv" + ".tb.sv" + let verified = try Simulator.simulate( + verifying: definitionName, + in: netlist, + with: models, + ports: myPorts, + inputs: myInputs, + bypassingWithBehavior: bypass.simulationValues, + outputs: myOutputs, + clock: bypass.clock, + reset: bypass.reset.name, + resetActive: bypass.reset.active, + tms: tms, + tdi: tdi, + tck: tck, + tdo: tdo, + trst: trst, + output: testbecnh, + chainLength: internalCount + boundaryCount, + vecbinFile: testVectors!, + outbinFile: goldenOutput!, + vectorCount: vectorCount, + vectorLength: vectorLength, + outputLength: outputLength, + defines: defines, + using: iverilogExecutable, + with: vvpExecutable + ) + if verified { + print("Test vectors verified successfully.") + } else { + print("Test vector simulation failed.") + 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) + } } + } catch { + Stderr.print("Internal software error: \(error)") + Foundation.exit(EX_SOFTWARE) + } } + } } diff --git a/Sources/Fault/Errors.swift b/Sources/Fault/Errors.swift index 71d42b5..66e0670 100644 --- a/Sources/Fault/Errors.swift +++ b/Sources/Fault/Errors.swift @@ -13,13 +13,13 @@ // limitations under the License. struct RuntimeError: Error { - let description: String + let description: String - init(_ description: String) { - self.description = description - } + init(_ description: String) { + self.description = description + } - var errorDescription: String? { - description - } + var errorDescription: String? { + description + } } diff --git a/Sources/Fault/Future.swift b/Sources/Fault/Future.swift index 77fc68e..9f3bb2b 100644 --- a/Sources/Fault/Future.swift +++ b/Sources/Fault/Future.swift @@ -18,37 +18,37 @@ import Foundation var pool: threadpool? public class Future { - static var pool: threadpool? - - private var semaphore: DispatchSemaphore - private var store: Any? - private var executor: () -> Any - - init(executor: @escaping () -> Any) { - semaphore = DispatchSemaphore(value: 0) - self.executor = executor - - if Future.pool == nil { - Future.pool = thpool_init( - CInt( - ProcessInfo.processInfo.environment["FAULT_THREADS"] ?? "" - ) ?? CInt(clamping: ProcessInfo.processInfo.processorCount)) - } - - _ = thpool_add_work( - Future.pool!, - { - pointer in - let this = Unmanaged.fromOpaque(pointer!).takeUnretainedValue() - this.store = this.executor() - this.semaphore.signal() - }, Unmanaged.passUnretained(self).toOpaque() - ) - } + static var pool: threadpool? + + private var semaphore: DispatchSemaphore + private var store: Any? + private var executor: () -> Any - public var value: Any { - semaphore.wait() - let value = store! - return value + init(executor: @escaping () -> Any) { + semaphore = DispatchSemaphore(value: 0) + self.executor = executor + + if Future.pool == nil { + Future.pool = thpool_init( + CInt( + ProcessInfo.processInfo.environment["FAULT_THREADS"] ?? "" + ) ?? CInt(clamping: ProcessInfo.processInfo.processorCount)) } + + _ = thpool_add_work( + Future.pool!, + { + pointer in + let this = Unmanaged.fromOpaque(pointer!).takeUnretainedValue() + this.store = this.executor() + this.semaphore.signal() + }, Unmanaged.passUnretained(self).toOpaque() + ) + } + + public var value: Any { + semaphore.wait() + let value = store! + return value + } } diff --git a/Sources/Fault/JTAG.swift b/Sources/Fault/JTAG.swift index 9844007..b1221f8 100644 --- a/Sources/Fault/JTAG.swift +++ b/Sources/Fault/JTAG.swift @@ -17,164 +17,164 @@ import Foundation import PythonKit class TapCreator { - var name: String - private var Node: PythonObject - init( - name: String, - using Node: PythonObject - ) { - self.name = name - self.Node = Node - } + var name: String + private var Node: PythonObject + init( + name: String, + using Node: PythonObject + ) { + self.name = name + self.Node = Node + } - func create( - tapInfo: TapInfo, - tms: String, - tck: String, - tdi: String, - tdo: String, - tdoEnable_n: String, - trst: String, - sin: String, - sout: String, - shift: String, - test: String - ) -> (tapModule: PythonObject, wires: [PythonObject]) { - let pads = tapInfo.tap - let chain = tapInfo.chain + func create( + tapInfo: TapInfo, + tms: String, + tck: String, + tdi: String, + tdo: String, + tdoEnable_n: String, + trst: String, + sin: String, + sout: String, + shift: String, + test: String + ) -> (tapModule: PythonObject, wires: [PythonObject]) { + let pads = tapInfo.tap + let chain = tapInfo.chain - let wireDeclarations: [PythonObject] = [ - Node.Wire(pads.tdo), - Node.Wire(pads.tdoEnable_n), - Node.Wire(pads.tms), - Node.Wire(pads.tdi), - Node.Wire(pads.trst), - Node.Wire(chain.sin), - Node.Wire(chain.sout), - Node.Wire(chain.shift), - Node.Wire(chain.test), - ] + let wireDeclarations: [PythonObject] = [ + Node.Wire(pads.tdo), + Node.Wire(pads.tdoEnable_n), + Node.Wire(pads.tms), + Node.Wire(pads.tdi), + Node.Wire(pads.trst), + Node.Wire(chain.sin), + Node.Wire(chain.sout), + Node.Wire(chain.shift), + Node.Wire(chain.test), + ] - let portArguments = [ - // Tap Top Module Interface - Node.PortArg( - pads.tms, - Node.Identifier(tms) - ), - Node.PortArg( - pads.tck, - Node.Identifier(tck) - ), - Node.PortArg( - pads.trst, - Node.Identifier(trst) - ), - Node.PortArg( - pads.tdi, - Node.Identifier(tdi) - ), - Node.PortArg( - pads.tdo, - Node.Identifier(tdo) - ), - Node.PortArg( - pads.tdoEnable_n, - Node.Identifier(tdoEnable_n) - ), - // Chain Interface - Node.PortArg( - chain.sin, - Node.Identifier(sin) - ), - Node.PortArg( - chain.sout, - Node.Identifier(sout) - ), - Node.PortArg( - chain.test, - Node.Identifier(test) - ), - Node.PortArg( - chain.shift, - Node.Identifier(shift) - ), - ] + let portArguments = [ + // Tap Top Module Interface + Node.PortArg( + pads.tms, + Node.Identifier(tms) + ), + Node.PortArg( + pads.tck, + Node.Identifier(tck) + ), + Node.PortArg( + pads.trst, + Node.Identifier(trst) + ), + Node.PortArg( + pads.tdi, + Node.Identifier(tdi) + ), + Node.PortArg( + pads.tdo, + Node.Identifier(tdo) + ), + Node.PortArg( + pads.tdoEnable_n, + Node.Identifier(tdoEnable_n) + ), + // Chain Interface + Node.PortArg( + chain.sin, + Node.Identifier(sin) + ), + Node.PortArg( + chain.sout, + Node.Identifier(sout) + ), + Node.PortArg( + chain.test, + Node.Identifier(test) + ), + Node.PortArg( + chain.shift, + Node.Identifier(shift) + ), + ] - let submoduleInstance = Node.Instance( - name, - "__" + name + "__", - Python.tuple(portArguments), - Python.tuple() - ) + let submoduleInstance = Node.Instance( + name, + "__" + name + "__", + Python.tuple(portArguments), + Python.tuple() + ) - let tapModule = Node.InstanceList( - name, - Python.tuple(), - Python.tuple([submoduleInstance]) - ) - return (tapModule: tapModule, wires: wireDeclarations) - } + let tapModule = Node.InstanceList( + name, + Python.tuple(), + Python.tuple([submoduleInstance]) + ) + return (tapModule: tapModule, wires: wireDeclarations) + } } // Tap top module Interface struct Tap: Codable { - var tms: String - var tdi: String - var tdo: String - var trst: String - var tck: String - var tdoEnable_n: String - init( - tms: String, - tdi: String, - tdo: String, - trst: String, - tck: String, - tdoEnable_n: String - ) { - self.tms = tms - self.tdi = tdi - self.tdo = tdo - self.trst = trst - self.tck = tck - self.tdoEnable_n = tdoEnable_n - } + var tms: String + var tdi: String + var tdo: String + var trst: String + var tck: String + var tdoEnable_n: String + init( + tms: String, + tdi: String, + tdo: String, + trst: String, + tck: String, + tdoEnable_n: String + ) { + self.tms = tms + self.tdi = tdi + self.tdo = tdo + self.trst = trst + self.tck = tck + self.tdoEnable_n = tdoEnable_n + } } // Internal Chain Interface struct Chain: Codable { - var sin: String - var sout: String - var shift: String - var test: String + var sin: String + var sout: String + var shift: String + var test: String - init( - sin: String, - sout: String, - shift: String, - test: String - ) { - self.sin = sin - self.sout = sout - self.shift = shift - self.test = test - } + init( + sin: String, + sout: String, + shift: String, + test: String + ) { + self.sin = sin + self.sout = sout + self.shift = shift + self.test = test + } } // Aggregate struct TapInfo: Codable { - var tap: Tap - var chain: Chain + var tap: Tap + var chain: Chain - init( - tap: Tap, - chain: Chain - ) { - self.tap = tap - self.chain = chain - } + init( + tap: Tap, + chain: Chain + ) { + self.tap = tap + self.chain = chain + } - static let `default`: TapInfo = try! JSONDecoder().decode( - TapInfo.self, from: Data(TapCreator.info.utf8) - ) + static let `default`: TapInfo = try! JSONDecoder().decode( + TapInfo.self, from: Data(TapCreator.info.utf8) + ) } diff --git a/Sources/Fault/JTAGStrings.swift b/Sources/Fault/JTAGStrings.swift index 569cf37..dfc29fd 100644 --- a/Sources/Fault/JTAGStrings.swift +++ b/Sources/Fault/JTAGStrings.swift @@ -13,7 +13,7 @@ // limitations under the License. extension TapCreator { - static let info = """ + static let info = """ { "tap":{ "tms": "tms", @@ -32,7 +32,7 @@ extension TapCreator { } """ - static let wrapper: String = """ + static let wrapper: String = """ module tap_wrapper( // tap ports tdi, @@ -105,7 +105,7 @@ extension TapCreator { endmodule """ - static let top: String = """ + static let top: String = """ ////////////////////////////////////////////////////////////////////// //// //// //// tap_top.v //// diff --git a/Sources/Fault/Metadata.swift b/Sources/Fault/Metadata.swift index da526b6..d0fb60c 100644 --- a/Sources/Fault/Metadata.swift +++ b/Sources/Fault/Metadata.swift @@ -16,113 +16,113 @@ import Defile import Foundation struct ChainRegister: Codable { - enum Kind: String, Codable { - case input - case output - case dff - case bypassInput // bypass when loading the TV (loaded with zeros) - case bypassOutput // bypass when shifting out the response () - } + enum Kind: String, Codable { + case input + case output + case dff + case bypassInput // bypass when loading the TV (loaded with zeros) + case bypassOutput // bypass when shifting out the response () + } - var name: String - var kind: Kind - var width: Int - var ordinal: Int - init(name: String, kind: Kind, ordinal: Int = 0, width: Int = 1) { - self.name = name - self.kind = kind - self.ordinal = ordinal - self.width = width - } + var name: String + var kind: Kind + var width: Int + var ordinal: Int + init(name: String, kind: Kind, ordinal: Int = 0, width: Int = 1) { + self.name = name + self.kind = kind + self.ordinal = ordinal + self.width = width + } } struct ChainMetadata: Codable { - var boundaryCount: Int - var internalCount: Int - var order: [ChainRegister] - var shift: String - var sin: String - var sout: String - init( - boundaryCount: Int, - internalCount: Int, - order: [ChainRegister], - shift: String, - sin: String, - sout: String - ) { - self.boundaryCount = boundaryCount - self.internalCount = internalCount - self.order = order - self.shift = shift - self.sin = sin - self.sout = sout - } + var boundaryCount: Int + var internalCount: Int + var order: [ChainRegister] + var shift: String + var sin: String + var sout: String + init( + boundaryCount: Int, + internalCount: Int, + order: [ChainRegister], + shift: String, + sin: String, + sout: String + ) { + self.boundaryCount = boundaryCount + self.internalCount = internalCount + self.order = order + self.shift = shift + self.sin = sin + self.sout = sout + } - static func extract(file: String) -> ([ChainRegister], Int, Int) { - guard let string = File.read(file) else { - Stderr.print("Could not read file '\(file)'") - exit(EX_NOINPUT) - } - let components = string.components(separatedBy: "/* FAULT METADATA: '") - if components.count == 0 { - Stderr.print("Fault metadata not provided.") - exit(EX_NOINPUT) - } - let slice = components[1] - if !slice.contains("' END FAULT METADATA */") { - Stderr.print("Fault metadata not terminated.") - exit(EX_NOINPUT) - } - let decoder = JSONDecoder() - let metadataString = slice.components(separatedBy: "' END FAULT METADATA */")[0] - guard - let metadata = try? decoder.decode( - ChainMetadata.self, from: metadataString.data(using: .utf8)! - ) - else { - Stderr.print("Metadata json is invalid.") - exit(EX_DATAERR) - } - return ( - order: metadata.order, - boundaryCount: metadata.boundaryCount, - internalCount: metadata.internalCount - ) + static func extract(file: String) -> ([ChainRegister], Int, Int) { + guard let string = File.read(file) else { + Stderr.print("Could not read file '\(file)'") + exit(EX_NOINPUT) + } + let components = string.components(separatedBy: "/* FAULT METADATA: '") + if components.count == 0 { + Stderr.print("Fault metadata not provided.") + exit(EX_NOINPUT) + } + let slice = components[1] + if !slice.contains("' END FAULT METADATA */") { + Stderr.print("Fault metadata not terminated.") + exit(EX_NOINPUT) } + let decoder = JSONDecoder() + let metadataString = slice.components(separatedBy: "' END FAULT METADATA */")[0] + guard + let metadata = try? decoder.decode( + ChainMetadata.self, from: metadataString.data(using: .utf8)! + ) + else { + Stderr.print("Metadata json is invalid.") + exit(EX_DATAERR) + } + return ( + order: metadata.order, + boundaryCount: metadata.boundaryCount, + internalCount: metadata.internalCount + ) + } } struct binMetadata: Codable { - var count: Int - var length: Int - init( - count: Int, - length: Int - ) { - self.count = count - self.length = length - } + var count: Int + var length: Int + init( + count: Int, + length: Int + ) { + self.count = count + self.length = length + } - static func extract(file: String) -> (Int, Int) { - guard let binString = File.read(file) else { - Stderr.print("Could not read file '\(file)'") - exit(EX_NOINPUT) - } + static func extract(file: String) -> (Int, Int) { + guard let binString = File.read(file) else { + Stderr.print("Could not read file '\(file)'") + exit(EX_NOINPUT) + } - let slice = binString.components(separatedBy: "/* FAULT METADATA: '")[1] - if !slice.contains("' END FAULT METADATA */") { - Stderr.print("Fault metadata not terminated.") - exit(EX_NOINPUT) - } + let slice = binString.components(separatedBy: "/* FAULT METADATA: '")[1] + if !slice.contains("' END FAULT METADATA */") { + Stderr.print("Fault metadata not terminated.") + exit(EX_NOINPUT) + } - let decoder = JSONDecoder() - let metadataString = slice.components(separatedBy: "' END FAULT METADATA */")[0] - guard - let metadata = try? decoder.decode(binMetadata.self, from: metadataString.data(using: .utf8)!) - else { - Stderr.print("Metadata json is invalid.") - exit(EX_DATAERR) - } - return (count: metadata.count, length: metadata.length) + let decoder = JSONDecoder() + let metadataString = slice.components(separatedBy: "' END FAULT METADATA */")[0] + guard + let metadata = try? decoder.decode(binMetadata.self, from: metadataString.data(using: .utf8)!) + else { + Stderr.print("Metadata json is invalid.") + exit(EX_DATAERR) } + return (count: metadata.count, length: metadata.length) + } } diff --git a/Sources/Fault/Module.swift b/Sources/Fault/Module.swift index bc44608..ca46c73 100644 --- a/Sources/Fault/Module.swift +++ b/Sources/Fault/Module.swift @@ -18,216 +18,216 @@ import Foundation import PythonKit struct Port: Codable, Hashable { - enum Polarity: String, Codable { - case input - case output - case unknown - } - - var name: String - var polarity: Polarity? - var from: Int - var to: Int - var ordinal: Int - - var width: Int { - from < to ? to - from + 1 : from - to + 1 - } - - var bits: [Int] { - from < to ? [Int](from ... to) : [Int](to ... from) - } - - init(name: String, polarity: Polarity? = nil, from: Int = 0, to: Int = 0, at ordinal: Int) { - self.name = name - self.polarity = polarity - self.from = from - self.to = to - self.ordinal = ordinal - } - - static func extract(from definition: PythonObject) throws -> ( - ports: [String: Port], inputs: [Port], outputs: [Port] - ) { - var ports: [String: Port] = [:] - - var paramaters: [String: Int] = [:] - for (i, portDeclaration) in definition.portlist.ports.enumerated() { - var polarity: Polarity? = nil - var from = 0 - var to = 0 - var name: String! - if Bool(Python.hasattr(portDeclaration, "first"))! { - let declaration = portDeclaration[dynamicMember: "first"] - let type = "\(Python.type(declaration).__name__)" - if type == "Input" { - polarity = .input - } else { - polarity = .output - } - if declaration.width != Python.None { - let msb = Port.evaluate(expr: declaration.width.msb, params: paramaters) - let lsb = Port.evaluate(expr: declaration.width.lsb, params: paramaters) - from = msb - to = lsb - } - let firstChild = portDeclaration[dynamicMember: "first"] - name = "\(firstChild.name)" - } else { - name = "\(portDeclaration.name)" - } - - let port = Port(name: name, polarity: polarity, from: from, to: to, at: i) - ports[name] = port + enum Polarity: String, Codable { + case input + case output + case unknown + } + + var name: String + var polarity: Polarity? + var from: Int + var to: Int + var ordinal: Int + + var width: Int { + from < to ? to - from + 1 : from - to + 1 + } + + var bits: [Int] { + from < to ? [Int](from...to) : [Int](to...from) + } + + init(name: String, polarity: Polarity? = nil, from: Int = 0, to: Int = 0, at ordinal: Int) { + self.name = name + self.polarity = polarity + self.from = from + self.to = to + self.ordinal = ordinal + } + + static func extract(from definition: PythonObject) throws -> ( + ports: [String: Port], inputs: [Port], outputs: [Port] + ) { + var ports: [String: Port] = [:] + + var paramaters: [String: Int] = [:] + for (i, portDeclaration) in definition.portlist.ports.enumerated() { + var polarity: Polarity? = nil + var from = 0 + var to = 0 + var name: String! + if Bool(Python.hasattr(portDeclaration, "first"))! { + let declaration = portDeclaration[dynamicMember: "first"] + let type = "\(Python.type(declaration).__name__)" + if type == "Input" { + polarity = .input + } else { + polarity = .output } - - for itemDeclaration in definition.items { - let type = Python.type(itemDeclaration).__name__ - // Process port declarations further - if type == "Decl" { - let declaration = itemDeclaration.list[0] - let declType = Python.type(declaration).__name__ - if declType == "Parameter" { - paramaters["\(declaration.name)"] = - Port.evaluate(expr: declaration.value.var, params: paramaters) - } else if declType == "Input" || declType == "Output" { - guard var port = ports["\(declaration.name)"] else { - throw "Unknown port \(declaration.name)" - } - if declaration.width != Python.None { - let msb = Port.evaluate(expr: declaration.width.msb, params: paramaters) - let lsb = Port.evaluate(expr: declaration.width.lsb, params: paramaters) - port.from = msb - port.to = lsb - } - if declType == "Input" { - port.polarity = .input - } else { - port.polarity = .output - } - ports["\(declaration.name)"] = port - } - } + if declaration.width != Python.None { + let msb = Port.evaluate(expr: declaration.width.msb, params: paramaters) + let lsb = Port.evaluate(expr: declaration.width.lsb, params: paramaters) + from = msb + to = lsb } + let firstChild = portDeclaration[dynamicMember: "first"] + name = "\(firstChild.name)" + } else { + name = "\(portDeclaration.name)" + } + + let port = Port(name: name, polarity: polarity, from: from, to: to, at: i) + ports[name] = port + } - let inputs: [Port] = ports.values.filter { $0.polarity == .input }.sorted(by: { - $0.ordinal < $1.ordinal - }) - let outputs: [Port] = ports.values.filter { $0.polarity == .output }.sorted(by: { - $0.ordinal < $1.ordinal - }) - if ports.count != inputs.count + outputs.count { - throw RuntimeError( - "Some ports in \(definition.name) are not properly declared as an input or output.") + for itemDeclaration in definition.items { + let type = Python.type(itemDeclaration).__name__ + // Process port declarations further + if type == "Decl" { + let declaration = itemDeclaration.list[0] + let declType = Python.type(declaration).__name__ + if declType == "Parameter" { + paramaters["\(declaration.name)"] = + Port.evaluate(expr: declaration.value.var, params: paramaters) + } else if declType == "Input" || declType == "Output" { + guard var port = ports["\(declaration.name)"] else { + throw "Unknown port \(declaration.name)" + } + if declaration.width != Python.None { + let msb = Port.evaluate(expr: declaration.width.msb, params: paramaters) + let lsb = Port.evaluate(expr: declaration.width.lsb, params: paramaters) + port.from = msb + port.to = lsb + } + if declType == "Input" { + port.polarity = .input + } else { + port.polarity = .output + } + ports["\(declaration.name)"] = port } - - return (ports: ports, inputs: inputs, outputs: outputs) + } } - private static func evaluate(expr: PythonObject, params: [String: Int]) -> Int { - let type = "\((Python.type(expr)).__name__)" - var value = 0 - switch type { - case "Minus", - "Plus", - "Sll": - let left = Port.evaluate(expr: expr.left, params: params) - let right = Port.evaluate(expr: expr.right, params: params) - value = Port.op[type]!(left, right) - case "IntConst": - value = Int("\(expr.value)")! - case "Identifier": - value = params["\(expr.name)"]! - default: - Stderr.print( - "Got unknown expression type \(type) while evaluating port expression \(expr)") - exit(EX_DATAERR) - } - return value + let inputs: [Port] = ports.values.filter { $0.polarity == .input }.sorted(by: { + $0.ordinal < $1.ordinal + }) + let outputs: [Port] = ports.values.filter { $0.polarity == .output }.sorted(by: { + $0.ordinal < $1.ordinal + }) + if ports.count != inputs.count + outputs.count { + throw RuntimeError( + "Some ports in \(definition.name) are not properly declared as an input or output.") } - static let op = [ - "Minus": sub, - "Plus": add, - "Sll": sll, - ] + return (ports: ports, inputs: inputs, outputs: outputs) + } + + private static func evaluate(expr: PythonObject, params: [String: Int]) -> Int { + let type = "\((Python.type(expr)).__name__)" + var value = 0 + switch type { + case "Minus", + "Plus", + "Sll": + let left = Port.evaluate(expr: expr.left, params: params) + let right = Port.evaluate(expr: expr.right, params: params) + value = Port.op[type]!(left, right) + case "IntConst": + value = Int("\(expr.value)")! + case "Identifier": + value = params["\(expr.name)"]! + default: + Stderr.print( + "Got unknown expression type \(type) while evaluating port expression \(expr)") + exit(EX_DATAERR) + } + return value + } + + static let op = [ + "Minus": sub, + "Plus": add, + "Sll": sll, + ] } extension Port: CustomStringConvertible { - var description: String { - var pfx = "" - if from != to || from != 0 { - pfx = "[\(from)..\(to)]" - } - return "Port@\(ordinal)(\(name)\(pfx): \(polarity ?? .unknown))" + var description: String { + var pfx = "" + if from != to || from != 0 { + pfx = "[\(from)..\(to)]" } + return "Port@\(ordinal)(\(name)\(pfx): \(polarity ?? .unknown))" + } } func add(left: Int, right: Int) -> Int { - left + right + left + right } func sub(left: Int, right: Int) -> Int { - left - right + left - right } func sll(left: Int, right: Int) -> Int { - left << right + left << right } struct Module { - var name: String - var inputs: [Port] - var outputs: [Port] - var definition: PythonObject - var ports: [Port] - var portsByName: [String: Port] - - init(name: String, inputs: [Port], outputs: [Port], definition: PythonObject) { - self.name = name - self.inputs = inputs - self.outputs = outputs - self.definition = definition - ports = inputs + outputs - portsByName = ports.reduce(into: [String: Port]()) { $0[$1.name] = $1 } - } - - static func getModules(in files: [String], filter filterOpt: Set? = nil) throws - -> OrderedDictionary - { - let parse = Python.import("pyverilog.vparser.parser").parse - var result: OrderedDictionary = [:] - - for file in files { - print("Processing file \(file)…") - let parseResult = parse([file]) - let ast = parseResult[0] - let description = ast[dynamicMember: "description"] - for definition in description.definitions { - let type = Python.type(definition).__name__ - if type != "ModuleDef" { - continue - } - let name = String(describing: definition.name) - print("Processing module \(name)…") - if let filter = filterOpt { - if !filter.contains(name) { - continue - } - } - let (_, inputs, outputs) = try Port.extract(from: definition) - result[name] = Module( - name: name, inputs: inputs, outputs: outputs, definition: definition - ) - } + var name: String + var inputs: [Port] + var outputs: [Port] + var definition: PythonObject + var ports: [Port] + var portsByName: [String: Port] + + init(name: String, inputs: [Port], outputs: [Port], definition: PythonObject) { + self.name = name + self.inputs = inputs + self.outputs = outputs + self.definition = definition + ports = inputs + outputs + portsByName = ports.reduce(into: [String: Port]()) { $0[$1.name] = $1 } + } + + static func getModules(in files: [String], filter filterOpt: Set? = nil) throws + -> OrderedDictionary + { + let parse = Python.import("pyverilog.vparser.parser").parse + var result: OrderedDictionary = [:] + + for file in files { + print("Processing file \(file)…") + let parseResult = parse([file]) + let ast = parseResult[0] + let description = ast[dynamicMember: "description"] + for definition in description.definitions { + let type = Python.type(definition).__name__ + if type != "ModuleDef" { + continue } - - return result + let name = String(describing: definition.name) + print("Processing module \(name)…") + if let filter = filterOpt { + if !filter.contains(name) { + continue + } + } + let (_, inputs, outputs) = try Port.extract(from: definition) + result[name] = Module( + name: name, inputs: inputs, outputs: outputs, definition: definition + ) + } } + + return result + } } extension Module: CustomStringConvertible { - var description: String { - "" - } + var description: String { + "" + } } diff --git a/Sources/Fault/Mux.swift b/Sources/Fault/Mux.swift index c4d8b9e..349fa56 100644 --- a/Sources/Fault/Mux.swift +++ b/Sources/Fault/Mux.swift @@ -16,64 +16,64 @@ import Foundation import PythonKit class MuxCreator { - var Node: PythonObject - var muxInfo: MuxInfo - init(using Node: PythonObject, muxInfo: MuxInfo) { - self.Node = Node - self.muxInfo = muxInfo - } + var Node: PythonObject + var muxInfo: MuxInfo + init(using Node: PythonObject, muxInfo: MuxInfo) { + self.Node = Node + self.muxInfo = muxInfo + } - func create( - for instance: String, - selection: PythonObject, - a: PythonObject, - b: PythonObject - ) -> ( - cellDeclarations: [PythonObject], wireDeclarations: [PythonObject], - replacementHook: PythonObject - ) { - let muxName = instance + "__scanchain_mux" - let outputWireName = "\(muxName)_\(muxInfo.y)" - let outputWireDecl = Node.Wire(outputWireName) - let outputWire = Node.Identifier(outputWireName) + func create( + for instance: String, + selection: PythonObject, + a: PythonObject, + b: PythonObject + ) -> ( + cellDeclarations: [PythonObject], wireDeclarations: [PythonObject], + replacementHook: PythonObject + ) { + let muxName = instance + "__scanchain_mux" + let outputWireName = "\(muxName)_\(muxInfo.y)" + let outputWireDecl = Node.Wire(outputWireName) + let outputWire = Node.Identifier(outputWireName) - // Cell - let portArguments = [ - Node.PortArg(muxInfo.a, a), - Node.PortArg(muxInfo.b, b), - Node.PortArg(muxInfo.s, selection), - Node.PortArg(muxInfo.y, outputWire), - ] - let instance = Node.Instance( - muxInfo.name, - muxName, - Python.tuple(portArguments), - Python.tuple() - ) - let instanceDecl = Node.InstanceList( - muxInfo.name, - Python.tuple(), - Python.tuple([instance]) - ) - let pragma = Node.Pragma( - Node.PragmaEntry( - "keep" - ) - ) + // Cell + let portArguments = [ + Node.PortArg(muxInfo.a, a), + Node.PortArg(muxInfo.b, b), + Node.PortArg(muxInfo.s, selection), + Node.PortArg(muxInfo.y, outputWire), + ] + let instance = Node.Instance( + muxInfo.name, + muxName, + Python.tuple(portArguments), + Python.tuple() + ) + let instanceDecl = Node.InstanceList( + muxInfo.name, + Python.tuple(), + Python.tuple([instance]) + ) + let pragma = Node.Pragma( + Node.PragmaEntry( + "keep" + ) + ) - // Hook - var hook = outputWire - if muxInfo.invertedOutput { - hook = Node.Unot(hook) - } - return ( - [ - pragma, - instanceDecl, - ], - [ - outputWireDecl, - ], hook - ) + // Hook + var hook = outputWire + if muxInfo.invertedOutput { + hook = Node.Unot(hook) } + return ( + [ + pragma, + instanceDecl, + ], + [ + outputWireDecl + ], hook + ) + } } diff --git a/Sources/Fault/RNG.swift b/Sources/Fault/RNG.swift index a6cd9c9..5f8f84a 100644 --- a/Sources/Fault/RNG.swift +++ b/Sources/Fault/RNG.swift @@ -17,9 +17,9 @@ //===----------------------------------------------------------------------===// #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - import Darwin + import Darwin #else - import Glibc + import Glibc #endif /// A type that provides seedable deterministic pseudo-random data. @@ -37,18 +37,18 @@ /// must form a deterministic sequence that depends only on the seed provided /// upon initialization. public protocol SeedableRandomNumberGenerator: RandomNumberGenerator { - init(seed: [UInt8]) - init(seed: T) + init(seed: [UInt8]) + init(seed: T) } -public extension SeedableRandomNumberGenerator { - init(seed: some BinaryInteger) { - var newSeed: [UInt8] = [] - for i in 0 ..< seed.bitWidth / UInt8.bitWidth { - newSeed.append(UInt8(truncatingIfNeeded: seed >> (UInt8.bitWidth * i))) - } - self.init(seed: newSeed) +extension SeedableRandomNumberGenerator { + public init(seed: some BinaryInteger) { + var newSeed: [UInt8] = [] + for i in 0..> (UInt8.bitWidth * i))) } + self.init(seed: newSeed) + } } /// An implementation of `SeedableRandomNumberGenerator` using ARC4. @@ -64,49 +64,49 @@ public extension SeedableRandomNumberGenerator { /// suitable for cryptographic applications. @frozen public struct ARC4RandomNumberGenerator: SeedableRandomNumberGenerator { - public static var global = ARC4RandomNumberGenerator(seed: UInt32(time(nil))) - var state: [UInt8] = Array(0 ... 255) - var iPos: UInt8 = 0 - var jPos: UInt8 = 0 + public static var global = ARC4RandomNumberGenerator(seed: UInt32(time(nil))) + var state: [UInt8] = Array(0...255) + var iPos: UInt8 = 0 + var jPos: UInt8 = 0 - /// Initialize ARC4RandomNumberGenerator using an array of UInt8. The array - /// must have length between 1 and 256 inclusive. - public init(seed: [UInt8]) { - precondition(seed.count > 0, "Length of seed must be positive") - precondition(seed.count <= 256, "Length of seed must be at most 256") - var j: UInt8 = 0 - for i: UInt8 in 0 ... 255 { - j &+= S(i) &+ seed[Int(i) % seed.count] - swapAt(i, j) - } + /// Initialize ARC4RandomNumberGenerator using an array of UInt8. The array + /// must have length between 1 and 256 inclusive. + public init(seed: [UInt8]) { + precondition(seed.count > 0, "Length of seed must be positive") + precondition(seed.count <= 256, "Length of seed must be at most 256") + var j: UInt8 = 0 + for i: UInt8 in 0...255 { + j &+= S(i) &+ seed[Int(i) % seed.count] + swapAt(i, j) } + } - // Produce the next random UInt64 from the stream, and advance the internal - // state. - public mutating func next() -> UInt64 { - var result: UInt64 = 0 - for _ in 0 ..< UInt64.bitWidth / UInt8.bitWidth { - result <<= UInt8.bitWidth - result += UInt64(nextByte()) - } - return result + // Produce the next random UInt64 from the stream, and advance the internal + // state. + public mutating func next() -> UInt64 { + var result: UInt64 = 0 + for _ in 0.. UInt8 { - state[Int(index)] - } + // Helper to access the state. + private func S(_ index: UInt8) -> UInt8 { + state[Int(index)] + } - // Helper to swap elements of the state. - private mutating func swapAt(_ i: UInt8, _ j: UInt8) { - state.swapAt(Int(i), Int(j)) - } + // Helper to swap elements of the state. + private mutating func swapAt(_ i: UInt8, _ j: UInt8) { + state.swapAt(Int(i), Int(j)) + } - // Generates the next byte in the keystream. - private mutating func nextByte() -> UInt8 { - iPos &+= 1 - jPos &+= S(iPos) - swapAt(iPos, jPos) - return S(S(iPos) &+ S(jPos)) - } + // Generates the next byte in the keystream. + private mutating func nextByte() -> UInt8 { + iPos &+= 1 + jPos &+= S(iPos) + swapAt(iPos, jPos) + return S(S(iPos) &+ S(jPos)) + } } diff --git a/Sources/Fault/SCLConfiguration.swift b/Sources/Fault/SCLConfiguration.swift index 8c471dc..e2379c0 100644 --- a/Sources/Fault/SCLConfiguration.swift +++ b/Sources/Fault/SCLConfiguration.swift @@ -14,58 +14,59 @@ import PythonKit class DFFMatch: Codable, CustomStringConvertible { - var name: String - var clk: String - var d: String - var q: String + var name: String + var clk: String + var d: String + var q: String - init(name: String, clk: String, d: String, q: String) { - self.name = name - self.clk = clk - self.d = d - self.q = q - } + init(name: String, clk: String, d: String, q: String) { + self.name = name + self.clk = clk + self.d = d + self.q = q + } - var description: String { - " \(q)>" - } + var description: String { + " \(q)>" + } } -func getMatchingDFFInfo(from list: [DFFMatch], for cell: String, fnmatch: PythonObject) -> DFFMatch? { - for dffinfo in list { - for name in dffinfo.name.components(separatedBy: ",") { - if Bool(fnmatch.fnmatch(cell, name))! { - return dffinfo - } - } +func getMatchingDFFInfo(from list: [DFFMatch], for cell: String, fnmatch: PythonObject) -> DFFMatch? +{ + for dffinfo in list { + for name in dffinfo.name.components(separatedBy: ",") { + if Bool(fnmatch.fnmatch(cell, name))! { + return dffinfo + } } - return nil + } + return nil } class MuxInfo: Codable { - var name: String - var a: String - var b: String - var y: String - var s: String - var invertedOutput: Bool = false + var name: String + var a: String + var b: String + var y: String + var s: String + var invertedOutput: Bool = false - init(name: String, a: String, b: String, y: String, s: String, invertedOutput: Bool = false) { - self.name = name - self.a = a - self.b = b - self.y = y - self.s = s - self.invertedOutput = invertedOutput - } + init(name: String, a: String, b: String, y: String, s: String, invertedOutput: Bool = false) { + self.name = name + self.a = a + self.b = b + self.y = y + self.s = s + self.invertedOutput = invertedOutput + } } class SCLConfiguration: Codable { - var dffMatches: [DFFMatch] - var muxInfo: MuxInfo? + var dffMatches: [DFFMatch] + var muxInfo: MuxInfo? - init(dffMatches: [DFFMatch], muxInfo: MuxInfo? = nil) { - self.dffMatches = dffMatches - self.muxInfo = muxInfo - } + init(dffMatches: [DFFMatch], muxInfo: MuxInfo? = nil) { + self.dffMatches = dffMatches + self.muxInfo = muxInfo + } } diff --git a/Sources/Fault/SerialVectorFormat.swift b/Sources/Fault/SerialVectorFormat.swift index d2509fd..71a38f2 100644 --- a/Sources/Fault/SerialVectorFormat.swift +++ b/Sources/Fault/SerialVectorFormat.swift @@ -15,64 +15,64 @@ import BigInt enum SerialVectorCreator { - static func create( - tvInfo: TVInfo - ) throws -> String { - var scanStatements = "" + static func create( + tvInfo: TVInfo + ) throws -> String { + var scanStatements = "" - let chainLength: Int = { - var count = 0 - for input in tvInfo.inputs { - count += input.width - } - return count - }() + let chainLength: Int = { + var count = 0 + for input in tvInfo.inputs { + count += input.width + } + return count + }() - for tvcPair in tvInfo.coverageList { - var tdi = "" - let testVector = tvcPair.vector - for (index, port) in testVector.enumerated() { - let portVector = String(port, radix: 2) - let offset = tvInfo.inputs[index].width - portVector.count - tdi = String(repeating: "0", count: offset) + portVector + tdi - } + for tvcPair in tvInfo.coverageList { + var tdi = "" + let testVector = tvcPair.vector + for (index, port) in testVector.enumerated() { + let portVector = String(port, radix: 2) + let offset = tvInfo.inputs[index].width - portVector.count + tdi = String(repeating: "0", count: offset) + portVector + tdi + } - if let tdiInt = BigUInt(tdi, radix: 2) { - let tdiHex = String(tdiInt, radix: 16) + if let tdiInt = BigUInt(tdi, radix: 2) { + let tdiHex = String(tdiInt, radix: 16) - let mask = String(repeating: "f", count: tdiHex.count) - if let output = BigUInt(tvcPair.goldenOutput, radix: 16) { - let hexOutput = String(output, radix: 16) - scanStatements += - "SDR \(chainLength) TDI (\(tdiHex)) MASK (\(mask)) TDO (\(hexOutput)); \n" - } else { - print("TV golden output \(tvcPair.goldenOutput) is invalid.") - } - } else { - print("TV \(tvcPair.vector) is invalid.") - } + let mask = String(repeating: "f", count: tdiHex.count) + if let output = BigUInt(tvcPair.goldenOutput, radix: 16) { + let hexOutput = String(output, radix: 16) + scanStatements += + "SDR \(chainLength) TDI (\(tdiHex)) MASK (\(mask)) TDO (\(hexOutput)); \n" + } else { + print("TV golden output \(tvcPair.goldenOutput) is invalid.") } + } else { + print("TV \(tvcPair.vector) is invalid.") + } + } - var svf: String { - """ - ! Begin Test Program - ! Disable Test Reset line - TRST OFF; - ! Initialize UUT - STATE RESET; - ! End IR scans in DRPAUSE - ENDIR DRPAUSE; - ! Trailer & Headers for IR & DR - HIR 0; - TIR 0; - HDR 0; - TDR 0; - ! INTEST Instruction - SIR 4 TDI (4); - ! San Test Vectors through the scan chain with length: \(chainLength) - \(scanStatements) - """ - } - return svf + var svf: String { + """ + ! Begin Test Program + ! Disable Test Reset line + TRST OFF; + ! Initialize UUT + STATE RESET; + ! End IR scans in DRPAUSE + ENDIR DRPAUSE; + ! Trailer & Headers for IR & DR + HIR 0; + TIR 0; + HDR 0; + TDR 0; + ! INTEST Instruction + SIR 4 TDI (4); + ! San Test Vectors through the scan chain with length: \(chainLength) + \(scanStatements) + """ } + return svf + } } diff --git a/Sources/Fault/Simulation.swift b/Sources/Fault/Simulation.swift index 426d18f..667f6f2 100644 --- a/Sources/Fault/Simulation.swift +++ b/Sources/Fault/Simulation.swift @@ -19,960 +19,960 @@ import Foundation import PythonKit struct CoverageMeta: Codable { - let ratio: Float - let faultPoints: [String] - let sa0Covered: [String] - let sa1Covered: [String] - let sa0Uncovered: [String] - let sa1Uncovered: [String] + let ratio: Float + let faultPoints: [String] + let sa0Covered: [String] + let sa1Covered: [String] + let sa0Uncovered: [String] + let sa1Uncovered: [String] } enum Simulator { - enum Behavior: Int { - case holdHigh = 1 - case holdLow = 0 - } - - static func pseudoRandomVerilogGeneration( - using testVector: TestVector, - for faultPoints: Set, - in file: String, - module: String, - with models: [String], - ports: [String: Port], - inputs: [Port], - bypassingWithBehavior: OrderedDictionary, - outputs: [Port], - stuckAt: Int, - cleanUp: Bool, - goldenOutput: Bool, - clock: String?, - filePrefix: String = ".", - defines: Set = [], - using _: String, - with _: String - ) throws -> (faults: [String], goldenOutput: String) { - var portWires = "" - var portHooks = "" - var portHooksGM = "" - for (rawName, port) in ports { - let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += - " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" - portWires += - " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name).gm ;\n" - portHooks += ".\(name) ( \(name) ) , " - portHooksGM += ".\(name) ( \(name).gm ) , " - } + enum Behavior: Int { + case holdHigh = 1 + case holdLow = 0 + } + + static func pseudoRandomVerilogGeneration( + using testVector: TestVector, + for faultPoints: Set, + in file: String, + module: String, + with models: [String], + ports: [String: Port], + inputs: [Port], + bypassingWithBehavior: OrderedDictionary, + outputs: [Port], + stuckAt: Int, + cleanUp: Bool, + goldenOutput: Bool, + clock: String?, + filePrefix: String = ".", + defines: Set = [], + using _: String, + with _: String + ) throws -> (faults: [String], goldenOutput: String) { + var portWires = "" + var portHooks = "" + var portHooksGM = "" + for (rawName, port) in ports { + let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name).gm ;\n" + portHooks += ".\(name) ( \(name) ) , " + portHooksGM += ".\(name) ( \(name).gm ) , " + } - let folderName = "\(filePrefix)/thr\(Unmanaged.passUnretained(Thread.current).toOpaque())" - try FileManager.default.createDirectory( - atPath: folderName, withIntermediateDirectories: true, attributes: nil - ) + let folderName = "\(filePrefix)/thr\(Unmanaged.passUnretained(Thread.current).toOpaque())" + try FileManager.default.createDirectory( + atPath: folderName, withIntermediateDirectories: true, attributes: nil + ) - var inputAssignment = "" - var fmtString = "" - var inputList = "" + var inputAssignment = "" + var fmtString = "" + var inputList = "" - for (i, input) in inputs.enumerated() { - let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" + for (i, input) in inputs.enumerated() { + let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" - inputAssignment += " \(name) = \(testVector[i]) ;\n" - inputAssignment += " \(name).gm = \(name) ;\n" + inputAssignment += " \(name) = \(testVector[i]) ;\n" + inputAssignment += " \(name).gm = \(name) ;\n" - fmtString += "%d " - inputList += "\(name) , " - } + fmtString += "%d " + inputList += "\(name) , " + } - for (rawName, behavior) in bypassingWithBehavior { - guard ports[rawName] != nil else { - continue // black-box module ignore probably - } + for (rawName, behavior) in bypassingWithBehavior { + guard ports[rawName] != nil else { + continue // black-box module ignore probably + } - let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" + let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - inputAssignment += " \(name) = \(behavior.rawValue) ;\n" - inputAssignment += " \(name).gm = \(behavior.rawValue) ;\n" - } + inputAssignment += " \(name) = \(behavior.rawValue) ;\n" + inputAssignment += " \(name).gm = \(behavior.rawValue) ;\n" + } - fmtString = String(fmtString.dropLast(1)) - inputList = String(inputList.dropLast(2)) + fmtString = String(fmtString.dropLast(1)) + inputList = String(inputList.dropLast(2)) - var defineStatements = "" - for def in defines { - defineStatements += "-D\(def) " - } + var defineStatements = "" + for def in defines { + defineStatements += "-D\(def) " + } - var outputCount = 0 - var outputComparison = "" - var outputAssignment = "" - for output in outputs { - let name = (output.name.hasPrefix("\\")) ? output.name : "\\\(output.name)" - outputComparison += " ( \(name) != \(name).gm ) || " - if output.width > 1 { - for i in 0 ..< output.width { - outputAssignment += - " assign goldenOutput[\(outputCount)] = gm.\(output.name) [\(i)] ; \n" - outputCount += 1 - } - } else { - outputAssignment += " assign goldenOutput[\(outputCount)] = gm.\(name) ; \n" - outputCount += 1 - } - } - outputComparison = String(outputComparison.dropLast(3)) - - var faultForces = "" - for fault in faultPoints { - faultForces += " force uut.\(fault) = \(stuckAt) ; \n" - faultForces += " #1 ; \n" - faultForces += " if (difference) $display(\"\(fault)\") ; \n" - faultForces += " #1 ; \n" - faultForces += " release uut.\(fault) ;\n" - faultForces += " #1 ; \n" - } + var outputCount = 0 + var outputComparison = "" + var outputAssignment = "" + for output in outputs { + let name = (output.name.hasPrefix("\\")) ? output.name : "\\\(output.name)" + outputComparison += " ( \(name) != \(name).gm ) || " + if output.width > 1 { + for i in 0..&1 > /dev/null" - .sh() - if iverilogResult != EX_OK { - exit(Int32(iverilogResult)) - } + integer counter; - let vvpTask = "'\(vvpExecutable)' \(aoutName) > \(intermediate)".sh() - if vvpTask != EX_OK { - exit(vvpTask) - } + initial begin + \(inputAssignment) + \(faultForces) + \(goldenOutput ? " $displayb(\"%b\", goldenOutput);" : "") + $finish; + end - let output = File.read(intermediate)! - defer { - if cleanUp { - try? FileManager.default.removeItem(atPath: folderName) - } else { - print("Find testbenches at : \(folderName)") - } - } - var faults = output.components(separatedBy: "\n").filter { - !$0.trimmingCharacters(in: .whitespaces).isEmpty && !$0.contains("$finish") - } - var gmOutput = "" - if goldenOutput { - let last = faults.removeLast() - if let bin = BigUInt(last, radix: 2) { - gmOutput = String(bin, radix: 16) - } else { - print("[Warning]: Golden output was invalid: '\(last)'.") - } - } - - return (faults: faults, goldenOutput: gmOutput) - } - - static func simulate( - for faultPoints: Set, - in file: String, - module: String, - with models: [String], - ports: [String: Port], - inputs: [Port], - bypassingWithBehavior: OrderedDictionary, - outputs: [Port], - initialVectorCount: Int, - incrementingBy increment: Int, - minimumCoverage: Float, - ceiling: Int, - tvGenerator: TVGenerator.Type, - rngSeed: UInt, - initialTVInfo: TVInfo? = nil, - externalTestVectors: [TestVector], - sampleRun: Bool, - clock: String?, - defines: Set = [], - using iverilogExecutable: String, - with vvpExecutable: String - ) throws -> (coverageList: [TVCPair], coverageMeta: CoverageMeta) { - var testVectorHash: Set = [] - var coverageList: [TVCPair] = [] - var sa0Covered: Set = [] - sa0Covered.reserveCapacity(faultPoints.count) - var sa1Covered: Set = [] - sa1Covered.reserveCapacity(faultPoints.count) - - if let tvInfo = initialTVInfo { - coverageList = tvInfo.coverageList - for tvcPair in coverageList { - testVectorHash.insert(tvcPair.vector) - sa0Covered.formUnion(tvcPair.coverage.sa0) - sa1Covered.formUnion(tvcPair.coverage.sa1) - } - } - - var coverage = - Float(sa0Covered.count + sa1Covered.count) / Float(2 * faultPoints.count) - print("Initial coverage: \(coverage * 100)%") - - var totalTVAttempts = 0 - var tvAttempts = min(initialVectorCount, ceiling, sampleRun ? 1 : Int.max) - - let simulateOnly = (externalTestVectors.count != 0) - let totalBitWidth = inputs.reduce(0) { $0 + $1.width } - var rng: TVGenerator = tvGenerator.init(allBits: totalBitWidth, seed: rngSeed) - - while coverage < minimumCoverage, totalTVAttempts < ceiling { - if totalTVAttempts > 0 { - if sampleRun { - break - } - print( - "Minimum coverage not met (\(coverage * 100)%/\(minimumCoverage * 100)%,) incrementing to \(totalTVAttempts + tvAttempts)…" - ) - } - - var futureList: [Future] = [] - var testVectors: [TestVector] = [] - for index in 0 ..< tvAttempts { - let overallIndex = totalTVAttempts + index - rng.generate(count: overallIndex) - // print(rng.current.pad(digits: totalBitWidth, radix: 2)) - var testVector: TestVector = [] - if simulateOnly { - testVector = externalTestVectors[overallIndex] - } else { - var bitsSoFar = 0 - for input in inputs { - let value = rng.get(bits: input.width) - bitsSoFar += input.width - testVector.append(value) - } - } - if testVectorHash.contains(testVector) { - continue - } - testVectorHash.insert(testVector) - testVectors.append(testVector) - } - - if testVectors.count < tvAttempts { - print("Skipped \(tvAttempts - testVectors.count) duplicate generated test vectors.") - } - let tempDir = "." - - for vector in testVectors { - let future = Future { - do { - let (sa0, output) = - try Simulator.pseudoRandomVerilogGeneration( - using: vector, - for: faultPoints, - in: file, - module: module, - with: models, - ports: ports, - inputs: inputs, - bypassingWithBehavior: bypassingWithBehavior, - outputs: outputs, - stuckAt: 0, - cleanUp: !sampleRun, - goldenOutput: true, - clock: clock, - filePrefix: tempDir, - defines: defines, - using: iverilogExecutable, - with: vvpExecutable - ) - - let (sa1, _) = - try Simulator.pseudoRandomVerilogGeneration( - using: vector, - for: faultPoints, - in: file, - module: module, - with: models, - ports: ports, - inputs: inputs, - bypassingWithBehavior: bypassingWithBehavior, - outputs: outputs, - stuckAt: 1, - cleanUp: !sampleRun, - goldenOutput: false, - clock: clock, - filePrefix: tempDir, - defines: defines, - using: iverilogExecutable, - with: vvpExecutable - ) - - return (Covers: Coverage(sa0: sa0, sa1: sa1), Output: output) - } catch { - print("IO Error @ vector \(vector)") - return (Covers: Coverage(sa0: [], sa1: []), Output: "") - } - } - futureList.append(future) - } - - for (i, future) in futureList.enumerated() { - let (coverLists, output) = future.value as! (Coverage, String) - for cover in coverLists.sa0 { - sa0Covered.insert(cover) - } - for cover in coverLists.sa1 { - sa1Covered.insert(cover) - } - coverageList.append( - TVCPair( - vector: testVectors[i], - coverage: coverLists, - goldenOutput: output - ) - ) - } - - coverage = - Float(sa0Covered.count + sa1Covered.count) / Float(2 * faultPoints.count) - - totalTVAttempts += tvAttempts - let remainingTV = ceiling - totalTVAttempts - tvAttempts = (remainingTV < increment) ? remainingTV : increment - } + endmodule + """ - if coverage < minimumCoverage { - print("Hit ceiling. Settling for current coverage.") - } - - return ( - coverageList: coverageList, - coverageMeta: CoverageMeta( - ratio: coverage, - faultPoints: [String](faultPoints), - sa0Covered: sa0Covered.sorted(), - sa1Covered: sa1Covered.sorted(), - sa0Uncovered: faultPoints.filter { !sa0Covered.contains($0) }, - sa1Uncovered: faultPoints.filter { !sa0Covered.contains($0) } - ) - ) + let tbName = "\(folderName)/tb.sv" + try File.open(tbName, mode: .write) { + try $0.print(bench) } - enum Active { - case low - case high - } - - static func simulate( - verifying module: String, - in file: String, - with models: [String], - ports: [String: Port], - inputs: [Port], - outputs _: [Port], - chainLength: Int, - clock: String, - tck: String, - reset: String, - sin: String, - sout: String, - resetActive: Active = .low, - shift: String, - test: String, - output: String, - defines: Set = [], - using _: String, - with _: String - ) throws -> Bool { - var portWires = "" - var portHooks = "" - for (rawName, port) in ports { - let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += - " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" - portHooks += ".\(name) ( \(name) ) , " - } - - var inputAssignment = "" - for input in inputs { - let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" - if input.name == reset { - inputAssignment += " \(name) = \(resetActive == .low ? 0 : 1) ;\n" - } else { - inputAssignment += " \(name) = 0 ;\n" - } - } - - var serial = "0" - for _ in 0 ..< chainLength - 1 { - serial += "\(Int.random(in: 0 ... 1))" - } - - var clockCreator = "" - if !clock.isEmpty { - clockCreator = " always #(`CLOCK_PERIOD / 2) \(clock) = ~\(clock); \n" - clockCreator += " always #(`CLOCK_PERIOD / 2) \(tck) = ~\(tck); \n" - } - - var includes = "" - for model in models { - includes += "`include \"\(model)\"\n" - } - includes += "`include \"\(file)\"\n" - - var defineStatements = "" - for def in defines { - defineStatements += "-D\(def) " - } - - let bench = """ - \(String.boilerplate) - \(includes) - - `ifndef CLOCK_PERIOD - `define CLOCK_PERIOD 4 - `endif - - module testbench; - \(portWires) - \(clockCreator) - \(module) uut( - \(portHooks.dropLast(2)) - ); - - wire[\(chainLength - 1):0] __serializable__ = - \(chainLength)'b\(serial); - reg[\(chainLength - 1):0] __serial__; - integer i; - initial begin - `ifdef VCD - $dumpfile("chain.vcd"); - $dumpvars(0, testbench); - `endif - \(inputAssignment) - #(`CLOCK_PERIOD*5); - \(reset) = ~\(reset); - \(shift) = 1; - \(test) = 1; - for (i = 0; i < \(chainLength); i = i + 1) begin - \(sin) = __serializable__[i]; - #(`CLOCK_PERIOD); - end - for (i = 0; i < \(chainLength); i = i + 1) begin - __serial__[i] = \(sout); - #(`CLOCK_PERIOD); - end - if (__serializable__ === __serial__) begin - $display("Success: expected %b got %b", __serializable__, __serial__); - $display("SUCCESS_STRING"); - end else begin - $display("Failed: expected %b got %b", __serializable__, __serial__); - end - $finish; - end - endmodule - """ - - return try Simulator.run( - define: defineStatements, - bench: bench, - output: output - ) + let aoutName = "\(folderName)/a.out" + let intermediate = "\(folderName)/intermediate" + let env = ProcessInfo.processInfo.environment + let iverilogExecutable = env["FAULT_IVERILOG"] ?? "iverilog" + let vvpExecutable = env["FAULT_VVP"] ?? "vvp" + + let iverilogResult = + "'\(iverilogExecutable)' -B '\(iverilogBase)' -Ttyp \(defineStatements) -o \(aoutName) \(tbName) 2>&1 > /dev/null" + .sh() + if iverilogResult != EX_OK { + exit(Int32(iverilogResult)) } - static func simulate( - verifying module: String, - in file: String, - with models: [String], - ports: [String: Port], - inputs: [Port], - outputs _: [Port], - chainLength: Int, - clock: String, - reset: String, - resetActive: Active = .low, - tms: String, - tdi: String, - tck: String, - tdo: String, - trst: String, - output: String, - defines: Set = [], - using _: String, - with _: String - ) throws -> Bool { - var portWires = "" - var portHooks = "" - for (rawName, port) in ports { - let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += - " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" - portHooks += ".\(name) ( \(name) ) , " - } + let vvpTask = "'\(vvpExecutable)' \(aoutName) > \(intermediate)".sh() + if vvpTask != EX_OK { + exit(vvpTask) + } - var inputInit = "" - for input in inputs { - let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" - if input.name == reset { - inputInit += " \(name) = \(resetActive == .low ? 0 : 1) ;\n" - } else { - inputInit += " \(name) = 0 ;\n" - } - } + let output = File.read(intermediate)! + defer { + if cleanUp { + try? FileManager.default.removeItem(atPath: folderName) + } else { + print("Find testbenches at : \(folderName)") + } + } + var faults = output.components(separatedBy: "\n").filter { + !$0.trimmingCharacters(in: .whitespaces).isEmpty && !$0.contains("$finish") + } + var gmOutput = "" + if goldenOutput { + let last = faults.removeLast() + if let bin = BigUInt(last, radix: 2) { + gmOutput = String(bin, radix: 16) + } else { + print("[Warning]: Golden output was invalid: '\(last)'.") + } + } - var clockCreator = "" - if !clock.isEmpty { - clockCreator = "always #(`CLOCK_PERIOD / 2) \(clock) = ~\(clock);" - } + return (faults: faults, goldenOutput: gmOutput) + } + + static func simulate( + for faultPoints: Set, + in file: String, + module: String, + with models: [String], + ports: [String: Port], + inputs: [Port], + bypassingWithBehavior: OrderedDictionary, + outputs: [Port], + initialVectorCount: Int, + incrementingBy increment: Int, + minimumCoverage: Float, + ceiling: Int, + tvGenerator: TVGenerator.Type, + rngSeed: UInt, + initialTVInfo: TVInfo? = nil, + externalTestVectors: [TestVector], + sampleRun: Bool, + clock: String?, + defines: Set = [], + using iverilogExecutable: String, + with vvpExecutable: String + ) throws -> (coverageList: [TVCPair], coverageMeta: CoverageMeta) { + var testVectorHash: Set = [] + var coverageList: [TVCPair] = [] + var sa0Covered: Set = [] + sa0Covered.reserveCapacity(faultPoints.count) + var sa1Covered: Set = [] + sa1Covered.reserveCapacity(faultPoints.count) + + if let tvInfo = initialTVInfo { + coverageList = tvInfo.coverageList + for tvcPair in coverageList { + testVectorHash.insert(tvcPair.vector) + sa0Covered.formUnion(tvcPair.coverage.sa0) + sa1Covered.formUnion(tvcPair.coverage.sa1) + } + } - var resetToggler = "" - if !reset.isEmpty { - resetToggler = "\(reset) = ~\(reset);" - } + var coverage = + Float(sa0Covered.count + sa1Covered.count) / Float(2 * faultPoints.count) + print("Initial coverage: \(coverage * 100)%") - var serial = "" - for _ in 0 ..< chainLength { - serial += "\(Int.random(in: 0 ... 1))" - } + var totalTVAttempts = 0 + var tvAttempts = min(initialVectorCount, ceiling, sampleRun ? 1 : Int.max) - var includes = "" - for model in models { - includes += "`include \"\(model)\"\n" - } - includes += "`include \"\(file)\"\n" + let simulateOnly = (externalTestVectors.count != 0) + let totalBitWidth = inputs.reduce(0) { $0 + $1.width } + var rng: TVGenerator = tvGenerator.init(allBits: totalBitWidth, seed: rngSeed) - var defineStatements = "" - for def in defines { - defineStatements += "-D\(def) " + while coverage < minimumCoverage, totalTVAttempts < ceiling { + if totalTVAttempts > 0 { + if sampleRun { + break } - - let bench = """ - \(String.boilerplate) - - \(includes) - - `ifndef CLOCK_PERIOD - `define CLOCK_PERIOD 4 - `endif - - module testbench; - \(portWires) - - \(clockCreator) - always #(`CLOCK_PERIOD / 2) \(tck) = ~\(tck); - - \(module) uut( - \(portHooks.dropLast(2)) - ); - - integer i; - - wire[\(chainLength - 1):0] __serializable__ = - \(chainLength)'b\(serial); - reg[\(chainLength - 1):0] __serial__; - - wire[7:0] __tmsPattern__ = 8'b 01100110; - wire[3:0] __preload_chain__ = 4'b0011; - - wire __tdo_pad_out__ = tdo_paden_o ? 1'bz : \(tdo); - - initial begin - `ifdef VCD - $dumpfile("dut.vcd"); - $dumpvars(0, testbench); - `endif - \(inputInit) - \(tms) = 1; - #(`CLOCK_PERIOD) ; - \(resetToggler) - \(trst) = 1; - #(`CLOCK_PERIOD) ; - - /* - Test PreloadChain Instruction - */ - shiftIR(__preload_chain__); - enterShiftDR(); - - for (i = 0; i < \(chainLength); i = i + 1) begin - \(tdi) = __serializable__[i]; - #(`CLOCK_PERIOD) ; - end - for(i = 0; i< \(chainLength); i = i + 1) begin - __serial__[i] = __tdo_pad_out__; - #(`CLOCK_PERIOD) ; - end - - if(__serial__ !== __serializable__) begin - $error("EXECUTING_PRELOAD_CHAIN_INST_FAILED"); - $finish; - end - exitDR(); - - $display("SUCCESS_STRING"); - $finish; - end - - \(Simulator.createTasks(tms: tms, tdi: tdi)) - endmodule - """ - - return try Simulator.run( - define: defineStatements, - bench: bench, - output: output + print( + "Minimum coverage not met (\(coverage * 100)%/\(minimumCoverage * 100)%,) incrementing to \(totalTVAttempts + tvAttempts)…" ) - } - - static func simulate( - verifying module: String, - in file: String, - with models: [String], - ports: [String: Port], - inputs: [Port], - bypassingWithBehavior: OrderedDictionary, - outputs _: [Port], - clock: String, - reset: String, - resetActive: Active = .low, - tms: String, - tdi: String, - tck: String, - tdo: String, - trst: String, - output: String, - chainLength _: Int, - vecbinFile: String, - outbinFile: String, - vectorCount: Int, - vectorLength: Int, - outputLength: Int, - defines: Set = [], - using _: String, - with _: String - ) throws -> Bool { - var portWires = "" - var portHooks = "" - for (rawName, port) in ports { - let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" - portWires += - " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" - portHooks += ".\(name) ( \(name) ) , " - } + } + + var futureList: [Future] = [] + var testVectors: [TestVector] = [] + for index in 0.. = [], + using _: String, + with _: String + ) throws -> Bool { + var portWires = "" + var portHooks = "" + for (rawName, port) in ports { + let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portHooks += ".\(name) ( \(name) ) , " + } - let bench = """ + var inputAssignment = "" + for input in inputs { + let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" + if input.name == reset { + inputAssignment += " \(name) = \(resetActive == .low ? 0 : 1) ;\n" + } else { + inputAssignment += " \(name) = 0 ;\n" + } + } - \(String.boilerplate) - \(includes) + var serial = "0" + for _ in 0.. = [], + using _: String, + with _: String + ) throws -> Bool { + var portWires = "" + var portHooks = "" + for (rawName, port) in ports { + let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portHooks += ".\(name) ( \(name) ) , " + } - integer i, __all_errors__, __error__; + var inputInit = "" + for input in inputs { + let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" + if input.name == reset { + inputInit += " \(name) = \(resetActive == .low ? 0 : 1) ;\n" + } else { + inputInit += " \(name) = 0 ;\n" + } + } - reg [\(outputLength - 1):0] __scanInSerial__; - reg [\(vectorLength - 1):0] __vectors__ [0:\(vectorCount - 1)]; - reg [\(outputLength - 1):0] __gmOutput__ [0:\(vectorCount - 1)]; + var clockCreator = "" + if !clock.isEmpty { + clockCreator = "always #(`CLOCK_PERIOD / 2) \(clock) = ~\(clock);" + } - wire[7:0] __tmsPattern__ = 8'b 01100110; - wire[3:0] __preloadChain__ = 4'b 0011; + var resetToggler = "" + if !reset.isEmpty { + resetToggler = "\(reset) = ~\(reset);" + } - wire __tdo_pad_out__ = tdo_paden_o ? 1'bz : \(tdo); + var serial = "" + for _ in 0.. 0) begin - $error("SIMULATING_TV_FAILED"); - $finish; - end else begin - $display("SUCCESS_STRING"); - $finish; - end - end + var includes = "" + for model in models { + includes += "`include \"\(model)\"\n" + } + includes += "`include \"\(file)\"\n" - task test; - input [31:0] __ordinal__; // integer - input [\(vectorLength - 1):0] __vector__; - input [\(outputLength - 1):0] __goldenOutput__; - begin - $display("Testing vector %0d...", __ordinal__); - // Preload Scan-Chain with TV - - shiftIR(__preloadChain__); - enterShiftDR(); - - for (i = 0; i < \(vectorLength); i = i + 1) begin - \(tdi) = __vector__[i]; - if (i == \(vectorLength - 3)) begin - \(tms) = 1; // Exit-DR - end - if (i == \(vectorLength - 2)) begin - \(tms) = 0; // Pause-DR - end - if (i == \(vectorLength - 1)) begin - \(tms) = 1; // Exit2-DR - end - #(`CLOCK_PERIOD) ; - end + var defineStatements = "" + for def in defines { + defineStatements += "-D\(def) " + } - \(tms) = 0; // Shift-DR - #(`CLOCK_PERIOD) ; - // Shift-out response - __error__ = 0; - for (i = 0; i< \(outputLength);i = i + 1) begin - \(tdi) = 0; - __scanInSerial__[i] = __tdo_pad_out__; - if (__scanInSerial__[i] !=? __goldenOutput__[i]) begin - $display("@%0d:\\t\\tExpected %0b, Got %0b", i, __goldenOutput__[i], __scanInSerial__[i]); - __error__ = __error__ + 1; - end - if(i == \(outputLength - 1)) begin - \(tms) = 1; // Exit-DR - end - #(`CLOCK_PERIOD) ; - end - __all_errors__ += __error__; - \(tms) = 1; // update-DR - #(`CLOCK_PERIOD) ; - \(tms) = 0; // run-test-idle - #(`CLOCK_PERIOD) ; + let bench = """ + \(String.boilerplate) + + \(includes) + + `ifndef CLOCK_PERIOD + `define CLOCK_PERIOD 4 + `endif + + module testbench; + \(portWires) + + \(clockCreator) + always #(`CLOCK_PERIOD / 2) \(tck) = ~\(tck); + + \(module) uut( + \(portHooks.dropLast(2)) + ); + + integer i; + + wire[\(chainLength - 1):0] __serializable__ = + \(chainLength)'b\(serial); + reg[\(chainLength - 1):0] __serial__; + + wire[7:0] __tmsPattern__ = 8'b 01100110; + wire[3:0] __preload_chain__ = 4'b0011; + + wire __tdo_pad_out__ = tdo_paden_o ? 1'bz : \(tdo); + + initial begin + `ifdef VCD + $dumpfile("dut.vcd"); + $dumpvars(0, testbench); + `endif + \(inputInit) + \(tms) = 1; + #(`CLOCK_PERIOD) ; + \(resetToggler) + \(trst) = 1; + #(`CLOCK_PERIOD) ; + + /* + Test PreloadChain Instruction + */ + shiftIR(__preload_chain__); + enterShiftDR(); + + for (i = 0; i < \(chainLength); i = i + 1) begin + \(tdi) = __serializable__[i]; + #(`CLOCK_PERIOD) ; + end + for(i = 0; i< \(chainLength); i = i + 1) begin + __serial__[i] = __tdo_pad_out__; + #(`CLOCK_PERIOD) ; + end + + if(__serial__ !== __serializable__) begin + $error("EXECUTING_PRELOAD_CHAIN_INST_FAILED"); + $finish; + end + exitDR(); + + $display("SUCCESS_STRING"); + $finish; + end + + \(Simulator.createTasks(tms: tms, tdi: tdi)) + endmodule + """ + + return try Simulator.run( + define: defineStatements, + bench: bench, + output: output + ) + } + + static func simulate( + verifying module: String, + in file: String, + with models: [String], + ports: [String: Port], + inputs: [Port], + bypassingWithBehavior: OrderedDictionary, + outputs _: [Port], + clock: String, + reset: String, + resetActive: Active = .low, + tms: String, + tdi: String, + tck: String, + tdo: String, + trst: String, + output: String, + chainLength _: Int, + vecbinFile: String, + outbinFile: String, + vectorCount: Int, + vectorLength: Int, + outputLength: Int, + defines: Set = [], + using _: String, + with _: String + ) throws -> Bool { + var portWires = "" + var portHooks = "" + for (rawName, port) in ports { + let name = (rawName.hasPrefix("\\")) ? rawName : "\\\(rawName)" + portWires += + " \(port.polarity == .input ? "reg" : "wire")[\(port.from):\(port.to)] \(name) ;\n" + portHooks += ".\(name) ( \(name) ) , " + } - if(__scanInSerial__ !=? __goldenOutput__) begin - $display("Test vector simulation failed: %0d bits mismatched", __error__); - $display("Expected:\\t%0b", __goldenOutput__); - $display("Got:\\t\\t%0b", __scanInSerial__); - end else begin - $display("Passed vector %0d.", __ordinal__); - end - end - endtask + var inputAssignment = "" + 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.rawValue) ;\n" + } - \(Simulator.createTasks(tms: tms, tdi: tdi)) - endmodule - """ + let tapPorts = [tck, trst, tdi] + for input in inputs { + let name = (input.name.hasPrefix("\\")) ? input.name : "\\\(input.name)" + if input.name == reset { + inputAssignment += " \(name) = \(resetActive == .low ? 0 : 1) ;\n" + } else if input.name == tms { + inputAssignment += " \(name) = 1 ;\n" + } else { + inputAssignment += " \(name) = 0 ;\n" + if input.name != clock, !tapPorts.contains(input.name) {} + } + } - return try Simulator.run( - define: defineStatements, - bench: bench, - output: output - ) + var clockCreator = "" + if !clock.isEmpty { + clockCreator = "always #(`CLOCK_PERIOD / 2) \(clock) = ~\(clock);" + } + var resetToggler = "" + if !reset.isEmpty { + resetToggler = "\(reset) = ~\(reset);" + } + var testStatements = "" + for i in 0.. Bool { - try File.open(output, mode: .write) { - try $0.print(bench) - } + var defineStatements = "" + for def in defines { + defineStatements += "-D\(def) " + } - let outputLog = "\(output).log" - print("Starting simulation steps (log: \(outputLog))…") + let bench = """ + + \(String.boilerplate) + \(includes) + + `ifndef CLOCK_PERIOD + `define CLOCK_PERIOD 4 + `endif + + module testbench; + \(portWires) + + \(clockCreator) + always #(`CLOCK_PERIOD / 2) \(tck) = ~\(tck); + + \(module) uut( + \(portHooks.dropLast(2)) + ); + + integer i, __all_errors__, __error__; + + reg [\(outputLength - 1):0] __scanInSerial__; + reg [\(vectorLength - 1):0] __vectors__ [0:\(vectorCount - 1)]; + reg [\(outputLength - 1):0] __gmOutput__ [0:\(vectorCount - 1)]; + + wire[7:0] __tmsPattern__ = 8'b 01100110; + wire[3:0] __preloadChain__ = 4'b 0011; + + wire __tdo_pad_out__ = tdo_paden_o ? 1'bz : \(tdo); + + initial begin + `ifdef VCD + $dumpfile("dut.vcd"); // DEBUG + $dumpvars(0, testbench); + `endif + __all_errors__ = 0; + \(inputAssignment) + $readmemb("\(vecbinFile)", __vectors__); + $readmemb("\(outbinFile)", __gmOutput__); + #(`CLOCK_PERIOD) ; + \(resetToggler) + \(trst) = 1; + #(`CLOCK_PERIOD) ; + \(testStatements) + if (__all_errors__ > 0) begin + $error("SIMULATING_TV_FAILED"); + $finish; + end else begin + $display("SUCCESS_STRING"); + $finish; + end + end + + task test; + input [31:0] __ordinal__; // integer + input [\(vectorLength - 1):0] __vector__; + input [\(outputLength - 1):0] __goldenOutput__; + begin + $display("Testing vector %0d...", __ordinal__); + // Preload Scan-Chain with TV + + shiftIR(__preloadChain__); + enterShiftDR(); + + for (i = 0; i < \(vectorLength); i = i + 1) begin + \(tdi) = __vector__[i]; + if (i == \(vectorLength - 3)) begin + \(tms) = 1; // Exit-DR + end + if (i == \(vectorLength - 2)) begin + \(tms) = 0; // Pause-DR + end + if (i == \(vectorLength - 1)) begin + \(tms) = 1; // Exit2-DR + end + #(`CLOCK_PERIOD) ; + end + + \(tms) = 0; // Shift-DR + #(`CLOCK_PERIOD) ; + // Shift-out response + __error__ = 0; + for (i = 0; i< \(outputLength);i = i + 1) begin + \(tdi) = 0; + __scanInSerial__[i] = __tdo_pad_out__; + if (__scanInSerial__[i] !=? __goldenOutput__[i]) begin + $display("@%0d:\\t\\tExpected %0b, Got %0b", i, __goldenOutput__[i], __scanInSerial__[i]); + __error__ = __error__ + 1; + end + if(i == \(outputLength - 1)) begin + \(tms) = 1; // Exit-DR + end + #(`CLOCK_PERIOD) ; + end + __all_errors__ += __error__; + \(tms) = 1; // update-DR + #(`CLOCK_PERIOD) ; + \(tms) = 0; // run-test-idle + #(`CLOCK_PERIOD) ; + + if(__scanInSerial__ !=? __goldenOutput__) begin + $display("Test vector simulation failed: %0d bits mismatched", __error__); + $display("Expected:\\t%0b", __goldenOutput__); + $display("Got:\\t\\t%0b", __scanInSerial__); + end else begin + $display("Passed vector %0d.", __ordinal__); + end + end + endtask + + \(Simulator.createTasks(tms: tms, tdi: tdi)) + endmodule + """ + + return try Simulator.run( + define: defineStatements, + bench: bench, + output: output + ) + } + + private static func run( + define: String, + bench: String, + output: String, + clean: Bool = true + ) throws -> Bool { + try File.open(output, mode: .write) { + try $0.print(bench) + } - guard let outputFile = File.open(outputLog, mode: .write) else { - throw RuntimeError("Failed to open log file.") - } + let outputLog = "\(output).log" + print("Starting simulation steps (log: \(outputLog))…") - let aoutName = "\(output).a.out" - defer { - if clean { - let _ = "rm \(aoutName)".sh() - } - } - let iverilogCmd = - "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" - try outputFile.write(string: "$ \(iverilogCmd)\n") + guard let outputFile = File.open(outputLog, mode: .write) else { + throw RuntimeError("Failed to open log file.") + } - let iverilogResult = - "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" - .shOutput() - try outputFile.write(string: iverilogResult.output) + let aoutName = "\(output).a.out" + defer { + if clean { + let _ = "rm \(aoutName)".sh() + } + } + let iverilogCmd = + "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" + try outputFile.write(string: "$ \(iverilogCmd)\n") - if iverilogResult.terminationStatus != EX_OK { - throw RuntimeError("Failed to run iverilog.") - } + let iverilogResult = + "'\(iverilogExecutable)' -B '\(iverilogBase)' \(define) -Ttyp -o \(aoutName) \(output) 2>&1" + .shOutput() + try outputFile.write(string: iverilogResult.output) - let vvpCmd = "'\(vvpExecutable)' \(aoutName)" - try outputFile.write(string: "$ \(vvpCmd)\n") - let vvpResult = vvpCmd.shOutput() - try outputFile.write(string: vvpResult.output) + if iverilogResult.terminationStatus != EX_OK { + throw RuntimeError("Failed to run iverilog.") + } - if iverilogResult.terminationStatus != EX_OK { - throw RuntimeError("Failed to run vvp.") - } + let vvpCmd = "'\(vvpExecutable)' \(aoutName)" + try outputFile.write(string: "$ \(vvpCmd)\n") + let vvpResult = vvpCmd.shOutput() + try outputFile.write(string: vvpResult.output) - if vvpResult.output.contains("SUCCESS_STRING") { - return true - } else { - return false - } + if iverilogResult.terminationStatus != EX_OK { + throw RuntimeError("Failed to run vvp.") } - private static func createTasks(tms: String, tdi: String) -> String { - """ - task shiftIR; - input[3:0] __instruction__; - integer i; - begin - for (i = 0; i< 5; i = i + 1) begin - \(tms) = __tmsPattern__[i]; - #(`CLOCK_PERIOD) ; - end - - // At shift-IR: shift new instruction on tdi line - for (i = 0; i < 4; i = i + 1) begin - \(tdi) = __instruction__[i]; - if(i == 3) begin - \(tms) = __tmsPattern__[5]; // exit-ir - end - #(`CLOCK_PERIOD) ; - end + if vvpResult.output.contains("SUCCESS_STRING") { + return true + } else { + return false + } + } - \(tms) = __tmsPattern__[6]; // update-ir + private static func createTasks(tms: String, tdi: String) -> String { + """ + task shiftIR; + input[3:0] __instruction__; + integer i; + begin + for (i = 0; i< 5; i = i + 1) begin + \(tms) = __tmsPattern__[i]; #(`CLOCK_PERIOD) ; - \(tms) = __tmsPattern__[7]; // run test-idle - #(`CLOCK_PERIOD * 3) ; end - endtask - task enterShiftDR; - begin - \(tms) = 1; // select DR + // At shift-IR: shift new instruction on tdi line + for (i = 0; i < 4; i = i + 1) begin + \(tdi) = __instruction__[i]; + if(i == 3) begin + \(tms) = __tmsPattern__[5]; // exit-ir + end #(`CLOCK_PERIOD) ; - \(tms) = 0; // capture DR -- shift DR - #(`CLOCK_PERIOD * 2) ; end - endtask - task exitDR; - begin - \(tms) = 1; // Exit DR -- update DR - #(`CLOCK_PERIOD * 2) ; - \(tms) = 0; // Run test-idle - #(`CLOCK_PERIOD) ; - end - endtask - """ - } + \(tms) = __tmsPattern__[6]; // update-ir + #(`CLOCK_PERIOD) ; + \(tms) = __tmsPattern__[7]; // run test-idle + #(`CLOCK_PERIOD * 3) ; + end + endtask + + task enterShiftDR; + begin + \(tms) = 1; // select DR + #(`CLOCK_PERIOD) ; + \(tms) = 0; // capture DR -- shift DR + #(`CLOCK_PERIOD * 2) ; + end + endtask + + task exitDR; + begin + \(tms) = 1; // Exit DR -- update DR + #(`CLOCK_PERIOD * 2) ; + \(tms) = 0; // Run test-idle + #(`CLOCK_PERIOD) ; + end + endtask + """ + } } diff --git a/Sources/Fault/String.swift b/Sources/Fault/String.swift index 0c7cd9d..46178f4 100644 --- a/Sources/Fault/String.swift +++ b/Sources/Fault/String.swift @@ -16,91 +16,91 @@ import Defile import Foundation extension String { - func shOutput() -> (terminationStatus: Int32, output: String) { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/bin/sh") - task.arguments = ["-c", self] - - let pipe = Pipe() - task.standardOutput = pipe + func shOutput() -> (terminationStatus: Int32, output: String) { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/bin/sh") + task.arguments = ["-c", self] + + let pipe = Pipe() + task.standardOutput = pipe + + do { + try task.run() + } catch { + Stderr.print("Could not launch task `\(self)': \(error)") + exit(EX_UNAVAILABLE) + } - do { - try task.run() - } catch { - Stderr.print("Could not launch task `\(self)': \(error)") - exit(EX_UNAVAILABLE) - } + let data = pipe.fileHandleForReading.readDataToEndOfFile() - let data = pipe.fileHandleForReading.readDataToEndOfFile() + task.waitUntilExit() - task.waitUntilExit() + let output = String(data: data, encoding: .utf8) - let output = String(data: data, encoding: .utf8) + return (terminationStatus: task.terminationStatus, output: output!) + } - return (terminationStatus: task.terminationStatus, output: output!) + func sh(silent: Bool = false) -> Int32 { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/bin/sh") + task.arguments = ["-c", self] + // print("$ \(self)") + if silent { + task.standardOutput = FileHandle.nullDevice + task.standardError = FileHandle.nullDevice } - func sh(silent: Bool = false) -> Int32 { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/bin/sh") - task.arguments = ["-c", self] - // print("$ \(self)") - if silent { - task.standardOutput = FileHandle.nullDevice - task.standardError = FileHandle.nullDevice - } - - do { - try task.run() - } catch { - Stderr.print("Could not launch task `\(self)': \(error)") - exit(EX_UNAVAILABLE) - } - - task.waitUntilExit() - - return task.terminationStatus + do { + try task.run() + } catch { + Stderr.print("Could not launch task `\(self)': \(error)") + exit(EX_UNAVAILABLE) } - 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 + task.waitUntilExit() + + 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 {} extension Encodable { - func toJSON() -> String? { - let encoder = JSONEncoder() - do { - let data = try encoder.encode(self) - return String(data: data, encoding: .utf8) - } catch { - return nil - } + func toJSON() -> String? { + let encoder = JSONEncoder() + do { + let data = try encoder.encode(self) + return String(data: data, encoding: .utf8) + } catch { + return nil } + } } extension String { - static var boilerplate: String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - let date = Date() - let dateString = dateFormatter.string(from: date) - - return """ - /* - Automatically generated by Fault - Do not modify. - Generated on: \(dateString) - */ - """ - } + static var boilerplate: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let date = Date() + let dateString = dateFormatter.string(from: date) + + return """ + /* + Automatically generated by Fault + Do not modify. + Generated on: \(dateString) + */ + """ + } } diff --git a/Sources/Fault/Synthesis.swift b/Sources/Fault/Synthesis.swift index be9272b..884cfc4 100644 --- a/Sources/Fault/Synthesis.swift +++ b/Sources/Fault/Synthesis.swift @@ -13,68 +13,68 @@ // limitations under the License. enum Synthesis { - static func script( - for module: String, - in files: [String], - cutting: Bool = false, - checkHierarchy: Bool = true, - liberty libertyFile: String, - blackboxing blackboxedModules: [String] = [], - output: String, - optimize: Bool = true - ) -> String { - let opt = optimize ? "opt" : "" - return """ - # read liberty - read_liberty -lib -ignore_miss_dir -setattr blackbox \(libertyFile) - - # read black boxes - read_verilog -sv -lib \(blackboxedModules.map { "'\($0)'" }.joined(separator: " ")) - - # read design - read_verilog -sv \(files.map { "'\($0)'" }.joined(separator: " ")) - - # check design hierarchy - hierarchy \(checkHierarchy ? "-check" : "") -top \(module) - flatten; - - # translate processes (always blocks) - proc; \(opt) - - # detect and optimize FSM encodings - fsm; \(opt) - - # implement memories (arrays) - memory; \(opt) - - # convert to gate logic - techmap; \(opt) - - # flatten - flatten; \(opt) - - # mapping flip-flops to mycells.lib - dfflibmap -liberty \(libertyFile) - - # expose dff - \(cutting ? "expose -cut -evert-dff; \(opt)" : "") - - # mapping logic to mycells.lib - abc -liberty \(libertyFile) - splitnets - - # print gate count - stat - - # cleanup - opt_clean -purge - - # names - # autoname - - 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 - """ - } + static func script( + for module: String, + in files: [String], + cutting: Bool = false, + checkHierarchy: Bool = true, + liberty libertyFile: String, + blackboxing blackboxedModules: [String] = [], + output: String, + optimize: Bool = true + ) -> String { + let opt = optimize ? "opt" : "" + return """ + # read liberty + read_liberty -lib -ignore_miss_dir -setattr blackbox \(libertyFile) + + # read black boxes + read_verilog -sv -lib \(blackboxedModules.map { "'\($0)'" }.joined(separator: " ")) + + # read design + read_verilog -sv \(files.map { "'\($0)'" }.joined(separator: " ")) + + # check design hierarchy + hierarchy \(checkHierarchy ? "-check" : "") -top \(module) + flatten; + + # translate processes (always blocks) + proc; \(opt) + + # detect and optimize FSM encodings + fsm; \(opt) + + # implement memories (arrays) + memory; \(opt) + + # convert to gate logic + techmap; \(opt) + + # flatten + flatten; \(opt) + + # mapping flip-flops to mycells.lib + dfflibmap -liberty \(libertyFile) + + # expose dff + \(cutting ? "expose -cut -evert-dff; \(opt)" : "") + + # mapping logic to mycells.lib + abc -liberty \(libertyFile) + splitnets + + # print gate count + stat + + # cleanup + opt_clean -purge + + # names + # autoname + + 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 c05ce1f..ea7abe6 100644 --- a/Sources/Fault/TVGenerator.swift +++ b/Sources/Fault/TVGenerator.swift @@ -17,324 +17,324 @@ import Defile import Foundation protocol TVGenerator { - var current: BigUInt { get set } + var current: BigUInt { get set } - init(allBits: Int, seed: UInt) + init(allBits: Int, seed: UInt) - func generate(count: Int) + func generate(count: Int) } extension TVGenerator { - mutating func get(bits: Int) -> BigUInt { - let mask = (BigUInt(1) << bits) - 1 - let result = current & mask - current >>= bits - return result - } + mutating func get(bits: Int) -> BigUInt { + let mask = (BigUInt(1) << bits) - 1 + let result = current & mask + current >>= bits + return result + } } enum TVGeneratorFactory { - private static var registry: [String: TVGenerator.Type] = [:] + private static var registry: [String: TVGenerator.Type] = [:] - static func register(name: String, type: (some TVGenerator).Type) -> Bool { - registry[name] = type - return true - } + static func register(name: String, type: (some TVGenerator).Type) -> Bool { + registry[name] = type + return true + } - static func get(name: String) -> TVGenerator.Type? { - guard let metaType = registry[name] else { - return nil - } - return metaType + static func get(name: String) -> TVGenerator.Type? { + guard let metaType = registry[name] else { + return nil } + return metaType + } - static var validNames: [String] { - [String](registry.keys) - } + static var validNames: [String] { + [String](registry.keys) + } } class SwiftRNG: TVGenerator { - var current: BigUInt - var bits: Int - var rng: ARC4RandomNumberGenerator - - required init(allBits bits: Int, seed: UInt) { - current = 0 - self.bits = bits - rng = ARC4RandomNumberGenerator(seed: seed) - } + var current: BigUInt + var bits: Int + var rng: ARC4RandomNumberGenerator - func generate(count _: Int) { - current = BigUInt.randomInteger(withMaximumWidth: bits, using: &rng) - } + required init(allBits bits: Int, seed: UInt) { + current = 0 + self.bits = bits + rng = ARC4RandomNumberGenerator(seed: seed) + } - static let registered = TVGeneratorFactory.register(name: "swift", type: SwiftRNG.self) + func generate(count _: Int) { + current = BigUInt.randomInteger(withMaximumWidth: bits, using: &rng) + } + + static let registered = TVGeneratorFactory.register(name: "swift", type: SwiftRNG.self) } class LFSR: TVGenerator { - var current: BigUInt - var bits: Int - - static let taps: [UInt: [UInt]] = [ - // nbits : Feedback Polynomial - 2: [2, 1], - 3: [3, 2], - 4: [4, 3], - 5: [5, 3], - 6: [6, 5], - 7: [7, 6], - 8: [8, 6, 5, 4], - 9: [9, 5], - 10: [10, 7], - 11: [11, 9], - 12: [12, 11, 10, 4], - 13: [13, 12, 11, 8], - 14: [14, 13, 12, 2], - 15: [15, 14], - 16: [16, 15, 13, 4], - 17: [17, 14], - 18: [18, 11], - 19: [19, 18, 17, 14], - 20: [20, 17], - 21: [21, 19], - 22: [22, 21], - 23: [23, 18], - 24: [24, 23, 22, 17], - 25: [25, 22], - 26: [26, 6, 2, 1], - 27: [27, 5, 2, 1], - 28: [28, 25], - 29: [29, 27], - 30: [30, 6, 4, 1], - 31: [31, 28], - 32: [32, 30, 26, 25], - 64: [64, 63, 61, 60], - ] - - var seed: UInt - var polynomialHex: UInt - let nbits: UInt - - required init(allBits bits: Int, nbits: UInt, seed: UInt) { - self.seed = seed - self.nbits = nbits - self.bits = bits - current = 0 - - let polynomial = LFSR.taps[nbits]! - - polynomialHex = 0 - - for tap in polynomial { - polynomialHex = polynomialHex | (1 << (nbits - tap)) - } + var current: BigUInt + var bits: Int + + static let taps: [UInt: [UInt]] = [ + // nbits : Feedback Polynomial + 2: [2, 1], + 3: [3, 2], + 4: [4, 3], + 5: [5, 3], + 6: [6, 5], + 7: [7, 6], + 8: [8, 6, 5, 4], + 9: [9, 5], + 10: [10, 7], + 11: [11, 9], + 12: [12, 11, 10, 4], + 13: [13, 12, 11, 8], + 14: [14, 13, 12, 2], + 15: [15, 14], + 16: [16, 15, 13, 4], + 17: [17, 14], + 18: [18, 11], + 19: [19, 18, 17, 14], + 20: [20, 17], + 21: [21, 19], + 22: [22, 21], + 23: [23, 18], + 24: [24, 23, 22, 17], + 25: [25, 22], + 26: [26, 6, 2, 1], + 27: [27, 5, 2, 1], + 28: [28, 25], + 29: [29, 27], + 30: [30, 6, 4, 1], + 31: [31, 28], + 32: [32, 30, 26, 25], + 64: [64, 63, 61, 60], + ] + + var seed: UInt + var polynomialHex: UInt + let nbits: UInt + + required init(allBits bits: Int, nbits: UInt, seed: UInt) { + self.seed = seed + self.nbits = nbits + self.bits = bits + current = 0 + + let polynomial = LFSR.taps[nbits]! + + polynomialHex = 0 + + for tap in polynomial { + polynomialHex = polynomialHex | (1 << (nbits - tap)) } - - required convenience init(allBits: Int, seed: UInt) { - self.init(allBits: allBits, nbits: 64, seed: seed) + } + + required convenience init(allBits: Int, seed: UInt) { + self.init(allBits: allBits, nbits: 64, seed: seed) + } + + static func parity(number: UInt) -> UInt { + var parityVal: UInt = 0 + var numberTemp = number + while numberTemp != 0 { + parityVal ^= 1 + numberTemp = numberTemp & (numberTemp - 1) } - - static func parity(number: UInt) -> UInt { - var parityVal: UInt = 0 - var numberTemp = number - while numberTemp != 0 { - parityVal ^= 1 - numberTemp = numberTemp & (numberTemp - 1) - } - return parityVal + return parityVal + } + + func rand() -> UInt { + let feedbackBit: UInt = LFSR.parity(number: seed & polynomialHex) + seed = (seed >> 1) | (feedbackBit << (nbits - 1)) + return seed + } + + func generate(count _: Int) { + var returnValue: BigUInt = 0 + var generations = bits / Int(nbits) + let leftover = bits % Int(nbits) + while generations > 0 { + returnValue <<= BigUInt(nbits) + returnValue |= BigUInt(rand()) + generations -= 1 } - - func rand() -> UInt { - let feedbackBit: UInt = LFSR.parity(number: seed & polynomialHex) - seed = (seed >> 1) | (feedbackBit << (nbits - 1)) - return seed + if leftover > 0 { + returnValue <<= BigUInt(leftover) + returnValue |= BigUInt(rand() % ((1 << leftover) - 1)) } + current = returnValue + } - func generate(count _: Int) { - var returnValue: BigUInt = 0 - var generations = bits / Int(nbits) - let leftover = bits % Int(nbits) - while generations > 0 { - returnValue <<= BigUInt(nbits) - returnValue |= BigUInt(rand()) - generations -= 1 - } - if leftover > 0 { - returnValue <<= BigUInt(leftover) - returnValue |= BigUInt(rand() % ((1 << leftover) - 1)) - } - current = returnValue - } - - static let registered = TVGeneratorFactory.register(name: "LFSR", type: LFSR.self) + static let registered = TVGeneratorFactory.register(name: "LFSR", type: LFSR.self) } class PatternGenerator: TVGenerator { - var current: BigUInt = 0 - var bits: Int = 0 - - required init(allBits bits: Int, seed _: UInt) { - self.bits = bits - current = 0 + var current: BigUInt = 0 + var bits: Int = 0 + + required init(allBits bits: Int, seed _: UInt) { + self.bits = bits + current = 0 + } + + func generate(count: Int) { + let selector = count / 2 + let complement = (count % 2) == 1 + current = 0 + if selector == 0 { + // Nothing, it's already all zeroes + } else if selector == 1 { + // Half-and-half + let halfBits = bits / 2 + current = (BigUInt(1) << halfBits) - 1 + } else if selector == 2 { + // Alternating 0s and 1s + for _ in 0.. ([TestVector], [Port]) + init() + func generate(file: String, module: String) -> ([TestVector], [Port]) } enum ETVGFactory { - private static var registry: [String: ExternalTestVectorGenerator.Type] = [:] + private static var registry: [String: ExternalTestVectorGenerator.Type] = [:] - static func register(name: String, type: (some ExternalTestVectorGenerator).Type) -> Bool { - registry[name] = type - return true - } + static func register(name: String, type: (some ExternalTestVectorGenerator).Type) -> Bool { + registry[name] = type + return true + } - static func get(name: String) -> ExternalTestVectorGenerator? { - guard let metaType = registry[name] else { - return nil - } - return metaType.init() + static func get(name: String) -> ExternalTestVectorGenerator? { + guard let metaType = registry[name] else { + return nil } + return metaType.init() + } - static var validNames: [String] { - [String](registry.keys) - } + static var validNames: [String] { + [String](registry.keys) + } } class Atalanta: ExternalTestVectorGenerator { - required init() {} - - func generate(file: String, module _: String) -> ([TestVector], [Port]) { - let output = file.replacingExtension(".bench", with: ".test") - let atalanta = "atalanta -t \(output) \(file)".sh() - - if atalanta != EX_OK { - exit(atalanta) - } - - 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) - } + required init() {} + + func generate(file: String, module _: String) -> ([TestVector], [Port]) { + let output = file.replacingExtension(".bench", with: ".test") + let atalanta = "atalanta -t \(output) \(file)".sh() + + if atalanta != EX_OK { + exit(atalanta) } - static let registered = ETVGFactory.register(name: "Atalanta", type: Atalanta.self) + 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: "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) - } + 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) } - static let registered = ETVGFactory.register(name: "Quaigh", type: Quaigh.self) + 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() {} - - 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).out" - let podem = "atpg-podem -output \(output) \(file) > /dev/null 2>&1".sh() - - if podem != EX_OK { - exit(podem) - } - do { - let (testvectors, inputs) = try TVSet.readFromText(file: output) - return (vectors: testvectors, inputs: inputs) - } catch { - Stderr.print("Internal software error: \(error)") - exit(EX_SOFTWARE) - } + 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) } - static let registered = ETVGFactory.register(name: "PODEM", type: PODEM.self) + let output = "\(folderName)/\(module).out" + let podem = "atpg-podem -output \(output) \(file) > /dev/null 2>&1".sh() + + if podem != EX_OK { + exit(podem) + } + do { + let (testvectors, inputs) = try TVSet.readFromText(file: output) + return (vectors: testvectors, inputs: inputs) + } catch { + Stderr.print("Internal software error: \(error)") + exit(EX_SOFTWARE) + } + } + + static let registered = ETVGFactory.register(name: "PODEM", type: PODEM.self) } class PodemQuest: ExternalTestVectorGenerator { - required init() {} - - func generate(file: String, module _: String) -> ([TestVector], [Port]) { - let output = file.replacingExtension(".bench", with: ".test") - let podemQuest = "podemquest -i\(file) -o \(output)".sh() - - if podemQuest != EX_OK { - exit(podemQuest) - } - - 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) - } + required init() {} + + func generate(file: String, module _: String) -> ([TestVector], [Port]) { + let output = file.replacingExtension(".bench", with: ".test") + let podemQuest = "podemquest -i\(file) -o \(output)".sh() + + if podemQuest != EX_OK { + exit(podemQuest) + } + + 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: "PodemQuest", type: PodemQuest.self) + static let registered = ETVGFactory.register(name: "PodemQuest", type: PodemQuest.self) } diff --git a/Sources/Fault/TestVector.swift b/Sources/Fault/TestVector.swift index 8a8f558..7c7297b 100644 --- a/Sources/Fault/TestVector.swift +++ b/Sources/Fault/TestVector.swift @@ -20,154 +20,154 @@ import Foundation typealias TestVector = [BigUInt] extension BigUInt { - func pad(digits: Int, radix: Int) -> String { - var padded = String(self, radix: radix) - let length = padded.count - if digits > length { - for _ in 0 ..< (digits - length) { - padded = "0" + padded - } - } - return padded + func pad(digits: Int, radix: Int) -> String { + var padded = String(self, radix: radix) + let length = padded.count + if digits > length { + for _ in 0..<(digits - length) { + padded = "0" + padded + } } + return padded + } } struct Coverage: Codable { - var sa0: [String] - var sa1: [String] - init(sa0: [String], sa1: [String]) { - self.sa0 = sa0 - self.sa1 = sa1 - } + var sa0: [String] + var sa1: [String] + init(sa0: [String], sa1: [String]) { + self.sa0 = sa0 + self.sa1 = sa1 + } } struct TVCPair: Codable { - var vector: TestVector - var coverage: Coverage - var goldenOutput: String - init(vector: TestVector, coverage: Coverage, goldenOutput: String) { - self.vector = vector - self.coverage = coverage - self.goldenOutput = goldenOutput - } + var vector: TestVector + var coverage: Coverage + var goldenOutput: String + init(vector: TestVector, coverage: Coverage, goldenOutput: String) { + self.vector = vector + self.coverage = coverage + self.goldenOutput = goldenOutput + } } struct TVInfo: Codable { - var inputs: [Port] - var outputs: [Port] - var coverageList: [TVCPair] - init( - inputs: [Port], - outputs: [Port], - coverageList: [TVCPair] - ) { - self.inputs = inputs - self.outputs = outputs - self.coverageList = coverageList - } - - static func fromJSON(file: String) throws -> TVInfo { - let data = try Data(contentsOf: URL(fileURLWithPath: file), options: .mappedIfSafe) - return try JSONDecoder().decode(TVInfo.self, from: data) - } + var inputs: [Port] + var outputs: [Port] + var coverageList: [TVCPair] + init( + inputs: [Port], + outputs: [Port], + coverageList: [TVCPair] + ) { + self.inputs = inputs + self.outputs = outputs + self.coverageList = coverageList + } + + static func fromJSON(file: String) throws -> TVInfo { + let data = try Data(contentsOf: URL(fileURLWithPath: file), options: .mappedIfSafe) + return try JSONDecoder().decode(TVInfo.self, from: data) + } } enum TVSet { - static func readFromJson(file: String) throws -> ([TestVector], [Port]) { - guard let tvInfo = try? TVInfo.fromJSON(file: file) else { - Stderr.print("File '\(file)' is invalid.") - exit(EX_DATAERR) - } - let vectors = tvInfo.coverageList.map(\.vector) - return (vectors: vectors, inputs: tvInfo.inputs) + static func readFromJson(file: String) throws -> ([TestVector], [Port]) { + guard let tvInfo = try? TVInfo.fromJSON(file: file) else { + Stderr.print("File '\(file)' is invalid.") + exit(EX_DATAERR) } + let vectors = tvInfo.coverageList.map(\.vector) + return (vectors: vectors, inputs: tvInfo.inputs) + } - static func readFromText(file path: String) throws -> ([TestVector], [Port]) { - var inputs: [Port] = [] - var vectors: [TestVector] = [] + static func readFromText(file path: String) throws -> ([TestVector], [Port]) { + var inputs: [Port] = [] + var vectors: [TestVector] = [] - guard let file = File.open(path) else { - Stderr.print("Test vector set input file '\(path)' not found.") - exit(EX_DATAERR) - } + guard let file = File.open(path) else { + Stderr.print("Test vector set input file '\(path)' not found.") + exit(EX_DATAERR) + } - let lines = file.lines! + let lines = file.lines! - let ports = lines[0].components(separatedBy: " ") + let ports = lines[0].components(separatedBy: " ") + for (index, port) in ports.enumerated() { + if port != "PI" { + inputs.append(Port(name: port, at: index)) + } + } + inputs = inputs.dropLast(1) + + var readPorts = true + for line in lines[1...] { + let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines) + + if trimmedLine[trimmedLine.startIndex].isNumber { + let testvector = Array(trimmedLine).map { BigUInt(String($0), radix: 2)! } + vectors.append(testvector) + readPorts = false + } else if readPorts { + let ports = trimmedLine.components(separatedBy: " ") for (index, port) in ports.enumerated() { - if port != "PI" { - inputs.append(Port(name: port, at: index)) - } + if port != "PI" { + inputs.append(Port(name: port, at: index)) + } } inputs = inputs.dropLast(1) - - var readPorts = true - for line in lines[1...] { - let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines) - - if trimmedLine[trimmedLine.startIndex].isNumber { - let testvector = Array(trimmedLine).map { BigUInt(String($0), radix: 2)! } - vectors.append(testvector) - readPorts = false - } else if readPorts { - let ports = trimmedLine.components(separatedBy: " ") - for (index, port) in ports.enumerated() { - if port != "PI" { - inputs.append(Port(name: port, at: index)) - } - } - inputs = inputs.dropLast(1) - } else { - Stderr.print("Warning: Dropped invalid testvector line \(line)") - } - } - return (vectors: vectors, inputs: inputs) + } else { + Stderr.print("Warning: Dropped invalid testvector line \(line)") + } + } + return (vectors: vectors, inputs: inputs) + } + + 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 + } } - 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 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) } - - 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) - } - let tv: TestVector = inputs.map { - input in - let value = vectorCat & ((1 << input.width) - 1) - vectorCat >>= input.width - return value - } - vectors.append(tv) - } + let tv: TestVector = inputs.map { + input in + let value = vectorCat & ((1 << input.width) - 1) + vectorCat >>= input.width + return value } - - return (vectors: vectors, inputs: inputs) + vectors.append(tv) + } } + + return (vectors: vectors, inputs: inputs) + } }