Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# AGENTS.md - Hub for fsdr-blocks

Building blocks for FutureSDR signal processing library for SDR and real-time DSP.

## Tech Stack
- **Language:** Rust (Edition 2024, **Nightly channel**)
- **Core Library:** [FutureSDR](https://www.futuresdr.org)
- **Acceleration:** Explicit SIMD via `std::simd` (portable SIMD)
- **Serialization:** Serde, custom PMT (Polymorphic Types)
- **Testing:** Cargo test, QuickCheck, Criterion (benchmarks)

## Critical Commands
- **Install:** `cargo build`
- **Lint:** `./check.sh` (Runs fmt, clippy, and tests with all features)
- **Test:** `cargo test --all-features`
- **Bench:** `cargo bench --all-features`

## Documentation Index
- [Architecture](agent_docs/architecture.md): **Trigger:** Designing new blocks or understanding flowgraph connectivity.
- [Conventions](agent_docs/conventions.md): **Trigger:** Before writing any code to ensure alignment with Rust 2024 and FutureSDR idioms.
- [SDR & DSP](agent_docs/sdr_dsp.md): **Trigger:** Modifying signal processing logic, gain control, or frequency shifts.
- [SigMF](agent_docs/sigmf.md): **Trigger:** Working with Signal Metadata Format (SigMF) recordings or collections.

## Verification Loop
You MUST run `./check.sh` and ensure all tests pass before declaring a task "done."
Always verify that your changes didn't break conditional feature flags (`crossbeam`, `async-channel`, `cw`).
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ serde_json = "1"

[features]
default = []
simd = []
crossbeam = ["dep:crossbeam-channel"]
async-channel = ["dep:async-channel"]
cw = ["dep:bimap"]
Expand Down Expand Up @@ -70,3 +71,18 @@ name = "shared"
path = "benches/cw/shared.rs"
harness = false
required-features = ["cw"]

[[bench]]
name = "deinterleave"
path = "benches/stream/deinterleave.rs"
harness = false

[[bench]]
name = "type_converters"
path = "benches/type_converters.rs"
harness = false

[[bench]]
name = "freq_shift"
path = "benches/math/freq_shift.rs"
harness = false
41 changes: 41 additions & 0 deletions SIMD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SIMD Optimization Roadmap - fsdr-blocks

This document outlines the prioritized blocks for SIMD refactoring based on their impact in typical SDR pipelines (e.g., `csdr` style flows).

## Prioritization Matrix

| Priority | Block | File | Rationale |
| :--- | :--- | :--- | :--- |
| **High** | `convert_u8_f` | `src/type_converters.rs` | Entry point for raw samples; high throughput, simple math. |
| **High** | `FreqShift` | `src/math/freq_shift.rs` | Per-sample complex rotation; solves loop-carried dependencies. |
| **Medium** | `FmDemod` | TBD | Quadrature math (cross-products) is "heavy" and benefits from vectorization. |
| **Medium** | `BinarySlicer` | TBD | Comparisons are perfect for SIMD masks and bit-packing. |
| **Low** | `Deinterleave` | `src/stream/deinterleave.rs` | **COMPLETED.** Memory-bound but established the pattern. |

## Implementation Pattern: SIMD + Specialization

To maintain compatibility and maximize performance, we use the following pattern:

1. **Nightly Features:** Enable `#![feature(portable_simd)]` and `#![feature(specialization)]`.
2. **Specialization Trait:** Define a `*Supported` trait (e.g., `TypeConvertSupported`).
3. **Default Logic:** Provide a `default` scalar implementation for all types.
4. **SIMD Specialization:** Implement specialized SIMD versions for `f32`, `u8`, `i16`, etc.
5. **Generic Helpers:** Use internal `_scalar_logic` and `_simd_logic` functions to minimize duplication.
6. **Macros:** Employ `macro_rules!` to apply the same SIMD logic across multiple types (e.g., `u8`, `i8`).

## Completed Optimizations

### 1. Deinterleave (`src/stream/deinterleave.rs`)
- **Status:** Done.
- **Impact:** ~6% gain on `f32` (memory-bound).
- **Pattern:** `DeinterleaveSupported` trait with macro-based SIMD implementations.

### 2. TypeConverter (`src/type_converters.rs`)
- **Status:** Done.
- **Impact:** ~305% gain (3.05x speedup) on `u8` -> `f32` scaled conversion.
- **Pattern:** `TypeConvertSupported` trait with specialized SIMD path for `u8` -> `f32`.

### 3. FreqShift (`src/math/freq_shift.rs`)
- **Status:** Done.
- **Impact:** ~4.4x speedup on `Complex32` rotation compared to naive scalar loop.
- **Pattern:** `FreqShiftSupported` trait with SIMD complex multiplication and periodic NCO re-sync for precision.
20 changes: 20 additions & 0 deletions agent_docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Architecture - fsdr-blocks

This project follows the **Flowgraph/Block** paradigm provided by FutureSDR.

## Core Concepts
- **Blocks:** The fundamental units of processing. Defined using `#[derive(Block)]`.
- **Flowgraph:** A directed acyclic graph (usually) where blocks are nodes and streams/messages are edges.
- **Kernels:** The execution logic of a block. Most blocks in this repository are CPU-based and implement the `Kernel` trait.

## Data Movement
- **Streams:** High-throughput data (e.g., IQ samples) passed via `CpuBufferReader`/`CpuBufferWriter`.
- **Messages:** Asynchronous control signals or metadata passed as `Pmt` (Polymorphic Types).

## Dependency on FutureSDR
This project is tightly coupled with `futuresdr`. It uses a local path dependency in `Cargo.toml` by default (`../FutureSDR`), which indicates it is often developed alongside the core library.

## Block Types
- **Sources/Sinks:** Handle I/O (e.g., `StdinSink`, `SigmfSource`).
- **Processing Blocks:** Transform data (e.g., `Agc`, `FreqShift`).
- **Adapters:** Connect different async runtimes or channel types (e.g., `CrossbeamSink`).
32 changes: 32 additions & 0 deletions agent_docs/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Conventions - fsdr-blocks

## Rust Standards
- **Edition:** 2024. Use modern idioms (e.g., `async fn` in traits, let-else).
- **Formatting:** Strictly adhere to `cargo fmt`.

## Block Implementation Boilerplate
Every block should typically include:
1. **Struct Definition:** Use `#[derive(Block)]` and specify `#[message_inputs(...)]` if applicable.
2. **Implementation:** A `new` method and message handler methods (returning `Result<Pmt>`).
3. **Kernel Trait:** Implement `async fn work` to handle the data processing loop.
4. **Builder Pattern:** Use a `BlockBuilder` struct for complex configuration (see `src/agc.rs`).

## Performance Acceleration
For blocks in hot loops (AGC, Frequency Shift, Deinterleave), prefer explicit SIMD over compiler-dependent autovectorization:
1. **Feature Flags:** Ensure `portable_simd` and `specialization` are enabled in `src/lib.rs`.
2. **Specialization Pattern:** Define a `*Supported` trait (e.g., `DeinterleaveSupported`) with a `default` scalar implementation and specialized SIMD implementations for `f32`, `u8`, `i8`, `i16`.
3. **Macro Reuse:** Use macros to implement SIMD logic across different types to avoid code duplication.
4. **Benchmarking:** Every accelerated block MUST have a corresponding `Criterion` benchmark in `benches/`.

## Error Handling
- Use `futuresdr::anyhow::Result` for block operations.
- Prefer `Context` from `anyhow` for descriptive error messages in I/O operations.

## Feature Management
- Use `#[cfg(feature = "...")]` for blocks that depend on optional crates like `crossbeam-channel` or `async-channel`.
- Always test with `--all-features` to ensure no regressions in optional components.

## Testing
- **Unit Tests:** Located in `tests/`.
- **Property-Based Testing:** Use `quickcheck` for robust validation of DSP algorithms.
- **Benchmarks:** Located in `benches/`, using `Criterion`.
16 changes: 16 additions & 0 deletions agent_docs/sdr_dsp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SDR & DSP - fsdr-blocks

## Signal Processing Idioms
- **Complex Numbers:** Use `num_complex::Complex32` (or generic `T: ComplexFloat`).
- **Buffers:** Always use `input.slice()` and `output.slice()` in the `work` function.
- **Consumption/Production:** Explicitly call `input.consume(n)` and `output.produce(n)` after processing.

## Key Blocks
- **AGC (Automatic Gain Control):** Implements a feedback loop to maintain target power. See `src/agc.rs`.
- **FreqShift:** Performs digital down-conversion/up-conversion. See `src/math/freq_shift.rs`.
- **Type Converters:** Crucial for translating between raw bytes and SDR-specific types.

## Math Operations
- **Prefer Explicit SIMD:** Use `std::simd` and specialization (see `DeinterleaveSupported`) for performance-critical blocks in hot loops. This avoids dependency on brittle compiler autovectorization.
- **Precision:** Be mindful of floating-point precision and squelch thresholds.
- **Error Accumulation:** Periodically re-calculate phase in recurrence relations to prevent drift (e.g., in `FreqShift`).
16 changes: 16 additions & 0 deletions agent_docs/sigmf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SigMF - fsdr-blocks

## Overview
The `sigmf` crate (in `crates/sigmf`) provides a Rust implementation of the [Signal Metadata Format](https://github.com/sigmf/SigMF).

## Structure
- **Global:** Top-level metadata about the recording.
- **Captures:** Segment-specific metadata (sample rate, frequency).
- **Annotations:** Time/frequency-indexed labels.

## Usage in fsdr-blocks
- `SigmfSource`: Reads `.sigmf-meta` and `.sigmf-data` files into a FutureSDR flowgraph.
- `SigmfSink`: Records flowgraph data into SigMF-compliant files.

## Extensions
Supports SigMF extensions (e.g., `AntennaExtension`). New extensions should be added as modules in `crates/sigmf/src/`.
31 changes: 31 additions & 0 deletions benches/math/freq_shift.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use fsdr_blocks::math::FrequencyShifter;
use futuresdr::num_complex::Complex32;
use futuresdr::runtime::mocker::{Mocker, Reader, Writer};
use rand::RngExt;

pub fn freq_shift_c32(c: &mut Criterion) {
let n_samp = 8192;
let mut rng = rand::rng();
let input: Vec<Complex32> = (0..n_samp)
.map(|_| Complex32::new(rng.random(), rng.random()))
.collect();

let mut group = c.benchmark_group("math");
group.throughput(Throughput::Elements(n_samp as u64));

group.bench_function("freq_shift_c32", |b| {
b.iter(|| {
let block: FrequencyShifter<Complex32, Reader<Complex32>, Writer<Complex32>> =
FrequencyShifter::new(2000.0, 48000.0);
let mut mocker = Mocker::new(block);
mocker.input().set(input.clone());
mocker.run();
});
});

group.finish();
}

criterion_group!(benches, freq_shift_c32);
criterion_main!(benches);
28 changes: 28 additions & 0 deletions benches/stream/deinterleave.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use fsdr_blocks::stream::Deinterleave;
use futuresdr::runtime::mocker::{Mocker, Reader, Writer};
use rand::RngExt;

pub fn deinterleave_f32(c: &mut Criterion) {
let n_samp = 8192;
let mut rng = rand::rng();
let input: Vec<f32> = (0..n_samp).map(|_| rng.random()).collect();

let mut group = c.benchmark_group("deinterleave");
group.throughput(Throughput::Elements(n_samp as u64));

group.bench_function("deinterleave_f32", |b| {
b.iter(|| {
let block: Deinterleave<f32, Reader<f32>, Writer<f32>, Writer<f32>> =
Deinterleave::new();
let mut mocker = Mocker::new(block);
mocker.input().set(input.clone());
mocker.run();
});
});

group.finish();
}

criterion_group!(benches, deinterleave_f32);
criterion_main!(benches);
27 changes: 27 additions & 0 deletions benches/type_converters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use fsdr_blocks::type_converters::TypeConverter;
use futuresdr::runtime::mocker::{Mocker, Reader, Writer};
use rand::RngExt;

pub fn scale_convert_u8_f32(c: &mut Criterion) {
let n_samp = 8192;
let mut rng = rand::rng();
let input: Vec<u8> = (0..n_samp).map(|_| rng.random()).collect();

let mut group = c.benchmark_group("type_converters");
group.throughput(Throughput::Elements(n_samp as u64));

group.bench_function("scale_convert_u8_f32", |b| {
b.iter(|| {
let block: TypeConverter<u8, f32, Reader<u8>, Writer<f32>> = TypeConverter::new(true);
let mut mocker = Mocker::new(block);
mocker.input().set(input.clone());
mocker.run();
});
});

group.finish();
}

criterion_group!(benches, scale_convert_u8_f32);
criterion_main!(benches);
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! This library acts as a toolbox on top of [FutureSDR][`futuresdr`] to easily build your own flowgraph.
#![cfg_attr(feature = "simd", feature(portable_simd))]
#![cfg_attr(feature = "simd", feature(min_specialization))]
//! This library acts as a toolbox on top of [`futuresdr`] to easily build your own flowgraph.
//! It is made by the community for the community.

// #![feature(async_fn_in_trait)]
Expand Down
Loading
Loading