├── .gitignore ├── src ├── optim.rs ├── network.rs ├── main.rs ├── io │ ├── utils.rs │ ├── patterns.rs │ ├── bench.rs │ └── blif.rs ├── optim │ ├── infer_gates.rs │ └── share_logic.rs ├── io.rs ├── sim │ ├── incremental_sim.rs │ ├── fault.rs │ └── simple_sim.rs ├── network │ ├── generators.rs │ ├── area.rs │ ├── stats.rs │ ├── signal.rs │ ├── matcher.rs │ └── network.rs ├── lib.rs ├── cmd.rs ├── sim.rs └── atpg.rs ├── scripts ├── download_benchmarks.sh ├── run_atpg.sh ├── run_optimize.sh └── run_convert.sh ├── CITATION.cff ├── Cargo.toml ├── LICENSE-MIT ├── .github └── workflows │ ├── benchmarks.yml │ └── build.yml ├── nix └── patches │ └── rustsat-kissat.patch ├── flake.nix ├── TODO.md ├── flake.lock ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | benchmarks/ 4 | *.bench 5 | *.test 6 | -------------------------------------------------------------------------------- /src/optim.rs: -------------------------------------------------------------------------------- 1 | //! Optimization of logic networks 2 | 3 | mod infer_gates; 4 | mod share_logic; 5 | 6 | pub use infer_gates::{infer_dffe, infer_xor_mux}; 7 | pub use share_logic::share_logic; 8 | -------------------------------------------------------------------------------- /scripts/download_benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Obtain a compressed version of the benchmarks 3 | 4 | wget -qN https://github.com/Coloquinte/LogicBenchmarks/releases/download/v1.0.1/logic_benchmarks.zip 5 | mkdir benchmarks 6 | unzip logic_benchmarks.zip -d benchmarks 7 | -------------------------------------------------------------------------------- /src/network.rs: -------------------------------------------------------------------------------- 1 | //! Representation and handling of logic networks 2 | 3 | pub mod area; 4 | mod gates; 5 | pub mod generators; 6 | pub mod matcher; 7 | mod network; 8 | mod signal; 9 | pub mod stats; 10 | 11 | pub use gates::{BinaryType, Gate, NaryType, TernaryType}; 12 | pub use network::Network; 13 | pub use signal::Signal; 14 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: Quaigh, a Logic Analysis Toolbox 3 | message: >- 4 | If you use this software and want to cite it, please use 5 | the citation below. 6 | type: software 7 | authors: 8 | - family-names: Gouvine 9 | given-names: Gabriel 10 | orcid: 0000-0003-3404-6659 11 | repository-code: https://github.com/Coloquinte/quaigh 12 | keywords: 13 | - Logic optimization 14 | - Test pattern generation 15 | - AIG 16 | - ATPG 17 | license: MIT or Apache-2.0 18 | date-released: 2023-08-10 19 | -------------------------------------------------------------------------------- /scripts/run_atpg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dirs="atpg" 4 | 5 | cd benchmarks 6 | 7 | for dir in $dirs 8 | do 9 | mkdir -p "${dir}" 10 | done 11 | 12 | for benchmark in bench/iscas85*.bench 13 | do 14 | name=$(basename "${benchmark}" .bench) 15 | echo "Running atpg on ${name}" 16 | output_file="atpg/${name}.test" 17 | quaigh atpg "${benchmark}" -o "${output_file}" || { echo "ATPG failure on ${name}"; exit 1; } 18 | determinism_output_file="atpg/${name}_check.test" 19 | quaigh atpg "${benchmark}" -o "${determinism_output_file}" || { echo "ATPG failure on ${name}"; exit 1; } 20 | diff "${output_file}" "${determinism_output_file}" || { echo "ATPG determinism failure on ${name}"; exit 1; } 21 | done 22 | 23 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! Binary for Quaigh 2 | 3 | #![warn(missing_docs)] 4 | 5 | mod cmd; 6 | 7 | pub mod atpg; 8 | pub mod equiv; 9 | pub mod io; 10 | pub mod network; 11 | pub mod optim; 12 | pub mod sim; 13 | 14 | use clap::Parser; 15 | pub use network::{Gate, NaryType, Network, Signal}; 16 | 17 | #[doc(hidden)] 18 | fn main() { 19 | let cli = cmd::Cli::parse(); 20 | 21 | match cli.command { 22 | cmd::Commands::CheckEquivalence(a) => a.run(), 23 | cmd::Commands::Optimize(a) => a.run(), 24 | cmd::Commands::Show(a) => a.run(), 25 | cmd::Commands::Simulate(a) => a.run(), 26 | cmd::Commands::Atpg(a) => a.run(), 27 | cmd::Commands::AtpgReport(a) => a.run(), 28 | cmd::Commands::Convert(a) => a.run(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quaigh" 3 | version = "0.0.6" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | authors = ["Gabriel Gouvine "] 9 | description = "Logic optimization library" 10 | license = "MIT OR Apache-2.0" 11 | keywords = ["logic-optimization", "AIG", "ATPG", "EDA"] 12 | repository = "https://github.com/Coloquinte/quaigh" 13 | homepage = "https://github.com/Coloquinte/quaigh" 14 | categories = ["mathematics", "algorithms"] 15 | 16 | [dependencies] 17 | rustsat-kissat = "0.1" 18 | rustsat = "0.4" 19 | volute = "1.1.3" 20 | clap = { version = "4.4", features = ["derive"] } 21 | rand = { version = "0.8.5", features = ["small_rng"] } 22 | itertools = "0.12.0" 23 | fxhash = "0.2.1" 24 | kdam = { version = "0.5", features = ["template"] } 25 | 26 | -------------------------------------------------------------------------------- /scripts/run_optimize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dirs="opt" 4 | 5 | cd benchmarks 6 | 7 | for dir in $dirs 8 | do 9 | mkdir -p "${dir}" 10 | done 11 | 12 | for benchmark in bench/iscas*.bench 13 | do 14 | name=$(basename "${benchmark}" .bench) 15 | echo "Running benchmark ${name}" 16 | output_file="opt/${name}.bench" 17 | quaigh opt "${benchmark}" -o "${output_file}" || { echo "Optimization failure on ${name}"; exit 1; } 18 | determinism_output_file="opt/${name}_check.bench" 19 | quaigh opt "${benchmark}" -o "${determinism_output_file}" || { echo "Optimization failure on ${name}"; exit 1; } 20 | diff "${output_file}" "${determinism_output_file}" || { echo "Optimization determinism failure on ${name}"; exit 1; } 21 | echo "Initial stats:" 22 | quaigh show "${benchmark}" 23 | echo "Final stats:" 24 | quaigh show "${output_file}" 25 | echo -ne '\tOptimization done\n\t' 26 | quaigh equiv "${benchmark}" "${output_file}" -c 5 || { echo "Equivalence failure on ${name}"; exit 1; } 27 | done 28 | -------------------------------------------------------------------------------- /scripts/run_convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dirs="convert" 4 | 5 | cd benchmarks 6 | 7 | for dir in $dirs 8 | do 9 | mkdir -p "${dir}" 10 | done 11 | 12 | for benchmark in bench/iscas*.bench 13 | do 14 | name=$(basename "${benchmark}" .bench) 15 | echo "Running conversion of ${name} from .bench to .blif" 16 | output_file="convert/${name}.blif" 17 | quaigh convert "${benchmark}" "${output_file}" || { echo "Conversion failure on ${name}"; exit 1; } 18 | echo -ne '\tConversion done\n\t' 19 | quaigh equiv "${benchmark}" "${output_file}" -c 5 || { echo "Equivalence failure on ${name}"; exit 1; } 20 | done 21 | 22 | for benchmark in blif/iscas*.blif 23 | do 24 | name=$(basename "${benchmark}" .blif) 25 | echo "Running conversion of ${name} from .blif to .bench" 26 | output_file="convert/${name}.bench" 27 | quaigh convert "${benchmark}" "${output_file}" || { echo "Conversion failure on ${name}"; exit 1; } 28 | echo -ne '\tConversion done\n\t' 29 | quaigh equiv "${benchmark}" "${output_file}" -c 5 || { echo "Equivalence failure on ${name}"; exit 1; } 30 | done 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/workflows/benchmarks.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: [ "*" ] 5 | pull_request: 6 | branches: [ "*" ] 7 | 8 | 9 | name: Benchmarks 10 | 11 | jobs: 12 | benchmarks: 13 | name: Run 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest] 17 | features: [""] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup Rust 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | 30 | - name: Install 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: install 34 | args: ${{ matrix.features }} --path . 35 | 36 | - name: Download benchmarks 37 | run: bash scripts/download_benchmarks.sh 38 | 39 | - name: Run optimization 40 | run: bash scripts/run_optimize.sh 41 | 42 | - name: Run file format conversion 43 | run: bash scripts/run_convert.sh 44 | 45 | - name: Run test pattern generation 46 | run: bash scripts/run_atpg.sh 47 | -------------------------------------------------------------------------------- /src/io/utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use crate::{Gate, Network, Signal}; 4 | 5 | /// Ad-hoc to_string function to represent signals in bench files 6 | pub fn sig_to_string(s: &Signal) -> String { 7 | if *s == Signal::one() { 8 | return "vdd".to_string(); 9 | } 10 | if *s == Signal::zero() { 11 | return "gnd".to_string(); 12 | } 13 | s.without_inversion().to_string() + (if s.is_inverted() { "_n" } else { "" }) 14 | } 15 | 16 | /// Find the set of signals that are used inverted 17 | pub fn get_inverted_signals(aig: &Network) -> Vec { 18 | // Generate signals where the inversion is required 19 | let mut signals_with_inv = HashSet::new(); 20 | for o in 0..aig.nb_outputs() { 21 | let s = aig.output(o); 22 | if s.is_inverted() && !s.is_constant() { 23 | signals_with_inv.insert(!s); 24 | } 25 | } 26 | for i in 0..aig.nb_nodes() { 27 | if matches!(aig.gate(i), Gate::Buf(_)) { 28 | // Buf(!x) is exported directly as a Not 29 | continue; 30 | } 31 | for s in aig.gate(i).dependencies() { 32 | if s.is_inverted() && !s.is_constant() { 33 | signals_with_inv.insert(!s); 34 | } 35 | } 36 | } 37 | let mut signals_with_inv = signals_with_inv.into_iter().collect::>(); 38 | signals_with_inv.sort(); 39 | signals_with_inv 40 | } 41 | -------------------------------------------------------------------------------- /nix/patches/rustsat-kissat.patch: -------------------------------------------------------------------------------- 1 | diff --git a/build.rs b/build.rs 2 | index 3199616..5e6777b 100644 3 | --- a/build.rs 4 | +++ b/build.rs 5 | @@ -9,38 +9,12 @@ use std::{ 6 | }; 7 | 8 | fn main() { 9 | - if std::env::var("DOCS_RS").is_ok() { 10 | - // don't build c library on docs.rs due to network restrictions 11 | - return; 12 | - } 13 | - 14 | - // Select commit based on features. If conflict, always choose newest release 15 | - let tag = if cfg!(feature = "v3-1-1") { 16 | - "refs/tags/rel-3.1.1" 17 | - } else if cfg!(feature = "v3-1-0") { 18 | - "refs/tags/rel-3.1.0" 19 | - } else if cfg!(feature = "v3-0-0") { 20 | - "refs/tags/rel-3.0.0" 21 | - } else if cfg!(feature = "sc2022-light") { 22 | - "refs/tags/sc2022-light" 23 | - } else if cfg!(feature = "sc2022-hyper") { 24 | - "refs/tags/sc2022-hyper" 25 | - } else if cfg!(feature = "sc2022-bulky") { 26 | - "refs/tags/sc2022-bulky" 27 | - } else { 28 | - // default to newest version 29 | - "refs/tags/rel-3.1.1" 30 | - }; 31 | - 32 | - // Build C library 33 | - // Full commit hash needs to be provided 34 | - build("https://github.com/arminbiere/kissat.git", "master", tag); 35 | - 36 | - let out_dir = env::var("OUT_DIR").unwrap(); 37 | + let out_dir = env::var("NIX_KISSAT_DIR").unwrap(); 38 | 39 | // Built solver is in out_dir 40 | println!("cargo:rustc-link-search={}", out_dir); 41 | println!("cargo:rustc-link-search={}/lib", out_dir); 42 | + println!("cargo:rustc-link-lib=kissat"); 43 | } 44 | 45 | fn build(repo: &str, branch: &str, reference: &str) { 46 | -------------------------------------------------------------------------------- /src/optim/infer_gates.rs: -------------------------------------------------------------------------------- 1 | //! Infer Xor and Mux gates from And gates 2 | 3 | use crate::network::matcher::Matcher; 4 | use crate::{Gate, Network, Signal}; 5 | 6 | fn mux_pattern() -> Network { 7 | let mut pattern = Network::new(); 8 | let s = pattern.add_input(); 9 | let a = pattern.add_input(); 10 | let b = pattern.add_input(); 11 | let x0 = pattern.add(Gate::and(s, !a)); 12 | let x1 = pattern.add(Gate::and(!s, !b)); 13 | let o = pattern.add(Gate::and(!x0, !x1)); 14 | pattern.add_output(o); 15 | pattern 16 | } 17 | 18 | /// Rebuild Xor and Mux gates from And gates 19 | pub fn infer_xor_mux(aig: &mut Network) { 20 | let mut ret = aig.clone(); 21 | 22 | let pattern = mux_pattern(); 23 | let mut matcher = Matcher::from_pattern(&pattern); 24 | for i in 0..ret.nb_nodes() { 25 | if let Some(v) = matcher.matches(&ret, i) { 26 | ret.replace(i, Gate::mux(v[0], v[1], v[2])); 27 | } 28 | } 29 | ret.cleanup(); 30 | ret.make_canonical(); 31 | *aig = ret; 32 | } 33 | 34 | fn dffe_pattern() -> Network { 35 | let mut pattern = Network::new(); 36 | let d = pattern.add_input(); 37 | let en = pattern.add_input(); 38 | let var = Signal::from_var(1); 39 | let mx = pattern.add(Gate::mux(en, d, var)); 40 | let q = pattern.add(Gate::dff(mx, Signal::one(), Signal::zero())); 41 | pattern.add_output(q); 42 | assert_eq!(q, var); 43 | pattern 44 | } 45 | 46 | /// Rebuild Dffe from Mux gates 47 | pub fn infer_dffe(aig: &mut Network) { 48 | let mut ret = aig.clone(); 49 | 50 | let pattern = dffe_pattern(); 51 | let mut matcher = Matcher::from_pattern(&pattern); 52 | for i in 0..ret.nb_nodes() { 53 | if let Some(v) = matcher.matches(&ret, i) { 54 | ret.replace(i, Gate::dff(v[0], v[1], Signal::zero())); 55 | } 56 | } 57 | ret.cleanup(); 58 | ret.make_canonical(); 59 | *aig = ret; 60 | } 61 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; 4 | rust-overlay = { 5 | url = "github:oxalica/rust-overlay/master"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | cargo2nix = { 9 | url = "github:cargo2nix/cargo2nix/release-0.12"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | inputs.rust-overlay.follows = "rust-overlay"; 12 | }; 13 | }; 14 | 15 | outputs = { 16 | self, 17 | nixpkgs, 18 | cargo2nix, 19 | ... 20 | }: { 21 | overlays = { 22 | default = pkgs': pkgs: { 23 | quaigh = pkgs'.rustPkgs.workspace.quaigh {}; 24 | rustPkgs = pkgs'.rustBuilder.makePackageSet { 25 | rustVersion = "1.86.0"; 26 | # You can regenerate Cargo.nix using this command: 27 | # nix run github:cargo2nix/cargo2nix/v0.12.0 28 | packageFun = import ./Cargo.nix; 29 | 30 | packageOverrides = pkgs: 31 | pkgs.rustBuilder.overrides.all 32 | ++ [ 33 | (pkgs.rustBuilder.rustLib.makeOverride { 34 | name = "rustsat-kissat"; 35 | overrideAttrs = { 36 | buildInputs = 37 | [ 38 | pkgs.kissat 39 | ] 40 | ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ 41 | pkgs.openssl 42 | ]; 43 | patches = [ 44 | ./nix/patches/rustsat-kissat.patch 45 | ]; 46 | NIX_KISSAT_DIR = "${pkgs.kissat.lib}"; 47 | }; 48 | }) 49 | ]; 50 | }; 51 | }; 52 | }; 53 | 54 | legacyPackages = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"] ( 55 | system: 56 | import nixpkgs { 57 | inherit system; 58 | overlays = [cargo2nix.overlays.default self.overlays.default]; 59 | } 60 | ); 61 | packages = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"] (system: { 62 | inherit (self.legacyPackages."${system}") quaigh; 63 | default = self.legacyPackages."${system}".quaigh; 64 | }); 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | This file lists useful developments ideas for Quaigh. 3 | 4 | 5 | # Optimization and transformations 6 | 7 | ## 2-level simplification 8 | 9 | 2-level simplification of Sum-of-Products and Exclusive-Sum-of-Products is very common and useful. 10 | Ideally this would take the sharing of And gates into account, which is not usually done. 11 | 12 | Types of 2-level simplification to handle: 13 | * Sum of Products (SoP): most typical 14 | * Exclusive Sum of Products (ESoP): common too 15 | * Sum of Exclusive Sums (SoES): not a general model, but would be useful 16 | 17 | Exact algorithms: 18 | * For SoP and SoES: enumerate maximum cubes, then solve a variant of minimum set cover problem 19 | * For ESoP, enumerate all cubes, then solve a xor-constrained minimization problem 20 | 21 | ## Local rewriting 22 | 23 | The typical approach to local rewriting is with a dictionary of "optimal" 4-input or 5-input functions. 24 | I'd like multi-output local rewriting instead, using a low-depth dictionary of common functions. 25 | 26 | ## AIG/MIG transformation 27 | 28 | Simple transformation to go back to an And-based or Mux-based view. 29 | 30 | 31 | # Technology mapping 32 | 33 | ## Cut enumeration for FPGAs 34 | 35 | Cut enumeration is necessary for any FPGA techmapping. I'd like ours to go a bit further and include Dff in the Cuts. 36 | 37 | ## Techmapping API 38 | 39 | Technology mapping is "just" a question of dependencies between cuts, which each have their own area and delay. 40 | Solving it can be almost completely separate from cut enumeration. 41 | 42 | This paves the way for additional optimizations: 43 | * techmapping for FPGA and ASIC can be shared 44 | * multiple choices can be exposed without having them in the logic: N-input gates can be cut a number of ways 45 | 46 | 47 | # Test pattern generation 48 | 49 | ## Path activation 50 | 51 | We want to be able to activate whole critical paths. This is a bit more complicated to grasp. 52 | 53 | ## Faster simulation 54 | 55 | The current simulation is OK but basic. 56 | A transformation to an And or Mux graph and a single array for indexing would go a long way to make it faster. 57 | On the other hand this requires an additional translation layer. 58 | 59 | ## Connected components 60 | 61 | Partition the circuit in order to handle disjoint parts separately. 62 | 63 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | //! Read and write logic networks to files 2 | 3 | mod bench; 4 | mod blif; 5 | mod patterns; 6 | mod utils; 7 | 8 | use std::fs::File; 9 | use std::path::PathBuf; 10 | 11 | pub use bench::{read_bench, write_bench}; 12 | pub use blif::{read_blif, write_blif}; 13 | pub use patterns::{read_patterns, write_patterns}; 14 | 15 | use crate::Network; 16 | 17 | /// Read a logic network from a file 18 | /// 19 | /// .bench and .blif formats are supported, with limitations to the .blif format support 20 | pub fn read_network_file(path: &PathBuf) -> Network { 21 | let ext = path.extension(); 22 | let f = File::open(path).unwrap(); 23 | match ext { 24 | None => panic!("No extension given"), 25 | Some(s) => { 26 | if s == "bench" { 27 | read_bench(f).unwrap() 28 | } else if s == "blif" { 29 | read_blif(f).unwrap() 30 | } else { 31 | panic!("Unknown extension {}", s.to_string_lossy()); 32 | } 33 | } 34 | } 35 | } 36 | 37 | /// Write a logic network to a file 38 | /// 39 | /// .bench and .blif formats are supported 40 | pub fn write_network_file(path: &PathBuf, aig: &Network) { 41 | let ext = path.extension(); 42 | match ext { 43 | None => panic!("No extension given"), 44 | Some(s) => { 45 | let mut f = File::create(path).unwrap(); 46 | if s == "bench" { 47 | write_bench(&mut f, aig); 48 | } else if s == "blif" { 49 | write_blif(&mut f, aig); 50 | } else { 51 | panic!("Unknown extension {}", s.to_string_lossy()); 52 | } 53 | } 54 | } 55 | } 56 | 57 | /// Read patterns from a file 58 | /// 59 | /// Each pattern may contain multiple timesteps. For each timestep, the value of each circuit input is given. 60 | pub fn read_pattern_file(path: &PathBuf) -> Vec>> { 61 | let f = File::open(path).unwrap(); 62 | read_patterns(f).unwrap() 63 | } 64 | 65 | /// Write patterns to a file 66 | /// 67 | /// Each pattern may contain multiple timesteps. For each timestep, the value of each circuit input is given. 68 | pub fn write_pattern_file(path: &PathBuf, patterns: &Vec>>) { 69 | let mut f = File::create(path).unwrap(); 70 | write_patterns(&mut f, patterns); 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: [ "*" ] 5 | pull_request: 6 | branches: [ "*" ] 7 | 8 | 9 | name: Test 10 | 11 | jobs: 12 | build: 13 | name: Test/Cargo 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest] 17 | features: [""] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup Rust 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | 30 | - name: Install dependencies 31 | run: rustup component add rustfmt 32 | 33 | - name: Check 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: check 37 | 38 | - name: Test 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: test 42 | 43 | - name: Format 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: fmt 47 | args: --all -- --check 48 | 49 | - name: Readme 50 | run: | 51 | cargo install cargo-rdme 52 | cargo rdme --check 53 | nix: 54 | name: Test/Nix (${{ matrix.system.nix }}) 55 | strategy: 56 | matrix: 57 | system: 58 | [ 59 | { gha: ubuntu-latest, nix: "x86_64-linux" }, 60 | { gha: macos-13, nix: "x86_64-darwin" }, 61 | { gha: macos-14, nix: "aarch64-darwin" }, 62 | ] 63 | runs-on: ${{ matrix.system.gha }} 64 | steps: 65 | - name: Check out Git repository 66 | uses: actions/checkout@v3 67 | - uses: DeterminateSystems/nix-installer-action@main 68 | - uses: DeterminateSystems/magic-nix-cache-action@main 69 | - run: nix build 70 | clippy: 71 | name: Clippy 72 | runs-on: ubuntu-latest 73 | permissions: 74 | checks: write 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: actions-rs/toolchain@v1 78 | with: 79 | toolchain: stable 80 | components: clippy 81 | override: true 82 | - uses: actions-rs/clippy-check@v1 83 | with: 84 | token: ${{ secrets.GITHUB_TOKEN }} 85 | args: --all-features 86 | name: Clippy Output 87 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "cargo2nix": { 4 | "inputs": { 5 | "flake-compat": "flake-compat", 6 | "flake-utils": "flake-utils", 7 | "nixpkgs": [ 8 | "nixpkgs" 9 | ], 10 | "rust-overlay": [ 11 | "rust-overlay" 12 | ] 13 | }, 14 | "locked": { 15 | "lastModified": 1750364353, 16 | "narHash": "sha256-l06DIwnB4JHwP1isUUXk85F+AHQkUSUyAWnAmRxXICg=", 17 | "owner": "cargo2nix", 18 | "repo": "cargo2nix", 19 | "rev": "a709c74619e1a2b68ed12bb398e12fbe29d69657", 20 | "type": "github" 21 | }, 22 | "original": { 23 | "owner": "cargo2nix", 24 | "ref": "release-0.12", 25 | "repo": "cargo2nix", 26 | "type": "github" 27 | } 28 | }, 29 | "flake-compat": { 30 | "flake": false, 31 | "locked": { 32 | "lastModified": 1696426674, 33 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 34 | "owner": "edolstra", 35 | "repo": "flake-compat", 36 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 37 | "type": "github" 38 | }, 39 | "original": { 40 | "owner": "edolstra", 41 | "repo": "flake-compat", 42 | "type": "github" 43 | } 44 | }, 45 | "flake-utils": { 46 | "inputs": { 47 | "systems": "systems" 48 | }, 49 | "locked": { 50 | "lastModified": 1694529238, 51 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 52 | "owner": "numtide", 53 | "repo": "flake-utils", 54 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 55 | "type": "github" 56 | }, 57 | "original": { 58 | "owner": "numtide", 59 | "repo": "flake-utils", 60 | "type": "github" 61 | } 62 | }, 63 | "nixpkgs": { 64 | "locked": { 65 | "lastModified": 1751211869, 66 | "narHash": "sha256-1Cu92i1KSPbhPCKxoiVG5qnoRiKTgR5CcGSRyLpOd7Y=", 67 | "owner": "nixos", 68 | "repo": "nixpkgs", 69 | "rev": "b43c397f6c213918d6cfe6e3550abfe79b5d1c51", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "nixos", 74 | "ref": "nixos-25.05", 75 | "repo": "nixpkgs", 76 | "type": "github" 77 | } 78 | }, 79 | "root": { 80 | "inputs": { 81 | "cargo2nix": "cargo2nix", 82 | "nixpkgs": "nixpkgs", 83 | "rust-overlay": "rust-overlay" 84 | } 85 | }, 86 | "rust-overlay": { 87 | "inputs": { 88 | "nixpkgs": [ 89 | "nixpkgs" 90 | ] 91 | }, 92 | "locked": { 93 | "lastModified": 1751338093, 94 | "narHash": "sha256-/yd9nPcTfUZPFtwjRbdB5yGLdt3LTPqz6Ja63Joiahs=", 95 | "owner": "oxalica", 96 | "repo": "rust-overlay", 97 | "rev": "6cfb7821732dac2d3e2dea857a5613d3b856c20c", 98 | "type": "github" 99 | }, 100 | "original": { 101 | "owner": "oxalica", 102 | "ref": "master", 103 | "repo": "rust-overlay", 104 | "type": "github" 105 | } 106 | }, 107 | "systems": { 108 | "locked": { 109 | "lastModified": 1681028828, 110 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 111 | "owner": "nix-systems", 112 | "repo": "default", 113 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 114 | "type": "github" 115 | }, 116 | "original": { 117 | "owner": "nix-systems", 118 | "repo": "default", 119 | "type": "github" 120 | } 121 | } 122 | }, 123 | "root": "root", 124 | "version": 7 125 | } 126 | -------------------------------------------------------------------------------- /src/sim/incremental_sim.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Reverse; 2 | use std::collections::BinaryHeap; 3 | 4 | use crate::network::stats; 5 | use crate::Network; 6 | 7 | use super::simple_sim::SimpleSimulator; 8 | use super::Fault; 9 | 10 | /// Structure for simulation that only touches the values that were modified 11 | pub struct IncrementalSimulator<'a> { 12 | /// Whether a gate is an output 13 | is_output: Vec, 14 | /// Gates that use each gate 15 | gate_users: Vec>, 16 | /// Simple simulator for the initial simulation 17 | sim: SimpleSimulator<'a>, 18 | /// Simulator that will be updated incrementally 19 | incr_sim: SimpleSimulator<'a>, 20 | /// Queue of nodes to update, lowest index first 21 | update_queue: BinaryHeap>, 22 | /// List of modified value 23 | touched_gates: Vec, 24 | /// Whether each value is on the queue 25 | is_touched: Vec, 26 | } 27 | 28 | impl<'a> IncrementalSimulator<'a> { 29 | /// Build a simulator by capturing a network 30 | pub fn from_aig(aig: &'a Network) -> IncrementalSimulator<'a> { 31 | assert!(aig.is_topo_sorted()); 32 | let sim = SimpleSimulator::from_aig(aig); 33 | let incr_sim = sim.clone(); 34 | IncrementalSimulator { 35 | is_output: stats::gate_is_output(aig), 36 | gate_users: stats::gate_users(aig), 37 | sim, 38 | incr_sim, 39 | update_queue: BinaryHeap::new(), 40 | touched_gates: Vec::new(), 41 | is_touched: vec![false; aig.nb_nodes()], 42 | } 43 | } 44 | 45 | /// Reset the state of the simulator 46 | fn reset(&mut self) { 47 | for v in &self.touched_gates { 48 | self.incr_sim.node_values[*v] = self.sim.node_values[*v]; 49 | self.is_touched[*v] = false; 50 | } 51 | assert!(self.update_queue.is_empty()); 52 | self.touched_gates.clear(); 53 | } 54 | 55 | /// Run the simulation from a fault 56 | pub fn run_initial(&mut self, input_values: &Vec) { 57 | self.sim.reset(); 58 | self.sim.copy_inputs(input_values); 59 | self.sim.run_comb(); 60 | self.incr_sim = self.sim.clone(); 61 | } 62 | 63 | /// Update a single gate 64 | fn update_gate(&mut self, i: usize, value: u64) { 65 | let old_val = self.incr_sim.node_values[i]; 66 | if old_val == value { 67 | return; 68 | } 69 | if !self.is_touched[i] { 70 | // Check it explicitly for the first gate 71 | self.is_touched[i] = true; 72 | self.touched_gates.push(i); 73 | } 74 | self.incr_sim.node_values[i] = value; 75 | for &j in &self.gate_users[i] { 76 | if !self.is_touched[j] { 77 | self.is_touched[j] = true; 78 | self.update_queue.push(Reverse(j)); 79 | self.touched_gates.push(j); 80 | } 81 | } 82 | } 83 | 84 | /// Run the simulation from a fault 85 | fn run_incremental(&mut self, fault: Fault) { 86 | match fault { 87 | Fault::OutputStuckAtFault { gate, value } => { 88 | self.update_gate(gate, if value { !0 } else { 0 }); 89 | } 90 | Fault::InputStuckAtFault { gate, input, value } => { 91 | let value = self.incr_sim.run_gate_with_input_stuck(gate, input, value); 92 | self.update_gate(gate, value); 93 | } 94 | } 95 | while let Some(Reverse(i)) = self.update_queue.pop() { 96 | let v = self.incr_sim.run_gate(i); 97 | self.update_gate(i, v); 98 | } 99 | } 100 | 101 | /// Whether an output has been modified by the incremental run 102 | fn output_modified(&self) -> u64 { 103 | let mut ret = 0; 104 | for i in &self.touched_gates { 105 | if self.is_output[*i] { 106 | ret |= self.incr_sim.node_values[*i] ^ self.sim.node_values[*i]; 107 | } 108 | } 109 | ret 110 | } 111 | 112 | /// Whether the given fault is detected by the pattern 113 | pub fn detects_fault(&mut self, fault: Fault) -> u64 { 114 | self.run_incremental(fault); 115 | let ret = self.output_modified(); 116 | self.reset(); 117 | ret 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Quaigh crate](https://img.shields.io/crates/v/quaigh.svg)](https://crates.io/crates/quaigh) 2 | [![Quaigh documentation](https://docs.rs/quaigh/badge.svg)](https://docs.rs/quaigh) 3 | [![Build status](https://github.com/Coloquinte/quaigh/actions/workflows/build.yml/badge.svg)](https://github.com/Coloquinte/quaigh/actions/workflows/build.yml) 4 | 5 | # Quaigh 6 | 7 | 8 | 9 | Logic simplification and analysis tools 10 | 11 | This crate provides tools for logic optimization, synthesis, technology mapping and analysis. 12 | Our goal is to provide an easy-to-use library, and improve its quality over time to match industrial tools. 13 | 14 | ## Usage and features 15 | 16 | Quaigh provides a command line tool, that can be installed using 17 | [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html): 18 | `cargo install quaigh`. 19 | 20 | To show available commands: 21 | ```bash 22 | quaigh help 23 | ``` 24 | 25 | The `atpg` command performs [automatic test pattern generation](https://en.wikipedia.org/wiki/Automatic_test_pattern_generation), 26 | to create test vectors for a design. 27 | ```bash 28 | quaigh atpg mydesign.bench -o atpg.test 29 | ``` 30 | 31 | The `check-equivalence` command performs bounded [equivalence checking](https://en.wikipedia.org/wiki/Formal_equivalence_checking) 32 | to confirm that a design's functionality is preserved after transformations. 33 | ```bash 34 | quaigh equiv mydesign.bench optimized.bench 35 | ``` 36 | 37 | The `optimize` command performs [logic optimization](https://en.wikipedia.org/wiki/Logic_optimization). 38 | At the moment, logic optimization is far from state of the art: for production designs, you should 39 | generally stick to the tools included in [Yosys](https://github.com/YosysHQ/yosys). 40 | ```bash 41 | quaigh opt mydesign.bench -o optimized.bench 42 | ``` 43 | 44 | Quaigh supports a subset of the [Blif](https://course.ece.cmu.edu/~ee760/760docs/blif.pdf) file format, as well 45 | as the simple Bench file format used by ISCAS benchmarks. Benchmarks can be downloaded 46 | [here](https://github.com/Coloquinte/moosic-yosys-plugin/releases/download/iscas_benchmarks/benchmarks.tar.xz). 47 | More features will be added over time, such as technology mapping, operator optimization, ... 48 | The complete documentation is available on [docs.rs](https://docs.rs/crate/quaigh/latest). 49 | 50 | ## Development 51 | 52 | The main datastructure, [`Network`](https://docs.rs/quaigh/latest/quaigh/network/struct.Network.html), is a typical Gate-Inverter-Graph representation of a logic circuit. 53 | Inverters are implicit, occupying just one bit in [`Signal`](https://docs.rs/quaigh/latest/quaigh/network/struct.Signal.html). 54 | It supports many kinds of logic, and all can coexist in the same circuit: 55 | * Complex gates such as Xor, Mux and Maj3 are all first class citizens; 56 | * Flip-flops with enable and reset are represented directly. 57 | 58 | In most logic optimization libraries ([ABC](https://github.com/berkeley-abc/abc), [Mockturtle](https://github.com/lsils/mockturtle), ...), 59 | there are many different ways to represent logic, with separate datastructures: AIG, MIG, LUT, ... 60 | Depending on the circuit, one view or the other might be preferable. 61 | Taking advantage of them all may require [splitting the circuit](https://github.com/lnis-uofu/LSOracle), making most operations much more complex. 62 | More generic netlists, like [Yosys RTLIL](https://yosyshq.readthedocs.io/projects/yosys/en/latest/CHAPTER_Overview.html#the-rtl-intermediate-language-rtlil), 63 | will allow all kind of logic gates in a single datastructure. 64 | Since they do not restrict the functions represented, they are difficult to work with directly for logic optimization. 65 | 66 | Quaigh aims in-between. All algorithms share the same netlist representation, [`Network`](https://docs.rs/quaigh/latest/quaigh/network/struct.Network.html), 67 | but there are some limitations to make it easy to optimize: 68 | * all gates have a single output, representing a single binary value, 69 | * the gates are kept in topological order (a gate has an index higher than its inputs), 70 | * names and design hierarchy are not represented. 71 | 72 | For example, here is a full adder circuit: 73 | ```rust 74 | let mut net = Network::new(); 75 | let i0 = net.add_input(); 76 | let i1 = net.add_input(); 77 | let i2 = net.add_input(); 78 | let carry = net.add(Gate::maj(i0, i1, i2)); 79 | let out = net.add(Gate::xor3(i0, i1, i2)); 80 | net.add_output(carry); 81 | net.add_output(out); 82 | ``` 83 | 84 | Apart from the core datastructure, Quaigh has algorithms for [logic optimization](https://docs.rs/quaigh/latest/quaigh/optim/index.html), 85 | [simulation](https://docs.rs/quaigh/latest/quaigh/sim/index.html) (including fault simulation) and 86 | [test pattern generation](https://docs.rs/quaigh/latest/quaigh/atpg/index.html). 87 | For optimization and equivalence checking, Quaigh relies on other packages as much as possible: 88 | * [Kissat](https://github.com/arminbiere/kissat) (using [rustsat](https://docs.rs/rustsat/)) as a Sat solver, 89 | * [Highs](https://github.com/ERGO-Code/HiGHS) (using [good_lp](https://docs.rs/good_lp/)) as an optimization solver. 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/network/generators.rs: -------------------------------------------------------------------------------- 1 | //! Network generators and templates 2 | 3 | /// Adder generators 4 | pub mod adder { 5 | use crate::{Gate, Network, Signal}; 6 | 7 | /// A simple and slow ripple-carry adder 8 | pub fn ripple_carry(len: usize) -> Network { 9 | let mut ret = Network::new(); 10 | let mut c = Signal::zero(); 11 | for _ in 0..len { 12 | let a = ret.add_input(); 13 | let b = ret.add_input(); 14 | let next_c = ret.add(Gate::maj(a, b, c)); 15 | let o = ret.add(Gate::xor3(a, b, c)); 16 | ret.add_output(o); 17 | c = next_c; 18 | } 19 | ret.add_output(c); 20 | ret.check(); 21 | ret 22 | } 23 | } 24 | 25 | /// Carry chain generators 26 | pub mod carry_chain { 27 | use crate::{Network, Signal}; 28 | 29 | /// A simple and slow ripple-carry chain 30 | pub fn ripple_carry(len: usize) -> Network { 31 | let mut ret = Network::new(); 32 | let mut c = Signal::zero(); 33 | for _ in 0..len { 34 | let propagate = ret.add_input(); 35 | let generate = ret.add_input(); 36 | let d = ret.and(propagate, c); 37 | c = !ret.and(!generate, !d); 38 | ret.add_output(c); 39 | } 40 | ret 41 | } 42 | } 43 | 44 | /// Simple generators to test functionality 45 | pub mod testcases { 46 | use crate::{Network, Signal}; 47 | 48 | /// A circular chain of Dffs with a Xor with input at the start; used to test topological sorting 49 | pub fn toggle_chain(len: usize, has_en: bool, has_res: bool) -> Network { 50 | assert!(len > 0); 51 | let mut ret = Network::new(); 52 | let input = ret.add_input(); 53 | let en = if has_en { 54 | ret.add_input() 55 | } else { 56 | Signal::one() 57 | }; 58 | let res = if has_res { 59 | ret.add_input() 60 | } else { 61 | Signal::zero() 62 | }; 63 | let xor_output = Signal::from_var(len as u32); 64 | let mut x = input; 65 | for _ in 0..len { 66 | x = ret.dff(x, en, res); 67 | } 68 | x = ret.xor(x, input); 69 | ret.add_output(x); 70 | assert_eq!(x, xor_output); 71 | ret.check(); 72 | assert!(ret.is_topo_sorted()); 73 | ret 74 | } 75 | 76 | /// An expanding tree of Dffs, used to test deduplication 77 | pub fn ff_tree(depth: usize, has_en: bool, has_res: bool, expansion: usize) -> Network { 78 | let mut ret = Network::new(); 79 | let input = ret.add_input(); 80 | let en = if has_en { 81 | ret.add_input() 82 | } else { 83 | Signal::one() 84 | }; 85 | let res = if has_res { 86 | ret.add_input() 87 | } else { 88 | Signal::zero() 89 | }; 90 | let mut stage = vec![input]; 91 | for _ in 0..depth { 92 | let mut next_stage = Vec::new(); 93 | for s in stage { 94 | for _ in 0..expansion { 95 | next_stage.push(ret.dff(s, en, res)); 96 | } 97 | } 98 | stage = next_stage; 99 | } 100 | for s in stage { 101 | ret.add_output(s); 102 | } 103 | ret.check(); 104 | ret 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::{adder, carry_chain, testcases}; 111 | 112 | #[test] 113 | fn test_adder() { 114 | for i in [0, 1, 2, 4, 8, 16, 32, 64, 128] { 115 | adder::ripple_carry(i); 116 | } 117 | } 118 | 119 | #[test] 120 | fn test_carry_chain() { 121 | for i in [0, 1, 2, 4, 8, 16, 32, 64, 128] { 122 | carry_chain::ripple_carry(i); 123 | } 124 | } 125 | 126 | #[test] 127 | fn test_toggle_chain() { 128 | for i in [1, 2, 4, 8, 16, 32, 64, 128] { 129 | for has_en in [false, true] { 130 | for has_res in [false, true] { 131 | let mut aig = testcases::toggle_chain(i, has_en, has_res); 132 | assert_eq!(aig.nb_nodes(), i + 1); 133 | aig.cleanup(); 134 | aig.make_canonical(); 135 | assert_eq!(aig.nb_nodes(), i + 1); 136 | } 137 | } 138 | } 139 | } 140 | 141 | #[test] 142 | fn test_ff_tree() { 143 | for i in [0, 1, 2, 3, 4, 5] { 144 | for expansion in [1, 2] { 145 | for has_en in [false, true] { 146 | for has_res in [false, true] { 147 | let mut aig = testcases::ff_tree(i, has_en, has_res, expansion); 148 | aig.cleanup(); 149 | for _ in 0..i { 150 | // Run dedup several times, once per Dff level 151 | aig.make_canonical(); 152 | } 153 | assert_eq!(aig.nb_nodes(), i); 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Logic simplification and analysis tools 2 | //! 3 | //! This crate provides tools for logic optimization, synthesis, technology mapping and analysis. 4 | //! Our goal is to provide an easy-to-use library, and improve its quality over time to match industrial tools. 5 | //! 6 | //! # Usage and features 7 | //! 8 | //! Quaigh provides a command line tool, that can be installed using 9 | //! [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html): 10 | //! `cargo install quaigh`. 11 | //! 12 | //! To show available commands: 13 | //! ```bash 14 | //! quaigh help 15 | //! ``` 16 | //! 17 | //! The `atpg` command performs [automatic test pattern generation](https://en.wikipedia.org/wiki/Automatic_test_pattern_generation), 18 | //! to create test vectors for a design. 19 | //! ```bash 20 | //! quaigh atpg mydesign.bench -o atpg.test 21 | //! ``` 22 | //! 23 | //! The `check-equivalence` command performs bounded [equivalence checking](https://en.wikipedia.org/wiki/Formal_equivalence_checking) 24 | //! to confirm that a design's functionality is preserved after transformations. 25 | //! ```bash 26 | //! quaigh equiv mydesign.bench optimized.bench 27 | //! ``` 28 | //! 29 | //! The `optimize` command performs [logic optimization](https://en.wikipedia.org/wiki/Logic_optimization). 30 | //! At the moment, logic optimization is far from state of the art: for production designs, you should 31 | //! generally stick to the tools included in [Yosys](https://github.com/YosysHQ/yosys). 32 | //! ```bash 33 | //! quaigh opt mydesign.bench -o optimized.bench 34 | //! ``` 35 | //! 36 | //! Quaigh supports a subset of the [Blif](https://course.ece.cmu.edu/~ee760/760docs/blif.pdf) file format, as well 37 | //! as the simple Bench file format used by ISCAS benchmarks. Benchmarks can be downloaded 38 | //! [here](https://github.com/Coloquinte/moosic-yosys-plugin/releases/download/iscas_benchmarks/benchmarks.tar.xz). 39 | //! More features will be added over time, such as technology mapping, operator optimization, ... 40 | //! The complete documentation is available on [docs.rs](https://docs.rs/crate/quaigh/latest). 41 | //! 42 | //! # Development 43 | //! 44 | //! The main datastructure, [`Network`](https://docs.rs/quaigh/latest/quaigh/network/struct.Network.html), is a typical Gate-Inverter-Graph representation of a logic circuit. 45 | //! Inverters are implicit, occupying just one bit in [`Signal`](https://docs.rs/quaigh/latest/quaigh/network/struct.Signal.html). 46 | //! It supports many kinds of logic, and all can coexist in the same circuit: 47 | //! * Complex gates such as Xor, Mux and Maj3 are all first class citizens; 48 | //! * Flip-flops with enable and reset are represented directly. 49 | //! 50 | //! In most logic optimization libraries ([ABC](https://github.com/berkeley-abc/abc), [Mockturtle](https://github.com/lsils/mockturtle), ...), 51 | //! there are many different ways to represent logic, with separate datastructures: AIG, MIG, LUT, ... 52 | //! Depending on the circuit, one view or the other might be preferable. 53 | //! Taking advantage of them all may require [splitting the circuit](https://github.com/lnis-uofu/LSOracle), making most operations much more complex. 54 | //! More generic netlists, like [Yosys RTLIL](https://yosyshq.readthedocs.io/projects/yosys/en/latest/CHAPTER_Overview.html#the-rtl-intermediate-language-rtlil), 55 | //! will allow all kind of logic gates in a single datastructure. 56 | //! Since they do not restrict the functions represented, they are difficult to work with directly for logic optimization. 57 | //! 58 | //! Quaigh aims in-between. All algorithms share the same netlist representation, [`Network`](https://docs.rs/quaigh/latest/quaigh/network/struct.Network.html), 59 | //! but there are some limitations to make it easy to optimize: 60 | //! * all gates have a single output, representing a single binary value, 61 | //! * the gates are kept in topological order (a gate has an index higher than its inputs), 62 | //! * names and design hierarchy are not represented. 63 | //! 64 | //! For example, here is a full adder circuit: 65 | //! ``` 66 | //! # use quaigh::{Gate, Network}; 67 | //! let mut net = Network::new(); 68 | //! let i0 = net.add_input(); 69 | //! let i1 = net.add_input(); 70 | //! let i2 = net.add_input(); 71 | //! let carry = net.add(Gate::maj(i0, i1, i2)); 72 | //! let out = net.add(Gate::xor3(i0, i1, i2)); 73 | //! net.add_output(carry); 74 | //! net.add_output(out); 75 | //! ``` 76 | //! 77 | //! Apart from the core datastructure, Quaigh has algorithms for [logic optimization](https://docs.rs/quaigh/latest/quaigh/optim/index.html), 78 | //! [simulation](https://docs.rs/quaigh/latest/quaigh/sim/index.html) (including fault simulation) and 79 | //! [test pattern generation](https://docs.rs/quaigh/latest/quaigh/atpg/index.html). 80 | //! For optimization and equivalence checking, Quaigh relies on other packages as much as possible: 81 | //! * [Kissat](https://github.com/arminbiere/kissat) (using [rustsat](https://docs.rs/rustsat/)) as a Sat solver, 82 | //! * [Highs](https://github.com/ERGO-Code/HiGHS) (using [good_lp](https://docs.rs/good_lp/)) as an optimization solver. 83 | 84 | #![warn(missing_docs)] 85 | 86 | pub mod atpg; 87 | pub mod equiv; 88 | pub mod io; 89 | pub mod network; 90 | pub mod optim; 91 | pub mod sim; 92 | 93 | pub use network::{Gate, Network, Signal}; 94 | -------------------------------------------------------------------------------- /src/io/patterns.rs: -------------------------------------------------------------------------------- 1 | //! IO for test patterns 2 | 3 | use std::io::{BufRead, BufReader, Read, Write}; 4 | 5 | /// Read test patterns in Atalanta format 6 | /// 7 | /// Each pattern may contain multiple timesteps. For each timestep, the value of each circuit input is given. 8 | /// The patterns are formatted as follows: 9 | /// ```text 10 | /// * This is a comment 11 | /// 12 | /// * Input pattern with five input values set to zero 13 | /// 1: 00000 14 | /// 15 | /// * The index increments at each pattern 16 | /// 2: 00000 17 | /// 18 | /// * A pattern that contains three timesteps 19 | /// 3: 01110 00111 01000 20 | /// 21 | /// * The index is optional when reading patterns 22 | /// 01110 00111 01000 23 | /// ``` 24 | pub fn read_patterns(r: R) -> Result>>, String> { 25 | let mut ret = Vec::new(); 26 | let mut pattern_ind: usize = 1; 27 | let mut line_ind = 0; 28 | for l in BufReader::new(r).lines() { 29 | if let Ok(s) = l { 30 | line_ind += 1; 31 | let t = s.trim(); 32 | if t.is_empty() || t.starts_with('*') { 33 | continue; 34 | } 35 | let sp = t.split(':').collect::>(); 36 | if sp.len() >= 3 || sp.is_empty() { 37 | return Err( 38 | "Expected line of the form INDEX: TIMESTEP_1 TIMESTEP_2 ... TIMESTEP_N" 39 | .to_owned(), 40 | ); 41 | } 42 | if sp.len() == 2 { 43 | let parse_ind = sp[0].trim().parse::(); 44 | if parse_ind.is_err() || parse_ind.unwrap() != pattern_ind { 45 | println!( 46 | "Index {} on a line does not match expected {}", 47 | sp[0], pattern_ind 48 | ); 49 | } 50 | } 51 | let patterns = if sp.len() == 2 { 52 | sp[1].split_whitespace() 53 | } else { 54 | sp[0].split_whitespace() 55 | }; 56 | let mut invalid = false; 57 | let mut seq_ret = Vec::new(); 58 | for p in patterns { 59 | let mut comb_ret = Vec::new(); 60 | for c in p.chars() { 61 | if c == '0' { 62 | comb_ret.push(false); 63 | } else if c == '1' { 64 | comb_ret.push(true); 65 | } else if !invalid { 66 | invalid = true; 67 | println!("Ignoring line {line_ind} with invalid characters"); 68 | } 69 | } 70 | seq_ret.push(comb_ret); 71 | } 72 | if !invalid { 73 | ret.push(seq_ret); 74 | pattern_ind += 1; 75 | } 76 | } 77 | } 78 | Ok(ret) 79 | } 80 | 81 | /// Write test patterns in Atalanta format 82 | /// 83 | /// Each pattern may contain multiple timesteps. For each timestep, the value of each circuit input is given. 84 | /// The patterns are formatted as follows: 85 | /// ```text 86 | /// * This is a comment 87 | /// 88 | /// * Input pattern with five input values set to zero 89 | /// 1: 00000 90 | /// 91 | /// * The index increments at each pattern 92 | /// 2: 00000 93 | /// 94 | /// * A pattern that contains three timesteps 95 | /// 3: 01110 00111 01000 96 | /// ``` 97 | pub fn write_patterns(w: &mut W, patterns: &Vec>>) { 98 | writeln!(w, "* Test pattern file").unwrap(); 99 | writeln!(w, "* generated by quaigh").unwrap(); 100 | for (i, v) in patterns.iter().enumerate() { 101 | write!(w, "{}:", i + 1).unwrap(); 102 | for seq_pattern in v { 103 | write!(w, " ").unwrap(); 104 | for inp_value in seq_pattern { 105 | write!(w, "{}", if *inp_value { "1" } else { "0" }).unwrap(); 106 | } 107 | } 108 | writeln!(w).unwrap(); 109 | } 110 | } 111 | 112 | mod test { 113 | #[test] 114 | fn test_read_pattern() { 115 | let example = " * comment1 116 | *comment2 117 | 1: 00000 00000 118 | 2: 01010 11111\t11111 119 | 3: 120 | :00000 121 | *comment 3 122 | 5: 00000 123 | 00110"; 124 | let patterns = super::read_patterns(example.as_bytes()).unwrap(); 125 | assert_eq!(patterns.len(), 6); 126 | assert_eq!( 127 | patterns[0], 128 | vec![ 129 | vec![false, false, false, false, false], 130 | vec![false, false, false, false, false] 131 | ] 132 | ); 133 | assert_eq!( 134 | patterns[1], 135 | vec![ 136 | vec![false, true, false, true, false], 137 | vec![true, true, true, true, true], 138 | vec![true, true, true, true, true] 139 | ] 140 | ); 141 | assert_eq!(patterns[2], Vec::>::new()); 142 | assert_eq!(patterns[3], vec![vec![false, false, false, false, false],]); 143 | assert_eq!(patterns[4], vec![vec![false, false, false, false, false],]); 144 | assert_eq!(patterns[5], vec![vec![false, false, true, true, false],]); 145 | } 146 | 147 | #[test] 148 | fn test_write_pattern() { 149 | use std::io::BufWriter; 150 | 151 | let example = vec![ 152 | vec![vec![false, true], vec![true, false]], 153 | vec![vec![true, true]], 154 | ]; 155 | let mut buf = BufWriter::new(Vec::new()); 156 | super::write_patterns(&mut buf, &example); 157 | let s = String::from_utf8(buf.into_inner().unwrap()).unwrap(); 158 | assert_eq!( 159 | s, 160 | "* Test pattern file 161 | * generated by quaigh 162 | 1: 01 10 163 | 2: 11 164 | " 165 | ); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/sim/fault.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::network::{stats, NaryType}; 4 | use crate::{Gate, Network, Signal}; 5 | 6 | /// Representation of a fault, with its type and location 7 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] 8 | pub enum Fault { 9 | /// Output stuck-at fault: the output of the given gate is stuck at a fixed value 10 | OutputStuckAtFault { 11 | /// Gate where the fault is located 12 | gate: usize, 13 | /// Fault value 14 | value: bool, 15 | }, 16 | /// Input stuck-at fault: the input of the given gate is stuck at a fixed value 17 | InputStuckAtFault { 18 | /// Gate where the fault is located 19 | gate: usize, 20 | /// Input where the fault is located 21 | input: usize, 22 | /// Fault value 23 | value: bool, 24 | }, 25 | } 26 | 27 | impl Fault { 28 | /// Get all possible faults in a network 29 | pub fn all(aig: &Network) -> Vec { 30 | let mut ret = Vec::new(); 31 | for gate in 0..aig.nb_nodes() { 32 | for value in [false, true] { 33 | ret.push(Fault::OutputStuckAtFault { gate, value }); 34 | } 35 | for input in 0..aig.gate(gate).dependencies().len() { 36 | for value in [false, true] { 37 | ret.push(Fault::InputStuckAtFault { gate, input, value }); 38 | } 39 | } 40 | } 41 | ret 42 | } 43 | 44 | /// Get all possible non-redundant faults in a network 45 | pub fn all_unique(aig: &Network) -> Vec { 46 | let mut ret = Fault::all(aig); 47 | let redundant = Fault::redundant_faults(aig); 48 | ret.retain(|f| !redundant.binary_search(f).is_ok()); 49 | ret 50 | } 51 | 52 | /// List the redundant faults in a network 53 | /// 54 | /// A fault is redundant if it is covered by other faults. 55 | /// The redundancy found here must be acyclic, so that we do not discard a group of equivalent faults. 56 | /// When determining redundancy, we always keep the output stuck-at fault, and if equivalent 57 | /// faults are the same type we keep the later one. 58 | pub fn redundant_faults(aig: &Network) -> Vec { 59 | let usage = stats::count_gate_usage(aig); 60 | // Returns whether the signal is a variable that is used once, so that its input stuck-at fault and output stuck-at fault are equivalent 61 | let is_single_use = |s: &Signal| -> bool { s.is_var() && usage[s.var() as usize] <= 1 }; 62 | let mut ret = Vec::new(); 63 | for gate in 0..aig.nb_nodes() { 64 | let g = aig.gate(gate); 65 | for (input, s) in g.dependencies().iter().enumerate() { 66 | for value in [false, true] { 67 | if is_single_use(s) { 68 | // Fault covered by a previous output stuck-at fault, because the output is used only once 69 | ret.push(Fault::InputStuckAtFault { gate, input, value }); 70 | } 71 | if g.is_xor_like() || g.is_buf_like() { 72 | // Fault redundant because this is a Xor-like gate: it is equivalent to faults on the output 73 | ret.push(Fault::InputStuckAtFault { gate, input, value }); 74 | if is_single_use(s) { 75 | ret.push(Fault::OutputStuckAtFault { 76 | gate: s.var() as usize, 77 | value, 78 | }); 79 | } 80 | } 81 | if g.is_and_like() { 82 | // Some faults are redundant because this is an And-like gate: one of the values forces the gate 83 | let input_inv = matches!( 84 | g, 85 | Gate::Nary(_, NaryType::Or) | Gate::Nary(_, NaryType::Nor) 86 | ); 87 | if value == input_inv { 88 | ret.push(Fault::InputStuckAtFault { gate, input, value }); 89 | if is_single_use(s) { 90 | ret.push(Fault::OutputStuckAtFault { 91 | gate: s.var() as usize, 92 | value, 93 | }); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | } 100 | ret.sort(); 101 | ret.dedup(); 102 | ret 103 | } 104 | 105 | /// Return true if there are two faults with the same gate in the vector 106 | pub fn has_duplicate_gate(faults: &Vec) -> bool { 107 | let mut gates = Vec::new(); 108 | for f in faults { 109 | match f { 110 | Fault::OutputStuckAtFault { gate, .. } => gates.push(*gate), 111 | Fault::InputStuckAtFault { gate, .. } => gates.push(*gate), 112 | } 113 | } 114 | gates.sort(); 115 | for i in 1..gates.len() { 116 | if gates[i - 1] == gates[i] { 117 | return true; 118 | } 119 | } 120 | false 121 | } 122 | } 123 | 124 | impl fmt::Display for Fault { 125 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 126 | match self { 127 | Fault::OutputStuckAtFault { gate, value } => { 128 | write!(f, "Gate {} output stuck at {}", gate, i32::from(*value)) 129 | } 130 | Fault::InputStuckAtFault { gate, input, value } => { 131 | write!( 132 | f, 133 | "Gate {} input {} stuck at {}", 134 | gate, 135 | input, 136 | i32::from(*value) 137 | ) 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/network/area.rs: -------------------------------------------------------------------------------- 1 | //! Compute an approximation of the area or of the complexity of a network 2 | //! 3 | //! ``` 4 | //! # use quaigh::Network; 5 | //! # let aig = Network::new(); 6 | //! use quaigh::network::area::AreaParameters; 7 | //! 8 | //! // To estimate area for VLSI designs 9 | //! println!("VLSI cost: {}", AreaParameters::vlsi().area(&aig)); 10 | //! 11 | //! // To estimate area for FPGA designs 12 | //! println!("FPGA cost: {}", AreaParameters::fpga().area(&aig)); 13 | //! 14 | //! // To estimate complexity for SAT solving and proofs 15 | //! println!("SAT cost: {}", AreaParameters::sat().area(&aig)); 16 | //! ``` 17 | 18 | use std::fmt; 19 | 20 | use crate::network::gates::{BinaryType, NaryType, TernaryType}; 21 | use crate::{Gate, Network}; 22 | 23 | /// Area estimation parameters for optimization 24 | /// 25 | /// Most gates have an area cost. N-ary gates are extrapolated and buffers are ignored. 26 | /// This is obviously very inaccurate, and is meant to be used as an objective during logic optimization. 27 | #[derive(Clone, Copy, Debug)] 28 | pub struct AreaParameters { 29 | /// Cost of And2 30 | pub and: usize, 31 | /// Cost of And3 32 | pub and3: usize, 33 | /// Cost of Xor2 34 | pub xor: usize, 35 | /// Cost of Xor3 36 | pub xor3: usize, 37 | /// Cost of Mux 38 | pub mux: usize, 39 | /// Cost of Maj 40 | pub maj: usize, 41 | /// Cost of Dff 42 | pub dff: usize, 43 | } 44 | 45 | impl AreaParameters { 46 | /// Good default parameters for VLSI design 47 | /// 48 | /// In VLSI, And gates are cheap and easy to merge together, while Xor is more expensive. 49 | /// We use roughly the area the cells would use in a standard cell library. 50 | pub fn vlsi() -> AreaParameters { 51 | AreaParameters { 52 | and: 4, 53 | and3: 6, 54 | xor: 8, 55 | xor3: 16, 56 | mux: 9, 57 | maj: 6, 58 | dff: 24, 59 | } 60 | } 61 | 62 | /// Good default parameters for FPGA design 63 | /// 64 | /// FPGAs represent all logic functions with LUTs, whose cost only depends on the number of inputs. 65 | pub fn fpga() -> AreaParameters { 66 | AreaParameters { 67 | and: 2, 68 | and3: 3, 69 | xor: 2, 70 | xor3: 3, 71 | mux: 3, 72 | maj: 3, 73 | dff: 4, 74 | } 75 | } 76 | 77 | /// Good default parameters for SAT solving 78 | /// 79 | /// We use the number of literals in the formula as a proxy for solving complexity. 80 | pub fn sat() -> AreaParameters { 81 | AreaParameters { 82 | and: 7, 83 | and3: 10, 84 | xor: 12, 85 | xor3: 24, 86 | mux: 13, 87 | maj: 18, 88 | dff: 20, 89 | } 90 | } 91 | 92 | /// Extrapolate the cost of the n-ary and 93 | fn andn(&self, n: usize) -> usize { 94 | if n < 2 { 95 | 0 96 | } else { 97 | self.and + (n - 2) * (self.and3 - self.and) 98 | } 99 | } 100 | 101 | /// Extrapolate the cost of the n-ary xor 102 | fn xorn(&self, n: usize) -> usize { 103 | if n < 2 { 104 | 0 105 | } else { 106 | self.xor + (n - 2) * (self.xor3 - self.xor) 107 | } 108 | } 109 | 110 | /// Compute the area of a gate 111 | pub fn gate_area(&self, g: &Gate) -> usize { 112 | use Gate::*; 113 | match g { 114 | Binary(_, BinaryType::And) => self.and, 115 | Ternary(_, TernaryType::And) => self.and3, 116 | Binary(_, BinaryType::Xor) => self.xor, 117 | Ternary(_, TernaryType::Xor) => self.xor3, 118 | Nary(v, tp) => match tp { 119 | NaryType::And | NaryType::Or | NaryType::Nand | NaryType::Nor => self.andn(v.len()), 120 | NaryType::Xor | NaryType::Xnor => self.xorn(v.len()), 121 | }, 122 | Dff(_) => self.dff, 123 | Ternary(_, TernaryType::Mux) => self.mux, 124 | Ternary(_, TernaryType::Maj) => self.maj, 125 | Buf(_) => 0, 126 | Lut(_) => todo!("LUT area not modeled"), 127 | } 128 | } 129 | 130 | /// Compute the area of a network 131 | pub fn area(&self, a: &Network) -> usize { 132 | let mut ret = 0; 133 | for i in 0..a.nb_nodes() { 134 | ret += self.gate_area(a.gate(i)); 135 | } 136 | ret 137 | } 138 | 139 | /// Perform a consistency check to verify that the parameters are consistent 140 | pub fn check(&self) { 141 | // Everything positive (except maybe Dff) 142 | assert!(self.and > 0); 143 | assert!(self.xor > 0); 144 | assert!(self.and3 > 0); 145 | assert!(self.xor3 > 0); 146 | assert!(self.mux > 0); 147 | assert!(self.maj > 0); 148 | 149 | // Strict cost to having more inputs 150 | assert!(self.and3 > self.and); 151 | assert!(self.xor3 > self.xor); 152 | assert!(self.maj > self.and); 153 | 154 | // Do not force usage of small arities 155 | assert!(self.and3 <= 2 * self.and); 156 | assert!(self.xor3 <= 2 * self.xor); 157 | 158 | // Do not force replacement of And/Xor by Mux 159 | assert!(self.xor < self.mux); 160 | assert!(self.and < self.mux); 161 | } 162 | } 163 | 164 | impl fmt::Display for AreaParameters { 165 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 166 | writeln!(f, "Area costs:")?; 167 | writeln!(f, " And2: {}", self.and)?; 168 | writeln!(f, " And3: {}", self.and3)?; 169 | writeln!(f, " Xor2: {}", self.xor)?; 170 | writeln!(f, " Xor3: {}", self.xor3)?; 171 | writeln!(f, " Mux: {}", self.mux)?; 172 | writeln!(f, " Maj: {}", self.maj)?; 173 | writeln!(f, " Dff: {}", self.dff)?; 174 | fmt::Result::Ok(()) 175 | } 176 | } 177 | 178 | #[cfg(test)] 179 | mod tests { 180 | use super::AreaParameters; 181 | 182 | #[test] 183 | fn test_consistent() { 184 | AreaParameters::vlsi().check(); 185 | AreaParameters::fpga().check(); 186 | AreaParameters::sat().check(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/network/stats.rs: -------------------------------------------------------------------------------- 1 | //! Compute gate statistics 2 | //! 3 | //! ``` 4 | //! # use quaigh::Network; 5 | //! # let aig = Network::new(); 6 | //! use quaigh::network::stats::stats; 7 | //! let stats = stats(&aig); 8 | //! 9 | //! // Check that there is no Xor2 gate 10 | //! assert_eq!(stats.nb_xor, 0); 11 | //! 12 | //! // Show the statistics 13 | //! println!("{}", stats); 14 | //! ``` 15 | 16 | use std::fmt; 17 | 18 | use crate::network::gates::{BinaryType, NaryType, TernaryType}; 19 | use crate::{Gate, Network}; 20 | 21 | /// Number of inputs, outputs and gates in a network 22 | #[derive(Clone, Debug)] 23 | pub struct NetworkStats { 24 | /// Number of inputs 25 | pub nb_inputs: usize, 26 | /// Number of outputs 27 | pub nb_outputs: usize, 28 | /// Number of And and similar gates 29 | pub nb_and: usize, 30 | /// Arity of And gates 31 | pub and_arity: Vec, 32 | /// Number of Xor and similar gates 33 | pub nb_xor: usize, 34 | /// Arity of Xor gates 35 | pub xor_arity: Vec, 36 | /// Number of Lut and similar gates 37 | pub nb_lut: usize, 38 | /// Arity of Lut gates 39 | pub lut_arity: Vec, 40 | /// Number of Mux 41 | pub nb_mux: usize, 42 | /// Number of Maj 43 | pub nb_maj: usize, 44 | /// Number of positive Buf 45 | pub nb_buf: usize, 46 | /// Number of Not (negative Buf) 47 | pub nb_not: usize, 48 | /// Number of Dff 49 | pub nb_dff: usize, 50 | /// Number of Dff with enable 51 | pub nb_dffe: usize, 52 | /// Number of Dff with reset 53 | pub nb_dffr: usize, 54 | } 55 | 56 | impl NetworkStats { 57 | /// Total number of gates, including Dff 58 | pub fn nb_gates(&self) -> usize { 59 | self.nb_and + self.nb_xor + self.nb_mux + self.nb_maj + self.nb_buf + self.nb_dff 60 | } 61 | 62 | /// Record a new and 63 | fn add_and(&mut self, sz: usize) { 64 | self.nb_and += 1; 65 | while self.and_arity.len() <= sz { 66 | self.and_arity.push(0); 67 | } 68 | self.and_arity[sz] += 1; 69 | } 70 | 71 | /// Record a new xor 72 | fn add_xor(&mut self, sz: usize) { 73 | self.nb_xor += 1; 74 | while self.xor_arity.len() <= sz { 75 | self.xor_arity.push(0); 76 | } 77 | self.xor_arity[sz] += 1; 78 | } 79 | 80 | /// Record a new lut 81 | fn add_lut(&mut self, sz: usize) { 82 | self.nb_lut += 1; 83 | while self.lut_arity.len() <= sz { 84 | self.lut_arity.push(0); 85 | } 86 | self.lut_arity[sz] += 1; 87 | } 88 | } 89 | 90 | impl fmt::Display for NetworkStats { 91 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | writeln!(f, "Stats:")?; 93 | writeln!(f, " Inputs: {}", self.nb_inputs)?; 94 | writeln!(f, " Outputs: {}", self.nb_outputs)?; 95 | writeln!(f, " Gates: {}", self.nb_gates())?; 96 | if self.nb_dff != 0 { 97 | writeln!(f, " Dff: {}", self.nb_dff)?; 98 | if self.nb_dffe != 0 { 99 | writeln!(f, " enable: {}", self.nb_dff)?; 100 | } 101 | if self.nb_dffr != 0 { 102 | writeln!(f, " reset: {}", self.nb_dff)?; 103 | } 104 | } 105 | if self.nb_and != 0 { 106 | writeln!(f, " And: {}", self.nb_and)?; 107 | for (i, nb) in self.and_arity.iter().enumerate() { 108 | if *nb != 0 { 109 | writeln!(f, " {}: {}", i, nb)?; 110 | } 111 | } 112 | } 113 | if self.nb_xor != 0 { 114 | writeln!(f, " Xor: {}", self.nb_xor)?; 115 | for (i, nb) in self.xor_arity.iter().enumerate() { 116 | if *nb != 0 { 117 | writeln!(f, " {}: {}", i, nb)?; 118 | } 119 | } 120 | } 121 | if self.nb_lut != 0 { 122 | writeln!(f, " Lut: {}", self.nb_lut)?; 123 | for (i, nb) in self.lut_arity.iter().enumerate() { 124 | if *nb != 0 { 125 | writeln!(f, " {}: {}", i, nb)?; 126 | } 127 | } 128 | } 129 | if self.nb_mux != 0 { 130 | writeln!(f, " Mux: {}", self.nb_mux)?; 131 | } 132 | if self.nb_maj != 0 { 133 | writeln!(f, " Maj: {}", self.nb_maj)?; 134 | } 135 | if self.nb_not != 0 { 136 | writeln!(f, " Not: {}", self.nb_not)?; 137 | } 138 | if self.nb_buf != 0 { 139 | writeln!(f, " Buf: {}", self.nb_buf)?; 140 | } 141 | fmt::Result::Ok(()) 142 | } 143 | } 144 | 145 | /// Compute the statistics of the network 146 | pub fn stats(a: &Network) -> NetworkStats { 147 | use Gate::*; 148 | let mut ret = NetworkStats { 149 | nb_inputs: a.nb_inputs(), 150 | nb_outputs: a.nb_outputs(), 151 | nb_and: 0, 152 | and_arity: Vec::new(), 153 | nb_xor: 0, 154 | xor_arity: Vec::new(), 155 | nb_lut: 0, 156 | lut_arity: Vec::new(), 157 | nb_maj: 0, 158 | nb_mux: 0, 159 | nb_buf: 0, 160 | nb_not: 0, 161 | nb_dff: 0, 162 | nb_dffe: 0, 163 | nb_dffr: 0, 164 | }; 165 | for i in 0..a.nb_nodes() { 166 | match a.gate(i) { 167 | Binary(_, BinaryType::And) => ret.add_and(2), 168 | Ternary(_, TernaryType::And) => ret.add_and(3), 169 | Binary(_, BinaryType::Xor) => ret.add_xor(2), 170 | Ternary(_, TernaryType::Xor) => ret.add_xor(3), 171 | Ternary(_, TernaryType::Mux) => ret.nb_mux += 1, 172 | Ternary(_, TernaryType::Maj) => ret.nb_maj += 1, 173 | Buf(s) => { 174 | if !s.is_constant() { 175 | // Do not count buffered constants that may be created for I/O 176 | if s.is_inverted() { 177 | ret.nb_not += 1; 178 | } else { 179 | ret.nb_buf += 1; 180 | } 181 | } 182 | } 183 | Dff([_, en, res]) => { 184 | ret.nb_dff += 1; 185 | if !en.is_constant() { 186 | ret.nb_dffe += 1; 187 | } 188 | if !res.is_constant() { 189 | ret.nb_dffr += 1; 190 | } 191 | } 192 | Nary(v, tp) => match tp { 193 | NaryType::And | NaryType::Or | NaryType::Nand | NaryType::Nor => { 194 | ret.add_and(v.len()); 195 | } 196 | NaryType::Xor | NaryType::Xnor => { 197 | ret.add_xor(v.len()); 198 | } 199 | }, 200 | Lut(lut) => { 201 | ret.add_lut(lut.inputs.len()); 202 | } 203 | } 204 | } 205 | 206 | ret 207 | } 208 | 209 | /// Count how many times each gate is used, including as output 210 | pub fn count_gate_usage(aig: &Network) -> Vec { 211 | let mut ret = vec![0; aig.nb_nodes()]; 212 | for i in 0..aig.nb_nodes() { 213 | for j in aig.gate(i).vars() { 214 | ret[j as usize] += 1; 215 | } 216 | } 217 | for i in 0..aig.nb_outputs() { 218 | let s = aig.output(i); 219 | if s.is_var() { 220 | ret[s.var() as usize] += 1; 221 | } 222 | } 223 | ret 224 | } 225 | 226 | /// Return which gates use each gate 227 | pub fn gate_users(aig: &Network) -> Vec> { 228 | let mut ret = vec![vec![]; aig.nb_nodes()]; 229 | for i in 0..aig.nb_nodes() { 230 | for j in aig.gate(i).vars() { 231 | ret[j as usize].push(i); 232 | } 233 | } 234 | ret 235 | } 236 | 237 | /// Mark whether each gate is an output 238 | pub fn gate_is_output(aig: &Network) -> Vec { 239 | let mut ret = vec![false; aig.nb_nodes()]; 240 | for i in 0..aig.nb_outputs() { 241 | if aig.output(i).is_var() { 242 | ret[aig.output(i).var() as usize] = true; 243 | } 244 | } 245 | ret 246 | } 247 | -------------------------------------------------------------------------------- /src/network/signal.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{BitXor, BitXorAssign, Not}; 3 | 4 | /// Representation of a signal (a boolean variable or its complement) 5 | /// 6 | /// May be 0, 1, x or !x. 7 | /// Design inputs and constants get a special representation. 8 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] 9 | pub struct Signal { 10 | a: u32, 11 | } 12 | 13 | impl Signal { 14 | /// Constant zero signal 15 | pub fn zero() -> Signal { 16 | Signal { a: 0 } 17 | } 18 | 19 | /// Constant one signal 20 | pub fn one() -> Signal { 21 | Signal { a: 1 } 22 | } 23 | 24 | /// A placeholder signal 25 | pub(crate) fn placeholder() -> Signal { 26 | Signal { a: 0x8000_0000 } 27 | } 28 | 29 | /// Create a signal from a variable index 30 | pub fn from_var(v: u32) -> Signal { 31 | Self::from_ind(v + 1) 32 | } 33 | 34 | /// Create a signal from a design input index 35 | pub fn from_input(v: u32) -> Signal { 36 | Self::from_ind(!v) 37 | } 38 | 39 | /// Create a signal from an index (including zero signal at index 0) 40 | pub(crate) fn from_ind(v: u32) -> Signal { 41 | Signal { a: v << 1 } 42 | } 43 | 44 | /// Obtain the variable index associated with the signal 45 | pub fn var(&self) -> u32 { 46 | assert!(self.is_var()); 47 | self.ind() - 1u32 48 | } 49 | 50 | /// Obtain the design input index associated with the signal 51 | pub fn input(&self) -> u32 { 52 | assert!(self.is_input()); 53 | !self.ind() & !0x8000_0000 54 | } 55 | 56 | /// Obtain the internal index associated with the signal: 0 for a constant, otherwise var() + 1 57 | pub fn ind(&self) -> u32 { 58 | self.a >> 1 59 | } 60 | 61 | /// Returns true if the signal represents a constant 62 | pub fn is_constant(&self) -> bool { 63 | self.ind() == 0 64 | } 65 | 66 | /// Returns true if the signal represents a design input 67 | pub fn is_input(&self) -> bool { 68 | self.a & 0x8000_0000 != 0 69 | } 70 | 71 | /// Returns true if the signal represents an internal variable 72 | pub fn is_var(&self) -> bool { 73 | !self.is_input() && !self.is_constant() 74 | } 75 | 76 | /// Clear the inversion, if set 77 | pub(crate) fn without_inversion(&self) -> Signal { 78 | Signal { a: self.a & !1u32 } 79 | } 80 | 81 | /// Returns true if the signal is implicitly inverted 82 | /// 83 | /// False for inputs, variables and zero. 84 | /// True for their complement and for one. 85 | pub fn is_inverted(&self) -> bool { 86 | self.a & 1 != 0 87 | } 88 | 89 | /// Return the internal representation of the signal 90 | pub fn raw(&self) -> u32 { 91 | self.a 92 | } 93 | 94 | /// Apply a remapping of variable order to the signal 95 | pub(crate) fn remap_order(&self, t: &[Signal]) -> Signal { 96 | if !self.is_var() { 97 | *self 98 | } else { 99 | t[self.var() as usize] ^ self.is_inverted() 100 | } 101 | } 102 | } 103 | 104 | impl From for Signal { 105 | fn from(b: bool) -> Signal { 106 | if b { 107 | Signal::one() 108 | } else { 109 | Signal::zero() 110 | } 111 | } 112 | } 113 | 114 | impl Not for Signal { 115 | type Output = Signal; 116 | fn not(self) -> Signal { 117 | Signal { a: self.a ^ 1u32 } 118 | } 119 | } 120 | 121 | impl Not for &'_ Signal { 122 | type Output = Signal; 123 | fn not(self) -> Signal { 124 | Signal { a: self.a ^ 1u32 } 125 | } 126 | } 127 | 128 | impl BitXorAssign for Signal { 129 | fn bitxor_assign(&mut self, rhs: bool) { 130 | self.a ^= rhs as u32; 131 | } 132 | } 133 | 134 | impl BitXor for Signal { 135 | type Output = Signal; 136 | fn bitxor(self, rhs: bool) -> Self::Output { 137 | let mut l = self; 138 | l ^= rhs; 139 | l 140 | } 141 | } 142 | 143 | impl BitXor for &'_ Signal { 144 | type Output = Signal; 145 | fn bitxor(self, rhs: bool) -> Self::Output { 146 | let mut l = *self; 147 | l ^= rhs; 148 | l 149 | } 150 | } 151 | 152 | impl BitXor<&bool> for Signal { 153 | type Output = Signal; 154 | fn bitxor(self, rhs: &bool) -> Self::Output { 155 | self ^ *rhs 156 | } 157 | } 158 | 159 | impl BitXor<&bool> for &'_ Signal { 160 | type Output = Signal; 161 | fn bitxor(self, rhs: &bool) -> Self::Output { 162 | self ^ *rhs 163 | } 164 | } 165 | 166 | impl fmt::Display for Signal { 167 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 168 | if self.is_constant() { 169 | let a = self.a & 1; 170 | write!(f, "{a}") 171 | } else { 172 | if self.is_inverted() { 173 | write!(f, "!")?; 174 | } 175 | if *self == Signal::placeholder() { 176 | write!(f, "##") 177 | } else if self.is_input() { 178 | // Representation of inputs 179 | let v = self.input(); 180 | write!(f, "i{v}") 181 | } else { 182 | let v = self.var(); 183 | write!(f, "x{v}") 184 | } 185 | } 186 | } 187 | } 188 | 189 | impl fmt::Debug for Signal { 190 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 191 | fmt::Display::fmt(self, f) 192 | } 193 | } 194 | 195 | #[cfg(test)] 196 | mod tests { 197 | use super::*; 198 | 199 | #[test] 200 | fn test_constant() { 201 | let l0 = Signal::zero(); 202 | let l1 = Signal::one(); 203 | assert_eq!(l0, !l1); 204 | assert_eq!(l1, !l0); 205 | assert!(!l0.is_inverted()); 206 | assert!(l1.is_inverted()); 207 | assert_eq!(format!("{l0}"), "0"); 208 | assert_eq!(format!("{l1}"), "1"); 209 | } 210 | 211 | #[test] 212 | fn test_var() { 213 | for v in 0u32..10u32 { 214 | let l = Signal::from_var(v); 215 | assert!(l.is_var()); 216 | assert!(!l.is_constant()); 217 | assert!(!l.is_input()); 218 | assert_eq!(l.var(), v); 219 | assert_eq!((!l).var(), v); 220 | assert!(!l.is_inverted()); 221 | assert!((!l).is_inverted()); 222 | assert_eq!(l ^ false, l); 223 | assert_eq!(l ^ true, !l); 224 | assert_eq!(format!("{l}"), format!("x{v}")); 225 | } 226 | } 227 | 228 | #[test] 229 | fn test_input() { 230 | for v in 0u32..10u32 { 231 | let l = Signal::from_input(v); 232 | assert!(!l.is_var()); 233 | assert!(!l.is_constant()); 234 | assert!(l.is_input()); 235 | assert_eq!(l.input(), v); 236 | assert_eq!((!l).input(), v); 237 | assert!(!l.is_inverted()); 238 | assert!((!l).is_inverted()); 239 | assert_eq!(l ^ false, l); 240 | assert_eq!(l ^ true, !l); 241 | assert_eq!(format!("{l}"), format!("i{v}")); 242 | } 243 | } 244 | 245 | #[test] 246 | fn test_placeholder() { 247 | let s = Signal::placeholder(); 248 | assert!(s.is_input()); 249 | assert_eq!(s.input(), 0x3fff_ffff); 250 | assert_eq!(format!("{s}"), "##"); 251 | } 252 | 253 | #[test] 254 | fn test_comparison() { 255 | // Boolean conversion 256 | assert_eq!(Signal::from(false), Signal::zero()); 257 | assert_eq!(Signal::from(true), Signal::one()); 258 | assert_ne!(Signal::from(false), Signal::one()); 259 | assert_ne!(Signal::from(true), Signal::zero()); 260 | 261 | // Design variable 262 | assert_ne!(Signal::from_var(0), Signal::one()); 263 | assert_ne!(Signal::from_var(0), Signal::zero()); 264 | assert_ne!(Signal::from_var(0), Signal::from_var(1)); 265 | assert_ne!(Signal::from_var(0), Signal::from_var(1)); 266 | 267 | // Design input 268 | assert_ne!(Signal::from_input(0), Signal::from_var(0)); 269 | assert_ne!(Signal::from_input(0), Signal::from_var(0)); 270 | assert_ne!(Signal::from_input(0), Signal::one()); 271 | assert_ne!(Signal::from_input(0), Signal::zero()); 272 | 273 | // Xor 274 | assert_eq!(Signal::zero() ^ false, Signal::zero()); 275 | assert_eq!(Signal::zero() ^ true, Signal::one()); 276 | assert_eq!(Signal::one() ^ false, Signal::one()); 277 | assert_eq!(Signal::one() ^ true, Signal::zero()); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | //! Command line interface 2 | 3 | use crate::atpg::{ 4 | expose_dff, generate_comb_test_patterns, generate_random_seq_patterns, 5 | report_comb_test_patterns, 6 | }; 7 | use crate::equiv::check_equivalence_bounded; 8 | use crate::io::{read_network_file, read_pattern_file, write_network_file, write_pattern_file}; 9 | use crate::optim; 10 | use crate::sim::simulate; 11 | use clap::{Args, Parser, Subcommand}; 12 | use std::path::PathBuf; 13 | 14 | /// Command line arguments 15 | #[derive(Parser)] 16 | #[command(author, version, about, long_about = None)] 17 | #[command(propagate_version = true)] 18 | pub struct Cli { 19 | #[command(subcommand)] 20 | pub command: Commands, 21 | } 22 | 23 | /// Command line arguments 24 | #[derive(Subcommand)] 25 | pub enum Commands { 26 | /// Show statistics about a logic network 27 | /// 28 | /// Will print statistics on the number of inputs, outputs and gates in the network. 29 | #[clap()] 30 | Show(ShowArgs), 31 | 32 | /// Optimize a logic network 33 | /// 34 | /// At the moment this is simple constant propagation and deduplication, 35 | /// but will grow in power over time. 36 | #[clap(visible_alias = "opt")] 37 | Optimize(OptArgs), 38 | 39 | /// Simulate a logic network 40 | /// 41 | /// This uses the same test pattern format as Atalanta, with one bit per input: 42 | /// 1: 00011101 43 | /// 2: 01110000 44 | #[clap(visible_alias = "sim")] 45 | Simulate(SimulateArgs), 46 | 47 | /// Test pattern generation for a logic network 48 | /// 49 | /// Generate patterns to find all possible faults in a design, assuming 50 | /// that the primary inputs, outputs and flip-flops can be scanned. 51 | /// Full fault coverage is achieved using a SAT solver. 52 | /// 53 | /// Fault types are: 54 | /// * Output stuck-at fault, where the output of the gate is stuck at a constant value 55 | /// * Input stuck-at fault, where the input of the gate is stuck at a constant value 56 | #[clap()] 57 | Atpg(AtpgArgs), 58 | 59 | /// Analyze the results of test pattern generation 60 | #[clap(hide = true)] 61 | AtpgReport(AtpgReportArgs), 62 | 63 | /// Check equivalence between two logic networks 64 | /// 65 | /// The command will fail if the two networks are not equivalent, and will output the 66 | /// failing test pattern. 67 | #[clap(visible_alias = "equiv")] 68 | CheckEquivalence(EquivArgs), 69 | 70 | /// Read a logic network and write it in another format 71 | #[clap()] 72 | Convert(ConvertArgs), 73 | } 74 | 75 | /// Command arguments for equivalence checking 76 | #[derive(Args)] 77 | pub struct EquivArgs { 78 | /// First network to compare 79 | file1: PathBuf, 80 | /// Second network to compare 81 | file2: PathBuf, 82 | 83 | /// Number of clock cycles considered 84 | #[arg(short = 'c', long, default_value_t = 1)] 85 | num_cycles: usize, 86 | 87 | /// Use only the Sat solver, skipping internal optimizations 88 | #[arg(long)] 89 | sat_only: bool, 90 | } 91 | 92 | impl EquivArgs { 93 | pub fn run(&self) { 94 | let aig1 = read_network_file(&self.file1); 95 | let aig2 = read_network_file(&self.file2); 96 | if aig1.nb_inputs() != aig2.nb_inputs() { 97 | println!( 98 | "Different number of inputs: {} vs {}. Networks are not equivalent", 99 | aig1.nb_inputs(), 100 | aig2.nb_inputs() 101 | ); 102 | std::process::exit(1); 103 | } 104 | if aig1.nb_outputs() != aig2.nb_outputs() { 105 | println!( 106 | "Different number of outputs: {} vs {}. Networks are not equivalent", 107 | aig1.nb_outputs(), 108 | aig2.nb_outputs() 109 | ); 110 | std::process::exit(1); 111 | } 112 | let res = check_equivalence_bounded(&aig1, &aig2, self.num_cycles, !self.sat_only); 113 | let is_comb = aig1.is_comb() && aig2.is_comb(); 114 | match res { 115 | Err(err) => { 116 | println!("Networks are not equivalent"); 117 | println!("Test pattern:"); 118 | // TODO: extract the names here 119 | for v in err { 120 | print!("\t"); 121 | for b in v { 122 | print!("{}", if b { "0" } else { "1" }); 123 | } 124 | println!(); 125 | } 126 | std::process::exit(1); 127 | } 128 | Ok(()) => { 129 | if is_comb { 130 | println!("Networks are equivalent"); 131 | } else { 132 | println!("Networks are equivalent up to {} cycles", self.num_cycles); 133 | } 134 | std::process::exit(0); 135 | } 136 | } 137 | } 138 | } 139 | 140 | /// Command arguments for optimization 141 | #[derive(Args)] 142 | pub struct OptArgs { 143 | /// Network to optimize 144 | file: PathBuf, 145 | 146 | /// Output file for optimized network 147 | #[arg(short = 'o', long)] 148 | output: PathBuf, 149 | 150 | /// Effort level 151 | #[arg(long, default_value_t = 1)] 152 | effort: u64, 153 | 154 | /// Seed for randomized algorithms 155 | #[arg(long)] 156 | seed: Option, 157 | } 158 | 159 | impl OptArgs { 160 | pub fn run(&self) { 161 | let mut aig = read_network_file(&self.file); 162 | if let Some(s) = self.seed { 163 | aig.shuffle(s); 164 | } 165 | aig.cleanup(); 166 | aig.make_canonical(); 167 | optim::share_logic(&mut aig, 64); 168 | for _ in 0..self.effort { 169 | optim::infer_xor_mux(&mut aig); 170 | optim::infer_dffe(&mut aig); 171 | optim::share_logic(&mut aig, 64); 172 | } 173 | write_network_file(&self.output, &aig); 174 | } 175 | } 176 | 177 | /// Command arguments for network informations 178 | #[derive(Args)] 179 | pub struct ShowArgs { 180 | /// Network to show 181 | file: PathBuf, 182 | } 183 | 184 | impl ShowArgs { 185 | pub fn run(&self) { 186 | use crate::network::stats::stats; 187 | let aig = read_network_file(&self.file); 188 | println!("Network stats:\n{}\n\n", stats(&aig)); 189 | } 190 | } 191 | 192 | /// Command arguments for file conversion 193 | #[derive(Args)] 194 | pub struct ConvertArgs { 195 | /// Network to convert 196 | file: PathBuf, 197 | 198 | /// Destination file 199 | destination: PathBuf, 200 | } 201 | 202 | impl ConvertArgs { 203 | pub fn run(&self) { 204 | let aig = read_network_file(&self.file); 205 | write_network_file(&self.destination, &aig); 206 | } 207 | } 208 | 209 | /// Command arguments for simulation 210 | #[derive(Args)] 211 | pub struct SimulateArgs { 212 | /// Network to simulate 213 | network: PathBuf, 214 | 215 | /// Input patterns file 216 | #[arg(short = 'i', long)] 217 | input: PathBuf, 218 | 219 | /// Output file for output patterns 220 | #[arg(short = 'o', long)] 221 | output: PathBuf, 222 | 223 | /// Expose flip-flops as primary inputs. Used after test pattern generation 224 | #[arg(long)] 225 | expose_ff: bool, 226 | } 227 | 228 | impl SimulateArgs { 229 | pub fn run(&self) { 230 | let mut aig = read_network_file(&self.network); 231 | if self.expose_ff { 232 | aig = expose_dff(&aig); 233 | } 234 | let input_values = read_pattern_file(&self.input); 235 | let mut output_values = Vec::new(); 236 | for pattern in &input_values { 237 | output_values.push(simulate(&aig, pattern)); 238 | } 239 | write_pattern_file(&self.output, &output_values); 240 | } 241 | } 242 | 243 | /// Command arguments for test pattern generation 244 | #[derive(Args)] 245 | pub struct AtpgArgs { 246 | /// Network to write test patterns for 247 | network: PathBuf, 248 | 249 | /// Output file for test patterns 250 | #[arg(short = 'o', long)] 251 | output: PathBuf, 252 | 253 | /// Random seed for test pattern generation 254 | #[arg(long, default_value_t = 1)] 255 | seed: u64, 256 | 257 | /// Attempt to generate sequential patterns (random only) 258 | #[arg(short = 'c', long)] 259 | num_cycles: Option, 260 | 261 | /// Number of random patterns to generate 262 | #[arg(short = 'r', long)] 263 | num_random: Option, 264 | 265 | /// Do not remove redundant faults beforehand 266 | #[arg(long, default_value_t = false)] 267 | with_redundant_faults: bool, 268 | } 269 | 270 | impl AtpgArgs { 271 | pub fn run(&self) { 272 | let mut aig = read_network_file(&self.network); 273 | 274 | if self.num_cycles.is_none() && self.num_random.is_none() { 275 | if !aig.is_comb() { 276 | println!("Exposing flip-flops for a sequential network"); 277 | aig = expose_dff(&aig); 278 | } 279 | let patterns = generate_comb_test_patterns(&aig, self.seed, self.with_redundant_faults); 280 | let seq_patterns = patterns.iter().map(|p| vec![p.clone()]).collect(); 281 | write_pattern_file(&self.output, &seq_patterns); 282 | } else { 283 | println!("Generating only random patterns for multiple cycles"); 284 | let nb_timesteps = self.num_cycles.unwrap_or(1); 285 | let nb_patterns = self.num_random.unwrap_or(4 * (aig.nb_inputs() + 1)); 286 | let seq_patterns = 287 | generate_random_seq_patterns(aig.nb_inputs(), nb_timesteps, nb_patterns, self.seed); 288 | write_pattern_file(&self.output, &seq_patterns); 289 | } 290 | } 291 | } 292 | 293 | /// Command arguments for test pattern generation report 294 | #[derive(Args)] 295 | pub struct AtpgReportArgs { 296 | /// Network to analyze 297 | network: PathBuf, 298 | 299 | /// Test pattern file 300 | patterns: PathBuf, 301 | 302 | /// Do not remove redundant faults beforehand 303 | #[arg(long, default_value_t = false)] 304 | with_redundant_faults: bool, 305 | } 306 | 307 | impl AtpgReportArgs { 308 | pub fn run(&self) { 309 | let mut aig = read_network_file(&self.network); 310 | 311 | if !aig.is_comb() { 312 | println!("Exposing flip-flops for a sequential network"); 313 | aig = expose_dff(&aig); 314 | } 315 | let seq_patterns = read_pattern_file(&self.patterns); 316 | let patterns = seq_patterns.iter().map(|p| p[0].clone()).collect(); 317 | report_comb_test_patterns(&aig, patterns, self.with_redundant_faults); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/io/bench.rs: -------------------------------------------------------------------------------- 1 | //! IO for .bench (ISCAS) files 2 | 3 | use std::collections::HashMap; 4 | use std::io::{BufRead, BufReader, Read, Write}; 5 | 6 | use volute::Lut; 7 | 8 | use crate::network::{BinaryType, NaryType, TernaryType}; 9 | use crate::{Gate, Network, Signal}; 10 | 11 | use super::utils::{get_inverted_signals, sig_to_string}; 12 | 13 | fn build_name_to_sig( 14 | statements: &Vec>, 15 | inputs: &Vec, 16 | ) -> HashMap { 17 | let mut ret = HashMap::new(); 18 | for (i, name) in inputs.iter().enumerate() { 19 | let present = ret 20 | .insert(name.clone(), Signal::from_input(i as u32)) 21 | .is_some(); 22 | assert!(!present, "{} is defined twice", name) 23 | } 24 | for (i, s) in statements.iter().enumerate() { 25 | let present = ret 26 | .insert(s[0].to_string(), Signal::from_var(i as u32)) 27 | .is_some(); 28 | assert!(!present, "{} is defined twice", s[0].to_string()) 29 | } 30 | 31 | // ABC-style naming for constant signals 32 | if !ret.contains_key("vdd") { 33 | ret.insert("vdd".to_string(), Signal::one()); 34 | } 35 | if !ret.contains_key("gnd") { 36 | ret.insert("gnd".to_string(), Signal::zero()); 37 | } 38 | ret 39 | } 40 | 41 | fn check_statement(statement: &Vec, name_to_sig: &HashMap) { 42 | let deps = &statement[2..]; 43 | for dep in deps { 44 | assert!( 45 | name_to_sig.contains_key(dep), 46 | "Gate input {dep} is not generated anywhere" 47 | ); 48 | } 49 | match statement[1].to_uppercase().as_str() { 50 | "DFF" | "BUF" | "BUFF" | "NOT" => assert_eq!(deps.len(), 1), 51 | "VDD" | "VSS" => assert_eq!(deps.len(), 0), 52 | "MUX" | "MAJ" => assert_eq!(deps.len(), 3), 53 | _ => (), 54 | }; 55 | } 56 | 57 | fn gate_dependencies( 58 | statement: &Vec, 59 | name_to_sig: &HashMap, 60 | ) -> Box<[Signal]> { 61 | statement[2..].iter().map(|n| name_to_sig[n]).collect() 62 | } 63 | 64 | fn network_from_statements( 65 | statements: &Vec>, 66 | inputs: &Vec, 67 | outputs: &Vec, 68 | ) -> Result { 69 | let mut ret = Network::new(); 70 | ret.add_inputs(inputs.len()); 71 | 72 | // Compute a mapping between the two 73 | let name_to_sig = build_name_to_sig(statements, inputs); 74 | 75 | // Check everything 76 | for statement in statements { 77 | check_statement(statement, &name_to_sig); 78 | } 79 | for output in outputs { 80 | assert!( 81 | name_to_sig.contains_key(output), 82 | "Output {output} is not generated anywhere" 83 | ); 84 | } 85 | 86 | // Setup the variables based on the mapping 87 | for s in statements { 88 | let sigs: Box<[Signal]> = gate_dependencies(s, &name_to_sig); 89 | match s[1].to_uppercase().as_str() { 90 | "DFF" => { 91 | ret.add(Gate::Dff([sigs[0], Signal::one(), Signal::zero()])); 92 | } 93 | "DFFRSE" => { 94 | assert_eq!(sigs[1], Signal::zero()); 95 | ret.add(Gate::Dff([sigs[0], sigs[3], sigs[1]])); 96 | } 97 | "BUF" | "BUFF" => { 98 | ret.add(Gate::Buf(sigs[0])); 99 | } 100 | "NOT" => { 101 | ret.add(Gate::Buf(!sigs[0])); 102 | } 103 | "VDD" => { 104 | ret.add(Gate::Buf(Signal::one())); 105 | } 106 | "VSS" | "GND" => { 107 | ret.add(Gate::Buf(Signal::zero())); 108 | } 109 | "AND" => { 110 | ret.add(Gate::Nary(sigs, NaryType::And)); 111 | } 112 | "NAND" => { 113 | ret.add(Gate::Nary(sigs, NaryType::Nand)); 114 | } 115 | "OR" => { 116 | ret.add(Gate::Nary(sigs, NaryType::Or)); 117 | } 118 | "NOR" => { 119 | ret.add(Gate::Nary(sigs, NaryType::Nor)); 120 | } 121 | "XOR" => { 122 | ret.add(Gate::Nary(sigs, NaryType::Xor)); 123 | } 124 | "XNOR" => { 125 | ret.add(Gate::Nary(sigs, NaryType::Xnor)); 126 | } 127 | "MUX" => { 128 | ret.add(Gate::mux(sigs[0], sigs[1], sigs[2])); 129 | } 130 | "MAJ" => { 131 | ret.add(Gate::maj(sigs[0], sigs[1], sigs[2])); 132 | } 133 | _ => { 134 | if s[1].starts_with("LUT 0x") { 135 | ret.add(Gate::lut( 136 | sigs.as_ref(), 137 | Lut::from_hex_string(sigs.len(), &s[1][6..]).unwrap(), 138 | )); 139 | } else { 140 | return Err(format!("Unknown gate type {}", s[1])); 141 | } 142 | } 143 | } 144 | } 145 | for o in outputs { 146 | ret.add_output(name_to_sig[o]); 147 | } 148 | ret.topo_sort(); 149 | ret.check(); 150 | Ok(ret) 151 | } 152 | 153 | /// Read a network in .bench format, as used by the ISCAS benchmarks 154 | /// 155 | /// These files describe the design with simple statements like: 156 | /// ```text 157 | /// # This is a comment 158 | /// INPUT(i0) 159 | /// INPUT(i1) 160 | /// x0 = AND(i0, i1) 161 | /// x1 = NAND(x0, i1) 162 | /// x2 = OR(x0, i0) 163 | /// x3 = NOR(i0, x1) 164 | /// x4 = XOR(x3, x2) 165 | /// x5 = BUF(x4) 166 | /// x6 = NOT(x5) 167 | /// x7 = gnd 168 | /// x8 = vdd 169 | /// OUTPUT(x0) 170 | /// ``` 171 | pub fn read_bench(r: R) -> Result { 172 | let mut statements = Vec::new(); 173 | let mut inputs = Vec::new(); 174 | let mut outputs = Vec::new(); 175 | for l in BufReader::new(r).lines() { 176 | if let Ok(s) = l { 177 | let t = s.trim().to_owned(); 178 | if t.is_empty() || t.starts_with('#') { 179 | continue; 180 | } 181 | if !t.contains("=") { 182 | let parts: Vec<_> = t 183 | .split(&['(', ')']) 184 | .map(|s| s.trim()) 185 | .filter(|s| !s.is_empty()) 186 | .collect(); 187 | assert_eq!(parts.len(), 2); 188 | if ["INPUT", "PINPUT"].contains(&parts[0]) { 189 | inputs.push(parts[1].to_string()); 190 | } else if ["OUTPUT", "POUTPUT"].contains(&parts[0]) { 191 | outputs.push(parts[1].to_string()); 192 | } else { 193 | return Err(format!("Unknown keyword {}", parts[0])); 194 | } 195 | } else { 196 | let parts: Vec<_> = t 197 | .split(&['=', '(', ',', ')']) 198 | .map(|s| s.trim().to_owned()) 199 | .filter(|s| !s.is_empty()) 200 | .collect(); 201 | assert!(parts.len() >= 2); 202 | statements.push(parts); 203 | } 204 | } else { 205 | return Err("Error during file IO".to_string()); 206 | } 207 | } 208 | network_from_statements(&statements, &inputs, &outputs) 209 | } 210 | 211 | /// Write a network in .bench format, as used by the ISCAS benchmarks 212 | /// 213 | /// These files describe the design with simple statements like: 214 | /// ```text 215 | /// # This is a comment 216 | /// INPUT(i0) 217 | /// INPUT(i1) 218 | /// x0 = AND(i0, i1) 219 | /// x1 = NAND(x0, i1) 220 | /// x2 = OR(x0, i0) 221 | /// x3 = NOR(i0, x1) 222 | /// x4 = XOR(x3, x2) 223 | /// x5 = BUF(x4) 224 | /// x6 = NOT(x5) 225 | /// x7 = gnd 226 | /// x8 = vdd 227 | /// OUTPUT(x0) 228 | /// ``` 229 | pub fn write_bench(w: &mut W, aig: &Network) { 230 | writeln!(w, "# .bench (ISCAS) file").unwrap(); 231 | writeln!(w, "# Generated by quaigh").unwrap(); 232 | for i in 0..aig.nb_inputs() { 233 | writeln!(w, "INPUT({})", aig.input(i)).unwrap(); 234 | } 235 | writeln!(w).unwrap(); 236 | for i in 0..aig.nb_outputs() { 237 | writeln!(w, "OUTPUT({})", sig_to_string(&aig.output(i))).unwrap(); 238 | } 239 | writeln!(w).unwrap(); 240 | for i in 0..aig.nb_nodes() { 241 | use Gate::*; 242 | let g = aig.gate(i); 243 | let rep = g 244 | .dependencies() 245 | .iter() 246 | .map(sig_to_string) 247 | .collect::>() 248 | .join(", "); 249 | write!(w, "x{} = ", i).unwrap(); 250 | match g { 251 | Binary(_, BinaryType::And) | Ternary(_, TernaryType::And) => { 252 | writeln!(w, "AND({})", rep).unwrap(); 253 | } 254 | Binary(_, BinaryType::Xor) | Ternary(_, TernaryType::Xor) => { 255 | writeln!(w, "XOR({})", rep).unwrap(); 256 | } 257 | Nary(_, tp) => match tp { 258 | NaryType::And => writeln!(w, "AND({})", rep).unwrap(), 259 | NaryType::Or => writeln!(w, "OR({})", rep).unwrap(), 260 | NaryType::Nand => writeln!(w, "NAND({})", rep).unwrap(), 261 | NaryType::Nor => writeln!(w, "NOR({})", rep).unwrap(), 262 | NaryType::Xor => writeln!(w, "XOR({})", rep).unwrap(), 263 | NaryType::Xnor => writeln!(w, "XNOR({})", rep).unwrap(), 264 | }, 265 | Dff([d, en, res]) => { 266 | if *en != Signal::one() || *res != Signal::zero() { 267 | writeln!( 268 | w, 269 | "DFFRSE({}, {}, gnd, {})", 270 | sig_to_string(d), 271 | sig_to_string(res), 272 | sig_to_string(en) 273 | ) 274 | .unwrap(); 275 | } else { 276 | writeln!(w, "DFF({})", sig_to_string(d)).unwrap(); 277 | } 278 | } 279 | Ternary(_, TernaryType::Mux) => { 280 | writeln!(w, "MUX({})", rep).unwrap(); 281 | } 282 | Ternary(_, TernaryType::Maj) => { 283 | writeln!(w, "MAJ({})", rep).unwrap(); 284 | } 285 | Buf(s) => { 286 | if s.is_constant() { 287 | writeln!(w, "{}", sig_to_string(s)).unwrap(); 288 | } else if s.is_inverted() { 289 | writeln!(w, "NOT({})", sig_to_string(&!s)).unwrap(); 290 | } else { 291 | writeln!(w, "BUF({})", rep).unwrap(); 292 | } 293 | } 294 | Lut(lut) => { 295 | writeln!(w, "LUT 0x{}({})", lut.lut.to_hex_string(), rep).unwrap(); 296 | } 297 | } 298 | } 299 | 300 | let signals_with_inv = get_inverted_signals(aig); 301 | for s in signals_with_inv { 302 | writeln!(w, "{}_n = NOT({})", s, s).unwrap(); 303 | } 304 | } 305 | 306 | mod test { 307 | #[test] 308 | fn test_basic_readwrite() { 309 | use std::io::BufWriter; 310 | 311 | let example = "# .bench (ISCAS) file 312 | # Generated by quaigh 313 | INPUT(i0) 314 | INPUT(i1) 315 | 316 | OUTPUT(x0) 317 | OUTPUT(x1) 318 | OUTPUT(x2) 319 | OUTPUT(x3) 320 | OUTPUT(x4) 321 | OUTPUT(x5) 322 | OUTPUT(x6) 323 | 324 | x0 = AND(i0, i1) 325 | x1 = NAND(i0, i1) 326 | x2 = OR(i0, i1) 327 | x3 = NOR(i0, i1) 328 | x4 = XOR(i0, i1) 329 | x5 = BUF(i0) 330 | x6 = NOT(i1) 331 | x7 = NOT(x2) 332 | x8 = gnd 333 | x9 = vdd 334 | x10 = XOR( i0, i1 ) 335 | x11 = gnd 336 | x12 = LUT 0x45fc (x0, x1, x2, x3) 337 | "; 338 | let aig = super::read_bench(example.as_bytes()).unwrap(); 339 | assert_eq!(aig.nb_inputs(), 2); 340 | assert_eq!(aig.nb_outputs(), 7); 341 | assert_eq!(aig.nb_nodes(), 13); 342 | let mut buf = BufWriter::new(Vec::new()); 343 | super::write_bench(&mut buf, &aig); 344 | String::from_utf8(buf.into_inner().unwrap()).unwrap(); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/sim.rs: -------------------------------------------------------------------------------- 1 | //! Simulation of a logic network. Faster, multi-pattern simulation methods are available internally. 2 | 3 | mod fault; 4 | mod incremental_sim; 5 | mod simple_sim; 6 | 7 | use crate::sim::incremental_sim::IncrementalSimulator; 8 | use crate::Network; 9 | 10 | pub use fault::Fault; 11 | 12 | /// Simple conversion to 64b format 13 | fn bool_to_multi(values: &Vec>) -> Vec> { 14 | let mut ret = Vec::>::new(); 15 | for v in values { 16 | ret.push(v.iter().map(|b| if *b { !0 } else { 0 }).collect()); 17 | } 18 | ret 19 | } 20 | 21 | /// Simple conversion from 64b format 22 | fn multi_to_bool(values: &Vec>) -> Vec> { 23 | let mut ret = Vec::new(); 24 | for v in values { 25 | ret.push(v.iter().map(|b| *b != 0).collect()); 26 | } 27 | ret 28 | } 29 | 30 | /// Simulate a network over multiple timesteps; return the output values 31 | pub fn simulate(a: &Network, input_values: &Vec>) -> Vec> { 32 | let multi_input = bool_to_multi(input_values); 33 | let multi_ret = simulate_multi(a, &multi_input); 34 | multi_to_bool(&multi_ret) 35 | } 36 | 37 | /// Simulate a combinatorial network; return the output values 38 | pub fn simulate_comb(a: &Network, input_values: &Vec) -> Vec { 39 | assert!(a.is_comb()); 40 | let input = vec![input_values.clone()]; 41 | let output = simulate(a, &input); 42 | output[0].clone() 43 | } 44 | 45 | /// Simulate a network over multiple timesteps, with faults injected; return the output values 46 | pub fn simulate_with_faults( 47 | a: &Network, 48 | input_values: &Vec>, 49 | faults: &Vec, 50 | ) -> Vec> { 51 | let multi_input = bool_to_multi(input_values); 52 | let multi_ret = simulate_multi_with_faults(a, &multi_input, faults); 53 | multi_to_bool(&multi_ret) 54 | } 55 | 56 | /// Simulate a combinatorial network, with faults injected; return the output values 57 | pub fn simulate_comb_with_faults( 58 | a: &Network, 59 | input_values: &Vec, 60 | faults: &Vec, 61 | ) -> Vec { 62 | assert!(a.is_comb()); 63 | let input = vec![input_values.clone()]; 64 | let output = simulate_with_faults(a, &input, faults); 65 | output[0].clone() 66 | } 67 | 68 | /// Simulate a network over multiple timesteps with 64b inputs; return the output values 69 | pub(crate) fn simulate_multi(a: &Network, input_values: &Vec>) -> Vec> { 70 | use simple_sim::SimpleSimulator; 71 | let mut sim = SimpleSimulator::from_aig(a); 72 | sim.run(input_values) 73 | } 74 | 75 | /// Simulate a network over multiple timesteps with 64b inputs; return the output values 76 | pub(crate) fn simulate_multi_with_faults( 77 | a: &Network, 78 | input_values: &Vec>, 79 | faults: &Vec, 80 | ) -> Vec> { 81 | use simple_sim::SimpleSimulator; 82 | let mut sim = SimpleSimulator::from_aig(a); 83 | sim.run_with_faults(input_values, faults) 84 | } 85 | 86 | /// Analyze which of a set of pattern detect a given fault 87 | pub(crate) fn detects_faults_multi( 88 | aig: &Network, 89 | pattern: &Vec, 90 | faults: &Vec, 91 | ) -> Vec { 92 | assert!(aig.is_comb()); 93 | assert!(aig.is_topo_sorted()); 94 | let mut incr_sim = IncrementalSimulator::from_aig(aig); 95 | incr_sim.run_initial(pattern); 96 | let mut detections = Vec::new(); 97 | for f in faults { 98 | detections.push(incr_sim.detects_fault(*f)); 99 | } 100 | detections 101 | } 102 | 103 | /// Analyze whether a pattern detects a given fault 104 | pub(crate) fn detects_faults(aig: &Network, pattern: &Vec, faults: &Vec) -> Vec { 105 | let multi_pattern = pattern 106 | .iter() 107 | .map(|b| if *b { !0u64 } else { 0u64 }) 108 | .collect(); 109 | let detections = detects_faults_multi(aig, &multi_pattern, faults); 110 | detections 111 | .iter() 112 | .map(|d| { 113 | debug_assert!(*d == 0u64 || *d == !0u64); 114 | *d != 0 115 | }) 116 | .collect() 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use volute::{Lut3, Lut5}; 122 | 123 | use crate::network::NaryType; 124 | use crate::sim::simulate_multi; 125 | use crate::{Gate, Network, Signal}; 126 | 127 | use super::simulate; 128 | 129 | #[test] 130 | fn test_basic() { 131 | let mut aig = Network::default(); 132 | let i0 = aig.add_input(); 133 | let i1 = aig.add_input(); 134 | let i2 = aig.add_input(); 135 | let x1 = aig.xor(i0, i1); 136 | let x2 = aig.and(i0, i2); 137 | let x3 = aig.and(x2, !i1); 138 | aig.add_output(x1); 139 | aig.add_output(x3); 140 | 141 | assert_eq!( 142 | simulate(&aig, &vec![vec![false, false, false]]), 143 | vec![vec![false, false]] 144 | ); 145 | assert_eq!( 146 | simulate(&aig, &vec![vec![true, false, false]]), 147 | vec![vec![true, false]] 148 | ); 149 | assert_eq!( 150 | simulate(&aig, &vec![vec![true, false, true]]), 151 | vec![vec![true, true]] 152 | ); 153 | assert_eq!( 154 | simulate(&aig, &vec![vec![true, true, true]]), 155 | vec![vec![false, false]] 156 | ); 157 | } 158 | 159 | #[test] 160 | fn test_dff() { 161 | let mut aig = Network::default(); 162 | let d = aig.add_input(); 163 | let en = aig.add_input(); 164 | let res = aig.add_input(); 165 | let x = aig.dff(d, en, res); 166 | aig.add_output(x); 167 | let pattern = vec![ 168 | vec![false, false, false], 169 | vec![false, true, false], 170 | vec![true, true, false], 171 | vec![true, false, false], 172 | vec![true, false, true], 173 | vec![false, false, false], 174 | ]; 175 | let expected = vec![ 176 | vec![false], 177 | vec![false], 178 | vec![false], 179 | vec![true], 180 | vec![true], 181 | vec![false], 182 | ]; 183 | assert_eq!(simulate(&aig, &pattern), expected); 184 | } 185 | 186 | #[test] 187 | fn test_nary() { 188 | let mut aig = Network::default(); 189 | let i0 = aig.add_input(); 190 | let i1 = aig.add_input(); 191 | let i2 = aig.add_input(); 192 | let i3 = aig.add_input(); 193 | let x0 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::And)); 194 | aig.add_output(x0); 195 | let x1 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::Xor)); 196 | aig.add_output(x1); 197 | let x2 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::Or)); 198 | aig.add_output(x2); 199 | let x3 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::Nand)); 200 | aig.add_output(x3); 201 | let x4 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::Nor)); 202 | aig.add_output(x4); 203 | let x5 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::Xnor)); 204 | aig.add_output(x5); 205 | 206 | let pattern = vec![ 207 | vec![false, false, false, false], 208 | vec![true, false, false, false], 209 | vec![false, true, false, false], 210 | vec![false, false, true, false], 211 | vec![false, false, false, true], 212 | vec![true, true, true, true], 213 | ]; 214 | let expected = vec![ 215 | vec![false, false, false, true, true, true], 216 | vec![false, true, true, true, false, false], 217 | vec![false, true, true, true, false, false], 218 | vec![false, true, true, true, false, false], 219 | vec![false, true, true, true, false, false], 220 | vec![true, false, true, false, false, true], 221 | ]; 222 | assert_eq!(simulate(&aig, &pattern), expected); 223 | } 224 | 225 | #[test] 226 | fn test_maj() { 227 | let mut aig = Network::default(); 228 | let i0 = aig.add_input(); 229 | let i1 = aig.add_input(); 230 | let i2 = aig.add_input(); 231 | let x0 = aig.add(Gate::maj(i0, i1, i2)); 232 | aig.add_output(x0); 233 | let pattern = vec![ 234 | vec![false, false, false], 235 | vec![true, false, false], 236 | vec![false, true, false], 237 | vec![false, false, true], 238 | vec![true, true, true], 239 | ]; 240 | let expected = vec![ 241 | vec![false], 242 | vec![false], 243 | vec![false], 244 | vec![false], 245 | vec![true], 246 | ]; 247 | assert_eq!(simulate(&aig, &pattern), expected); 248 | } 249 | 250 | #[test] 251 | fn test_lfsr() { 252 | let mut aig = Network::default(); 253 | 254 | // 3bit LFSR with seed 001, coefficients 101 255 | // expected steps: 001 100 110 111 011 101 010 001 256 | // expected outputs: 1 0 0 1 1 1 0 1 257 | // expected network: 258 | // 259 | // x3x2x1 transform to x3'x2'x1' with: 260 | // 261 | // x3' = (x3 ^ x1) 262 | // x2' = x3 263 | // x1' = x2 | reset 264 | // 265 | // and output = x1' 266 | 267 | let reset = aig.add_input(); 268 | let enable = aig.add_input(); 269 | let x3 = aig.dff( 270 | Signal::placeholder(), 271 | Signal::placeholder(), 272 | Signal::placeholder(), 273 | ); 274 | let x2 = aig.dff( 275 | Signal::placeholder(), 276 | Signal::placeholder(), 277 | Signal::placeholder(), 278 | ); 279 | let x1 = aig.dff( 280 | Signal::placeholder(), 281 | Signal::placeholder(), 282 | Signal::placeholder(), 283 | ); 284 | 285 | let x3_next = aig.xor(x3, x1); 286 | let x2_next = x3; 287 | let x1_next = aig.add(Gate::maj(x2, reset, Signal::one())); 288 | 289 | let enable_on_reset = aig.add(Gate::maj(enable, reset, Signal::one())); 290 | 291 | aig.replace(0, Gate::dff(x3_next, enable, reset)); 292 | aig.replace(1, Gate::dff(x2_next, enable, reset)); 293 | aig.replace(2, Gate::dff(x1_next, enable_on_reset, Signal::zero())); 294 | 295 | aig.add_output(x1); 296 | 297 | let pattern = vec![ 298 | vec![true, true], // step 0 reset to initial state 299 | vec![false, true], 300 | vec![false, true], 301 | vec![false, true], 302 | vec![false, true], 303 | vec![false, true], 304 | vec![false, true], 305 | vec![false, true], 306 | vec![false, true], 307 | ]; 308 | 309 | // expected outputs: 0(uinit) 1(inited) 0 0 1 1 1 0 1 310 | let expected: Vec> = vec![0, 1, 0, 0, 1, 1, 1, 0, 1] 311 | .into_iter() 312 | .map(|b| vec![b == 1]) 313 | .collect(); 314 | 315 | assert_eq!(simulate(&aig, &pattern), expected); 316 | } 317 | 318 | #[test] 319 | fn test_lut() { 320 | let mut aig = Network::default(); 321 | 322 | let i0 = aig.add_input(); 323 | let i1 = aig.add_input(); 324 | let i2 = aig.add_input(); 325 | let i3 = aig.add_input(); 326 | let i4 = aig.add_input(); 327 | let truth = Lut5::threshold(4); 328 | let lut = Gate::lut(&[i0, i1, i2, i3, i4], truth.into()); 329 | let o0 = aig.add(lut); 330 | aig.add_output(o0); 331 | 332 | let pattern = vec![ 333 | vec![0, 0, 0, 0, 0], 334 | vec![0b111110, 0b111100, 0b111000, 0b110000, 0b100000], 335 | ]; 336 | 337 | let expected: Vec> = vec![vec![0], vec![0b110000]]; 338 | 339 | assert_eq!(simulate_multi(&aig, &pattern), expected); 340 | } 341 | 342 | #[test] 343 | fn test_lut_input_order() { 344 | let mut aig = Network::default(); 345 | 346 | let i0 = aig.add_input(); 347 | let i1 = aig.add_input(); 348 | let i2 = aig.add_input(); 349 | for i in 0..3 { 350 | let o = aig.add(Gate::lut(&[i0, i1, i2], Lut3::nth_var(i).into())); 351 | aig.add_output(o); 352 | } 353 | 354 | let pattern = vec![vec![0b1110, 0b1100, 0b1000]]; 355 | 356 | let expected: Vec> = vec![vec![0b1110, 0b1100, 0b1000]]; 357 | 358 | assert_eq!(simulate_multi(&aig, &pattern), expected); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/sim/simple_sim.rs: -------------------------------------------------------------------------------- 1 | use volute::Lut; 2 | 3 | use crate::network::{BinaryType, NaryType, TernaryType}; 4 | use crate::{Network, Signal}; 5 | 6 | use super::Fault; 7 | 8 | /// Structure for simulation based directly on the network representation 9 | /// 10 | /// This is simple to write and relatively efficient, but could be greatly improved 11 | /// with a regular and- or mux-based structure. 12 | #[derive(Clone, Debug)] 13 | pub struct SimpleSimulator<'a> { 14 | aig: &'a Network, 15 | pub input_values: Vec, 16 | pub node_values: Vec, 17 | } 18 | 19 | /// Convert the inversion to a word for bitwise operations 20 | fn pol_to_word(s: Signal) -> u64 { 21 | let pol = s.raw() & 1; 22 | (!(pol as u64)).wrapping_add(1) 23 | } 24 | 25 | /// Majority function 26 | fn maj(a: u64, b: u64, c: u64) -> u64 { 27 | (b & c) | (a & (b | c)) 28 | } 29 | 30 | /// Multiplexer function 31 | fn mux(s: u64, a: u64, b: u64) -> u64 { 32 | (s & a) | (!s & b) 33 | } 34 | 35 | impl<'a> SimpleSimulator<'a> { 36 | /// Build a simulator by capturing a network 37 | pub fn from_aig(aig: &'a Network) -> SimpleSimulator<'a> { 38 | assert!(aig.is_topo_sorted()); 39 | SimpleSimulator { 40 | aig, 41 | input_values: vec![0; aig.nb_inputs()], 42 | node_values: vec![0; aig.nb_nodes()], 43 | } 44 | } 45 | 46 | /// Run the simulation 47 | pub fn run(&mut self, input_values: &Vec>) -> Vec> { 48 | self.check(); 49 | self.reset(); 50 | let mut ret = Vec::new(); 51 | for (i, v) in input_values.iter().enumerate() { 52 | if i != 0 { 53 | self.run_dff(); 54 | } 55 | self.copy_inputs(v.as_slice()); 56 | self.run_comb(); 57 | ret.push(self.get_output_values()); 58 | } 59 | ret 60 | } 61 | 62 | /// Run the simulation with a list of stuck-at-fault errors 63 | pub fn run_with_faults( 64 | &mut self, 65 | input_values: &Vec>, 66 | faults: &Vec, 67 | ) -> Vec> { 68 | self.check(); 69 | self.reset(); 70 | let mut ret = Vec::new(); 71 | for (i, v) in input_values.iter().enumerate() { 72 | if i != 0 { 73 | self.run_dff(); 74 | } 75 | self.copy_inputs(v.as_slice()); 76 | self.run_comb_with_faults(faults); 77 | ret.push(self.get_output_values()); 78 | } 79 | ret 80 | } 81 | 82 | pub fn reset(&mut self) { 83 | self.input_values = vec![0; self.aig.nb_inputs()]; 84 | self.node_values = vec![0; self.aig.nb_nodes()]; 85 | } 86 | 87 | fn check(&self) { 88 | assert!(self.aig.is_topo_sorted()); 89 | assert_eq!(self.input_values.len(), self.aig.nb_inputs()); 90 | assert_eq!(self.node_values.len(), self.aig.nb_nodes()); 91 | } 92 | 93 | // Get the value of a signal in the current state 94 | fn get_value(&self, s: Signal) -> u64 { 95 | if s == Signal::zero() { 96 | 0 97 | } else if s == Signal::one() { 98 | !0 99 | } else if s.is_input() { 100 | self.input_values[s.input() as usize] ^ pol_to_word(s) 101 | } else { 102 | debug_assert!(s.is_var()); 103 | self.node_values[s.var() as usize] ^ pol_to_word(s) 104 | } 105 | } 106 | 107 | // Copy the values of the inputs to the internal state 108 | pub fn copy_inputs(&mut self, inputs: &[u64]) { 109 | assert_eq!(inputs.len(), self.input_values.len()); 110 | self.input_values.copy_from_slice(inputs); 111 | } 112 | 113 | // Copy the values of the flip-flops for the next cycle 114 | pub fn run_dff(&mut self) { 115 | use crate::Gate::*; 116 | let mut next_values = self.node_values.clone(); 117 | for i in 0..self.aig.nb_nodes() { 118 | let g = self.aig.gate(i); 119 | if let Dff([d, en, res]) = g { 120 | let dv = self.get_value(*d); 121 | let env = self.get_value(*en); 122 | let resv = self.get_value(*res); 123 | let prevv = self.node_values[i]; 124 | let val = !resv & ((env & dv) | (!env & prevv)); 125 | next_values[i] = val; 126 | } 127 | } 128 | self.node_values = next_values; 129 | } 130 | 131 | /// Return the result of a single gate 132 | pub fn run_gate(&self, i: usize) -> u64 { 133 | use crate::Gate::*; 134 | let g = self.aig.gate(i); 135 | match g { 136 | Binary([a, b], tp) => { 137 | let va = self.get_value(*a); 138 | let vb = self.get_value(*b); 139 | match tp { 140 | BinaryType::And => va & vb, 141 | BinaryType::Xor => va ^ vb, 142 | } 143 | } 144 | Ternary([a, b, c], tp) => { 145 | let va = self.get_value(*a); 146 | let vb = self.get_value(*b); 147 | let vc = self.get_value(*c); 148 | match tp { 149 | TernaryType::And => va & vb & vc, 150 | TernaryType::Xor => va ^ vb ^ vc, 151 | TernaryType::Maj => maj(va, vb, vc), 152 | TernaryType::Mux => mux(va, vb, vc), 153 | } 154 | } 155 | Dff(_) => self.node_values[i], 156 | Nary(v, tp) => match tp { 157 | NaryType::And => self.compute_andn(v, false, false), 158 | NaryType::Or => self.compute_andn(v, true, true), 159 | NaryType::Nand => self.compute_andn(v, false, true), 160 | NaryType::Nor => self.compute_andn(v, true, false), 161 | NaryType::Xor => self.compute_xorn(v, false), 162 | NaryType::Xnor => self.compute_xorn(v, true), 163 | }, 164 | Buf(s) => self.get_value(*s), 165 | Lut(gate) => { 166 | let inputs = &gate.inputs; 167 | self.compute_lut(&gate.lut, inputs) 168 | } 169 | } 170 | } 171 | 172 | /// Return the result of a single gate with a fault on an input 173 | pub fn run_gate_with_input_stuck(&self, i: usize, input: usize, value: bool) -> u64 { 174 | // TODO: this is an ugly duplication but I don't see how to make it cleaner 175 | assert!(input < self.aig.gate(i).dependencies().len()); 176 | let v = if value { !0u64 } else { 0u64 }; 177 | use crate::Gate::*; 178 | let g = self.aig.gate(i); 179 | match g { 180 | Binary([a, b], tp) => { 181 | let va = if input == 0 { v } else { self.get_value(*a) }; 182 | let vb = if input == 1 { v } else { self.get_value(*b) }; 183 | match tp { 184 | BinaryType::And => va & vb, 185 | BinaryType::Xor => va ^ vb, 186 | } 187 | } 188 | Ternary([a, b, c], tp) => { 189 | let va = if input == 0 { v } else { self.get_value(*a) }; 190 | let vb = if input == 1 { v } else { self.get_value(*b) }; 191 | let vc = if input == 2 { v } else { self.get_value(*c) }; 192 | match tp { 193 | TernaryType::And => va & vb & vc, 194 | TernaryType::Xor => va ^ vb ^ vc, 195 | TernaryType::Maj => maj(va, vb, vc), 196 | TernaryType::Mux => mux(va, vb, vc), 197 | } 198 | } 199 | Dff(_) => self.node_values[i], 200 | Nary(v, tp) => match tp { 201 | NaryType::And => self.compute_andn_with_input_stuck(v, false, false, input, value), 202 | NaryType::Or => self.compute_andn_with_input_stuck(v, true, true, input, value), 203 | NaryType::Nand => self.compute_andn_with_input_stuck(v, false, true, input, value), 204 | NaryType::Nor => self.compute_andn_with_input_stuck(v, true, false, input, value), 205 | NaryType::Xor => self.compute_xorn_with_input_stuck(v, false, input, value), 206 | NaryType::Xnor => self.compute_xorn_with_input_stuck(v, true, input, value), 207 | }, 208 | Buf(_) => v, 209 | Lut(gate) => { 210 | let inputs = &gate.inputs; 211 | self.compute_lut_with_input_stuck(&gate.lut, inputs, input, value) 212 | } 213 | } 214 | } 215 | 216 | /// Run the combinatorial part of the design with a list of stuck-at-fault errors 217 | pub fn run_comb_with_faults(&mut self, faults: &Vec) { 218 | assert!(!Fault::has_duplicate_gate(faults)); 219 | for i in 0..self.aig.nb_nodes() { 220 | self.node_values[i] = self.run_gate(i); 221 | for f in faults { 222 | match f { 223 | Fault::OutputStuckAtFault { gate, value } => { 224 | if *gate == i { 225 | self.node_values[i] = if *value { !0u64 } else { 0u64 }; 226 | } 227 | } 228 | Fault::InputStuckAtFault { gate, input, value } => { 229 | if *gate == i { 230 | self.node_values[i] = 231 | self.run_gate_with_input_stuck(*gate, *input, *value); 232 | } 233 | } 234 | } 235 | } 236 | } 237 | } 238 | 239 | /// Run the combinatorial part of the design 240 | pub fn run_comb(&mut self) { 241 | for i in 0..self.aig.nb_nodes() { 242 | self.node_values[i] = self.run_gate(i); 243 | } 244 | } 245 | 246 | fn compute_andn(&self, v: &[Signal], inv_in: bool, inv_out: bool) -> u64 { 247 | let mut ret = !0u64; 248 | for s in v { 249 | ret &= self.get_value(s ^ inv_in); 250 | } 251 | if inv_out { 252 | !ret 253 | } else { 254 | ret 255 | } 256 | } 257 | 258 | fn compute_xorn(&self, v: &[Signal], inv_out: bool) -> u64 { 259 | let mut ret = 0u64; 260 | for s in v { 261 | ret ^= self.get_value(*s); 262 | } 263 | if inv_out { 264 | !ret 265 | } else { 266 | ret 267 | } 268 | } 269 | 270 | fn compute_andn_with_input_stuck( 271 | &self, 272 | v: &[Signal], 273 | inv_in: bool, 274 | inv_out: bool, 275 | input: usize, 276 | value: bool, 277 | ) -> u64 { 278 | let val = if value ^ inv_in { !0u64 } else { 0u64 }; 279 | let mut ret = !0u64; 280 | for (i, s) in v.iter().enumerate() { 281 | ret &= if i == input { 282 | val 283 | } else { 284 | self.get_value(s ^ inv_in) 285 | }; 286 | } 287 | if inv_out { 288 | !ret 289 | } else { 290 | ret 291 | } 292 | } 293 | 294 | fn compute_xorn_with_input_stuck( 295 | &self, 296 | v: &[Signal], 297 | inv_out: bool, 298 | input: usize, 299 | value: bool, 300 | ) -> u64 { 301 | let val = if value { !0u64 } else { 0u64 }; 302 | let mut ret = 0u64; 303 | for (i, s) in v.iter().enumerate() { 304 | ret ^= if i == input { val } else { self.get_value(*s) }; 305 | } 306 | if inv_out { 307 | !ret 308 | } else { 309 | ret 310 | } 311 | } 312 | 313 | fn get_output_values(&self) -> Vec { 314 | let mut ret = Vec::new(); 315 | for o in 0..self.aig.nb_outputs() { 316 | ret.push(self.get_value(self.aig.output(o))); 317 | } 318 | ret 319 | } 320 | 321 | fn compute_lut_with_input_stuck( 322 | &self, 323 | lut: &Lut, 324 | signals: &[Signal], 325 | input: usize, 326 | value: bool, 327 | ) -> u64 { 328 | let val = if value { !0u64 } else { 0u64 }; 329 | let signals = signals 330 | .iter() 331 | .enumerate() 332 | .map(|(i, s)| if i == input { val } else { self.get_value(*s) }) 333 | .collect::>(); 334 | compute_lut(lut, &signals) 335 | } 336 | 337 | fn compute_lut(&self, lut: &Lut, signals: &[Signal]) -> u64 { 338 | let signals: Vec<_> = signals.iter().map(|s| self.get_value(*s)).collect(); 339 | 340 | compute_lut(lut, &signals) 341 | } 342 | } 343 | 344 | #[inline] 345 | fn compute_lut(lut: &Lut, signals: &[u64]) -> u64 { 346 | (0..64).fold(0, |acc, i| { 347 | let msk = signals 348 | .iter() 349 | .enumerate() 350 | .fold(0, |msk, (idx, signal)| msk | ((signal >> i) & 1) << idx); 351 | 352 | let looked_up = lut.value(msk as usize) as u64; 353 | acc | ((looked_up & 1) << i) 354 | }) 355 | } 356 | -------------------------------------------------------------------------------- /src/network/matcher.rs: -------------------------------------------------------------------------------- 1 | //! Simple pattern matching to perform search/replace on logic networks 2 | 3 | use std::iter::zip; 4 | 5 | use crate::{Gate, Network, Signal}; 6 | 7 | /// Pattern matching algorithm 8 | /// 9 | /// This will find a correspondence between signals in the pattern and signals in the network, 10 | /// starting from an anchor gate. 11 | /// Patterns allow to use signals multiple times and can even have loops. 12 | /// Each signal in the pattern will match one signal in the network, but a signal in the network 13 | /// can be matched multiple times: pattern i0 & i1 will match both xi & xj and xi & xi. 14 | /// 15 | /// Variable length patterns are not supported. For example, there is no way to match a chain of 16 | /// buffers of arbitrary length or a gate with an arbitrary number of inputs, but you can make 17 | /// a pattern for a fixed length. 18 | /// 19 | /// Input order matters. a & (b & c) is a different pattern from (a & b) & c. 20 | pub struct Matcher<'a> { 21 | matches: Vec, 22 | pattern: &'a Network, 23 | } 24 | 25 | impl<'a> Matcher<'a> { 26 | /// Build the pattern matcher from a pattern 27 | pub fn from_pattern(pattern: &Network) -> Matcher { 28 | let matches = vec![Signal::placeholder(); pattern.nb_inputs() + pattern.nb_nodes()]; 29 | assert!(pattern.nb_outputs() == 1); 30 | assert!(!pattern.output(0).is_inverted()); 31 | assert!(!pattern.nb_nodes() >= 1); 32 | // TODO: check that the pattern has a path from output to all inputs and internal gates 33 | Matcher { matches, pattern } 34 | } 35 | 36 | /// Run the pattern matching algorithm on the given gate. Returns the matched inputs, if any 37 | pub fn matches(&mut self, aig: &Network, i: usize) -> Option> { 38 | let matched = self.try_match(self.pattern.output(0), aig, Signal::from_var(i as u32)); 39 | let ret = if matched { 40 | let v = (0..self.pattern.nb_inputs()) 41 | .map(|i| self.get_match(Signal::from_input(i as u32))) 42 | .collect(); 43 | Some(v) 44 | } else { 45 | None 46 | }; 47 | self.reset(); 48 | ret 49 | } 50 | 51 | /// Core recursive function for the pattern matching 52 | /// 53 | /// It works as follows: 54 | /// * Check whether the signal is already matched, and returns if a mismatch is found 55 | /// * Check that the gate types match 56 | /// * Call recursively on each gate input 57 | fn try_match(&mut self, repr: Signal, aig: &Network, s: Signal) -> bool { 58 | let existing_match = self.get_match(repr); 59 | if existing_match != Signal::placeholder() { 60 | return existing_match == s; 61 | } 62 | self.set_match(repr, s); 63 | if repr.is_var() { 64 | // Match a gate 65 | if !s.is_var() { 66 | return false; 67 | } 68 | // Needs to be used with the same polarity 69 | if s.is_inverted() != repr.is_inverted() { 70 | return false; 71 | } 72 | let g_repr = self.pattern.gate(repr.var() as usize); 73 | let g = aig.gate(s.var() as usize); 74 | if !Matcher::gate_type_matches(g_repr, g) { 75 | return false; 76 | } 77 | for (&repr_r, &s_r) in zip(g_repr.dependencies(), g.dependencies()) { 78 | if !self.try_match(repr_r, aig, s_r) { 79 | return false; 80 | } 81 | } 82 | true 83 | } else if repr.is_input() { 84 | true 85 | } else { 86 | // Constant 87 | repr == s 88 | } 89 | } 90 | 91 | /// Check whether a gate type matches 92 | fn gate_type_matches(g_repr: &Gate, g: &Gate) -> bool { 93 | use Gate::*; 94 | match (g_repr, g) { 95 | (Binary(_, t1), Binary(_, t2)) => t1 == t2, 96 | (Ternary(_, t1), Ternary(_, t2)) => t1 == t2, 97 | (Nary(v1, t1), Nary(v2, t2)) => t1 == t2 && v1.len() == v2.len(), 98 | (Buf(_), Buf(_)) => true, 99 | (Dff(_), Dff(_)) => true, 100 | _ => false, 101 | } 102 | } 103 | 104 | /// Get the signal currently matched to a given pattern signal 105 | fn get_match(&self, repr: Signal) -> Signal { 106 | if repr.is_constant() { 107 | return repr; 108 | } 109 | let ind = if repr.is_input() { 110 | repr.input() as usize 111 | } else { 112 | self.pattern.nb_inputs() + repr.var() as usize 113 | }; 114 | let m = self.matches[ind]; 115 | if m == Signal::placeholder() { 116 | m 117 | } else { 118 | m ^ repr.is_inverted() 119 | } 120 | } 121 | 122 | /// Set the signal currently matched to a given pattern signal 123 | fn set_match(&mut self, repr: Signal, val: Signal) { 124 | assert!(!repr.is_constant()); 125 | let ind = if repr.is_input() { 126 | repr.input() as usize 127 | } else { 128 | self.pattern.nb_inputs() + repr.var() as usize 129 | }; 130 | self.matches[ind] = val ^ repr.is_inverted(); 131 | } 132 | 133 | /// Reset the internal state, putting all signals to placeholder 134 | fn reset(&mut self) { 135 | for m in &mut self.matches { 136 | *m = Signal::placeholder(); 137 | } 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod test { 143 | use crate::{Gate, Network, Signal}; 144 | 145 | use super::Matcher; 146 | 147 | /// Test single gate pattern matching on and gates 148 | #[test] 149 | fn test_and() { 150 | let mut aig = Network::new(); 151 | aig.add_inputs(3); 152 | let i0 = Signal::from_input(0); 153 | let i1 = Signal::from_input(1); 154 | let i2 = Signal::from_input(2); 155 | aig.add(Gate::and(i0, i1)); 156 | aig.add(Gate::and(i0, i2)); 157 | aig.add(Gate::and(i2, i1)); 158 | aig.add(Gate::and(i0, !i1)); 159 | aig.add(Gate::and(!i0, i1)); 160 | aig.add(Gate::xor(i0, i1)); 161 | aig.add(Gate::xor(!i0, i1)); 162 | 163 | let mut pattern = Network::new(); 164 | pattern.add_inputs(2); 165 | let o = pattern.add(Gate::and(i0, i1)); 166 | pattern.add_output(o); 167 | 168 | let mut matcher = Matcher::from_pattern(&pattern); 169 | for i in 0..5 { 170 | assert!(matcher.matches(&aig, i).is_some()); 171 | } 172 | for i in 5..7 { 173 | assert!(matcher.matches(&aig, i).is_none()); 174 | } 175 | 176 | assert_eq!(matcher.matches(&aig, 0), Some(vec![i0, i1])); 177 | assert_eq!(matcher.matches(&aig, 1), Some(vec![i0, i2])); 178 | assert_eq!(matcher.matches(&aig, 2), Some(vec![i2, i1])); 179 | assert_eq!(matcher.matches(&aig, 3), Some(vec![i0, !i1])); 180 | assert_eq!(matcher.matches(&aig, 4), Some(vec![!i0, i1])); 181 | } 182 | 183 | /// Test more complex pattern matching 184 | #[test] 185 | fn test_complex_xor() { 186 | let mut aig = Network::new(); 187 | aig.add_inputs(2); 188 | let i0 = Signal::from_input(0); 189 | let i1 = Signal::from_input(1); 190 | let x0 = aig.add(Gate::and(i0, !i1)); 191 | let x1 = aig.add(Gate::and(!i0, i1)); 192 | aig.add(Gate::and(!x0, !x1)); 193 | aig.add(Gate::and(x0, x1)); 194 | aig.add(Gate::and(!x0, x1)); 195 | aig.add(Gate::and(!x1, !x0)); 196 | let x2 = aig.add(Gate::and(i0, i1)); 197 | let x3 = aig.add(Gate::and(!i0, !i1)); 198 | aig.add(Gate::and(!x2, !x3)); 199 | 200 | let mut pattern = Network::new(); 201 | pattern.add_inputs(2); 202 | let p0 = pattern.add(Gate::and(i0, !i1)); 203 | let p1 = pattern.add(Gate::and(!i0, i1)); 204 | let o = pattern.add(Gate::and(!p0, !p1)); 205 | pattern.add_output(o); 206 | 207 | let mut matcher = Matcher::from_pattern(&pattern); 208 | assert_eq!(matcher.matches(&aig, 2), Some(vec![i0, i1])); 209 | assert_eq!(matcher.matches(&aig, 3), None); 210 | assert_eq!(matcher.matches(&aig, 4), None); 211 | assert_eq!(matcher.matches(&aig, 5), Some(vec![!i0, !i1])); 212 | assert_eq!(matcher.matches(&aig, 8), Some(vec![i0, !i1])); 213 | } 214 | 215 | /// Test more complex pattern matching 216 | #[test] 217 | fn test_complex_mux() { 218 | let mut aig = Network::new(); 219 | aig.add_inputs(3); 220 | let i0 = Signal::from_input(0); 221 | let i1 = Signal::from_input(1); 222 | let i2 = Signal::from_input(2); 223 | let x0 = aig.add(Gate::and(i0, i1)); 224 | let x1 = aig.add(Gate::and(!i0, i2)); 225 | aig.add(Gate::and(!x0, !x1)); 226 | let x2 = aig.add(Gate::and(i0, i1)); 227 | let x3 = aig.add(Gate::and(!i0, !i1)); 228 | aig.add(Gate::and(!x2, !x3)); 229 | 230 | let mut pattern = Network::new(); 231 | pattern.add_inputs(3); 232 | let p0 = pattern.add(Gate::and(i0, !i1)); 233 | let p1 = pattern.add(Gate::and(!i0, !i2)); 234 | let o = pattern.add(Gate::and(!p0, !p1)); 235 | pattern.add_output(o); 236 | 237 | let mut matcher = Matcher::from_pattern(&pattern); 238 | assert_eq!(matcher.matches(&aig, 2), Some(vec![i0, !i1, !i2])); 239 | assert_eq!(matcher.matches(&aig, 5), Some(vec![i0, !i1, i1])); 240 | } 241 | 242 | /// Test the matching of constants 243 | #[test] 244 | fn test_constants() { 245 | let mut aig = Network::new(); 246 | aig.add_inputs(3); 247 | let i0 = Signal::from_input(0); 248 | let i1 = Signal::from_input(1); 249 | let i2 = Signal::from_input(2); 250 | aig.add(Gate::and(i0, Signal::zero())); 251 | aig.add(Gate::and(i0, Signal::one())); 252 | aig.add(Gate::and(!i0, Signal::zero())); 253 | aig.add(Gate::and(i1, Signal::zero())); 254 | aig.add(Gate::and(i1, Signal::one())); 255 | aig.add(Gate::and(!i1, Signal::zero())); 256 | aig.add(Gate::and(i2, Signal::zero())); 257 | aig.add(Gate::and(i2, Signal::one())); 258 | aig.add(Gate::and(!i2, Signal::zero())); 259 | 260 | let mut pattern = Network::new(); 261 | pattern.add_inputs(1); 262 | let o = pattern.add(Gate::and(i0, Signal::zero())); 263 | pattern.add_output(o); 264 | 265 | let mut matcher = Matcher::from_pattern(&pattern); 266 | assert_eq!(matcher.matches(&aig, 0), Some(vec![i0])); 267 | assert_eq!(matcher.matches(&aig, 1), None); 268 | assert_eq!(matcher.matches(&aig, 2), Some(vec![!i0])); 269 | assert_eq!(matcher.matches(&aig, 3), Some(vec![i1])); 270 | assert_eq!(matcher.matches(&aig, 4), None); 271 | assert_eq!(matcher.matches(&aig, 5), Some(vec![!i1])); 272 | assert_eq!(matcher.matches(&aig, 6), Some(vec![i2])); 273 | assert_eq!(matcher.matches(&aig, 7), None); 274 | assert_eq!(matcher.matches(&aig, 8), Some(vec![!i2])); 275 | } 276 | 277 | /// Test matching with a loop 278 | #[test] 279 | fn test_loop() { 280 | let mut aig = Network::new(); 281 | aig.add_inputs(3); 282 | let d = Signal::from_input(0); 283 | let en = Signal::from_input(1); 284 | 285 | // Loop corresponding to a Dff enable 286 | aig.add(Gate::mux(en, d, Signal::from_var(1))); 287 | aig.add(Gate::dff( 288 | Signal::from_var(0), 289 | Signal::one(), 290 | Signal::zero(), 291 | )); 292 | 293 | // Not a loop 294 | aig.add(Gate::mux(en, d, Signal::from_input(2))); 295 | aig.add(Gate::dff( 296 | Signal::from_var(2), 297 | Signal::one(), 298 | Signal::zero(), 299 | )); 300 | 301 | // Loop corresponding to the opposite Dff enable 302 | aig.add(Gate::mux(!en, d, Signal::from_var(5))); 303 | aig.add(Gate::dff( 304 | Signal::from_var(4), 305 | Signal::one(), 306 | Signal::zero(), 307 | )); 308 | 309 | // Loop with a toggle that shouldn't be matched 310 | aig.add(Gate::mux(en, d, !Signal::from_var(7))); 311 | aig.add(Gate::dff( 312 | Signal::from_var(6), 313 | Signal::one(), 314 | Signal::zero(), 315 | )); 316 | 317 | // Loop with a toggle that shouldn't be matched 318 | aig.add(Gate::mux(en, d, Signal::from_var(9))); 319 | aig.add(Gate::dff( 320 | !Signal::from_var(8), 321 | Signal::one(), 322 | Signal::zero(), 323 | )); 324 | 325 | let mut pattern = Network::new(); 326 | pattern.add_inputs(2); 327 | pattern.add(Gate::mux(en, d, Signal::from_var(1))); 328 | pattern.add(Gate::dff( 329 | Signal::from_var(0), 330 | Signal::one(), 331 | Signal::zero(), 332 | )); 333 | pattern.add_output(Signal::from_var(1)); 334 | 335 | let mut matcher = Matcher::from_pattern(&pattern); 336 | assert_eq!(matcher.matches(&aig, 1), Some(vec![d, en])); 337 | assert_eq!(matcher.matches(&aig, 3), None); 338 | assert_eq!(matcher.matches(&aig, 5), Some(vec![d, !en])); 339 | assert_eq!(matcher.matches(&aig, 7), None); 340 | assert_eq!(matcher.matches(&aig, 9), None); 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /src/optim/share_logic.rs: -------------------------------------------------------------------------------- 1 | //! Logic sharing, replacing N-input And and Xor gates by 2-input gates 2 | //! 3 | //! This pass will greedily replace the most used 2-input combination to 4 | //! maximize sharing between gates. 5 | 6 | use std::cmp; 7 | use std::iter::zip; 8 | 9 | use fxhash::{FxHashMap, FxHashSet}; 10 | use itertools::Itertools; 11 | 12 | use crate::network::NaryType; 13 | use crate::{Gate, Network, Signal}; 14 | 15 | /// Helper functions to merge N-input gates, to specialize by And/Xor 16 | fn merge_dependencies bool>( 17 | aig: &Network, 18 | g: &Gate, 19 | max_size: usize, 20 | pred: F, 21 | ) -> Box<[Signal]> { 22 | let v = g.dependencies(); 23 | let mut ret = Vec::new(); 24 | let mut remaining = v.len(); 25 | for s in v.iter() { 26 | remaining -= 1; 27 | if !s.is_var() || s.is_inverted() { 28 | ret.push(*s); 29 | } else { 30 | let prev_g = aig.gate(s.var() as usize); 31 | let prev_deps = prev_g.dependencies(); 32 | if pred(prev_g) && ret.len() + prev_deps.len() + remaining <= max_size { 33 | ret.extend(prev_deps); 34 | } else { 35 | ret.push(*s); 36 | } 37 | } 38 | } 39 | ret.into() 40 | } 41 | 42 | /// Completely flatten And and Xor gates in a network 43 | /// 44 | /// Gates will be completely merged. This can result in very large And and Xor gates which share many inputs. 45 | /// To avoid quadratic blowup, a maximum size can be specified. Gates that do not share inputs will be 46 | /// flattened regardless of their size. 47 | pub fn flatten_nary(aig: &Network, max_size: usize) -> Network { 48 | let mut ret = aig.clone(); 49 | for i in 0..ret.nb_nodes() { 50 | if ret.gate(i).is_and() { 51 | ret.replace( 52 | i, 53 | Gate::Nary( 54 | merge_dependencies(&ret, ret.gate(i), max_size, |t| t.is_and()), 55 | NaryType::And, 56 | ), 57 | ); 58 | } else if ret.gate(i).is_xor() { 59 | ret.replace( 60 | i, 61 | Gate::Nary( 62 | merge_dependencies(&ret, ret.gate(i), max_size, |t| t.is_xor()), 63 | NaryType::Xor, 64 | ), 65 | ); 66 | } 67 | } 68 | ret.cleanup(); 69 | ret.make_canonical(); 70 | ret 71 | } 72 | 73 | /// Datastructure representing the factorization process 74 | struct Factoring { 75 | /// Gates left to factor 76 | gate_signals: Vec>, 77 | /// Signals that occur only once and don't need tending 78 | gate_exclusive_signals: Vec>, 79 | /// Next variable index to be allocated 80 | next_var: u32, 81 | /// Pairs that have already been built 82 | built_pairs: Vec<(Signal, Signal)>, 83 | /// Pairs organized by bucket of usage count 84 | count_to_pair: Vec>, 85 | /// Pairs to their usage location 86 | pair_to_gates: FxHashMap<(Signal, Signal), FxHashSet>, 87 | // TODO: use faster hashmaps 88 | // TODO: handle the common case (no sharing) separately 89 | } 90 | 91 | impl Factoring { 92 | /// Build from the list of gates 93 | fn from_gates(gates: Vec>, next_var: u32) -> Factoring { 94 | Factoring { 95 | gate_signals: gates, 96 | gate_exclusive_signals: Vec::new(), 97 | next_var, 98 | built_pairs: Vec::new(), 99 | count_to_pair: Vec::new(), 100 | pair_to_gates: FxHashMap::default(), 101 | } 102 | } 103 | 104 | /// Create a pair from two signals 105 | fn make_pair(a: &Signal, b: &Signal) -> (Signal, Signal) { 106 | (cmp::min(*a, *b), cmp::max(*a, *b)) 107 | } 108 | 109 | /// Count the number of time each signal is used 110 | fn count_signal_usage(&self) -> FxHashMap { 111 | let mut count = FxHashMap::::default(); 112 | for v in &self.gate_signals { 113 | for s in v { 114 | count.entry(*s).and_modify(|e| *e += 1).or_insert(1); 115 | } 116 | } 117 | count 118 | } 119 | 120 | /// Separate signals that occur just once 121 | fn separate_exclusive_signals(&mut self) { 122 | assert!(self.gate_exclusive_signals.is_empty()); 123 | let cnt = self.count_signal_usage(); 124 | for g in &mut self.gate_signals { 125 | let mut exclusive = g.clone(); 126 | g.retain(|s| cnt[s] != 1); 127 | exclusive.retain(|s| cnt[s] == 1); 128 | self.gate_exclusive_signals.push(exclusive); 129 | } 130 | } 131 | 132 | /// Process binary gates first, as we need to have them anyway 133 | fn consume_binary_gates(&mut self) { 134 | for _ in 0..2 { 135 | // Two passes, just in case there are new opportunities 136 | for i in 0..self.gate_signals.len() { 137 | if self.gate_signals[i].len() == 2 { 138 | self.replace_pair(Factoring::make_pair( 139 | &self.gate_signals[i][0], 140 | &self.gate_signals[i][1], 141 | )); 142 | } 143 | } 144 | } 145 | } 146 | 147 | /// Gather the gates where each pair is used 148 | fn compute_pair_to_gates(&self) -> FxHashMap<(Signal, Signal), FxHashSet> { 149 | let mut ret = FxHashMap::<(Signal, Signal), FxHashSet>::default(); 150 | for (i, v) in self.gate_signals.iter().enumerate() { 151 | for (a, b) in v.iter().tuple_combinations() { 152 | let p = Factoring::make_pair(a, b); 153 | ret.entry(p) 154 | .and_modify(|e| { 155 | e.insert(i); 156 | }) 157 | .or_insert({ 158 | let mut hsh = FxHashSet::default(); 159 | hsh.insert(i); 160 | hsh 161 | }); 162 | } 163 | } 164 | ret 165 | } 166 | 167 | /// Setup the datastructures 168 | fn setup_initial(&mut self) { 169 | self.separate_exclusive_signals(); 170 | self.pair_to_gates = self.compute_pair_to_gates(); 171 | for (p, gates_touched) in &self.pair_to_gates { 172 | let cnt = gates_touched.len(); 173 | if self.count_to_pair.len() <= cnt { 174 | self.count_to_pair.resize(cnt + 1, FxHashSet::default()); 175 | } 176 | self.count_to_pair[cnt].insert(*p); 177 | } 178 | } 179 | 180 | /// Finalize the algorithm with the exclusive signals 181 | fn finalize(&mut self) { 182 | for (g1, g2) in zip(&mut self.gate_signals, &self.gate_exclusive_signals) { 183 | g1.extend(g2); 184 | } 185 | self.gate_exclusive_signals.clear(); 186 | for g in &mut self.gate_signals { 187 | // Create the tree of binary gates 188 | while g.len() > 1 { 189 | let mut next_g = Vec::new(); 190 | for i in (0..g.len() - 1).step_by(2) { 191 | let p = Signal::from_var(self.next_var); 192 | self.next_var += 1; 193 | self.built_pairs.push((g[i], g[i + 1])); 194 | next_g.push(p); 195 | } 196 | if g.len() % 2 != 0 { 197 | next_g.push(*g.last().unwrap()); 198 | } 199 | *g = next_g; 200 | } 201 | } 202 | } 203 | 204 | /// Remove one pair from everywhere it is used 205 | fn replace_pair(&mut self, p: (Signal, Signal)) { 206 | let p_out = Signal::from_var(self.next_var); 207 | self.next_var += 1; 208 | self.built_pairs.push(p); 209 | let gates_touched = self.pair_to_gates.remove(&p).unwrap(); 210 | self.count_to_pair[gates_touched.len()].remove(&p); 211 | for i in gates_touched { 212 | self.gate_signals[i].retain(|s| *s != p.0 && *s != p.1); 213 | for s in self.gate_signals[i].clone() { 214 | self.decrement_pair(Factoring::make_pair(&s, &p.0), i); 215 | self.decrement_pair(Factoring::make_pair(&s, &p.1), i); 216 | self.increment_pair(Factoring::make_pair(&s, &p_out), i); 217 | self.increment_pair(Factoring::make_pair(&s, &p_out), i); 218 | } 219 | self.gate_signals[i].push(p_out); 220 | } 221 | } 222 | 223 | /// Decrement the usage of one pair 224 | fn decrement_pair(&mut self, p: (Signal, Signal), gate: usize) { 225 | let cnt = self.pair_to_gates[&p].len(); 226 | self.pair_to_gates.entry(p).and_modify(|e| { 227 | e.remove(&gate); 228 | }); 229 | self.count_to_pair[cnt].remove(&p); 230 | if cnt > 1 { 231 | self.count_to_pair[cnt - 1].insert(p); 232 | } 233 | } 234 | 235 | /// Increment the usage of one pair 236 | fn increment_pair(&mut self, p: (Signal, Signal), gate: usize) { 237 | self.pair_to_gates 238 | .entry(p) 239 | .and_modify(|e| { 240 | e.insert(gate); 241 | }) 242 | .or_insert({ 243 | let mut hsh = FxHashSet::default(); 244 | hsh.insert(gate); 245 | hsh 246 | }); 247 | let cnt = self.pair_to_gates[&p].len(); 248 | if self.count_to_pair.len() <= cnt { 249 | self.count_to_pair.resize(cnt + 1, FxHashSet::default()); 250 | } 251 | self.count_to_pair[cnt - 1].remove(&p); 252 | self.count_to_pair[cnt].insert(p); 253 | } 254 | 255 | /// Find the pair to add 256 | fn find_best_pair(&mut self) -> Option<(Signal, Signal)> { 257 | while !self.count_to_pair.is_empty() { 258 | let pairs = self.count_to_pair.last().unwrap(); 259 | if let Some(p) = pairs.iter().next() { 260 | return Some(*p); 261 | } else { 262 | self.count_to_pair.pop(); 263 | } 264 | } 265 | None 266 | } 267 | 268 | /// Share logic between the pairs 269 | fn consume_pairs(&mut self) { 270 | self.setup_initial(); 271 | self.consume_binary_gates(); 272 | while let Some(p) = self.find_best_pair() { 273 | self.replace_pair(p); 274 | } 275 | for g in &self.gate_signals { 276 | assert!(g.len() <= 1); 277 | } 278 | self.finalize(); 279 | 280 | for g in &self.gate_signals { 281 | assert!(g.len() == 1); 282 | } 283 | } 284 | 285 | /// Run factoring of the gates, and return the resulting binary gates to create 286 | pub fn run(gates: Vec>, first_var: u32) -> (Vec<(Signal, Signal)>, Vec) { 287 | let mut f = Factoring::from_gates(gates, first_var); 288 | f.consume_pairs(); 289 | let replacement = f.gate_signals.iter().map(|g| g[0]).collect(); 290 | (f.built_pairs, replacement) 291 | } 292 | } 293 | 294 | /// Helper function to factor an Aig, to specialize by And/Xor 295 | fn factor_gates bool, G: Fn(Signal, Signal) -> Gate>( 296 | aig: &Network, 297 | pred: F, 298 | builder: G, 299 | ) -> Network { 300 | assert!(aig.is_topo_sorted()); 301 | 302 | let mut inds = Vec::new(); 303 | let mut gates = Vec::new(); 304 | for i in 0..aig.nb_nodes() { 305 | let g = aig.gate(i); 306 | if pred(g) && g.dependencies().len() > 1 { 307 | gates.push(g.dependencies().into()); 308 | inds.push(i); 309 | } 310 | } 311 | 312 | let mut ret = aig.clone(); 313 | let (binary_gates, replacements) = Factoring::run(gates, ret.nb_nodes() as u32); 314 | for (a, b) in binary_gates { 315 | ret.add(builder(a, b)); 316 | } 317 | 318 | for (i, g) in zip(inds, replacements) { 319 | ret.replace(i, Gate::Buf(g)); 320 | } 321 | 322 | // Necessary to cleanup as we have gates 323 | ret.topo_sort(); 324 | ret.make_canonical(); 325 | ret 326 | } 327 | 328 | /// Factor And or Xor gates with common inputs 329 | /// 330 | /// Transform large gates into trees of binary gates, sharing as many inputs as possible. 331 | /// The optimization is performed greedily by merging the most used pair of inputs at each step. 332 | /// There is no delay optimization yet. 333 | pub fn factor_nary(aig: &Network) -> Network { 334 | let aig1 = factor_gates(aig, |g| g.is_and(), |a, b| Gate::and(a, b)); 335 | let aig2 = factor_gates(&aig1, |g| g.is_xor(), |a, b| Gate::xor(a, b)); 336 | aig2 337 | } 338 | 339 | /// Share logic between N-ary gates 340 | /// 341 | /// Reorganizes logic into N-input gates, then creates trees of 2-input gates that share as much logic as possible 342 | pub fn share_logic(aig: &mut Network, flattening_limit: usize) { 343 | *aig = flatten_nary(&aig, flattening_limit); 344 | *aig = factor_nary(&aig); 345 | } 346 | 347 | #[cfg(test)] 348 | mod tests { 349 | use super::{factor_nary, flatten_nary}; 350 | use crate::network::NaryType; 351 | use crate::{Gate, Network, Signal}; 352 | 353 | #[test] 354 | fn test_flatten_and() { 355 | let mut aig = Network::new(); 356 | let i0 = aig.add_input(); 357 | let i1 = aig.add_input(); 358 | let i2 = aig.add_input(); 359 | aig.add_input(); 360 | let i4 = aig.add_input(); 361 | let x0 = aig.and(i0, i1); 362 | let x1 = aig.and(i0, !i2); 363 | let x2 = aig.and(x0, x1); 364 | let x3 = aig.and(x2, i4); 365 | aig.add_output(x3); 366 | aig = flatten_nary(&aig, 64); 367 | assert_eq!(aig.nb_nodes(), 1); 368 | assert_eq!( 369 | aig.gate(0), 370 | &Gate::Nary(Box::new([i4, !i2, i1, i0]), NaryType::And) 371 | ); 372 | } 373 | 374 | #[test] 375 | fn test_flatten_xor() { 376 | let mut aig = Network::new(); 377 | let i0 = aig.add_input(); 378 | let i1 = aig.add_input(); 379 | let i2 = aig.add_input(); 380 | aig.add_input(); 381 | let i4 = aig.add_input(); 382 | let x0 = aig.xor(i0, i1); 383 | let x1 = aig.xor(i0, !i2); 384 | let x2 = aig.xor(x0, x1); 385 | let x3 = aig.xor(x2, i4); 386 | aig.add_output(x3); 387 | aig = flatten_nary(&aig, 64); 388 | assert_eq!(aig.nb_nodes(), 1); 389 | assert_eq!(aig.gate(0), &Gate::xor3(i4, i2, i1)); 390 | assert_eq!(aig.output(0), !Signal::from_var(0)); 391 | } 392 | 393 | #[test] 394 | fn test_share_and() { 395 | let mut aig = Network::new(); 396 | let i0 = aig.add_input(); 397 | let i1 = aig.add_input(); 398 | let i2 = aig.add_input(); 399 | let i3 = aig.add_input(); 400 | let i4 = aig.add_input(); 401 | let x0 = aig.add(Gate::Nary(Box::new([i0, i1, i2]), NaryType::And)); 402 | let x1 = aig.add(Gate::Nary(Box::new([i0, i1, i2, i3]), NaryType::And)); 403 | let x2 = aig.add(Gate::Nary(Box::new([i1, i2, i4]), NaryType::And)); 404 | aig.add_output(x0); 405 | aig.add_output(x1); 406 | aig.add_output(x2); 407 | aig = factor_nary(&aig); 408 | assert_eq!(aig.nb_nodes(), 4); 409 | // Check that the first gate is the most shared 410 | assert_eq!(aig.gate(0), &Gate::and(i2, i1)); 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/atpg.rs: -------------------------------------------------------------------------------- 1 | //! Test pattern generation 2 | 3 | use std::iter::zip; 4 | 5 | use kdam::{tqdm, BarExt}; 6 | use rand::rngs::SmallRng; 7 | use rand::{Rng, SeedableRng}; 8 | 9 | use crate::equiv::{difference, prove}; 10 | use crate::sim::{detects_faults, detects_faults_multi, Fault}; 11 | use crate::{Gate, Network, Signal}; 12 | 13 | /// Expose flip_flops as inputs for ATPG 14 | /// 15 | /// Flip-flop outputs are exposed are primary inputs. Flip-flop inputs, including 16 | /// enable and reset, become primary outputs. 17 | /// The new inputs and outputs are added after the original inputs, and their order 18 | /// matches the order of the flip flops. 19 | pub fn expose_dff(aig: &Network) -> Network { 20 | let mut ret = Network::new(); 21 | ret.add_inputs(aig.nb_inputs()); 22 | for i in 0..aig.nb_outputs() { 23 | ret.add_output(aig.output(i)); 24 | } 25 | for i in 0..aig.nb_nodes() { 26 | if let Gate::Dff([d, en, res]) = aig.gate(i) { 27 | let new_input = ret.add_input(); 28 | ret.add(Gate::Buf(new_input)); 29 | ret.add_output(*d); 30 | if !en.is_constant() { 31 | ret.add_output(*en); 32 | } 33 | if !res.is_constant() { 34 | ret.add_output(*res); 35 | } 36 | } else { 37 | let g = aig.gate(i).clone(); 38 | ret.add(g); 39 | } 40 | } 41 | ret.check(); 42 | ret 43 | } 44 | 45 | /// Find a new test pattern for a specific fault using a SAT solver 46 | /// 47 | /// Each gate may be in one of two cases: 48 | /// * in the logic cone after the fault: those need to be duplicated with/without the fault 49 | /// * elsewhere, where they don't need to be duplicated 50 | /// To keep things simpler, we create the full network with/without the fault, and let basic 51 | /// deduplication handle the rest. 52 | fn find_pattern_detecting_fault(aig: &Network, fault: Fault) -> Option> { 53 | assert!(aig.is_comb()); 54 | 55 | let mut fault_aig = aig.clone(); 56 | match fault { 57 | Fault::OutputStuckAtFault { gate, value } => { 58 | fault_aig.replace(gate, Gate::Buf(Signal::from(value))); 59 | } 60 | Fault::InputStuckAtFault { gate, input, value } => { 61 | let g = 62 | aig.gate(gate).remap_with_ind( 63 | |s, i| { 64 | if i == input { 65 | Signal::from(value) 66 | } else { 67 | *s 68 | } 69 | }, 70 | ); 71 | fault_aig.replace(gate, g); 72 | } 73 | }; 74 | 75 | let mut diff = difference(aig, &fault_aig); 76 | diff.make_canonical(); 77 | diff.cleanup(); 78 | let ret = prove(&diff); 79 | if let Some(pattern) = &ret { 80 | assert_eq!(detects_faults(aig, &pattern, &vec![fault]), vec![true]); 81 | } 82 | ret 83 | } 84 | 85 | /// Generate random patterns with a given number of timesteps 86 | pub fn generate_random_seq_patterns( 87 | nb_inputs: usize, 88 | nb_timesteps: usize, 89 | nb_patterns: usize, 90 | seed: u64, 91 | ) -> Vec>> { 92 | let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); 93 | let mut ret = Vec::new(); 94 | for _ in 0..nb_patterns { 95 | let mut r1 = Vec::new(); 96 | for _ in 0..nb_timesteps { 97 | let mut r2 = Vec::new(); 98 | for _ in 0..nb_inputs { 99 | r2.push(rng.gen()); 100 | } 101 | r1.push(r2); 102 | } 103 | ret.push(r1); 104 | } 105 | ret 106 | } 107 | 108 | /// Generate random combinatorial patterns 109 | pub fn generate_random_comb_patterns( 110 | nb_inputs: usize, 111 | nb_patterns: usize, 112 | seed: u64, 113 | ) -> Vec> { 114 | let seq_patterns = generate_random_seq_patterns(nb_inputs, 1, nb_patterns, seed); 115 | seq_patterns.iter().map(|p| p[0].clone()).collect() 116 | } 117 | 118 | /// Handling of the actual test pattern generation 119 | struct TestPatternGenerator<'a> { 120 | aig: &'a Network, 121 | faults: Vec, 122 | patterns: Vec>, 123 | pattern_detections: Vec>, 124 | detection: Vec, 125 | rng: SmallRng, 126 | } 127 | 128 | impl<'a> TestPatternGenerator<'a> { 129 | pub fn nb_faults(&self) -> usize { 130 | self.faults.len() 131 | } 132 | 133 | pub fn nb_patterns(&self) -> usize { 134 | self.patterns.len() 135 | } 136 | 137 | pub fn nb_detected(&self) -> usize { 138 | self.detection.iter().filter(|b| **b).count() 139 | } 140 | 141 | /// Initialize the generator from a network and a seed 142 | pub fn from(aig: &'a Network, faults: Vec, seed: u64) -> TestPatternGenerator { 143 | assert!(aig.is_topo_sorted()); 144 | let nb_faults = faults.len(); 145 | TestPatternGenerator { 146 | aig, 147 | faults: faults, 148 | patterns: Vec::new(), 149 | pattern_detections: Vec::new(), 150 | detection: vec![false; nb_faults], 151 | rng: SmallRng::seed_from_u64(seed), 152 | } 153 | } 154 | 155 | /// Extend a vector of boolean vectors with 64 elements at once 156 | fn extend_vec(v: &mut Vec>, added: Vec) { 157 | for i in 0..64 { 158 | v.push(added.iter().map(|d| (d >> i) & 1 != 0).collect()); 159 | } 160 | } 161 | 162 | /// Obtain all faults, or only the ones that are not yet detected, and their index 163 | pub fn get_faults(&self, check_already_detected: bool) -> (Vec, Vec) { 164 | let mut faults = Vec::new(); 165 | let mut indices = Vec::new(); 166 | for (i, f) in self.faults.iter().enumerate() { 167 | if check_already_detected || !self.detection[i] { 168 | faults.push(*f); 169 | indices.push(i); 170 | } 171 | } 172 | (faults, indices) 173 | } 174 | 175 | /// Add a single pattern to the current set 176 | #[allow(dead_code)] 177 | pub fn add_single_pattern(&mut self, pattern: Vec, check_already_detected: bool) { 178 | let (faults, indices) = self.get_faults(check_already_detected); 179 | let detected = detects_faults(self.aig, &pattern, &faults); 180 | let mut det = vec![false; self.nb_faults()]; 181 | for (i, d) in zip(indices, detected) { 182 | self.detection[i] |= d; 183 | det[i] = d; 184 | } 185 | self.patterns.push(pattern); 186 | self.pattern_detections.push(det); 187 | } 188 | 189 | /// Add a single pattern and random variations to the current set 190 | pub fn add_random_patterns_from(&mut self, pattern: Vec, check_already_detected: bool) { 191 | let mut patterns = Vec::new(); 192 | let num_rounds = 4; // Generate mostly 0s, with 1/16 values being ones 193 | for b in pattern { 194 | let mut val = if b { !0 } else { 0 }; 195 | let mut change = !0; 196 | for _ in 0..num_rounds { 197 | change &= self.rng.gen::(); 198 | } 199 | val ^= change; 200 | val &= !1; // Ensure that the first pattern is the original one 201 | patterns.push(val); 202 | } 203 | self.add_patterns(patterns, check_already_detected); 204 | } 205 | 206 | /// Add a new set of patterns to the current set 207 | pub fn add_patterns(&mut self, patterns: Vec, check_already_detected: bool) { 208 | let (faults, indices) = self.get_faults(check_already_detected); 209 | let detected = detects_faults_multi(self.aig, &patterns, &faults); 210 | let mut det = vec![0; self.nb_faults()]; 211 | for (i, d) in zip(indices, detected) { 212 | self.detection[i] |= d != 0; 213 | det[i] = d; 214 | } 215 | Self::extend_vec(&mut self.patterns, patterns); 216 | Self::extend_vec(&mut self.pattern_detections, det); 217 | } 218 | 219 | /// Generate a random pattern and add it to the current set 220 | pub fn add_random_patterns(&mut self, check_already_detected: bool) { 221 | let pattern = (0..self.aig.nb_inputs()) 222 | .map(|_| self.rng.gen::()) 223 | .collect(); 224 | self.add_patterns(pattern, check_already_detected); 225 | } 226 | 227 | /// Check consistency 228 | pub fn check(&self) { 229 | assert_eq!(self.patterns.len(), self.pattern_detections.len()); 230 | for p in &self.patterns { 231 | assert_eq!(p.len(), self.aig.nb_inputs()); 232 | } 233 | for p in &self.pattern_detections { 234 | assert_eq!(p.len(), self.nb_faults()); 235 | } 236 | assert_eq!(self.detection.len(), self.nb_faults()); 237 | } 238 | 239 | /// Compress the existing patterns to keep as few as possible. 240 | /// This is a minimum set cover problem. 241 | /// At the moment we solve it with a simple greedy algorithm, 242 | /// taking the pattern that detects the most new faults each time. 243 | pub fn compress_patterns(&mut self) { 244 | let mut progress = 245 | tqdm!(total = 2 * self.nb_faults() * self.nb_patterns() + self.nb_detected()); 246 | progress.set_description("Compression progress"); 247 | progress 248 | .set_bar_format("{desc}{percentage:3.0}%|{animation}| [{elapsed}<{remaining}{postfix}]") 249 | .unwrap(); 250 | progress.set_postfix(format!("patterns=-")); 251 | let mut remaining_to_detect = self.nb_detected(); 252 | let mut it = 0; 253 | 254 | // Which patterns detect a given fault 255 | let mut fault_to_patterns = Vec::new(); 256 | for f in 0..self.nb_faults() { 257 | let mut patterns = Vec::new(); 258 | for p in 0..self.nb_patterns() { 259 | if self.pattern_detections[p][f] { 260 | patterns.push(p); 261 | } 262 | it += 1; 263 | if it % 256 == 0 { 264 | progress.update_to(it).unwrap(); 265 | } 266 | } 267 | fault_to_patterns.push(patterns); 268 | } 269 | 270 | // Which faults are detected by a given pattern 271 | let mut pattern_to_faults = Vec::new(); 272 | for p in 0..self.nb_patterns() { 273 | let mut faults = Vec::new(); 274 | for f in 0..self.nb_faults() { 275 | if self.pattern_detections[p][f] { 276 | faults.push(f); 277 | } 278 | it += 1; 279 | if it % 256 == 0 { 280 | progress.update_to(it).unwrap(); 281 | } 282 | } 283 | pattern_to_faults.push(faults); 284 | } 285 | 286 | // How many new faults each pattern detects 287 | let mut nb_detected_by_pattern: Vec<_> = 288 | pattern_to_faults.iter().map(|v| v.len()).collect(); 289 | assert_eq!(fault_to_patterns.len(), self.nb_faults()); 290 | assert_eq!(pattern_to_faults.len(), self.nb_patterns()); 291 | 292 | let mut selected_patterns = Vec::new(); 293 | progress.update_to(it).unwrap(); 294 | while remaining_to_detect > 0 { 295 | // Pick the pattern that detects the most faults 296 | let best_pattern = nb_detected_by_pattern 297 | .iter() 298 | .enumerate() 299 | .max_by(|(_, a), (_, b)| a.cmp(b)) 300 | .map(|(index, _)| index) 301 | .unwrap(); 302 | selected_patterns.push(best_pattern); 303 | remaining_to_detect -= nb_detected_by_pattern[best_pattern]; 304 | progress.set_postfix(format!("patterns={}", selected_patterns.len())); 305 | progress 306 | .update(nb_detected_by_pattern[best_pattern]) 307 | .unwrap(); 308 | 309 | // Remove the faults detected by the pattern from consideration 310 | assert!(nb_detected_by_pattern[best_pattern] > 0); 311 | for f in &pattern_to_faults[best_pattern] { 312 | for p in &fault_to_patterns[*f] { 313 | nb_detected_by_pattern[*p] -= 1; 314 | } 315 | // So we don't remove a fault twice 316 | fault_to_patterns[*f].clear(); 317 | } 318 | assert_eq!(nb_detected_by_pattern[best_pattern], 0); 319 | } 320 | 321 | let mut new_patterns = Vec::new(); 322 | let mut new_detections = Vec::new(); 323 | for p in selected_patterns { 324 | new_patterns.push(self.patterns[p].clone()); 325 | new_detections.push(self.pattern_detections[p].clone()); 326 | } 327 | self.patterns = new_patterns; 328 | self.pattern_detections = new_detections; 329 | println!(); 330 | } 331 | 332 | pub fn detect_faults(&mut self) { 333 | let mut progress = tqdm!(total = self.nb_faults()); 334 | progress.set_description("Detection progress"); 335 | progress 336 | .set_bar_format("{desc}{percentage:3.0}%|{animation}| [{elapsed}<{remaining}{postfix}]") 337 | .unwrap(); 338 | loop { 339 | let nb_detected_before = self.nb_detected(); 340 | self.add_random_patterns(true); 341 | let nb_detected_after = self.nb_detected(); 342 | progress.set_postfix(format!("patterns={}, unobservable=-", self.nb_patterns())); 343 | progress.update_to(self.nb_detected()).unwrap(); 344 | if nb_detected_after == self.nb_faults() { 345 | break; 346 | } 347 | if ((nb_detected_after - nb_detected_before) as f64) < (0.01 * self.nb_faults() as f64) 348 | { 349 | break; 350 | } 351 | } 352 | progress 353 | .write(format!( 354 | "Generated {} random patterns, detecting {}/{} faults ({:.2}% coverage)", 355 | self.nb_patterns(), 356 | self.nb_detected(), 357 | self.nb_faults(), 358 | 100.0 * (self.nb_detected() as f64) / (self.nb_faults() as f64) 359 | )) 360 | .unwrap(); 361 | let mut unobservable = 0; 362 | for i in 0..self.nb_faults() { 363 | if self.detection[i] { 364 | continue; 365 | } 366 | let p = find_pattern_detecting_fault(self.aig, self.faults[i]); 367 | if let Some(pattern) = p { 368 | self.add_random_patterns_from(pattern, false); 369 | } else { 370 | unobservable += 1; 371 | } 372 | progress.set_postfix(format!( 373 | "patterns={} unobservable={}", 374 | self.nb_patterns(), 375 | unobservable 376 | )); 377 | progress 378 | .update_to(self.nb_detected() + unobservable) 379 | .unwrap(); 380 | } 381 | progress 382 | .write(format!( 383 | "Generated {} patterns total, detecting {}/{} faults ({:.2}% coverage)", 384 | self.nb_patterns(), 385 | self.nb_detected(), 386 | self.nb_faults(), 387 | 100.0 * (self.nb_detected() as f64) / (self.nb_faults() as f64) 388 | )) 389 | .unwrap(); 390 | println!(); 391 | } 392 | } 393 | 394 | /// Generate combinatorial test patterns 395 | /// 396 | /// This will generate random test patterns, then try to exercize the remaining faults 397 | /// using a SAT solver. The network needs to be combinatorial. 398 | pub fn generate_comb_test_patterns( 399 | aig: &Network, 400 | seed: u64, 401 | with_redundant_faults: bool, 402 | ) -> Vec> { 403 | assert!(aig.is_comb()); 404 | let faults = Fault::all(aig); 405 | let unique_faults = Fault::all_unique(aig); 406 | 407 | println!( 408 | "Analyzing network with {} inputs, {} outputs, {} gates, {} possible faults, {} unique faults", 409 | aig.nb_inputs(), 410 | aig.nb_outputs(), 411 | aig.nb_nodes(), 412 | faults.len(), 413 | unique_faults.len(), 414 | ); 415 | 416 | let mut gen = TestPatternGenerator::from( 417 | aig, 418 | if with_redundant_faults { 419 | faults.clone() 420 | } else { 421 | unique_faults.clone() 422 | }, 423 | seed, 424 | ); 425 | gen.detect_faults(); 426 | gen.check(); 427 | gen.compress_patterns(); 428 | gen.check(); 429 | println!( 430 | "Kept {} patterns, detecting {}/{} faults ({:.2}% coverage)", 431 | gen.nb_patterns(), 432 | gen.nb_detected(), 433 | gen.nb_faults(), 434 | 100.0 * (gen.nb_detected() as f64) / (gen.nb_faults() as f64) 435 | ); 436 | gen.patterns 437 | } 438 | 439 | /// Analyze combinatorial test patterns 440 | /// 441 | /// This will show the coverage obtained by these test patterns. The network needs to be combinatorial. 442 | pub fn report_comb_test_patterns( 443 | aig: &Network, 444 | patterns: Vec>, 445 | with_redundant_faults: bool, 446 | ) { 447 | assert!(aig.is_comb()); 448 | let faults = Fault::all(aig); 449 | let unique_faults = Fault::all_unique(aig); 450 | 451 | println!( 452 | "Analyzing network with {} inputs, {} outputs, {} gates, {} possible faults, {} unique faults", 453 | aig.nb_inputs(), 454 | aig.nb_outputs(), 455 | aig.nb_nodes(), 456 | faults.len(), 457 | unique_faults.len(), 458 | ); 459 | 460 | let mut gen = TestPatternGenerator::from( 461 | aig, 462 | if with_redundant_faults { 463 | faults.clone() 464 | } else { 465 | unique_faults.clone() 466 | }, 467 | 0, 468 | ); 469 | for pattern in tqdm!(patterns.iter()) { 470 | // TODO: make it faster by using multi-pattern simulation 471 | gen.add_single_pattern(pattern.clone(), false); 472 | } 473 | 474 | println!( 475 | "Analyzed {} patterns, detecting {}/{} faults ({:.2}% coverage)", 476 | gen.nb_patterns(), 477 | gen.nb_detected(), 478 | gen.nb_faults(), 479 | 100.0 * (gen.nb_detected() as f64) / (gen.nb_faults() as f64) 480 | ); 481 | } 482 | -------------------------------------------------------------------------------- /src/network/network.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::collections::hash_map::Entry; 3 | use std::collections::HashMap; 4 | 5 | use rand::seq::SliceRandom; 6 | use rand::SeedableRng; 7 | 8 | use crate::network::gates::{Gate, Normalization}; 9 | use crate::network::signal::Signal; 10 | 11 | /// Representation of a logic network as a gate-inverter-graph, used as the main representation for all logic manipulations 12 | #[derive(Debug, Clone, Default)] 13 | pub struct Network { 14 | nb_inputs: usize, 15 | nodes: Vec, 16 | outputs: Vec, 17 | } 18 | 19 | impl Network { 20 | /// Create a new network 21 | pub fn new() -> Self { 22 | Self::default() 23 | } 24 | 25 | /// Return the number of primary inputs 26 | pub fn nb_inputs(&self) -> usize { 27 | self.nb_inputs 28 | } 29 | 30 | /// Return the number of primary outputs 31 | pub fn nb_outputs(&self) -> usize { 32 | self.outputs.len() 33 | } 34 | 35 | /// Return the number of nodes in the network 36 | pub fn nb_nodes(&self) -> usize { 37 | self.nodes.len() 38 | } 39 | 40 | /// Get the input at index i 41 | pub fn input(&self, i: usize) -> Signal { 42 | assert!(i < self.nb_inputs()); 43 | Signal::from_input(i as u32) 44 | } 45 | 46 | /// Get the output at index i 47 | pub fn output(&self, i: usize) -> Signal { 48 | assert!(i < self.nb_outputs()); 49 | self.outputs[i] 50 | } 51 | 52 | /// Get the variable at index i 53 | pub fn node(&self, i: usize) -> Signal { 54 | Signal::from_var(i as u32) 55 | } 56 | 57 | /// Get the gate at index i 58 | pub fn gate(&self, i: usize) -> &Gate { 59 | &self.nodes[i] 60 | } 61 | 62 | /// Add a new primary input 63 | pub fn add_input(&mut self) -> Signal { 64 | self.nb_inputs += 1; 65 | self.input(self.nb_inputs() - 1) 66 | } 67 | 68 | /// Add multiple primary inputs 69 | pub fn add_inputs(&mut self, nb: usize) { 70 | self.nb_inputs += nb; 71 | } 72 | 73 | /// Add a new primary output based on an existing literal 74 | pub fn add_output(&mut self, l: Signal) { 75 | self.outputs.push(l) 76 | } 77 | 78 | /// Create an And2 gate 79 | pub fn and(&mut self, a: Signal, b: Signal) -> Signal { 80 | self.add_canonical(Gate::and(a, b)) 81 | } 82 | 83 | /// Create a Xor2 gate 84 | pub fn xor(&mut self, a: Signal, b: Signal) -> Signal { 85 | self.add_canonical(Gate::xor(a, b)) 86 | } 87 | 88 | /// Create a Dff gate (flip flop) 89 | pub fn dff(&mut self, data: Signal, enable: Signal, reset: Signal) -> Signal { 90 | self.add_canonical(Gate::dff(data, enable, reset)) 91 | } 92 | 93 | /// Add a new gate, and make it canonical. The gate may be simplified immediately 94 | pub fn add_canonical(&mut self, gate: Gate) -> Signal { 95 | use Normalization::*; 96 | let g = gate.make_canonical(); 97 | match g { 98 | Copy(l) => l, 99 | Node(g, inv) => self.add(g) ^ inv, 100 | } 101 | } 102 | 103 | /// Add a new gate 104 | pub fn add(&mut self, gate: Gate) -> Signal { 105 | let l = Signal::from_var(self.nodes.len() as u32); 106 | self.nodes.push(gate); 107 | l 108 | } 109 | 110 | /// Replace an existing gate 111 | pub fn replace(&mut self, i: usize, gate: Gate) -> Signal { 112 | let l = Signal::from_var(i as u32); 113 | self.nodes[i] = gate; 114 | l 115 | } 116 | 117 | /// Return whether the network is purely combinatorial 118 | pub fn is_comb(&self) -> bool { 119 | self.nodes.iter().all(|g| g.is_comb()) 120 | } 121 | 122 | /// Return whether the network is already topologically sorted (except for flip-flops) 123 | pub(crate) fn is_topo_sorted(&self) -> bool { 124 | for (i, g) in self.nodes.iter().enumerate() { 125 | let ind = i as u32; 126 | if g.is_comb() { 127 | for v in g.vars() { 128 | if v >= ind { 129 | return false; 130 | } 131 | } 132 | } 133 | } 134 | true 135 | } 136 | 137 | /// Remap nodes; there may be holes in the translation 138 | fn remap(&mut self, order: &[u32]) -> Box<[Signal]> { 139 | // Create the translation 140 | let mut translation = vec![Signal::zero(); self.nb_nodes()]; 141 | for (new_i, old_i) in order.iter().enumerate() { 142 | translation[*old_i as usize] = Signal::from_var(new_i as u32); 143 | } 144 | 145 | // Remap the nodes 146 | let mut new_nodes = Vec::new(); 147 | for o in order { 148 | let i = *o as usize; 149 | let g = self.gate(i); 150 | assert!(translation[i].is_var()); 151 | assert_eq!(translation[i].var(), new_nodes.len() as u32); 152 | new_nodes.push(g.remap_order(translation.as_slice())); 153 | } 154 | self.nodes = new_nodes; 155 | 156 | // Remap the outputs 157 | self.remap_outputs(&translation); 158 | translation.into() 159 | } 160 | 161 | /// Shuffle the network randomly; this will invalidate all signals 162 | /// 163 | /// Returns the mapping of old variable indices to signals, if needed. 164 | pub fn shuffle(&mut self, seed: u64) -> Box<[Signal]> { 165 | let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); 166 | let mut order: Vec = (0..self.nb_nodes() as u32).collect(); 167 | order.shuffle(&mut rng); 168 | self.remap(&order); 169 | self.topo_sort() 170 | } 171 | 172 | /// Remap outputs 173 | fn remap_outputs(&mut self, translation: &[Signal]) { 174 | let new_outputs = self 175 | .outputs 176 | .iter() 177 | .map(|s| s.remap_order(translation)) 178 | .collect(); 179 | self.outputs = new_outputs; 180 | } 181 | 182 | /// Remove unused logic; this will invalidate all signals 183 | /// 184 | /// Returns the mapping of old variable indices to signals, if needed. 185 | /// Removed signals are mapped to zero. 186 | pub fn cleanup(&mut self) -> Box<[Signal]> { 187 | // Mark unused logic 188 | let mut visited = vec![false; self.nb_nodes()]; 189 | let mut to_visit = Vec::::new(); 190 | for o in 0..self.nb_outputs() { 191 | let output = self.output(o); 192 | if output.is_var() { 193 | to_visit.push(output.var()); 194 | } 195 | } 196 | while !to_visit.is_empty() { 197 | let node = to_visit.pop().unwrap() as usize; 198 | if visited[node] { 199 | continue; 200 | } 201 | visited[node] = true; 202 | to_visit.extend(self.gate(node).vars()); 203 | } 204 | 205 | // Now compute a mapping for all nodes that are reachable 206 | let mut order = Vec::new(); 207 | for (i, v) in visited.iter().enumerate() { 208 | if *v { 209 | order.push(i as u32); 210 | } 211 | } 212 | self.remap(order.as_slice()) 213 | } 214 | 215 | /// Remove duplicate logic and make all gates canonical; this will invalidate all signals 216 | /// 217 | /// Canonical gates are And, Xor, Mux, Maj and Lut. Everything else will be simplified. 218 | /// Returns the mapping of old variable indices to signals, if needed. 219 | pub fn make_canonical(&mut self) -> Box<[Signal]> { 220 | self.dedup(true) 221 | } 222 | 223 | /// Remove duplicate logic; this will invalidate all signals 224 | /// 225 | /// Returns the mapping of old variable indices to signals, if needed. 226 | pub fn deduplicate(&mut self) -> Box<[Signal]> { 227 | self.dedup(false) 228 | } 229 | 230 | /// Remove duplicate logic. Optionally make all gates canonical 231 | fn dedup(&mut self, make_canonical: bool) -> Box<[Signal]> { 232 | // Replace each node, in turn, by a simplified version or an equivalent existing node 233 | // We need the network to be topologically sorted, so that the gate inputs are already replaced 234 | // Dff gates are an exception to the sorting, and are handled separately 235 | assert!(self.is_topo_sorted()); 236 | let mut translation = (0..self.nb_nodes()) 237 | .map(|i| Signal::from_var(i as u32)) 238 | .collect::>(); 239 | 240 | /// Core function for deduplication 241 | fn dedup_node( 242 | g: &Gate, 243 | h: &mut HashMap, 244 | nodes: &mut Vec, 245 | make_canonical: bool, 246 | ) -> Signal { 247 | let normalized = if make_canonical { 248 | g.make_canonical() 249 | } else { 250 | Normalization::Node(g.clone(), false) 251 | }; 252 | match normalized { 253 | Normalization::Copy(sig) => sig, 254 | Normalization::Node(g, inv) => { 255 | let node_s = Signal::from_var(nodes.len() as u32); 256 | match h.entry(g.clone()) { 257 | Entry::Occupied(e) => e.get() ^ inv, 258 | Entry::Vacant(e) => { 259 | e.insert(node_s); 260 | nodes.push(g); 261 | node_s ^ inv 262 | } 263 | } 264 | } 265 | } 266 | } 267 | 268 | // TODO: use faster hashing + do not own the Gate 269 | let mut hsh = HashMap::new(); 270 | let mut new_nodes = Vec::new(); 271 | 272 | // Dedup flip flops 273 | for i in 0..self.nb_nodes() { 274 | let g = self.gate(i); 275 | if !g.is_comb() { 276 | translation[i] = dedup_node(g, &mut hsh, &mut new_nodes, make_canonical); 277 | } 278 | } 279 | 280 | // Remap and dedup combinatorial gates 281 | for i in 0..self.nb_nodes() { 282 | let g = self.gate(i).remap_order(translation.as_slice()); 283 | if g.is_comb() { 284 | translation[i] = dedup_node(&g, &mut hsh, &mut new_nodes, make_canonical); 285 | } 286 | } 287 | 288 | // Remap flip flops 289 | for i in 0..new_nodes.len() { 290 | if !new_nodes[i].is_comb() { 291 | new_nodes[i] = new_nodes[i].remap_order(translation.as_slice()); 292 | } 293 | } 294 | 295 | self.nodes = new_nodes; 296 | self.remap_outputs(&translation); 297 | self.check(); 298 | translation.into() 299 | } 300 | 301 | /// Topologically sort the network; this will invalidate all signals 302 | /// 303 | /// Ordering may be changed even if already sorted. Flip-flop ordering is kept as is. 304 | /// Returns the mapping of old variable indices to signals, if needed. 305 | pub(crate) fn topo_sort(&mut self) -> Box<[Signal]> { 306 | // Count the output dependencies of each gate 307 | let mut count_deps = vec![0u32; self.nb_nodes()]; 308 | for g in self.nodes.iter() { 309 | if g.is_comb() { 310 | for v in g.vars() { 311 | count_deps[v as usize] += 1; 312 | } 313 | } 314 | } 315 | 316 | // Compute the topological sort 317 | let mut rev_order: Vec = Vec::new(); 318 | let mut visited = vec![false; self.nb_nodes()]; 319 | 320 | // Handle Dff separately so they are not reordered 321 | for i in 0..self.nb_nodes() { 322 | if !self.gate(i).is_comb() { 323 | visited[i] = true; 324 | } 325 | } 326 | 327 | // Start with gates with no dependencies 328 | let mut to_visit: Vec = (0..self.nb_nodes()) 329 | .filter(|v| count_deps[*v] == 0 && !visited[*v]) 330 | .map(|v| v as u32) 331 | .collect(); 332 | while let Some(v) = to_visit.pop() { 333 | // TODO: allow for some randomness here 334 | // Visit the gate and mark the gates with satisfied dependencies 335 | if visited[v as usize] { 336 | continue; 337 | } 338 | visited[v as usize] = true; 339 | rev_order.push(v); 340 | let g = self.gate(v as usize); 341 | if g.is_comb() { 342 | for d in g.vars() { 343 | count_deps[d as usize] -= 1; 344 | if count_deps[d as usize] == 0 { 345 | to_visit.push(d); 346 | } 347 | } 348 | } 349 | } 350 | 351 | // Add Dff first to the order (first, so last in the reversed order) 352 | for i in (0..self.nb_nodes()).rev() { 353 | if !self.gate(i).is_comb() { 354 | rev_order.push(i as u32); 355 | } 356 | } 357 | 358 | if rev_order.len() != self.nb_nodes() { 359 | panic!("Unable to find a valid topological sort: there must be a combinatorial loop"); 360 | } 361 | rev_order.reverse(); 362 | let order = rev_order; 363 | 364 | self.remap(order.as_slice()) 365 | } 366 | 367 | /// Check consistency of the datastructure 368 | pub fn check(&self) { 369 | for i in 0..self.nb_nodes() { 370 | for v in self.gate(i).dependencies() { 371 | assert!(self.is_valid(*v), "Invalid signal {v}"); 372 | } 373 | } 374 | for i in 0..self.nb_outputs() { 375 | let v = self.output(i); 376 | assert!(self.is_valid(v), "Invalid output {v}"); 377 | } 378 | assert!(self.is_topo_sorted()); 379 | } 380 | 381 | /// Returns whether a signal is valid (within bounds) in the network 382 | pub(crate) fn is_valid(&self, s: Signal) -> bool { 383 | if s.is_input() { 384 | s.input() < self.nb_inputs() as u32 385 | } else if s.is_var() { 386 | s.var() < self.nb_nodes() as u32 387 | } else { 388 | true 389 | } 390 | } 391 | } 392 | 393 | impl fmt::Display for Network { 394 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 395 | writeln!( 396 | f, 397 | "Network with {} inputs, {} outputs:", 398 | self.nb_inputs(), 399 | self.nb_outputs() 400 | )?; 401 | for i in 0..self.nb_nodes() { 402 | writeln!(f, "\t{} = {}", self.node(i), self.gate(i))?; 403 | } 404 | for i in 0..self.nb_outputs() { 405 | writeln!(f, "\to{} = {}", i, self.output(i))?; 406 | } 407 | Ok(()) 408 | } 409 | } 410 | 411 | #[cfg(test)] 412 | mod tests { 413 | use crate::{Gate, Network, Signal}; 414 | 415 | #[test] 416 | fn test_basic() { 417 | let mut aig = Network::default(); 418 | let i0 = aig.add_input(); 419 | let i1 = aig.add_input(); 420 | let x = aig.xor(i0, i1); 421 | aig.add_output(x); 422 | 423 | // Basic properties 424 | assert_eq!(aig.nb_inputs(), 2); 425 | assert_eq!(aig.nb_outputs(), 1); 426 | assert_eq!(aig.nb_nodes(), 1); 427 | assert!(aig.is_comb()); 428 | assert!(aig.is_topo_sorted()); 429 | 430 | // Access 431 | assert_eq!(aig.input(0), i0); 432 | assert_eq!(aig.input(1), i1); 433 | assert_eq!(aig.output(0), x); 434 | } 435 | 436 | #[test] 437 | fn test_dff() { 438 | let mut aig = Network::default(); 439 | let i0 = aig.add_input(); 440 | let i1 = aig.add_input(); 441 | let i2 = aig.add_input(); 442 | let c0 = Signal::zero(); 443 | let c1 = Signal::one(); 444 | // Useful Dff 445 | assert_eq!(aig.dff(i0, i1, i2), Signal::from_var(0)); 446 | assert_eq!(aig.dff(i0, i1, c0), Signal::from_var(1)); 447 | // Dff that reduces to 0 448 | assert_eq!(aig.dff(c0, i1, i2), c0); 449 | assert_eq!(aig.dff(i0, c0, i2), c0); 450 | assert_eq!(aig.dff(i0, i1, c1), c0); 451 | assert!(!aig.is_comb()); 452 | assert!(aig.is_topo_sorted()); 453 | } 454 | 455 | #[test] 456 | fn test_sweep() { 457 | let mut aig = Network::default(); 458 | let i0 = aig.add_input(); 459 | let i1 = aig.add_input(); 460 | let x0 = aig.and(i0, i1); 461 | let x1 = !aig.and(!i0, !i1); 462 | let _ = aig.and(x0, i1); 463 | let x3 = !aig.and(!x1, !i1); 464 | aig.add_output(x3); 465 | let t = aig.cleanup(); 466 | assert_eq!(t.len(), 4); 467 | assert_eq!(aig.nb_nodes(), 2); 468 | assert_eq!(aig.nb_outputs(), 1); 469 | assert_eq!( 470 | t, 471 | vec![ 472 | Signal::zero(), 473 | Signal::from_var(0), 474 | Signal::zero(), 475 | Signal::from_var(1) 476 | ] 477 | .into() 478 | ); 479 | } 480 | 481 | #[test] 482 | fn test_dedup() { 483 | let mut aig = Network::default(); 484 | let i0 = aig.add_input(); 485 | let i1 = aig.add_input(); 486 | let i2 = aig.add_input(); 487 | let x0 = aig.and(i0, i1); 488 | let x0_s = aig.and(i0, i1); 489 | let x1 = aig.and(x0, i2); 490 | let x1_s = aig.and(x0_s, i2); 491 | aig.add_output(x1); 492 | aig.add_output(x1_s); 493 | aig.make_canonical(); 494 | assert_eq!(aig.nb_nodes(), 2); 495 | } 496 | 497 | #[test] 498 | fn test_topo_sort() { 499 | let mut aig = Network::default(); 500 | let i0 = aig.add_input(); 501 | let i1 = aig.add_input(); 502 | let i2 = aig.add_input(); 503 | let x0 = Gate::dff(i2, Signal::one(), Signal::zero()); 504 | let x1 = Gate::dff(i1, Signal::one(), Signal::zero()); 505 | let x2 = Gate::dff(i0, Signal::one(), Signal::zero()); 506 | let x3 = Gate::dff(i2, i1, Signal::zero()); 507 | aig.add(x0.clone()); 508 | aig.add(x1.clone()); 509 | aig.add(x2.clone()); 510 | aig.add(x3.clone()); 511 | aig.topo_sort(); 512 | assert_eq!(aig.nb_nodes(), 4); 513 | assert_eq!(aig.gate(0), &x0); 514 | assert_eq!(aig.gate(1), &x1); 515 | assert_eq!(aig.gate(2), &x2); 516 | assert_eq!(aig.gate(3), &x3); 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /src/io/blif.rs: -------------------------------------------------------------------------------- 1 | use core::panic; 2 | use std::collections::HashMap; 3 | use std::io::{BufRead, BufReader, Write}; 4 | use std::iter::zip; 5 | 6 | use crate::network::{BinaryType, NaryType, TernaryType}; 7 | use crate::{Gate, Network, Signal}; 8 | 9 | use super::utils::{get_inverted_signals, sig_to_string}; 10 | 11 | enum Statement { 12 | Model(String), 13 | End, 14 | Exdc, 15 | Inputs(Vec), 16 | Outputs(Vec), 17 | Latch { input: String, output: String }, 18 | Name(Vec), 19 | Cube(String), 20 | } 21 | 22 | fn build_name_to_sig(statements: &Vec) -> Result, String> { 23 | let mut found_model = false; 24 | 25 | let mut ret = HashMap::new(); 26 | let mut var_index = 0; 27 | let mut input_index = 0; 28 | for statement in statements { 29 | match statement { 30 | Statement::Model(_) => { 31 | if found_model { 32 | return Err("Multiple models in the same file are not supported".to_owned()); 33 | } 34 | found_model = true; 35 | } 36 | Statement::End => { 37 | if !found_model { 38 | return Err("End statement before the end of the model".to_owned()); 39 | } 40 | } 41 | Statement::Exdc => { 42 | break; 43 | } 44 | Statement::Inputs(inputs) => { 45 | for (_, name) in inputs.iter().enumerate() { 46 | let s = Signal::from_input(input_index as u32); 47 | input_index += 1; 48 | let present = ret.insert(name.clone(), s).is_some(); 49 | if present { 50 | return Err(format!("{} is defined twice", name)); 51 | } 52 | } 53 | } 54 | Statement::Outputs(_) => {} 55 | Statement::Latch { 56 | input: _, 57 | output: name, 58 | } => { 59 | let s = Signal::from_var(var_index as u32); 60 | var_index += 1; 61 | let present = ret.insert(name.clone(), s).is_some(); 62 | if present { 63 | return Err(format!("{} is defined twice", name)); 64 | } 65 | } 66 | Statement::Name(names) => { 67 | if names.is_empty() { 68 | return Err(".names statement with no output".to_owned()); 69 | } 70 | let s = Signal::from_var(var_index as u32); 71 | let name = names.last().unwrap(); 72 | var_index += 1; 73 | let present = ret.insert(name.clone(), s).is_some(); 74 | if present { 75 | return Err(format!("{} is defined twice", name)); 76 | } 77 | } 78 | Statement::Cube(_) => (), 79 | } 80 | } 81 | Ok(ret) 82 | } 83 | 84 | fn build_network( 85 | statements: &Vec, 86 | name_to_sig: &HashMap, 87 | ) -> Result { 88 | let mut ret: Network = Network::new(); 89 | 90 | let mut names_to_process = Vec::new(); 91 | 92 | for (i, statement) in statements.iter().enumerate() { 93 | match statement { 94 | Statement::Inputs(inputs) => ret.add_inputs(inputs.len()), 95 | Statement::Outputs(outputs) => { 96 | for name in outputs { 97 | let s = name_to_sig 98 | .get(name) 99 | .ok_or_else(|| format!("{} is not defined", name))?; 100 | ret.add_output(*s); 101 | } 102 | } 103 | Statement::Latch { input, output: _ } => { 104 | ret.add(Gate::dff(name_to_sig[input], Signal::one(), Signal::zero())); 105 | } 106 | Statement::Name(names) => { 107 | let mut deps = Vec::new(); 108 | for name in names.iter().take(names.len() - 1) { 109 | let s = name_to_sig 110 | .get(name) 111 | .ok_or_else(|| format!("{} is not defined", name))?; 112 | deps.push(*s); 113 | } 114 | names_to_process.push((i, ret.nb_nodes())); 115 | ret.add(Gate::andn(&deps)); 116 | } 117 | Statement::Cube(_) => (), 118 | Statement::Model(_) => (), 119 | Statement::Exdc => break, 120 | Statement::End => (), 121 | } 122 | } 123 | 124 | // Now that all gates have been added, we can process cubes that may require adding new gates 125 | for (i, gate) in names_to_process { 126 | let inputs = ret.gate(gate).dependencies(); 127 | let mut cubes = Vec::new(); 128 | for j in (i + 1)..statements.len() { 129 | if let Statement::Cube(s) = &statements[j] { 130 | cubes.push(s); 131 | } else { 132 | break; 133 | } 134 | } 135 | let mut cube_gates = Vec::new(); 136 | let mut polarities = Vec::new(); 137 | for s in cubes { 138 | let mut deps = Vec::new(); 139 | let t = s.split_whitespace().collect::>(); 140 | 141 | let (cube_inputs, cube_pol) = if t.len() == 2 { 142 | (t[0].as_bytes(), t[1]) 143 | } else if t.len() == 1 { 144 | ("".as_bytes(), t[0]) 145 | } else { 146 | return Err(format!("Invalid cube: {}", s)); 147 | }; 148 | if cube_inputs.len() != inputs.len() { 149 | return Err(format!( 150 | "Invalid cube: {} has {} inputs, expected {}", 151 | s, 152 | cube_inputs.len(), 153 | inputs.len() 154 | )); 155 | } 156 | for (c, s) in zip(cube_inputs, inputs) { 157 | if *c == '0' as u8 { 158 | deps.push(!s); 159 | } else if *c == '1' as u8 { 160 | deps.push(*s); 161 | } else if *c != '-' as u8 { 162 | return Err(format!("Invalid cube: {}", s)); 163 | } 164 | } 165 | let pol = match cube_pol { 166 | "0" => false, 167 | "1" => true, 168 | _ => return Err(format!("Invalid cube: {}", s)), 169 | }; 170 | polarities.push(pol); 171 | let g = if pol { 172 | if deps.len() == 0 { 173 | Gate::Buf(Signal::one()) 174 | } else if deps.len() == 1 { 175 | Gate::Buf(deps[0]) 176 | } else { 177 | Gate::andn(&deps) 178 | } 179 | } else { 180 | if deps.len() == 0 { 181 | Gate::Buf(Signal::zero()) 182 | } else if deps.len() == 1 { 183 | Gate::Buf(!deps[0]) 184 | } else { 185 | Gate::Nary(deps.into(), NaryType::Nand) 186 | } 187 | }; 188 | cube_gates.push(g); 189 | } 190 | if cube_gates.is_empty() { 191 | ret.replace(gate, Gate::Buf(Signal::zero())); 192 | } else if cube_gates.len() == 1 { 193 | ret.replace(gate, cube_gates[0].clone()); 194 | } else { 195 | for p in &polarities { 196 | if *p != polarities[0] { 197 | return Err("Inconsistent polarities in cubes".to_owned()); 198 | } 199 | } 200 | let mut deps = Vec::new(); 201 | for g in cube_gates { 202 | deps.push(ret.add(g)); 203 | } 204 | if polarities[0] { 205 | ret.replace(gate, Gate::Nary(deps.into(), NaryType::Or)); 206 | } else { 207 | ret.replace(gate, Gate::Nary(deps.into(), NaryType::Nand)); 208 | } 209 | } 210 | } 211 | ret.topo_sort(); 212 | Ok(ret) 213 | } 214 | 215 | fn read_single_statement(tokens: Vec<&str>) -> Result { 216 | match tokens[0] { 217 | ".model" => Ok(Statement::Model(tokens[1].to_owned())), 218 | ".inputs" => Ok(Statement::Inputs( 219 | tokens[1..].iter().map(|s| (*s).to_owned()).collect(), 220 | )), 221 | ".outputs" => Ok(Statement::Outputs( 222 | tokens[1..].iter().map(|s| (*s).to_owned()).collect(), 223 | )), 224 | ".latch" => Ok(Statement::Latch { 225 | input: tokens[1].to_owned(), 226 | output: tokens[2].to_owned(), 227 | }), 228 | ".names" => Ok(Statement::Name( 229 | tokens[1..].iter().map(|s| (*s).to_owned()).collect(), 230 | )), 231 | ".end" => Ok(Statement::End), 232 | ".exdc" => Ok(Statement::Exdc), 233 | _ => { 234 | if tokens[0].starts_with(".") { 235 | Err(format!("{} construct is not supported", tokens[0])) 236 | } else { 237 | Ok(Statement::Cube(tokens.join(" "))) 238 | } 239 | } 240 | } 241 | } 242 | 243 | fn read_statements(r: R) -> Result, String> { 244 | let mut ret: Vec = Vec::new(); 245 | 246 | // Buffer for multi-line strings 247 | let mut ss = String::new(); 248 | 249 | for l in BufReader::new(r).lines() { 250 | if let Ok(s) = l { 251 | // TODO: parse comments properly, not just at the beginning of the line 252 | let comment_pos = s.find('#'); 253 | 254 | // Extend multi-line buffers 255 | ss += " "; 256 | ss += &s[0..comment_pos.unwrap_or(s.len())]; 257 | 258 | let is_continuation = comment_pos.is_none() && ss.ends_with("\\"); 259 | if is_continuation { 260 | ss.pop().unwrap(); 261 | } 262 | if is_continuation || ss.is_empty() { 263 | continue; 264 | } 265 | 266 | let t = ss.trim(); 267 | let tokens: Vec<_> = t.split_whitespace().collect(); 268 | if !tokens.is_empty() { 269 | let statement = read_single_statement(tokens)?; 270 | ret.push(statement); 271 | } 272 | ss.clear(); 273 | } 274 | } 275 | 276 | // Handle a line continuation at the end of the file 277 | if !ss.is_empty() { 278 | let t = ss.trim(); 279 | let tokens: Vec<_> = t.split_whitespace().collect(); 280 | if !tokens.is_empty() { 281 | let statement = read_single_statement(tokens)?; 282 | ret.push(statement); 283 | } 284 | } 285 | Ok(ret) 286 | } 287 | 288 | /// Read a network in .blif format 289 | /// 290 | /// The format specification is available [here](https://course.ece.cmu.edu/~ee760/760docs/blif.pdf), 291 | /// with extensions introduced by [ABC](https://people.eecs.berkeley.edu/~alanmi/publications/other/boxes01.pdf) 292 | /// and [Yosys](https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/write_blif.html) and 293 | /// [VPR](https://docs.verilogtorouting.org/en/latest/vpr/file_formats/). 294 | /// 295 | /// Quaigh only support a small subset, with a single module and a single clock. 296 | pub fn read_blif(r: R) -> Result { 297 | let statements = read_statements(r)?; 298 | let name_to_sig = build_name_to_sig(&statements)?; 299 | build_network(&statements, &name_to_sig) 300 | } 301 | 302 | pub fn write_blif_cube(w: &mut W, mask: usize, num_vars: usize, val: bool) { 303 | for i in 0..num_vars { 304 | let val_i = (mask >> i) & 1 != 0; 305 | write!(w, "{}", if val_i { "1" } else { "0" }).unwrap(); 306 | } 307 | writeln!(w, "{}", if val { " 1" } else { " 0" }).unwrap(); 308 | } 309 | 310 | /// Write a network in .blif format 311 | /// 312 | /// The format specification is available [here](https://course.ece.cmu.edu/~ee760/760docs/blif.pdf), 313 | /// with extensions introduced by [ABC](https://people.eecs.berkeley.edu/~alanmi/publications/other/boxes01.pdf) 314 | /// and [Yosys](https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/write_blif.html) and 315 | /// [VPR](https://docs.verilogtorouting.org/en/latest/vpr/file_formats/). 316 | /// 317 | /// Quaigh only support a small subset, with a single module and a single clock. 318 | pub fn write_blif(w: &mut W, aig: &Network) { 319 | writeln!(w, "# .blif file").unwrap(); 320 | writeln!(w, "# Generated by quaigh").unwrap(); 321 | writeln!(w).unwrap(); 322 | writeln!(w, ".model quaigh").unwrap(); 323 | writeln!(w).unwrap(); 324 | 325 | // Write input specifiers 326 | write!(w, ".inputs").unwrap(); 327 | for i in 0..aig.nb_inputs() { 328 | write!(w, " {}", aig.input(i)).unwrap(); 329 | } 330 | writeln!(w).unwrap(); 331 | writeln!(w).unwrap(); 332 | 333 | // Write output specifiers 334 | write!(w, ".outputs").unwrap(); 335 | for i in 0..aig.nb_outputs() { 336 | write!(w, " {}", sig_to_string(&aig.output(i))).unwrap(); 337 | } 338 | writeln!(w).unwrap(); 339 | writeln!(w).unwrap(); 340 | 341 | // Write latches 342 | for i in 0..aig.nb_nodes() { 343 | if let Gate::Dff([d, en, res]) = aig.gate(i) { 344 | if *en != Signal::one() || *res != Signal::zero() { 345 | // ABC extension to blif 346 | write!(w, ".flop D={} Q=x{} init=0", sig_to_string(d), i).unwrap(); 347 | if *en != Signal::one() { 348 | write!(w, " E={}", en).unwrap(); 349 | } 350 | if *res != Signal::zero() { 351 | write!(w, " R={}", en).unwrap(); 352 | } 353 | writeln!(w).unwrap(); 354 | } else { 355 | writeln!(w, ".latch {} x{} 0", sig_to_string(d), i).unwrap(); 356 | } 357 | } 358 | } 359 | writeln!(w).unwrap(); 360 | 361 | // Write gates 362 | for i in 0..aig.nb_nodes() { 363 | let g = aig.gate(i); 364 | if !g.is_comb() { 365 | continue; 366 | } 367 | write!(w, ".names").unwrap(); 368 | if let Gate::Buf(s) = g { 369 | // Buffers handle the inversions themselves 370 | write!(w, " {}", sig_to_string(&s.without_inversion())).unwrap(); 371 | } else { 372 | // Other signals use a buffered signal for inverted inputs 373 | for s in g.dependencies() { 374 | write!(w, " {}", sig_to_string(s)).unwrap(); 375 | } 376 | } 377 | writeln!(w, " x{}", i).unwrap(); 378 | 379 | match g { 380 | Gate::Binary(_, BinaryType::And) => { 381 | writeln!(w, "11 1").unwrap(); 382 | } 383 | Gate::Binary(_, BinaryType::Xor) => { 384 | writeln!(w, "10 1").unwrap(); 385 | writeln!(w, "01 1").unwrap(); 386 | } 387 | Gate::Ternary(_, TernaryType::And) => { 388 | writeln!(w, "111 1").unwrap(); 389 | } 390 | Gate::Ternary(_, TernaryType::Xor) => { 391 | writeln!(w, "111 1").unwrap(); 392 | writeln!(w, "100 1").unwrap(); 393 | writeln!(w, "010 1").unwrap(); 394 | writeln!(w, "001 1").unwrap(); 395 | } 396 | Gate::Ternary(_, TernaryType::Mux) => { 397 | writeln!(w, "11- 1").unwrap(); 398 | writeln!(w, "0-1 1").unwrap(); 399 | } 400 | Gate::Ternary(_, TernaryType::Maj) => { 401 | writeln!(w, "11- 1").unwrap(); 402 | writeln!(w, "-11 1").unwrap(); 403 | writeln!(w, "1-1 1").unwrap(); 404 | } 405 | Gate::Nary(v, tp) => { 406 | if matches!( 407 | tp, 408 | NaryType::And | NaryType::Nand | NaryType::Nor | NaryType::Or 409 | ) { 410 | let input_inv = matches!(tp, NaryType::Nor | NaryType::Or); 411 | let output_inv = matches!(tp, NaryType::Or | NaryType::Nand); 412 | for _ in 0..v.len() { 413 | if input_inv { 414 | write!(w, "0").unwrap(); 415 | } else { 416 | write!(w, "1").unwrap(); 417 | } 418 | } 419 | if output_inv { 420 | writeln!(w, " 0").unwrap(); 421 | } else { 422 | writeln!(w, " 1").unwrap(); 423 | } 424 | } else { 425 | for mask in 0usize..(1 << v.len()) { 426 | let xor_val = mask.count_ones() % 2 != 0; 427 | let val = match tp { 428 | NaryType::Xor => xor_val, 429 | NaryType::Xnor => !xor_val, 430 | _ => unreachable!(), 431 | }; 432 | if val { 433 | write_blif_cube(w, mask, v.len(), val); 434 | } 435 | } 436 | } 437 | } 438 | Gate::Buf(s) => { 439 | if s.is_inverted() { 440 | writeln!(w, "0 1").unwrap(); 441 | } else { 442 | writeln!(w, "1 1").unwrap(); 443 | } 444 | } 445 | Gate::Lut(lut) => { 446 | for mask in 0..lut.lut.num_bits() { 447 | let val = lut.lut.value(mask); 448 | if val { 449 | write_blif_cube(w, mask, lut.lut.num_vars(), val); 450 | } 451 | } 452 | } 453 | _ => panic!("Gate type not supported"), 454 | } 455 | } 456 | 457 | // Write inverters 458 | let signals_with_inv = get_inverted_signals(aig); 459 | for s in signals_with_inv { 460 | writeln!(w, ".names {} {}_n", s, s).unwrap(); 461 | writeln!(w, "0 1").unwrap(); 462 | } 463 | 464 | // Write constants 465 | writeln!(w, ".names vdd").unwrap(); 466 | writeln!(w, "1").unwrap(); 467 | writeln!(w, ".names gnd").unwrap(); 468 | } 469 | 470 | mod test { 471 | #[test] 472 | fn test_basic_readwrite() { 473 | use std::io::BufWriter; 474 | 475 | let example = "# .blif file 476 | .model test_file # Comment 477 | .inputs a b c 478 | .outputs e \ 479 | f g # Comment # and more 480 | 481 | .names a b e 482 | 00 1 # Comment 483 | 484 | .names c b \ 485 | f 486 | 01 1 487 | 488 | .names g \ 489 | "; 490 | let aig = super::read_blif(example.as_bytes()).unwrap(); 491 | assert_eq!(aig.nb_inputs(), 3); 492 | assert_eq!(aig.nb_outputs(), 3); 493 | assert_eq!(aig.nb_nodes(), 3); 494 | let mut buf = BufWriter::new(Vec::new()); 495 | super::write_blif(&mut buf, &aig); 496 | String::from_utf8(buf.into_inner().unwrap()).unwrap(); 497 | } 498 | } 499 | --------------------------------------------------------------------------------