diff --git a/Package.swift b/Package.swift index 7e16b2c..6952a38 100644 --- a/Package.swift +++ b/Package.swift @@ -19,9 +19,10 @@ let package = Package( .package(url: "https://github.com/mipalgu/VHDLMachines", from: "4.0.1"), .package(url: "https://github.com/mipalgu/VHDLParsing", from: "2.4.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), - .package(url: "https://github.com/mipalgu/VHDLKripkeStructureGenerator.git", from: "0.3.1"), + .package(url: "https://github.com/mipalgu/VHDLKripkeStructureGenerator.git", from: "0.3.2"), .package(url: "https://github.com/CPSLabGU/SwiftUtils.git", from: "0.1.0"), - .package(url: "https://github.com/CPSLabGU/VHDLJSModels", from: "1.0.0") + .package(url: "https://github.com/CPSLabGU/VHDLJSModels", from: "1.0.0"), + .package(url: "https://github.com/CPSLabGU/VHDLKripkeStructures", from: "1.2.1") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -34,7 +35,8 @@ let package = Package( .product(name: "VHDLParsing", package: "VHDLParsing"), .product(name: "VHDLKripkeStructureGenerator", package: "VHDLKripkeStructureGenerator"), .product(name: "SwiftUtils", package: "SwiftUtils"), - .product(name: "VHDLJSModels", package: "VHDLJSModels") + .product(name: "VHDLJSModels", package: "VHDLJSModels"), + .product(name: "VHDLKripkeStructures", package: "VHDLKripkeStructures") ] ), .testTarget( @@ -47,7 +49,8 @@ let package = Package( .product(name: "VHDLKripkeStructureGenerator", package: "VHDLKripkeStructureGenerator"), .product(name: "SwiftUtils", package: "SwiftUtils"), .product(name: "VHDLJSModels", package: "VHDLJSModels"), - "TestHelpers" + "TestHelpers", + .product(name: "VHDLKripkeStructures", package: "VHDLKripkeStructures") ] ), .testTarget( @@ -55,7 +58,8 @@ let package = Package( dependencies: [ .product(name: "VHDLMachines", package: "VHDLMachines"), .product(name: "VHDLParsing", package: "VHDLParsing"), - .product(name: "VHDLJSModels", package: "VHDLJSModels") + .product(name: "VHDLJSModels", package: "VHDLJSModels"), + .product(name: "VHDLKripkeStructures", package: "VHDLKripkeStructures") ] ) ] diff --git a/Sources/MachineGenerator/GraphCommand.swift b/Sources/MachineGenerator/GraphCommand.swift new file mode 100644 index 0000000..a707cac --- /dev/null +++ b/Sources/MachineGenerator/GraphCommand.swift @@ -0,0 +1,157 @@ +// GraphCommand.swift +// LLFSMGenerate +// +// Created by Morgan McColl. +// Copyright © 2024 Morgan McColl. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// 3. All advertising materials mentioning features or use of this +// software must display the following acknowledgement: +// +// This product includes software developed by Morgan McColl. +// +// 4. Neither the name of the author nor the names of contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// This program is free software; you can redistribute it and/or +// modify it under the above terms or under the terms of the GNU +// General Public License as published by the Free Software Foundation; +// either version 2 of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see http://www.gnu.org/licenses/ +// or write to the Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. + +import ArgumentParser +import Foundation +import VHDLKripkeStructures + +/// A command for generating `GraphViz` files (`.dot`) from encoded Kripke Structures in JSON. +struct GraphCommand: ParsableCommand { + + /// The configuration of this command. + static var configuration = CommandConfiguration( + commandName: "graph", + abstract: "Generate a graphviz file (.dot) for the entire kripke structure." + ) + + /// Whether the `path` points to a machine folder. + @Flag(name: .customLong("machine"), help: "Whether the path is a machine folder.") + var isMachine = false + + /// The `path` to the Kripke Structure. + @Argument(help: """ + The path to the resource containing the Kripke Structure. This path may be the json file itself + or a path to a machine folder. + """ + ) + var path: String + + /// The destination location of the generated graphviz file. + @Option(help: "The path of the newly generated graphviz file.") + var destination: String? + + /// Generate the graphviz file. + func run() throws { + guard isMachine else { + let pathURL = URL(fileURLWithPath: path, isDirectory: false) + try self.generate(pathURL: pathURL) + return + } + let manager = FileManager.default + var isDirectory: ObjCBool = false + guard + path.hasSuffix(".machine"), + manager.fileExists(atPath: path, isDirectory: &isDirectory), + isDirectory.boolValue + else { + throw GenerationError.invalidInput(message: "The path must be a valid machines location.") + } + let pathURL = URL(fileURLWithPath: path, isDirectory: true) + .appendingPathComponent("output.json", isDirectory: false) + try self.generate(pathURL: pathURL) + } + + /// Generate the graphviz file from the kripke structure in `url`. + /// - Parameter url: The path to the kripke structure. + func generate(pathURL url: URL) throws { + let manager = FileManager.default + var isDirectory: ObjCBool = false + guard manager.fileExists(atPath: url.path, isDirectory: &isDirectory), !isDirectory.boolValue else { + throw GenerationError.invalidMachine( + message: "The Kripke structure does not exist at this specified location." + ) + } + let contents = try Data(contentsOf: url) + let decoder = JSONDecoder() + let structure = try decoder.decode(KripkeStructure.self, from: contents) + guard let data = structure.graphviz.data(using: .utf8) else { + throw GenerationError.invalidExportation( + message: "The Kripke structure could not be exported to Graphviz." + ) + } + let graphvizFile = self.graphvizFile(defaultName: url.deletingPathExtension().lastPathComponent) + let previousDirectory = graphvizFile.deletingLastPathComponent() + if !manager.fileExists(atPath: previousDirectory.path) { + try manager.createDirectory(at: previousDirectory, withIntermediateDirectories: true) + } + try data.write(to: graphvizFile, options: .atomic) + } + + /// Get the `URL` for the graphviz file. + /// - Parameter name: The default name of the file. + /// - Returns: The path to the graphviz file. + func graphvizFile(defaultName name: String) -> URL { + let manager = FileManager.default + guard let destination else { + let currentPath = URL(fileURLWithPath: manager.currentDirectoryPath, isDirectory: true) + return currentPath.appendingPathComponent("\(name).dot", isDirectory: false) + } + var isDirectory: ObjCBool = false + guard !manager.fileExists(atPath: destination, isDirectory: &isDirectory) else { + guard isDirectory.boolValue else { + return URL(fileURLWithPath: destination, isDirectory: false) + } + return URL(fileURLWithPath: destination, isDirectory: true) + .appendingPathComponent("\(name).dot", isDirectory: false) + } + guard destination.hasSuffix(".dot") else { + print("Cannot discern if destination is directory or file. Defaulting to directory.") + return URL(fileURLWithPath: destination, isDirectory: true) + .appendingPathComponent("\(name).dot", isDirectory: false) + } + return URL(fileURLWithPath: destination, isDirectory: false) + } + +} diff --git a/Sources/MachineGenerator/LLFSMGenerate.swift b/Sources/MachineGenerator/LLFSMGenerate.swift index c54ae0b..7b96a30 100644 --- a/Sources/MachineGenerator/LLFSMGenerate.swift +++ b/Sources/MachineGenerator/LLFSMGenerate.swift @@ -64,8 +64,10 @@ struct LLFSMGenerate: ParsableCommand { static var configuration = CommandConfiguration( commandName: "llfsmgenerate", abstract: "A utility for performing operations on LLFSM formats.", - version: "2.0.1", - subcommands: [Generate.self, VHDLGenerator.self, CleanCommand.self, InstallCommand.self] + version: "2.1.0", + subcommands: [ + Generate.self, VHDLGenerator.self, CleanCommand.self, InstallCommand.self, GraphCommand.self + ] ) } diff --git a/Tests/MachineGeneratorTests/GraphTests.swift b/Tests/MachineGeneratorTests/GraphTests.swift new file mode 100644 index 0000000..74fd234 --- /dev/null +++ b/Tests/MachineGeneratorTests/GraphTests.swift @@ -0,0 +1,181 @@ +// GraphTests.swift +// LLFSMGenerate +// +// Created by Morgan McColl. +// Copyright © 2024 Morgan McColl. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// 3. All advertising materials mentioning features or use of this +// software must display the following acknowledgement: +// +// This product includes software developed by Morgan McColl. +// +// 4. Neither the name of the author nor the names of contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// This program is free software; you can redistribute it and/or +// modify it under the above terms or under the terms of the GNU +// General Public License as published by the Free Software Foundation; +// either version 2 of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see http://www.gnu.org/licenses/ +// or write to the Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. + +@testable import MachineGenerator +import VHDLKripkeStructures +import XCTest + +/// Test class for ``GraphCommand``. +final class GraphTests: MachineTester { + + /// The path to the dot file. + var dotFile: URL { + self.packageRootPath.appendingPathComponent("output.dot", isDirectory: false) + } + + /// The dot file in the build folder. + var buildDotFile: URL { + self.pingMachineBuildFolder.appendingPathComponent("output.dot", isDirectory: false) + } + + /// Remove the dot file after every test. + override func tearDown() { + super.tearDown() + try? manager.removeItem(at: self.dotFile) + } + + /// Test the kripke structure is generated correctly for a machine. + func testMachineGeneration() throws { + XCTAssertFalse(manager.fileExists(atPath: dotFile.path)) + GraphCommand.main(["--machine", self.pingMachineFolder.path]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: dotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: dotFile).isEmpty) + } + + /// Test the kripke structure is generated correctly for a machine with a destination. + func testMachineGenerationWithDestination() throws { + XCTAssertFalse(manager.fileExists(atPath: buildDotFile.path)) + GraphCommand.main(["--machine", self.pingMachineFolder.path, "--destination", self.buildDotFile.path]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: buildDotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: buildDotFile).isEmpty) + } + + /// Test the generation given the file as a path. + func testGeneration() throws { + XCTAssertFalse(manager.fileExists(atPath: dotFile.path)) + GraphCommand.main([self.pingMachineKripkeStructure.path]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: dotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: dotFile).isEmpty) + } + + /// Test that generation works with a destination. + func testGenerationWithDestination() throws { + XCTAssertFalse(manager.fileExists(atPath: buildDotFile.path)) + GraphCommand.main([self.pingMachineKripkeStructure.path, "--destination", self.buildDotFile.path]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: buildDotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: buildDotFile).isEmpty) + } + + /// Test that the command throws an error for an invalid machine. + func testGenerationThrowsErrorForInvalidMachine() throws { + let command = try GraphCommand.parse(["--machine", self.pingMachineKripkeStructure.path]) + XCTAssertThrowsError(try command.run()) { + guard let error = $0 as? GenerationError, case .invalidInput(let message) = error else { + XCTFail("Expected GenerationError but got \($0)") + return + } + XCTAssertEqual(message, "The path must be a valid machines location.") + } + XCTAssertFalse(manager.fileExists(atPath: dotFile.path)) + } + + /// Test that the correct error is thrown when the kripke structure is not present. + func testCommandThrowsErrorForMissingKripkeStructure() throws { + try manager.removeItem(at: self.pingMachineKripkeStructure) + let command = try GraphCommand.parse([self.pingMachineKripkeStructure.path]) + XCTAssertThrowsError(try command.run()) { + guard let error = $0 as? GenerationError, case .invalidMachine(let message) = error else { + XCTFail("Expected GenerationError but got \($0)") + return + } + XCTAssertEqual(message, "The Kripke structure does not exist at this specified location.") + } + XCTAssertFalse(manager.fileExists(atPath: dotFile.path)) + } + + /// Test command overwrites existing `.dot` file. + func testCommandOverwritesFile() throws { + XCTAssertTrue(manager.createFile(atPath: dotFile.path, contents: nil)) + GraphCommand.main([self.pingMachineKripkeStructure.path, "--destination", self.dotFile.path]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: dotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: dotFile).isEmpty) + } + + /// Test that the file is created in a subdirectory that exists. + func testCommandPlacesFileInFolderthatExists() throws { + try manager.createDirectory(at: self.pingMachineBuildFolder, withIntermediateDirectories: true) + XCTAssertFalse(manager.fileExists(atPath: self.buildDotFile.path)) + GraphCommand.main([ + self.pingMachineKripkeStructure.path, "--destination", self.pingMachineBuildFolder.path + ]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: buildDotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: buildDotFile).isEmpty) + } + + /// Test that the file is created in a subdirectory that doesn't exist. + func testCommandPlacesFileInFolder() throws { + XCTAssertFalse(manager.fileExists(atPath: self.pingMachineBuildFolder.path)) + GraphCommand.main([ + self.pingMachineKripkeStructure.path, "--destination", self.pingMachineBuildFolder.path + ]) + var isDirectory: ObjCBool = false + XCTAssertTrue(manager.fileExists(atPath: buildDotFile.path, isDirectory: &isDirectory)) + XCTAssertFalse(isDirectory.boolValue) + XCTAssertFalse(try Data(contentsOf: buildDotFile).isEmpty) + } + +} diff --git a/Tests/MachineGeneratorTests/MachineTester.swift b/Tests/MachineGeneratorTests/MachineTester.swift index fb99307..fe6aaf7 100644 --- a/Tests/MachineGeneratorTests/MachineTester.swift +++ b/Tests/MachineGeneratorTests/MachineTester.swift @@ -57,6 +57,7 @@ import Foundation import JavascriptModel import TestHelpers +import VHDLKripkeStructures import VHDLMachines import XCTest @@ -122,6 +123,16 @@ class MachineTester: XCTestCase { machinesFolder.appendingPathComponent("PingMachine.machine", isDirectory: true) } + /// The build folder in the ping machine. + var pingMachineBuildFolder: URL { + pingMachineFolder.appendingPathComponent("build", isDirectory: true) + } + + /// The path to the `PingMachine` kripke structure (`output.json`). + var pingMachineKripkeStructure: URL { + pingMachineFolder.appendingPathComponent("output.json", isDirectory: false) + } + /// Create test machines before every test. override func setUp() { let createDir: ()? = try? manager @@ -135,13 +146,15 @@ class MachineTester: XCTestCase { let arrangementData = try? encoder.encode( ArrangementModel.pingArrangement(path: self.pingMachineFolder) ), - let modelData = try? encoder.encode(MachineModel.machine0) + let modelData = try? encoder.encode(MachineModel.machine0), + let structureData = try? encoder.encode(KripkeStructure.pingPongStructure) else { XCTFail("Failed to create machine!") return } try? self.manager.createDirectory(at: self.pingMachineFolder, withIntermediateDirectories: true) try? pingData.write(to: pingMachineFolder.appendingPathComponent("model.json", isDirectory: false)) + try? structureData.write(to: pingMachineKripkeStructure) try? self.manager.createDirectory(at: arrangement1Folder, withIntermediateDirectories: true) try? arrangementData.write( to: arrangement1Folder.appendingPathComponent("model.json", isDirectory: false) diff --git a/Tests/TestHelpers/Arrangement+pingArrangement.swift b/Tests/TestHelpers/Arrangement+pingArrangement.swift index 6818d3d..2aa7aa6 100644 --- a/Tests/TestHelpers/Arrangement+pingArrangement.swift +++ b/Tests/TestHelpers/Arrangement+pingArrangement.swift @@ -80,7 +80,7 @@ public extension Arrangement { signals: [ LocalSignal(type: .stdLogic, name: .ping), LocalSignal(type: .stdLogic, name: .pong) ], - clocks: [Clock(name: .clk, frequency: 125, unit: .MHz)] + clocks: [Clock(name: .clk, frequency: 5, unit: .MHz)] )! } diff --git a/Tests/TestHelpers/ArrangementModel+pingArrangement.swift b/Tests/TestHelpers/ArrangementModel+pingArrangement.swift index 9ee7a4b..e5d86e8 100644 --- a/Tests/TestHelpers/ArrangementModel+pingArrangement.swift +++ b/Tests/TestHelpers/ArrangementModel+pingArrangement.swift @@ -64,7 +64,7 @@ public extension ArrangementModel { /// - Returns: The arrangement model. static func pingArrangement(path: URL) -> ArrangementModel { ArrangementModel( - clocks: [ClockModel(name: "clk", frequency: "125 MHz")], + clocks: [ClockModel(name: "clk", frequency: "5 MHz")], externalVariables: "externalPing: out std_logic; externalPong: out std_logic;", machines: [ MachineReference( diff --git a/Tests/TestHelpers/KripkeStructure+testStructure.swift b/Tests/TestHelpers/KripkeStructure+testStructure.swift new file mode 100644 index 0000000..ecde748 --- /dev/null +++ b/Tests/TestHelpers/KripkeStructure+testStructure.swift @@ -0,0 +1,982 @@ +// KripkeStructure+testStructure.swift +// LLFSMGenerate +// +// Created by Morgan McColl. +// Copyright © 2024 Morgan McColl. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// 3. All advertising materials mentioning features or use of this +// software must display the following acknowledgement: +// +// This product includes software developed by Morgan McColl. +// +// 4. Neither the name of the author nor the names of contributors +// may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------- +// This program is free software; you can redistribute it and/or +// modify it under the above terms or under the terms of the GNU +// General Public License as published by the Free Software Foundation; +// either version 2 of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, see http://www.gnu.org/licenses/ +// or write to the Free Software Foundation, Inc., 51 Franklin Street, +// Fifth Floor, Boston, MA 02110-1301, USA. + +import Foundation +import VHDLKripkeStructures +import VHDLParsing + +// swiftlint:disable file_length + +/// Add constant kripke structures. +extension KripkeStructure { + + /// The ping pong kripke structure. + public static let pingPongStructure: KripkeStructure = { + let decoder = JSONDecoder() + // swiftlint:disable:next force_unwrapping + return (try? decoder.decode(KripkeStructure.self, from: KripkeStructure.raw.data(using: .utf8)!))! + }() + + /// The raw JSON string for the ping pong kripke structure. + static let raw = """ + { + "edges" : [ + { + "currentState" : "Initial", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + } + } + ], + { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 1, + "exponent" : -6 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "write" : { + + } + } + } + } + ], + { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "write" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + } + } + ], + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "write" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + } + }, + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + } + } + ], + { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + } + }, + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + } + } + ], + { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 1, + "exponent" : -6 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + } + } + ], + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + } + }, + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 0, + "exponent" : 0 + } + }, + "target" : { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + } + } + ], + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 1, + "exponent" : -6 + } + }, + "target" : { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "write" : { + + } + } + } + } + ], + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 1, + "exponent" : -6 + } + }, + "target" : { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + } + } + ], + { + "currentState" : "Initial", + "executeOnEntry" : true, + "nextState" : "Initial", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + [ + { + "cost" : { + "energy" : { + "coefficient" : 0, + "exponent" : 0 + }, + "time" : { + "coefficient" : 1, + "exponent" : -6 + } + }, + "target" : { + "currentState" : "Initial", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + } + } + ] + ], + "initialStates" : [ + { + "currentState" : "Initial", + "executeOnEntry" : true, + "nextState" : "Initial", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + } + ], + "nodes" : [ + { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + }, + { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + }, + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "write" : { + + } + } + }, + { + "currentState" : "Initial", + "executeOnEntry" : true, + "nextState" : "Initial", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'1'", + "ping", + "'1'", + "pong", + "'1'" + ], + "type" : { + "write" : { + + } + } + }, + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + }, + { + "currentState" : "WaitForPong", + "executeOnEntry" : true, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'1'", + "PingMachine_pong", + "'0'", + "ping", + "'1'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + { + "currentState" : "Initial", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "write" : { + + } + } + }, + { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'0'", + "ping", + "'0'", + "pong", + "'0'" + ], + "type" : { + "read" : { + + } + } + }, + { + "currentState" : "SendPing", + "executeOnEntry" : true, + "nextState" : "SendPing", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + }, + { + "currentState" : "WaitForPong", + "executeOnEntry" : false, + "nextState" : "WaitForPong", + "properties" : [ + "PingMachine_ping", + "'0'", + "PingMachine_pong", + "'1'", + "ping", + "'0'", + "pong", + "'1'" + ], + "type" : { + "read" : { + + } + } + } + ] + } + """ + +} + +// swiftlint:enable file_length diff --git a/Tests/TestHelpers/Machine+pingMachine.swift b/Tests/TestHelpers/Machine+pingMachine.swift index 6b1a95b..e812351 100644 --- a/Tests/TestHelpers/Machine+pingMachine.swift +++ b/Tests/TestHelpers/Machine+pingMachine.swift @@ -67,7 +67,7 @@ public extension Machine { PortSignal(type: .stdLogic, name: .ping, mode: .output), PortSignal(type: .stdLogic, name: .pong, mode: .input) ], - clocks: [Clock(name: .clk, frequency: 125, unit: .MHz)], + clocks: [Clock(name: .clk, frequency: 5, unit: .MHz)], drivingClock: 0, machineSignals: [], isParameterised: false, diff --git a/Tests/TestHelpers/MachineModel+pingMachine.swift b/Tests/TestHelpers/MachineModel+pingMachine.swift index 432a2bf..927689b 100644 --- a/Tests/TestHelpers/MachineModel+pingMachine.swift +++ b/Tests/TestHelpers/MachineModel+pingMachine.swift @@ -152,7 +152,7 @@ public extension MachineModel { ) ], initialState: "Initial", - clocks: [ClockModel(name: "clk", frequency: "125 MHz")] + clocks: [ClockModel(name: "clk", frequency: "5 MHz")] ) }