Merge branch 'master' into feature/pounder-timestamping

This commit is contained in:
Ryan Summers 2021-01-06 14:45:56 +01:00
commit 29a89637f8
16 changed files with 1087 additions and 199 deletions

View File

@ -5,7 +5,7 @@ rustflags = [
# The target (below) defaults to cortex-m4 # The target (below) defaults to cortex-m4
# There currently are two different options to go beyond that: # There currently are two different options to go beyond that:
# 1. cortex-m7 has the right flags and instructions (FPU) but no instruction schedule yet # 1. cortex-m7 has the right flags and instructions (FPU) but no instruction schedule yet
"-C", "target-cpu=cortex-m7", # "-C", "target-cpu=cortex-m7",
# 2. cortex-m4 with the additional fpv5 instructions and a potentially # 2. cortex-m4 with the additional fpv5 instructions and a potentially
# better-than-nothing instruction schedule # better-than-nothing instruction schedule
"-C", "target-feature=+fp-armv8d16", "-C", "target-feature=+fp-armv8d16",

View File

@ -18,28 +18,18 @@ jobs:
with: with:
profile: minimal profile: minimal
toolchain: nightly toolchain: nightly
target: thumbv7em-none-eabihf
override: true override: true
components: rustfmt components: rustfmt, clippy
- name: cargo fmt --check - name: cargo fmt --check
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: fmt command: fmt
args: --all -- --check args: --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
target: thumbv7em-none-eabihf
override: true
components: clippy
- uses: actions-rs/clippy-check@v1 - uses: actions-rs/clippy-check@v1
continue-on-error: true continue-on-error: true
with: with:
toolchain: stable
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
compile: compile:
@ -57,6 +47,7 @@ jobs:
toolchain: ${{ matrix.toolchain }} toolchain: ${{ matrix.toolchain }}
target: thumbv7em-none-eabihf target: thumbv7em-none-eabihf
override: true override: true
components: llvm-tools-preview
- name: cargo check - name: cargo check
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@ -71,11 +62,28 @@ jobs:
with: with:
command: build command: build
args: --release args: --release
- name: cargo build release+semihosting - name: cargo-binutils
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: install
args: --release --features semihosting args: cargo-binutils
- name: cargo size
uses: actions-rs/cargo@v1
with:
command: size
args: --release
- name: cargo objcopy
uses: actions-rs/cargo@v1
with:
command: objcopy
args: --release --verbose -- -O binary stabilizer-release.bin
- uses: actions/upload-artifact@v2
if: ${{ matrix.toolchain == 'stable' }}
with:
name: stabilizer_${{ github.sha }}
path: |
target/*/release/stabilizer
stabilizer-release.bin
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -95,6 +103,11 @@ jobs:
with: with:
command: test command: test
args: --package dsp --target=x86_64-unknown-linux-gnu args: --package dsp --target=x86_64-unknown-linux-gnu
- name: cargo bench
uses: actions-rs/cargo@v1
with:
command: bench
args: --package dsp --target=x86_64-unknown-linux-gnu
# Tell bors about it # Tell bors about it
# https://github.com/rtic-rs/cortex-m-rtic/blob/8a4f9c6b8ae91bebeea0791680f89375a78bffc6/.github/workflows/build.yml#L566-L603 # https://github.com/rtic-rs/cortex-m-rtic/blob/8a4f9c6b8ae91bebeea0791680f89375a78bffc6/.github/workflows/build.yml#L566-L603
@ -103,8 +116,8 @@ jobs:
if: github.event_name == 'push' && success() if: github.event_name == 'push' && success()
needs: needs:
- style - style
- clippy
- compile - compile
- test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Mark the job as a success - name: Mark the job as a success
@ -114,8 +127,8 @@ jobs:
if: github.event_name == 'push' && !success() if: github.event_name == 'push' && !success()
needs: needs:
- style - style
- clippy
- compile - compile
- test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Mark the job as a failure - name: Mark the job as a failure

2
.gitignore vendored
View File

@ -1,3 +1 @@
/target /target
/dsp/target
.gdb_history

494
Cargo.lock generated
View File

@ -40,6 +40,17 @@ dependencies = [
"embedded-hal", "embedded-hal",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -85,6 +96,24 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c147d86912d04bef727828fda769a76ca81629a46d8ba311a8d58a26aa91473d" checksum = "c147d86912d04bef727828fda769a76ca81629a46d8ba311a8d58a26aa91473d"
[[package]]
name = "bstr"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.3.4"
@ -106,6 +135,29 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"bitflags",
"textwrap",
"unicode-width",
]
[[package]]
name = "const_fn"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.6.4" version = "0.6.4"
@ -185,14 +237,125 @@ dependencies = [
"cortex-m", "cortex-m",
] ]
[[package]]
name = "criterion"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
dependencies = [
"cfg-if 1.0.0",
"const_fn",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "csv"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97"
dependencies = [
"bstr",
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "dsp" name = "dsp"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"criterion",
"libm", "libm",
"serde", "serde",
] ]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "embedded-dma" name = "embedded-dma"
version = "0.1.2" version = "0.1.2"
@ -260,6 +423,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "half"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177"
[[package]] [[package]]
name = "hash32" name = "hash32"
version = "0.1.1" version = "0.1.1"
@ -288,6 +457,15 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "hermit-abi"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.6.0" version = "1.6.0"
@ -298,6 +476,42 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "js-sys"
version = "0.3.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]] [[package]]
name = "libm" name = "libm"
version = "0.2.1" version = "0.2.1"
@ -310,7 +524,7 @@ version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
] ]
[[package]] [[package]]
@ -327,6 +541,21 @@ dependencies = [
"embedded-hal", "embedded-hal",
] ]
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
@ -342,6 +571,31 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]] [[package]]
name = "panic-halt" name = "panic-halt"
version = "0.2.0" version = "0.2.0"
@ -360,9 +614,21 @@ dependencies = [
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7151b083b0664ed58ed669fcdd92f01c3d2fdbf10af4931a301474950b52bfa9" checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1"
[[package]]
name = "plotters"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb"
dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@ -388,6 +654,55 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
[[package]]
name = "rayon"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
dependencies = [
"byteorder",
]
[[package]]
name = "regex-syntax"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
[[package]] [[package]]
name = "rtic-core" name = "rtic-core"
version = "0.3.1" version = "0.3.1"
@ -414,6 +729,27 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "semver" name = "semver"
version = "0.9.0" version = "0.9.0"
@ -448,6 +784,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_cbor"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
dependencies = [
"half",
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.118" version = "1.0.118"
@ -459,6 +805,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "smoltcp" name = "smoltcp"
version = "0.6.0" version = "0.6.0"
@ -543,12 +900,37 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "tinytemplate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.12.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.1" version = "0.2.1"
@ -581,3 +963,109 @@ checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
dependencies = [ dependencies = [
"vcell", "vcell",
] ]
[[package]]
name = "walkdir"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
[[package]]
name = "web-sys"
version = "0.3.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
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-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -24,6 +24,9 @@ maintenance = { status = "experimental" }
features = [] features = []
default-target = "thumbv7em-none-eabihf" default-target = "thumbv7em-none-eabihf"
[workspace]
members = ["ad9959", "dsp"]
[dependencies] [dependencies]
cortex-m = { version = "0.6", features = ["const-fn"] } cortex-m = { version = "0.6", features = ["const-fn"] }
cortex-m-rt = { version = "0.6", features = ["device"] } cortex-m-rt = { version = "0.6", features = ["device"] }

View File

@ -1 +1 @@
"05b1xcr9jachnih0d6i63cfjcb88xrddmr2kf4h3vfwpjf8y9w10" "0mrnm74wd5c1cl3av8iqndg6xrm07vs862882m59pjnrgy3z2zqj"

70
dsp/Cargo.lock generated
View File

@ -1,70 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "dsp"
version = "0.1.0"
dependencies = [
"libm",
"serde",
]
[[package]]
name = "libm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

View File

@ -8,5 +8,12 @@ edition = "2018"
libm = "0.2.1" libm = "0.2.1"
serde = { version = "1.0", features = ["derive"], default-features = false } serde = { version = "1.0", features = ["derive"], default-features = false }
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "trig"
harness = false
[features] [features]
nightly = [] nightly = []

28
dsp/benches/trig.rs Normal file
View File

@ -0,0 +1,28 @@
use core::f32::consts::PI;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dsp::trig::{atan2, cossin};
fn atan2_bench(c: &mut Criterion) {
let xi = (10 << 16) as i32;
let xf = xi as f32 / i32::MAX as f32;
let yi = (-26_328 << 16) as i32;
let yf = yi as f32 / i32::MAX as f32;
c.bench_function("atan2(y, x)", |b| {
b.iter(|| atan2(black_box(yi), black_box(xi)))
});
c.bench_function("y.atan2(x)", |b| {
b.iter(|| black_box(yf).atan2(black_box(xf)))
});
}
fn cossin_bench(c: &mut Criterion) {
let zi = -0x7304_2531_i32;
let zf = zi as f32 / i32::MAX as f32 * PI;
c.bench_function("cossin(zi)", |b| b.iter(|| cossin(black_box(zi))));
c.bench_function("zf.sin_cos()", |b| b.iter(|| black_box(zf).sin_cos()));
}
criterion_group!(benches, atan2_bench, cossin_bench);
criterion_main!(benches);

46
dsp/build.rs Normal file
View File

@ -0,0 +1,46 @@
use std::env;
use std::f64::consts::PI;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
fn write_cossin_table() {
const DEPTH: usize = 7;
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("cossin_table.rs");
let mut file = File::create(dest_path).unwrap();
writeln!(file, "pub(crate) const COSSIN_DEPTH: usize = {};", DEPTH)
.unwrap();
write!(
file,
"pub(crate) const COSSIN: [(u16, u16); 1 << COSSIN_DEPTH] = ["
)
.unwrap();
// Treat sin and cos as unsigned values since the sign will always be
// positive in the range [0, pi/4).
// No headroom for interpolation rounding error (this is needed for
// DEPTH = 6 for example).
const AMPLITUDE: f64 = u16::MAX as f64;
for i in 0..(1 << DEPTH) {
// use midpoint samples to save one entry in the LUT
let phase = (PI / 4. / (1 << DEPTH) as f64) * (i as f64 + 0.5);
// add one bit accuracy to cos due to 0.5 < cos(z) <= 1 for |z| < pi/4
let cos = ((phase.cos() - 0.5) * 2. * AMPLITUDE).round() as u16;
let sin = (phase.sin() * AMPLITUDE).round() as u16;
if i % 4 == 0 {
write!(file, "\n ").unwrap();
}
write!(file, " ({}, {}),", cos, sin).unwrap();
}
writeln!(file, "\n];").unwrap();
println!("cargo:rerun-if-changed=build.rs");
}
fn main() {
write_cossin_table();
}

View File

@ -1,85 +1,8 @@
use core::ops::{Add, Mul, Neg};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{abs, copysign, macc, max, min};
use core::f32; use core::f32;
// These are implemented here because core::f32 doesn't have them (yet).
// They are naive and don't handle inf/nan.
// `compiler-intrinsics`/llvm should have better (robust, universal, and
// faster) implementations.
fn abs<T>(x: T) -> T
where
T: PartialOrd + Default + Neg<Output = T>,
{
if x >= T::default() {
x
} else {
-x
}
}
fn copysign<T>(x: T, y: T) -> T
where
T: PartialOrd + Default + Neg<Output = T>,
{
if (x >= T::default() && y >= T::default())
|| (x <= T::default() && y <= T::default())
{
x
} else {
-x
}
}
#[cfg(not(feature = "nightly"))]
fn max<T>(x: T, y: T) -> T
where
T: PartialOrd,
{
if x > y {
x
} else {
y
}
}
#[cfg(not(feature = "nightly"))]
fn min<T>(x: T, y: T) -> T
where
T: PartialOrd,
{
if x < y {
x
} else {
y
}
}
#[cfg(feature = "nightly")]
fn max(x: f32, y: f32) -> f32 {
core::intrinsics::maxnumf32(x, y)
}
#[cfg(feature = "nightly")]
fn min(x: f32, y: f32) -> f32 {
core::intrinsics::minnumf32(x, y)
}
// Multiply-accumulate vectors `x` and `a`.
//
// A.k.a. dot product.
// Rust/LLVM optimize this nicely.
fn macc<T>(y0: T, x: &[T], a: &[T]) -> T
where
T: Add<Output = T> + Mul<Output = T> + Copy,
{
x.iter()
.zip(a)
.map(|(x, a)| *x * *a)
.fold(y0, |y, xa| y + xa)
}
/// IIR state and coefficients type. /// IIR state and coefficients type.
/// ///
/// To represent the IIR state (input and output memory) during the filter update /// To represent the IIR state (input and output memory) during the filter update

58
dsp/src/iir_int.rs Normal file
View File

@ -0,0 +1,58 @@
use serde::{Deserialize, Serialize};
pub type IIRState = [i32; 5];
fn macc(y0: i32, x: &[i32], a: &[i32], shift: u32) -> i32 {
// Rounding bias, half up
let y0 = ((y0 as i64) << shift) + (1 << (shift - 1));
let y = x
.iter()
.zip(a)
.map(|(x, a)| *x as i64 * *a as i64)
.fold(y0, |y, xa| y + xa);
(y >> shift) as i32
}
/// Integer biquad IIR
///
/// See `dsp::iir::IIR` for general implementation details.
/// Offset and limiting disabled to suit lowpass applications.
/// Coefficient scaling fixed and optimized.
#[derive(Copy, Clone, Deserialize, Serialize)]
pub struct IIR {
pub ba: IIRState,
// pub y_offset: i32,
// pub y_min: i32,
// pub y_max: i32,
}
impl IIR {
/// Coefficient fixed point: signed Q2.30.
/// Tailored to low-passes PI, II etc.
const SHIFT: u32 = 30;
/// Feed a new input value into the filter, update the filter state, and
/// return the new output. Only the state `xy` is modified.
///
/// # Arguments
/// * `xy` - Current filter state.
/// * `x0` - New input.
pub fn update(&self, xy: &mut IIRState, x0: i32) -> i32 {
let n = self.ba.len();
debug_assert!(xy.len() == n);
// `xy` contains x0 x1 y0 y1 y2
// Increment time x1 x2 y1 y2 y3
// Shift x1 x1 x2 y1 y2
// This unrolls better than xy.rotate_right(1)
xy.copy_within(0..n - 1, 1);
// Store x0 x0 x1 x2 y1 y2
xy[0] = x0;
// Compute y0 by multiply-accumulate
let y0 = macc(0, xy, &self.ba, IIR::SHIFT);
// Limit y0
// let y0 = y0.max(self.y_min).min(self.y_max);
// Store y0 x0 x1 y0 y1 y2
xy[n / 2] = y0;
y0
}
}

View File

@ -1,10 +1,107 @@
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
#![cfg_attr(feature = "nightly", feature(asm, core_intrinsics))] #![cfg_attr(feature = "nightly", feature(asm, core_intrinsics))]
use core::ops::{Add, Mul, Neg};
pub type Complex<T> = (T, T); pub type Complex<T> = (T, T);
/// Round up half.
///
/// # Arguments
///
/// `x` - Value to shift and round.
/// `shift` - Number of bits to right shift `x`.
///
/// # Returns
///
/// Shifted and rounded value.
#[inline(always)]
pub fn shift_round(x: i32, shift: usize) -> i32 {
(x + (1 << (shift - 1))) >> shift
}
fn abs<T>(x: T) -> T
where
T: PartialOrd + Default + Neg<Output = T>,
{
if x >= T::default() {
x
} else {
-x
}
}
// These are implemented here because core::f32 doesn't have them (yet).
// They are naive and don't handle inf/nan.
// `compiler-intrinsics`/llvm should have better (robust, universal, and
// faster) implementations.
fn copysign<T>(x: T, y: T) -> T
where
T: PartialOrd + Default + Neg<Output = T>,
{
if (x >= T::default() && y >= T::default())
|| (x <= T::default() && y <= T::default())
{
x
} else {
-x
}
}
#[cfg(not(feature = "nightly"))]
fn max<T>(x: T, y: T) -> T
where
T: PartialOrd,
{
if x > y {
x
} else {
y
}
}
#[cfg(not(feature = "nightly"))]
fn min<T>(x: T, y: T) -> T
where
T: PartialOrd,
{
if x < y {
x
} else {
y
}
}
#[cfg(feature = "nightly")]
fn max(x: f32, y: f32) -> f32 {
core::intrinsics::maxnumf32(x, y)
}
#[cfg(feature = "nightly")]
fn min(x: f32, y: f32) -> f32 {
core::intrinsics::minnumf32(x, y)
}
// Multiply-accumulate vectors `x` and `a`.
//
// A.k.a. dot product.
// Rust/LLVM optimize this nicely.
fn macc<T>(y0: T, x: &[T], a: &[T]) -> T
where
T: Add<Output = T> + Mul<Output = T> + Copy,
{
x.iter()
.zip(a)
.map(|(x, a)| *x * *a)
.fold(y0, |y, xa| y + xa)
}
pub mod iir; pub mod iir;
pub mod iir_int;
pub mod lockin; pub mod lockin;
pub mod pll; pub mod pll;
pub mod trig;
pub mod unwrap; pub mod unwrap;
#[cfg(test)] #[cfg(test)]

View File

@ -10,12 +10,14 @@ use serde::{Deserialize, Serialize};
/// stable for any gain (1 <= shift <= 30). It has a single parameter that determines the loop /// stable for any gain (1 <= shift <= 30). It has a single parameter that determines the loop
/// bandwidth in octave steps. The gain can be changed freely between updates. /// bandwidth in octave steps. The gain can be changed freely between updates.
/// ///
/// The frequency and phase settling time constants for an (any) frequency jump are `1 << shift` /// The frequency and phase settling time constants for a frequency/phase jump are `1 << shift`
/// update cycles. The loop bandwidth is about `1/(2*pi*(1 << shift))` in units of the sample rate. /// update cycles. The loop bandwidth is about `1/(2*pi*(1 << shift))` in units of the sample rate.
/// While the phase is being settled within one turn, there is a typically very small frequency
/// overshoot.
/// ///
/// All math is naturally wrapping 32 bit integer. Phase and frequency are understood modulo that /// All math is naturally wrapping 32 bit integer. Phase and frequency are understood modulo that
/// overflow in the first Nyquist zone. Expressing the IIR equations in other ways (e.g. single /// overflow in the first Nyquist zone. Expressing the IIR equations in other ways (e.g. single
/// (T)-DF-{I,II} biquad/IIR) would break on overflow. /// (T)-DF-{I,II} biquad/IIR) would break on overflow (i.e. every cycle).
/// ///
/// There are no floating point rounding errors here. But there is integer quantization/truncation /// There are no floating point rounding errors here. But there is integer quantization/truncation
/// error of the `shift` lowest bits leading to a phase offset for very low gains. Truncation /// error of the `shift` lowest bits leading to a phase offset for very low gains. Truncation
@ -23,8 +25,8 @@ use serde::{Deserialize, Serialize};
/// efficiently by dithering. /// efficiently by dithering.
/// ///
/// This PLL does not unwrap phase slips during lock acquisition. This can and should be /// This PLL does not unwrap phase slips during lock acquisition. This can and should be
/// implemented elsewhere by (down) scaling and then unwrapping the input phase and (up) scaling /// implemented elsewhere by unwrapping and scaling the input phase and un-scaling
/// and wrapping output phase and frequency. This affects dynamic range accordingly. /// and wrapping output phase and frequency. This affects dynamic range, gain, and noise accordingly.
/// ///
/// The extension to I^3,I^2,I behavior to track chirps phase-accurately or to i64 data to /// The extension to I^3,I^2,I behavior to track chirps phase-accurately or to i64 data to
/// increase resolution for extremely narrowband applications is obvious. /// increase resolution for extremely narrowband applications is obvious.
@ -39,25 +41,39 @@ pub struct PLL {
} }
impl PLL { impl PLL {
/// Update the PLL with a new phase sample. /// Update the PLL with a new phase sample. This needs to be called (sampled) periodically.
/// The signal's phase/frequency is reconstructed relative to the sampling period.
/// ///
/// Args: /// Args:
/// * `input`: New input phase sample. /// * `x`: New input phase sample.
/// * `shift`: Error scaling. The frequency gain per update is `1/(1 << shift)`. The phase gain /// * `shift_frequency`: Frequency error scaling. The frequency gain per update is
/// is always twice the frequency gain. /// `1/(1 << shift_frequency)`.
/// * `shift_phase`: Phase error scaling. The phase gain is `1/(1 << shift_phase)`
/// per update. A good value is typically `shift_frequency - 1`.
/// ///
/// Returns: /// Returns:
/// A tuple of instantaneous phase and frequency (the current phase increment). /// A tuple of instantaneous phase and frequency (the current phase increment).
pub fn update(&mut self, x: i32, shift: u8) -> (i32, i32) { pub fn update(
debug_assert!((1..=30).contains(&shift)); &mut self,
let bias = 1i32 << shift; x: i32,
shift_frequency: u8,
shift_phase: u8,
) -> (i32, i32) {
debug_assert!((1..=30).contains(&shift_frequency));
debug_assert!((1..=30).contains(&shift_phase));
let e = x.wrapping_sub(self.f); let e = x.wrapping_sub(self.f);
self.f = self.f.wrapping_add( self.f = self.f.wrapping_add(
(bias >> 1).wrapping_add(e).wrapping_sub(self.x) >> shift, (1i32 << (shift_frequency - 1))
.wrapping_add(e)
.wrapping_sub(self.x)
>> shift_frequency,
); );
self.x = x; self.x = x;
let f = self.f.wrapping_add( let f = self.f.wrapping_add(
bias.wrapping_add(e).wrapping_sub(self.y) >> (shift - 1), (1i32 << (shift_phase - 1))
.wrapping_add(e)
.wrapping_sub(self.y)
>> shift_phase,
); );
self.y = self.y.wrapping_add(f); self.y = self.y.wrapping_add(f);
(self.y, f) (self.y, f)
@ -70,8 +86,8 @@ mod tests {
#[test] #[test]
fn mini() { fn mini() {
let mut p = PLL::default(); let mut p = PLL::default();
let (y, f) = p.update(0x10000, 10); let (y, f) = p.update(0x10000, 8, 4);
assert_eq!(y, 0xc2); assert_eq!(y, 0x1100);
assert_eq!(f, y); assert_eq!(f, y);
} }
@ -79,17 +95,17 @@ mod tests {
fn converge() { fn converge() {
let mut p = PLL::default(); let mut p = PLL::default();
let f0 = 0x71f63049_i32; let f0 = 0x71f63049_i32;
let shift = 10; let shift = (10, 9);
let n = 31 << shift + 2; let n = 31 << shift.0 + 2;
let mut x = 0i32; let mut x = 0i32;
for i in 0..n { for i in 0..n {
x = x.wrapping_add(f0); x = x.wrapping_add(f0);
let (y, f) = p.update(x, shift); let (y, f) = p.update(x, shift.0, shift.1);
if i > n / 4 { if i > n / 4 {
assert_eq!(f.wrapping_sub(f0).abs() <= 1, true); assert_eq!(f.wrapping_sub(f0).abs() <= 1, true);
} }
if i > n / 2 { if i > n / 2 {
// The remaining error is removed by dithering. // The remaining error would be removed by dithering.
assert_eq!(y.wrapping_sub(x).abs() < 1 << 18, true); assert_eq!(y.wrapping_sub(x).abs() < 1 << 18, true);
} }
} }

View File

@ -1,6 +1,11 @@
#![allow(dead_code)]
use super::Complex; use super::Complex;
pub fn isclose(a: f32, b: f32, rtol: f32, atol: f32) -> bool { pub fn isclose(a: f64, b: f64, rtol: f64, atol: f64) -> bool {
(a - b).abs() <= a.abs().max(b.abs()) * rtol + atol
}
pub fn isclosef(a: f32, b: f32, rtol: f32, atol: f32) -> bool {
(a - b).abs() <= a.abs().max(b.abs()) * rtol + atol (a - b).abs() <= a.abs().max(b.abs()) * rtol + atol
} }
@ -10,7 +15,7 @@ pub fn complex_isclose(
rtol: f32, rtol: f32,
atol: f32, atol: f32,
) -> bool { ) -> bool {
isclose(a.0, b.0, rtol, atol) && isclose(a.1, b.1, rtol, atol) isclosef(a.0, b.0, rtol, atol) && isclosef(a.1, b.1, rtol, atol)
} }
pub fn complex_allclose( pub fn complex_allclose(
@ -19,9 +24,7 @@ pub fn complex_allclose(
rtol: f32, rtol: f32,
atol: f32, atol: f32,
) -> bool { ) -> bool {
let mut result: bool = true; a.iter()
a.iter().zip(b.iter()).for_each(|(i, j)| { .zip(b)
result &= complex_isclose(*i, *j, rtol, atol); .all(|(&i, &j)| complex_isclose(i, j, rtol, atol))
});
result
} }

278
dsp/src/trig.rs Normal file
View File

@ -0,0 +1,278 @@
use super::Complex;
use core::f64::consts::PI;
include!(concat!(env!("OUT_DIR"), "/cossin_table.rs"));
/// 2-argument arctangent function.
///
/// This implementation uses all integer arithmetic for fast
/// computation. It is designed to have high accuracy near the axes
/// and lower away from the axes. It is additionally designed so that
/// the error changes slowly with respect to the angle.
///
/// # Arguments
///
/// * `y` - Y-axis component.
/// * `x` - X-axis component.
///
/// # Returns
///
/// The angle between the x-axis and the ray to the point (x,y). The
/// result range is from i32::MIN to i32::MAX, where i32::MIN
/// represents -pi and, equivalently, +pi. i32::MAX represents one
/// count less than +pi.
pub fn atan2(y: i32, x: i32) -> i32 {
let sign = (x < 0, y < 0);
let mut y = y.wrapping_abs() as u32;
let mut x = x.wrapping_abs() as u32;
let y_greater = y > x;
if y_greater {
core::mem::swap(&mut y, &mut x);
}
let z = (16 - y.leading_zeros() as i32).max(0);
x >>= z;
if x == 0 {
return 0;
}
y >>= z;
let r = (y << 16) / x;
debug_assert!(r <= 1 << 16);
// Uses the general procedure described in the following
// Mathematics stack exchange answer:
//
// https://math.stackexchange.com/a/1105038/583981
//
// The atan approximation method has been modified to be cheaper
// to compute and to be more compatible with integer
// arithmetic. The approximation technique used here is
//
// pi / 4 * r + C * r * (1 - abs(r))
//
// which is taken from Rajan 2006: Efficient Approximations for
// the Arctangent Function.
//
// The least mean squared error solution is C = 0.279 (no the 0.285 that
// Rajan uses). K = C*4/pi.
// Q5 for K provides sufficient correction accuracy while preserving
// as much smoothness of the quadratic correction as possible.
const FP_K: usize = 5;
const K: u32 = (0.35489 * (1 << FP_K) as f64) as u32;
// debug_assert!(K == 11);
// `r` is unsigned Q16.16 and <= 1
// `angle` is signed Q1.31 with 1 << 31 == +- pi
// Since K < 0.5 and r*(1 - r) <= 0.25 the correction product can use
// 4 bits for K, and 15 bits for r and 1-r to remain within the u32 range.
let mut angle = ((r << 13)
+ ((K * (r >> 1) * ((1 << 15) - (r >> 1))) >> (FP_K + 1)))
as i32;
if y_greater {
angle = (1 << 30) - angle;
}
if sign.0 {
angle = i32::MAX - angle;
}
if sign.1 {
angle = angle.wrapping_neg();
}
angle
}
/// Compute the cosine and sine of an angle.
/// This is ported from the MiSoC cossin core.
/// (https://github.com/m-labs/misoc/blob/master/misoc/cores/cossin.py)
///
/// # Arguments
/// * `phase` - 32-bit phase.
///
/// # Returns
/// The cos and sin values of the provided phase as a `Complex<i32>`
/// value. With a 7-bit deep LUT there is 1e-5 max and 6e-8 RMS error
/// in each quadrature over 20 bit phase.
pub fn cossin(phase: i32) -> Complex<i32> {
// Phase bits excluding the three highes MSB
const OCTANT_BITS: usize = 32 - 3;
// This is a slightly more compact way to compute the four flags for
// octant mapping/unmapping used below.
let mut octant = (phase as u32) >> OCTANT_BITS;
octant ^= octant << 1;
// Mask off octant bits. This leaves the angle in the range [0, pi/4).
let mut phase = phase & ((1 << OCTANT_BITS) - 1);
if octant & 1 != 0 {
// phase = pi/4 - phase
phase = (1 << OCTANT_BITS) - 1 - phase;
}
let lookup = COSSIN[(phase >> (OCTANT_BITS - COSSIN_DEPTH)) as usize];
// 1/2 < cos(0 <= x <= pi/4) <= 1: Shift the cos
// values and scale the sine values as encoded in the LUT.
let mut cos = lookup.0 as i32 + u16::MAX as i32;
let mut sin = (lookup.1 as i32) << 1;
// 16 + 1 bits for cos/sin and 15 for dphi to saturate the i32 range.
const ALIGN_MSB: usize = 32 - 16 - 1;
phase >>= OCTANT_BITS - COSSIN_DEPTH - ALIGN_MSB;
phase &= (1 << ALIGN_MSB) - 1;
// The phase values used for the LUT are at midpoint for the truncated phase.
// Interpolate relative to the LUT entry midpoint.
phase -= (1 << (ALIGN_MSB - 1)) - (octant & 1) as i32;
// Fixed point pi/4.
const PI4: i32 = (PI / 4. * (1 << (32 - ALIGN_MSB)) as f64) as i32;
// No rounding bias necessary here since we keep enough low bits.
let dphi = (phase * PI4) >> (32 - ALIGN_MSB);
// Make room for the sign bit.
let dcos = (sin * dphi) >> (COSSIN_DEPTH + 1);
let dsin = (cos * dphi) >> (COSSIN_DEPTH + 1);
cos = (cos << (ALIGN_MSB - 1)) - dcos;
sin = (sin << (ALIGN_MSB - 1)) + dsin;
// Unmap using octant bits.
if octant & 2 != 0 {
core::mem::swap(&mut sin, &mut cos);
}
if octant & 4 != 0 {
cos *= -1;
}
if octant & 8 != 0 {
sin *= -1;
}
(cos, sin)
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
fn angle_to_axis(angle: f64) -> f64 {
let angle = angle % (PI / 2.);
(PI / 2. - angle).min(angle)
}
#[test]
fn atan2_absolute_error() {
const N: usize = 321;
let mut test_vals = [0i32; N + 4];
let scale = (1i64 << 31) as f64;
for i in 0..N {
test_vals[i] = (scale * (-1. + 2. * i as f64 / N as f64)) as i32;
}
assert!(test_vals.contains(&i32::MIN));
test_vals[N] = i32::MAX;
test_vals[N + 1] = 0;
test_vals[N + 2] = -1;
test_vals[N + 3] = 1;
let mut rms_err = 0f64;
let mut abs_err = 0f64;
let mut rel_err = 0f64;
for &x in test_vals.iter() {
for &y in test_vals.iter() {
let want = (y as f64 / scale).atan2(x as f64 / scale);
let have = atan2(y, x) as f64 * PI / scale;
let err = (have - want).abs();
abs_err = abs_err.max(err);
rms_err += err * err;
if err > 3e-5 {
rel_err = rel_err.max(err / angle_to_axis(want));
}
}
}
rms_err = rms_err.sqrt() / test_vals.len() as f64;
println!("max abs err: {:.2e}", abs_err);
println!("rms abs err: {:.2e}", rms_err);
println!("max rel err: {:.2e}", rel_err);
assert!(abs_err < 5e-3);
assert!(rms_err < 3e-3);
assert!(rel_err < 0.6);
}
#[test]
fn cossin_error_max_rms_all_phase() {
// Constant amplitude error due to LUT data range.
const AMPLITUDE: f64 = ((1i64 << 31) - (1i64 << 15)) as f64;
const MAX_PHASE: f64 = (1i64 << 32) as f64;
let mut rms_err: Complex<f64> = (0., 0.);
let mut sum_err: Complex<f64> = (0., 0.);
let mut max_err: Complex<f64> = (0., 0.);
let mut sum: Complex<f64> = (0., 0.);
let mut demod: Complex<f64> = (0., 0.);
// use std::{fs::File, io::{BufWriter, prelude::*}, path::Path};
// let mut file = BufWriter::new(File::create(Path::new("data.bin")).unwrap());
const PHASE_DEPTH: usize = 20;
for phase in 0..(1 << PHASE_DEPTH) {
let phase = (phase << (32 - PHASE_DEPTH)) as i32;
let have = cossin(phase);
// file.write(&have.0.to_le_bytes()).unwrap();
// file.write(&have.1.to_le_bytes()).unwrap();
let have = (have.0 as f64 / AMPLITUDE, have.1 as f64 / AMPLITUDE);
let radian_phase = 2. * PI * phase as f64 / MAX_PHASE;
let want = (radian_phase.cos(), radian_phase.sin());
sum.0 += have.0;
sum.1 += have.1;
demod.0 += have.0 * want.0 - have.1 * want.1;
demod.1 += have.1 * want.0 + have.0 * want.1;
let err = (have.0 - want.0, have.1 - want.1);
sum_err.0 += err.0;
sum_err.1 += err.1;
rms_err.0 += err.0 * err.0;
rms_err.1 += err.1 * err.1;
max_err.0 = max_err.0.max(err.0.abs());
max_err.1 = max_err.1.max(err.1.abs());
}
rms_err.0 /= MAX_PHASE;
rms_err.1 /= MAX_PHASE;
println!("sum: {:.2e} {:.2e}", sum.0, sum.1);
println!("demod: {:.2e} {:.2e}", demod.0, demod.1);
println!("sum_err: {:.2e} {:.2e}", sum_err.0, sum_err.1);
println!("rms: {:.2e} {:.2e}", rms_err.0.sqrt(), rms_err.1.sqrt());
println!("max: {:.2e} {:.2e}", max_err.0, max_err.1);
assert!(sum.0.abs() < 4e-10);
assert!(sum.1.abs() < 4e-10);
assert!(demod.0.abs() < 4e-10);
assert!(demod.1.abs() < 4e-10);
assert!(sum_err.0.abs() < 4e-10);
assert!(sum_err.1.abs() < 4e-10);
assert!(rms_err.0.sqrt() < 6e-8);
assert!(rms_err.1.sqrt() < 6e-8);
assert!(max_err.0 < 1.1e-5);
assert!(max_err.1 < 1.1e-5);
}
}