add python bindings
authorJacob Lifshay <programmerjake@gmail.com>
Wed, 8 Jul 2020 06:26:26 +0000 (23:26 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Wed, 8 Jul 2020 06:28:16 +0000 (23:28 -0700)
.gitignore
Cargo.lock
Cargo.toml
pyproject.toml [new file with mode: 0644]
src/instr_models.rs
src/lib.rs [new file with mode: 0644]
src/main.rs
src/python.rs [new file with mode: 0644]
src/serde_hex.rs
tests/__init__.py [new file with mode: 0644]
tests/test_power_instruction_analyzer.py [new file with mode: 0644]

index ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba..b33ee449529f180bb956c49397ceec086c90b391 100644 (file)
@@ -1 +1,3 @@
 /target
+__pycache__
+*.pyc
\ No newline at end of file
index a7b295ab2e8e36355727d94e5f8d33246a87538f..65c60093ae3ce456b2148d565752a3d93a0ee850 100644 (file)
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cloudabi"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ghost"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "indoc"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
+dependencies = [
+ "indoc-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "indoc-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unindent",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485"
+
+[[package]]
+name = "inventory"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "621b50c176968fd3b0bd71f821a28a0ea98db2b5aea966b2fbb8bd1b7d310328"
+dependencies = [
+ "ctor",
+ "ghost",
+ "inventory-impl",
+]
+
+[[package]]
+name = "inventory-impl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f99a4111304bade76468d05beab3487c226e4fe4c4de1c4e8f006e815762db73"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
 
+[[package]]
+name = "libc"
+version = "0.2.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
+
+[[package]]
+name = "lock_api"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
+dependencies = [
+ "cfg-if",
+ "cloudabi",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "paste"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
+dependencies = [
+ "paste-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "paste-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
+dependencies = [
+ "proc-macro-hack",
+]
+
 [[package]]
 name = "power-instruction-analyzer"
 version = "0.1.0"
 dependencies = [
+ "pyo3",
  "serde",
  "serde_json",
 ]
 
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.17"
@@ -23,6 +183,44 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "pyo3"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca8710ffa8211c9a62a8a3863c4267c710dc42a82a7fd29c97de465d7ea6b7d"
+dependencies = [
+ "ctor",
+ "indoc",
+ "inventory",
+ "libc",
+ "parking_lot",
+ "paste",
+ "pyo3cls",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-derive-backend"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ad070bf6967b0d29ea74931ffcf9c6bbe8402a726e9afbeafadc0a287cc2b3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3cls"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3fa17e1ea569d0bf3b7c00f2a9eea831ca05e55dd76f1794c541abba1c64baa"
+dependencies = [
+ "pyo3-derive-backend",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.6"
@@ -32,12 +230,24 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
+
 [[package]]
 name = "ryu"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
 
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
 [[package]]
 name = "serde"
 version = "1.0.110"
@@ -69,6 +279,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "smallvec"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
+
 [[package]]
 name = "syn"
 version = "1.0.27"
@@ -85,3 +301,31 @@ name = "unicode-xid"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+
+[[package]]
+name = "unindent"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942"
+
+[[package]]
+name = "winapi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
index 14461b00c2226dddeb0f49faf561d114f3bb3f6d..54b4f5cea79505c091aaf1a52ea5d8713abf1aab 100644 (file)
@@ -9,8 +9,14 @@ license = "LGPL-2.1-or-later"
 
 [features]
 native_instrs = []
-default = ["native_instrs"]
+python = ["pyo3"]
+python-extension = ["python", "pyo3/extension-module"]
+
+[lib]
+name = "power_instruction_analyzer"
+crate-type = ["rlib", "cdylib"]
 
 [dependencies]
 serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
\ No newline at end of file
+serde_json = "1.0"
+pyo3 = { version = "0.11", optional = true }
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644 (file)
index 0000000..1b8821c
--- /dev/null
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# See Notices.txt for copyright information
+[build-system]
+requires = ["maturin"]
+build-backend = "maturin"
+
+[tool.maturin]
+bindings = "pyo3"
+cargo-extra-args = "--features python-extension"
index 0c6c76a22649722091744d908d077a391ae3e82e..4b64aff1170005c0ba1da65b5a00a291db4cd9da 100644 (file)
@@ -1,6 +1,6 @@
-use crate::{OverflowFlags, TestDivInput, TestDivResult};
+use crate::{DivInput, DivResult, OverflowFlags};
 
-pub fn divdeo(inputs: TestDivInput) -> TestDivResult {
+pub fn divdeo(inputs: DivInput) -> DivResult {
     let dividend = i128::from(inputs.dividend as i64) << 64;
     let divisor = i128::from(inputs.divisor as i64);
     let overflow;
@@ -18,7 +18,7 @@ pub fn divdeo(inputs: TestDivInput) -> TestDivResult {
             overflow = false;
         }
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -27,7 +27,7 @@ pub fn divdeo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divdeuo(inputs: TestDivInput) -> TestDivResult {
+pub fn divdeuo(inputs: DivInput) -> DivResult {
     let dividend = u128::from(inputs.dividend) << 64;
     let divisor = u128::from(inputs.divisor);
     let overflow;
@@ -45,7 +45,7 @@ pub fn divdeuo(inputs: TestDivInput) -> TestDivResult {
             overflow = false;
         }
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -54,7 +54,7 @@ pub fn divdeuo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divdo(inputs: TestDivInput) -> TestDivResult {
+pub fn divdo(inputs: DivInput) -> DivResult {
     let dividend = inputs.dividend as i64;
     let divisor = inputs.divisor as i64;
     let overflow;
@@ -66,7 +66,7 @@ pub fn divdo(inputs: TestDivInput) -> TestDivResult {
         result = (dividend / divisor) as u64;
         overflow = false;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -75,7 +75,7 @@ pub fn divdo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divduo(inputs: TestDivInput) -> TestDivResult {
+pub fn divduo(inputs: DivInput) -> DivResult {
     let dividend: u64 = inputs.dividend;
     let divisor: u64 = inputs.divisor;
     let overflow;
@@ -87,7 +87,7 @@ pub fn divduo(inputs: TestDivInput) -> TestDivResult {
         result = dividend / divisor;
         overflow = false;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -96,7 +96,7 @@ pub fn divduo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divweo(inputs: TestDivInput) -> TestDivResult {
+pub fn divweo(inputs: DivInput) -> DivResult {
     let dividend = i64::from(inputs.dividend as i32) << 32;
     let divisor = i64::from(inputs.divisor as i32);
     let overflow;
@@ -114,7 +114,7 @@ pub fn divweo(inputs: TestDivInput) -> TestDivResult {
             overflow = false;
         }
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -123,7 +123,7 @@ pub fn divweo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divweuo(inputs: TestDivInput) -> TestDivResult {
+pub fn divweuo(inputs: DivInput) -> DivResult {
     let dividend = u64::from(inputs.dividend as u32) << 32;
     let divisor = u64::from(inputs.divisor as u32);
     let overflow;
@@ -141,7 +141,7 @@ pub fn divweuo(inputs: TestDivInput) -> TestDivResult {
             overflow = false;
         }
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -150,7 +150,7 @@ pub fn divweuo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divwo(inputs: TestDivInput) -> TestDivResult {
+pub fn divwo(inputs: DivInput) -> DivResult {
     let dividend = inputs.dividend as i32;
     let divisor = inputs.divisor as i32;
     let overflow;
@@ -162,7 +162,7 @@ pub fn divwo(inputs: TestDivInput) -> TestDivResult {
         result = (dividend / divisor) as u32 as u64;
         overflow = false;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -171,7 +171,7 @@ pub fn divwo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn divwuo(inputs: TestDivInput) -> TestDivResult {
+pub fn divwuo(inputs: DivInput) -> DivResult {
     let dividend = inputs.dividend as u32;
     let divisor = inputs.divisor as u32;
     let overflow;
@@ -183,7 +183,7 @@ pub fn divwuo(inputs: TestDivInput) -> TestDivResult {
         result = (dividend / divisor) as u64;
         overflow = false;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: Some(OverflowFlags {
             overflow,
@@ -192,7 +192,7 @@ pub fn divwuo(inputs: TestDivInput) -> TestDivResult {
     }
 }
 
-pub fn modsd(inputs: TestDivInput) -> TestDivResult {
+pub fn modsd(inputs: DivInput) -> DivResult {
     let dividend = inputs.dividend as i64;
     let divisor = inputs.divisor as i64;
     let result;
@@ -201,13 +201,13 @@ pub fn modsd(inputs: TestDivInput) -> TestDivResult {
     } else {
         result = (dividend % divisor) as u64;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: None,
     }
 }
 
-pub fn modud(inputs: TestDivInput) -> TestDivResult {
+pub fn modud(inputs: DivInput) -> DivResult {
     let dividend: u64 = inputs.dividend;
     let divisor: u64 = inputs.divisor;
     let result;
@@ -216,13 +216,13 @@ pub fn modud(inputs: TestDivInput) -> TestDivResult {
     } else {
         result = dividend % divisor;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: None,
     }
 }
 
-pub fn modsw(inputs: TestDivInput) -> TestDivResult {
+pub fn modsw(inputs: DivInput) -> DivResult {
     let dividend = inputs.dividend as i32;
     let divisor = inputs.divisor as i32;
     let result;
@@ -231,13 +231,13 @@ pub fn modsw(inputs: TestDivInput) -> TestDivResult {
     } else {
         result = (dividend % divisor) as u64;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: None,
     }
 }
 
-pub fn moduw(inputs: TestDivInput) -> TestDivResult {
+pub fn moduw(inputs: DivInput) -> DivResult {
     let dividend = inputs.dividend as u32;
     let divisor = inputs.divisor as u32;
     let result;
@@ -246,7 +246,7 @@ pub fn moduw(inputs: TestDivInput) -> TestDivResult {
     } else {
         result = (dividend % divisor) as u64;
     }
-    TestDivResult {
+    DivResult {
         result,
         overflow: None,
     }
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..d6a8082
--- /dev/null
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// See Notices.txt for copyright information
+
+#![cfg_attr(feature = "native_instrs", feature(llvm_asm))]
+
+#[cfg(all(feature = "native_instrs", not(target_arch = "powerpc64")))]
+compile_error!("native_instrs feature requires target_arch to be powerpc64");
+
+pub mod instr_models;
+mod python;
+mod serde_hex;
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct OverflowFlags {
+    pub overflow: bool,
+    pub overflow32: bool,
+}
+
+impl OverflowFlags {
+    pub fn from_xer(xer: u64) -> Self {
+        Self {
+            overflow: (xer & 0x4000_0000) != 0,
+            overflow32: (xer & 0x8_0000) != 0,
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct DivResult {
+    #[serde(with = "serde_hex::SerdeHex")]
+    pub result: u64,
+    #[serde(default, flatten, skip_serializing_if = "Option::is_none")]
+    pub overflow: Option<OverflowFlags>,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+pub struct DivInput {
+    #[serde(with = "serde_hex::SerdeHex")]
+    pub dividend: u64,
+    #[serde(with = "serde_hex::SerdeHex")]
+    pub divisor: u64,
+    #[serde(default, with = "serde_hex::SerdeHex")]
+    pub result_prev: u64,
+}
+
+fn is_false(v: &bool) -> bool {
+    !v
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+pub struct TestDivCase {
+    pub instr: DivInstr,
+    #[serde(flatten)]
+    pub inputs: DivInput,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub native_outputs: Option<DivResult>,
+    pub model_outputs: DivResult,
+    #[serde(default, skip_serializing_if = "is_false")]
+    pub model_mismatch: bool,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct WholeTest {
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub test_div_cases: Vec<TestDivCase>,
+    pub any_model_mismatch: bool,
+}
+
+macro_rules! make_div_functions {
+    (
+        #[div]
+        {
+            $($div_enum:ident = $div_fn:ident ($div_instr:literal),)+
+        }
+        #[rem]
+        {
+            $($rem_enum:ident = $rem_fn:ident ($rem_instr:literal),)+
+        }
+    ) => {
+        #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
+        pub enum DivInstr {
+            $(
+                #[serde(rename = $div_instr)]
+                $div_enum,
+            )+
+            $(
+                #[serde(rename = $rem_instr)]
+                $rem_enum,
+            )+
+        }
+
+        impl DivInstr {
+            #[cfg(feature = "native_instrs")]
+            pub fn get_native_fn(self) -> fn(DivInput) -> DivResult {
+                match self {
+                    $(
+                        Self::$div_enum => native_instrs::$div_fn,
+                    )+
+                    $(
+                        Self::$rem_enum => native_instrs::$rem_fn,
+                    )+
+                }
+            }
+            pub fn get_model_fn(self) -> fn(DivInput) -> DivResult {
+                match self {
+                    $(
+                        Self::$div_enum => instr_models::$div_fn,
+                    )+
+                    $(
+                        Self::$rem_enum => instr_models::$rem_fn,
+                    )+
+                }
+            }
+            pub fn name(self) -> &'static str {
+                match self {
+                    $(
+                        Self::$div_enum => $div_instr,
+                    )+
+                    $(
+                        Self::$rem_enum => $rem_instr,
+                    )+
+                }
+            }
+            pub const VALUES: &'static [Self] = &[
+                $(
+                    Self::$div_enum,
+                )+
+                $(
+                    Self::$rem_enum,
+                )+
+            ];
+        }
+
+        #[cfg(feature = "native_instrs")]
+        pub mod native_instrs {
+            use super::*;
+
+            $(
+                pub fn $div_fn(inputs: DivInput) -> DivResult {
+                    let DivInput {
+                        dividend,
+                        divisor,
+                        result_prev,
+                    } = inputs;
+                    let result: u64;
+                    let xer: u64;
+                    unsafe {
+                        llvm_asm!(
+                            concat!(
+                                $div_instr,
+                                " $0, $3, $4\n",
+                                "mfxer $1"
+                            )
+                            : "=&r"(result), "=&r"(xer)
+                            : "0"(result_prev), "r"(dividend), "r"(divisor)
+                            : "xer");
+                    }
+                    DivResult {
+                        result,
+                        overflow: Some(OverflowFlags::from_xer(xer)),
+                    }
+                }
+            )+
+            $(
+                pub fn $rem_fn(inputs: DivInput) -> DivResult {
+                    let DivInput {
+                        dividend,
+                        divisor,
+                        result_prev,
+                    } = inputs;
+                    let result: u64;
+                    unsafe {
+                        llvm_asm!(
+                            concat!(
+                                $rem_instr,
+                                " $0, $2, $3"
+                            )
+                            : "=&r"(result)
+                            : "0"(result_prev), "r"(dividend), "r"(divisor));
+                    }
+                    DivResult {
+                        result,
+                        overflow: None,
+                    }
+                }
+            )+
+        }
+    };
+}
+
+make_div_functions! {
+    #[div]
+    {
+        DivDEO = divdeo("divdeo"),
+        DivDEUO = divdeuo("divdeuo"),
+        DivDO = divdo("divdo"),
+        DivDUO = divduo("divduo"),
+        DivWEO = divweo("divweo"),
+        DivWEUO = divweuo("divweuo"),
+        DivWO = divwo("divwo"),
+        DivWUO = divwuo("divwuo"),
+    }
+    #[rem]
+    {
+        ModSD = modsd("modsd"),
+        ModUD = modud("modud"),
+        ModSW = modsw("modsw"),
+        ModUW = moduw("moduw"),
+    }
+}
index 60c2a121c6329d87b32fe9447d102608749cf98d..cc16cf0e5a391b94f562b52ef10df353c2b0a225 100644 (file)
@@ -1,211 +1,7 @@
 // SPDX-License-Identifier: LGPL-2.1-or-later
 // See Notices.txt for copyright information
 
-#![cfg_attr(feature = "native_instrs", feature(llvm_asm))]
-
-mod instr_models;
-mod serde_hex;
-
-use serde::{Deserialize, Serialize};
-
-#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct OverflowFlags {
-    pub overflow: bool,
-    pub overflow32: bool,
-}
-
-impl OverflowFlags {
-    pub fn from_xer(xer: u64) -> Self {
-        Self {
-            overflow: (xer & 0x4000_0000) != 0,
-            overflow32: (xer & 0x8_0000) != 0,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct TestDivResult {
-    #[serde(with = "serde_hex::SerdeHex")]
-    pub result: u64,
-    #[serde(default, flatten, skip_serializing_if = "Option::is_none")]
-    pub overflow: Option<OverflowFlags>,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
-pub struct TestDivInput {
-    #[serde(with = "serde_hex::SerdeHex")]
-    pub dividend: u64,
-    #[serde(with = "serde_hex::SerdeHex")]
-    pub divisor: u64,
-    #[serde(with = "serde_hex::SerdeHex")]
-    pub result_prev: u64,
-}
-
-fn is_false(v: &bool) -> bool {
-    !v
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
-pub struct TestDivCase {
-    pub instr: TestDivInstr,
-    #[serde(flatten)]
-    pub inputs: TestDivInput,
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub native_outputs: Option<TestDivResult>,
-    pub model_outputs: TestDivResult,
-    #[serde(default, skip_serializing_if = "is_false")]
-    pub model_mismatch: bool,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct WholeTest {
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub test_div_cases: Vec<TestDivCase>,
-    pub any_model_mismatch: bool,
-}
-
-macro_rules! make_div_functions {
-    (
-        #[div]
-        {
-            $($div_enum:ident = $div_fn:ident ($div_instr:literal),)+
-        }
-        #[rem]
-        {
-            $($rem_enum:ident = $rem_fn:ident ($rem_instr:literal),)+
-        }
-    ) => {
-        #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
-        pub enum TestDivInstr {
-            $(
-                #[serde(rename = $div_instr)]
-                $div_enum,
-            )+
-            $(
-                #[serde(rename = $rem_instr)]
-                $rem_enum,
-            )+
-        }
-
-        impl TestDivInstr {
-            #[cfg(feature = "native_instrs")]
-            pub fn get_native_fn(self) -> fn(TestDivInput) -> TestDivResult {
-                match self {
-                    $(
-                        Self::$div_enum => native_instrs::$div_fn,
-                    )+
-                    $(
-                        Self::$rem_enum => native_instrs::$rem_fn,
-                    )+
-                }
-            }
-            pub fn get_model_fn(self) -> fn(TestDivInput) -> TestDivResult {
-                match self {
-                    $(
-                        Self::$div_enum => instr_models::$div_fn,
-                    )+
-                    $(
-                        Self::$rem_enum => instr_models::$rem_fn,
-                    )+
-                }
-            }
-            pub fn name(self) -> &'static str {
-                match self {
-                    $(
-                        Self::$div_enum => $div_instr,
-                    )+
-                    $(
-                        Self::$rem_enum => $rem_instr,
-                    )+
-                }
-            }
-            pub const VALUES: &'static [Self] = &[
-                $(
-                    Self::$div_enum,
-                )+
-                $(
-                    Self::$rem_enum,
-                )+
-            ];
-        }
-
-        #[cfg(feature = "native_instrs")]
-        mod native_instrs {
-            use super::*;
-
-            $(
-                pub fn $div_fn(inputs: TestDivInput) -> TestDivResult {
-                    let TestDivInput {
-                        dividend,
-                        divisor,
-                        result_prev,
-                    } = inputs;
-                    let result: u64;
-                    let xer: u64;
-                    unsafe {
-                        llvm_asm!(
-                            concat!(
-                                $div_instr,
-                                " $0, $3, $4\n",
-                                "mfxer $1"
-                            )
-                            : "=&r"(result), "=&r"(xer)
-                            : "0"(result_prev), "r"(dividend), "r"(divisor)
-                            : "xer");
-                    }
-                    TestDivResult {
-                        result,
-                        overflow: Some(OverflowFlags::from_xer(xer)),
-                    }
-                }
-            )+
-            $(
-                pub fn $rem_fn(inputs: TestDivInput) -> TestDivResult {
-                    let TestDivInput {
-                        dividend,
-                        divisor,
-                        result_prev,
-                    } = inputs;
-                    let result: u64;
-                    unsafe {
-                        llvm_asm!(
-                            concat!(
-                                $rem_instr,
-                                " $0, $2, $3"
-                            )
-                            : "=&r"(result)
-                            : "0"(result_prev), "r"(dividend), "r"(divisor));
-                    }
-                    TestDivResult {
-                        result,
-                        overflow: None,
-                    }
-                }
-            )+
-        }
-    };
-}
-
-make_div_functions! {
-    #[div]
-    {
-        DivDEO = divdeo("divdeo"),
-        DivDEUO = divdeuo("divdeuo"),
-        DivDO = divdo("divdo"),
-        DivDUO = divduo("divduo"),
-        DivWEO = divweo("divweo"),
-        DivWEUO = divweuo("divweuo"),
-        DivWO = divwo("divwo"),
-        DivWUO = divwuo("divwuo"),
-    }
-    #[rem]
-    {
-        ModSD = modsd("modsd"),
-        ModUD = modud("modud"),
-        ModSW = modsw("modsw"),
-        ModUW = moduw("moduw"),
-    }
-}
+use power_instruction_analyzer::{DivInput, DivInstr, TestDivCase, WholeTest};
 
 const TEST_VALUES: &[u64] = &[
     0x0,
@@ -224,10 +20,10 @@ const TEST_VALUES: &[u64] = &[
 fn main() {
     let mut test_div_cases = Vec::new();
     let mut any_model_mismatch = false;
-    for &instr in TestDivInstr::VALUES {
+    for &instr in DivInstr::VALUES {
         for &dividend in TEST_VALUES {
             for &divisor in TEST_VALUES {
-                let inputs = TestDivInput {
+                let inputs = DivInput {
                     dividend,
                     divisor,
                     result_prev: 0xFECD_BA98_7654_3210,
diff --git a/src/python.rs b/src/python.rs
new file mode 100644 (file)
index 0000000..220663e
--- /dev/null
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// See Notices.txt for copyright information
+
+#![cfg(feature = "python")]
+
+use crate::{DivInput, DivResult, OverflowFlags};
+use pyo3::{prelude::*, wrap_pyfunction, PyObjectProtocol};
+use std::{borrow::Cow, cell::RefCell, fmt};
+
+trait ToPythonRepr {
+    fn to_python_repr(&self) -> Cow<str> {
+        struct Helper<T>(RefCell<Option<T>>);
+
+        impl<T: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for Helper<T> {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                self.0.borrow_mut().take().unwrap()(f)
+            }
+        }
+
+        impl<T: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result> Helper<T> {
+            fn new(f: T) -> Self {
+                Helper(RefCell::new(Some(f)))
+            }
+        }
+        Cow::Owned(format!(
+            "{}",
+            Helper::new(|f: &mut fmt::Formatter<'_>| -> fmt::Result { self.write(f) })
+        ))
+    }
+    fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(&self.to_python_repr())
+    }
+}
+
+fn write_list_body_to_python_repr<I: IntoIterator<Item = T>, T: ToPythonRepr>(
+    list: I,
+    f: &mut fmt::Formatter<'_>,
+    separator: &str,
+) -> fmt::Result {
+    let mut first = true;
+    for i in list {
+        if first {
+            first = false;
+        } else {
+            f.write_str(separator)?;
+        }
+        i.write(f)?;
+    }
+    Ok(())
+}
+
+struct NamedArgPythonRepr<'a> {
+    name: &'a str,
+    value: &'a (dyn ToPythonRepr + 'a),
+}
+
+impl ToPythonRepr for NamedArgPythonRepr<'_> {
+    fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(self.name)?;
+        f.write_str("=")?;
+        self.value.write(f)
+    }
+}
+
+impl<T: ToPythonRepr> ToPythonRepr for &'_ T {
+    fn to_python_repr(&self) -> Cow<str> {
+        (**self).to_python_repr()
+    }
+    fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (**self).write(f)
+    }
+}
+
+impl ToPythonRepr for bool {
+    fn to_python_repr(&self) -> Cow<str> {
+        Cow::Borrowed(match self {
+            true => "True",
+            false => "False",
+        })
+    }
+}
+
+impl<T: ToPythonRepr> ToPythonRepr for Option<T> {
+    fn to_python_repr(&self) -> Cow<str> {
+        match self {
+            Some(v) => v.to_python_repr(),
+            None => Cow::Borrowed("None"),
+        }
+    }
+    fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Some(v) => v.write(f),
+            None => f.write_str("None"),
+        }
+    }
+}
+
+impl<T: ToPythonRepr> ToPythonRepr for Vec<T> {
+    fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("[")?;
+        write_list_body_to_python_repr(self, f, ", ")?;
+        f.write_str("]")
+    }
+}
+
+macro_rules! impl_int_to_python_repr {
+    ($($int:ident,)*) => {
+        $(
+            impl ToPythonRepr for $int {
+                fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                    write!(f, "{}", self)
+                }
+            }
+        )*
+    };
+}
+
+impl_int_to_python_repr! {u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,}
+
+macro_rules! wrap_type {
+    (
+        #[pymodule($m:expr)]
+        // use tt to work around PyO3 bug fixed in PyO3#832
+        #[pyclass $($pyclass_args:tt)?]
+        #[wrapped($value:ident: $wrapped:ident)]
+        $(#[$meta:meta])*
+        struct $wrapper:ident {
+            $(
+                #[set=$setter_name:ident]
+                $(#[$field_meta:meta])*
+                $field_name:ident:$field_type:ty,
+            )*
+        }
+    ) => {
+        #[pyclass $($pyclass_args)?]
+        $(#[$meta])*
+        #[derive(Clone)]
+        struct $wrapper {
+            $value: $wrapped,
+        }
+
+        impl<'source> FromPyObject<'source> for $wrapped {
+            fn extract(ob: &'source PyAny) -> PyResult<Self> {
+                Ok(ob.extract::<$wrapper>()?.$value)
+            }
+        }
+
+        impl IntoPy<PyObject> for $wrapped {
+            fn into_py(self, py: Python) -> PyObject {
+                $wrapper { $value: self }.into_py(py)
+            }
+        }
+
+        #[pymethods]
+        impl $wrapper {
+            #[new]
+            fn new($($field_name:$field_type),*) -> Self {
+                Self {
+                    $value: $wrapped {
+                        $($field_name),*
+                    }
+                }
+            }
+            $(
+                #[getter]
+                $(#[$field_meta:meta])*
+                fn $field_name(&self) -> $field_type {
+                    self.$value.$field_name
+                }
+                #[setter]
+                fn $setter_name(&mut self, $field_name: $field_type) {
+                    self.$value.$field_name = $field_name;
+                }
+            )*
+        }
+
+        impl ToPythonRepr for $wrapped {
+            fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.write_str(concat!(stringify!($wrapped), "("))?;
+                write_list_body_to_python_repr(&[
+                    $(
+                        NamedArgPythonRepr {
+                            name: stringify!($field_name),
+                            value: &self.$field_name,
+                        },
+                    )*
+                    ], f, ", ")?;
+                f.write_str(")")
+            }
+        }
+
+        #[pyproto]
+        impl PyObjectProtocol for $wrapper {
+            fn __str__(&self) -> String {
+                serde_json::to_string(&self.$value).unwrap()
+            }
+            fn __repr__(&self) -> String {
+                self.$value.to_python_repr().into_owned()
+            }
+        }
+
+        $m.add_class::<$wrapper>()?;
+    };
+}
+
+macro_rules! wrap_instr_fns {
+    (
+        #![pymodule($m:ident)]
+        $(
+            // use tt to work around PyO3 bug fixed in PyO3#832
+            $(#[pyfunction $pyfunction_args:tt])?
+            $(#[$meta:meta])*
+            fn $name:ident(inputs: $inputs:ty) -> $result:ty;
+        )*
+    ) => {
+        $(
+            {
+                #[pyfunction $($pyfunction_args)?]
+                #[text_signature = "(inputs)"]
+                $(#[$meta])*
+                fn $name(inputs: $inputs) -> $result {
+                    $crate::instr_models::$name(inputs)
+                }
+
+                $m.add_wrapped(wrap_pyfunction!($name))?;
+            }
+        )*
+    };
+}
+
+#[pymodule]
+fn power_instruction_analyzer(_py: Python, m: &PyModule) -> PyResult<()> {
+    wrap_type! {
+        #[pymodule(m)]
+        #[pyclass(name = OverflowFlags)]
+        #[wrapped(value: OverflowFlags)]
+        #[text_signature = "(overflow, overflow32)"]
+        struct PyOverflowFlags {
+            #[set = set_overflow]
+            overflow: bool,
+            #[set = set_overflow32]
+            overflow32: bool,
+        }
+    }
+
+    wrap_type! {
+        #[pymodule(m)]
+        #[pyclass(name = DivInput)]
+        #[wrapped(value: DivInput)]
+        #[text_signature = "(dividend, divisor, result_prev)"]
+        struct PyDivInput {
+            #[set = set_dividend]
+            dividend: u64,
+            #[set = set_divisor]
+            divisor: u64,
+            #[set = set_result_prev]
+            result_prev: u64,
+        }
+    }
+
+    wrap_type! {
+        #[pymodule(m)]
+        #[pyclass(name = DivResult)]
+        #[wrapped(value: DivResult)]
+        #[text_signature = "(result, overflow)"]
+        struct PyDivResult {
+            #[set = set_result]
+            result: u64,
+            #[set = set_overflow]
+            overflow: Option<OverflowFlags>,
+        }
+    }
+    wrap_instr_fns! {
+        #![pymodule(m)]
+
+        fn divdeo(inputs: DivInput) -> DivResult;
+        fn divdeuo(inputs: DivInput) -> DivResult;
+        fn divdo(inputs: DivInput) -> DivResult;
+        fn divduo(inputs: DivInput) -> DivResult;
+        fn divweo(inputs: DivInput) -> DivResult;
+        fn divweuo(inputs: DivInput) -> DivResult;
+        fn divwo(inputs: DivInput) -> DivResult;
+        fn divwuo(inputs: DivInput) -> DivResult;
+        fn modsd(inputs: DivInput) -> DivResult;
+        fn modud(inputs: DivInput) -> DivResult;
+        fn modsw(inputs: DivInput) -> DivResult;
+        fn moduw(inputs: DivInput) -> DivResult;
+    }
+    Ok(())
+}
index c3f4bd04539a2a7b89633f16648cb5475c6c5c3f..17ec8a8b22fe9aaa4d0dad8e7b3fdad0ea8cdbb5 100644 (file)
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: LGPL-2.1-or-later
 // See Notices.txt for copyright information
 
-use serde::{Deserialize, Deserializer, Serializer};
+use serde::{de, Deserialize, Deserializer, Serializer};
 
 pub(crate) trait SerdeHex {
     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
@@ -10,12 +10,28 @@ pub(crate) trait SerdeHex {
         Self: Sized;
 }
 
-impl SerdeHex for u64 {
-    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
-        serializer.serialize_str(&format!("{:#X}", self))
-    }
-    fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-        let _text: &str = Deserialize::deserialize(deserializer)?;
-        todo!("parse text as 0x<HEXDIGITS>")
-    }
+macro_rules! impl_hex_for_uint {
+    ($ty:ty) => {
+        impl SerdeHex for $ty {
+            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+                serializer.serialize_str(&format!("{:#X}", self))
+            }
+            fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+                let text: &str = Deserialize::deserialize(deserializer)?;
+                const PREFIX: &str = "0x";
+                if text.starts_with(PREFIX) {
+                    let hex_digits = &text[PREFIX.len()..];
+                    Self::from_str_radix(hex_digits, 16).map_err(de::Error::custom)
+                } else {
+                    Err(de::Error::custom("hexadecimal field must start with 0x"))
+                }
+            }
+        }
+    };
 }
+
+impl_hex_for_uint!(u8);
+impl_hex_for_uint!(u16);
+impl_hex_for_uint!(u32);
+impl_hex_for_uint!(u64);
+impl_hex_for_uint!(u128);
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/test_power_instruction_analyzer.py b/tests/test_power_instruction_analyzer.py
new file mode 100644 (file)
index 0000000..da954a0
--- /dev/null
@@ -0,0 +1,107 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# See Notices.txt for copyright information
+
+import unittest
+import power_instruction_analyzer as pia
+
+
+class TestOverflowFlags(unittest.TestCase):
+    def test_text_signature(self):
+        self.assertEqual(pia.OverflowFlags.__text_signature__,
+                         "(overflow, overflow32)")
+
+    def test_fields(self):
+        v = pia.OverflowFlags(overflow=False, overflow32=True)
+        self.assertEqual(v.overflow, False)
+        self.assertEqual(v.overflow32, True)
+        v.overflow = True
+        self.assertEqual(v.overflow, True)
+        v.overflow32 = False
+        self.assertEqual(v.overflow32, False)
+
+    def test_str_repr(self):
+        v = pia.OverflowFlags(overflow=False, overflow32=True)
+        self.assertEqual(str(v),
+                         '{"overflow":false,"overflow32":true}')
+        self.assertEqual(repr(v),
+                         "OverflowFlags(overflow=False, overflow32=True)")
+
+
+class TestDivInput(unittest.TestCase):
+    def test_text_signature(self):
+        self.assertEqual(pia.DivInput.__text_signature__,
+                         "(dividend, divisor, result_prev)")
+
+    def test_fields(self):
+        v = pia.DivInput(dividend=123, divisor=456, result_prev=789)
+        self.assertEqual(v.dividend, 123)
+        self.assertEqual(v.divisor, 456)
+        self.assertEqual(v.result_prev, 789)
+        v.dividend = 1234
+        self.assertEqual(v.dividend, 1234)
+        v.divisor = 4567
+        self.assertEqual(v.divisor, 4567)
+        v.result_prev = 7890
+        self.assertEqual(v.result_prev, 7890)
+
+    def test_str_repr(self):
+        v = pia.DivInput(dividend=123, divisor=456, result_prev=789)
+        self.assertEqual(str(v),
+                         '{"dividend":"0x7B","divisor":"0x1C8","result_prev":"0x315"}')
+        self.assertEqual(repr(v),
+                         "DivInput(dividend=123, divisor=456, result_prev=789)")
+
+
+class TestDivResult(unittest.TestCase):
+    def test_text_signature(self):
+        self.assertEqual(pia.DivResult.__text_signature__,
+                         "(result, overflow)")
+
+    def test_fields(self):
+        v = pia.DivResult(result=1234,
+                          overflow=pia.OverflowFlags(overflow=False, overflow32=True))
+        self.assertEqual(v.result, 1234)
+        self.assertIsNotNone(v.overflow)
+        self.assertEqual(v.overflow.overflow, False)
+        self.assertEqual(v.overflow.overflow32, True)
+        v.result = 123
+        self.assertEqual(v.result, 123)
+        v.overflow = None
+        self.assertIsNone(v.overflow)
+
+    def test_str_repr(self):
+        v = pia.DivResult(result=1234,
+                          overflow=pia.OverflowFlags(overflow=False, overflow32=True))
+        self.assertEqual(str(v),
+                         '{"result":"0x4D2","overflow":false,"overflow32":true}')
+        self.assertEqual(repr(v),
+                         "DivResult(result=1234, overflow=OverflowFlags(overflow=False, overflow32=True))")
+
+
+class TestDivInstrs(unittest.TestCase):
+    cases = [
+        ("divdeo", '{"result":"0x0","overflow":true,"overflow32":true}'),
+        ("divdeuo", '{"result":"0x0","overflow":true,"overflow32":true}'),
+        ("divdo", '{"result":"0x36","overflow":false,"overflow32":false}'),
+        ("divduo", '{"result":"0x36","overflow":false,"overflow32":false}'),
+        ("divweo", '{"result":"0x0","overflow":true,"overflow32":true}'),
+        ("divweuo", '{"result":"0x0","overflow":true,"overflow32":true}'),
+        ("divwo", '{"result":"0x36","overflow":false,"overflow32":false}'),
+        ("divwuo", '{"result":"0x36","overflow":false,"overflow32":false}'),
+        ("modsd", '{"result":"0x10"}'),
+        ("modud", '{"result":"0x10"}'),
+        ("modsw", '{"result":"0x10"}'),
+        ("moduw", '{"result":"0x10"}'),
+    ]
+
+    def test(self):
+        v = pia.DivInput(dividend=0x1234, divisor=0x56, result_prev=0x789)
+        for fn_name, expected in self.cases:
+            with self.subTest(fn_name=fn_name):
+                fn = getattr(pia, fn_name)
+                results = fn(v)
+                self.assertEqual(str(results), expected)
+
+
+if __name__ == "__main__":
+    unittest.main()