gainlineup
RF signal chain (gain lineup) analysis for receiver and transmitter design.
What It Does
gainlineup models an RF signal chain as a sequence of blocks (amplifiers, filters, attenuators, mixers) and cascades their effects on signal power, noise, and linearity. Think of it as a spreadsheet-style RF lineup — but in Rust, with proper Friis equation cascading.
Quick Start
1. Define Your Input Signal
Every chain starts with an input signal: power level, frequency, bandwidth, and optionally a noise temperature (e.g., antenna sky temperature).
use ;
let input = Input ;
2. Define Your Blocks
Each block in the chain has a name, gain, noise figure, and optionally compression (P1dB) and linearity (IP3) specs.
use ;
let lna = Block ;
let mixer = Block ;
let if_amp = Block ;
3. Run the Cascade
Pass the input and blocks through the cascade to get signal nodes at each stage.
use ;
let input = Input ;
let lna = Block ;
let mixer = Block ;
let if_amp = Block ;
let blocks = vec!;
let nodes = cascade_vector_return_vector;
for node in &nodes
// Final cascade result
let output = nodes.last.unwrap;
println!;
4. What Gets Cascaded
At each node in the chain, the cascade computes:
| Parameter | Description |
|---|---|
| Signal Power (dBm) | Cumulative signal level, with compression |
| Noise Power (dBm) | Cumulative noise from all stages |
| Gain (dB) | Cumulative gain (accounts for compression) |
| Noise Figure (dB) | Cascaded NF via Friis equation |
| Noise Temperature (K) | Cascaded system temperature |
| OIP3 (dBm) | Cascaded output IP3 (when blocks have IP3 set) |
| SFDR (dB) | Spur-free dynamic range: 2/3 × (OIP3 − noise floor) |
Compression (P1dB)
When a block has output_p1db_dbm set, the output power clamps at P1dB + 1 dB. Signal and noise are compressed independently — noise only compresses if it actually exceeds P1dB (rare, but handled correctly).
use ;
let pa = Block ;
// Linear region
assert_eq!; // -20 + 30 = 10 (below P1dB)
assert_eq!; // full gain
// Compressed
assert_eq!; // 0 + 30 = 30, clamps to 21
assert_eq!; // reduced gain
Dynamic Range
Dynamic range tells you the usable power range of a block or chain: from the noise floor up to the compression point.
use ;
let lna = Block ;
// Output-referred: P1dB_out - noise_floor_out
let dr = lna.dynamic_range_db.unwrap;
println!;
// Input-referred: input_P1dB - input_noise_floor
let dr_in = lna.input_dynamic_range_db.unwrap;
println!;
Returns None when P1dB is not set (linear block, infinite dynamic range).
AM-AM Curves (Power Sweep)
Sweep input power to see how a block or chain behaves from linear through compression. This is the classic "Pin vs Pout" curve from amplifier datasheets.
Single Block
use ;
let lna = Block ;
// Pin vs Pout
let curve = lna.am_am_sweep;
for in &curve
// Pin vs Gain (shows compression directly)
let gc = lna.gain_compression_sweep;
for in &gc
Full Cascade
use ;
let lna = Block ;
let mixer = Block ;
let if_amp = Block ;
let blocks = vec!;
// Cascade Pin vs Pout
let am_am = cascade_am_am_sweep;
for in &am_am
// Cascade Pin vs Gain
let gc = cascade_gain_compression_sweep;
for in &gc
IMD3 (Intermodulation from IP3)
When a block has output_ip3_dbm set, you can compute third-order intermodulation products — the spurious signals that appear in a two-tone test.
use ;
let amp = Block ;
// Single point
let im3 = amp.imd3_output_power_dbm.unwrap;
println!; // -90 dBm
let rejection = amp.imd3_rejection_db.unwrap;
println!; // 80 dB below carrier
// Full two-tone sweep
let sweep = amp.imd3_sweep;
for pt in &sweep
Key relationships:
IM3_out = 3 × Pout - 2 × OIP3(all dBm)Rejection = 2 × (OIP3 - Pout)(dB)- IM3 follows the 3:1 slope rule: 3 dB increase per 1 dB input increase
Node-Level Dynamic Range Summary
After running a cascade, each SignalNode can produce a dynamic range summary that combines P1dB, noise floor, SFDR, and input limits into one struct.
use ;
let input = new;
let blocks = vec!;
let node = cascade_vector_return_output;
// Simple linear dynamic range
if let Some = node.dynamic_range_db
// Full summary
if let Some = node.dynamic_range_summary
Returns None when the node has no P1dB (e.g., a passive stage without a compression spec).
AmplifierModel + AM-PM
AmplifierModel wraps a Block and adds AM-PM (phase distortion) characterization. It's a separate struct — the core Block stays simple for cascade analysis, while AmplifierModel provides richer single-amplifier modeling.
use ;
let pa = Block ;
// Simple: no AM-PM
let model = new;
// With AM-PM coefficient (10 °/dB near P1dB)
let model = with_am_pm;
// Builder pattern for full configuration
let model = builder
.am_pm_coefficient
.saturation_power
.build;
// Phase shift at a given input power
if let Some = model.phase_shift_at
// Combined AM-AM + AM-PM sweep
let sweep = model.am_am_am_pm_sweep;
for pt in &sweep
// Required backoff for a phase budget
if let Some = model.backoff_for_target_phase
// EVM from AM-PM distortion
if let Some = model.evm_from_am_pm
CLI (TOML File Input)
The command-line tool reads a TOML file defining the input and blocks, runs the cascade, and generates an HTML table.
TOML Format
= -80.0
= 6.0e9
= 1.0e6
[[]]
= "explicit"
= "Low Noise Amplifier"
= 20.0
= 3.0
[[]]
= "explicit"
= "Mixer"
= 10.0
= 6.0
[[]]
= "explicit"
= "IF Amplifier"
= 15.0
= 5.0
Field Aliases
For brevity, you can use short field names. The unit-suffixed names are recommended for clarity.
| Full Name | Aliases |
|---|---|
gain_db |
gain |
noise_figure_db |
noise_figure, nf |
output_p1db_dbm |
output_p1db, op1db |
output_ip3_dbm |
output_ip3, oip3 |
input_power_dbm |
input_power, pin |
frequency_hz |
frequency, f |
bandwidth_hz |
bandwidth, bw |
noise_temperature_k |
noise_temperature |
Caution: Aliases hide unit suffixes.
pinis always dBm,fis always Hz. If you assume different units, you'll get wrong results silently.
HTML Output
The CLI generates an HTML visualization of the cascade:
API Summary
Core Types
| Type | Description |
|---|---|
Input |
Signal entering the chain (power, freq, BW, temp) |
Block |
A component: gain, NF, P1dB, IP3 |
SignalNode |
Result at each stage: power, noise, NF, gain, OIP3, SFDR |
Imd3Point |
Two-tone test result: carrier + IM3 levels |
DynamicRange |
Summary: linear DR, SFDR, MDS, max input |
AmplifierModel |
Block wrapper with AM-PM characterization |
AmplifierPoint |
Combined AM-AM + AM-PM sweep point |
Cascade Functions
| Function | Returns |
|---|---|
cascade_vector_return_output() |
Final SignalNode only |
cascade_vector_return_vector() |
Vec<SignalNode> at every stage |
cascade_am_am_sweep() |
Vec<(Pin, Pout)> through full chain |
cascade_gain_compression_sweep() |
Vec<(Pin, Gain)> through full chain |
Block Methods
| Method | Returns |
|---|---|
output_power(pin) |
Pout with compression |
power_gain(pin) |
Gain at a given input level |
dynamic_range_db(bw) |
Output-referred DR (P1dB - noise) |
input_dynamic_range_db(bw) |
Input-referred DR |
am_am_curve(powers) |
Vec<(Pin, Pout)> |
am_am_sweep(start, stop, step) |
Vec<(Pin, Pout)> evenly spaced |
gain_compression_curve(powers) |
Vec<(Pin, Gain)> |
gain_compression_sweep(..) |
Vec<(Pin, Gain)> evenly spaced |
imd3_output_power_dbm(pin) |
IM3 product power (dBm) |
imd3_rejection_db(pin) |
Carrier minus IM3 (dB) |
imd3_sweep(start, stop, step) |
Vec<Imd3Point> |
SignalNode Methods
| Method | Returns |
|---|---|
signal_to_noise_ratio_db() |
SNR at this node (dB) |
noise_spectral_density() |
Noise PSD (dBm/Hz) |
dynamic_range_db() |
Linear DR at node: P1dB − noise (dB) |
dynamic_range_summary() |
Full DynamicRange summary |
Features
Debug Output
Enable verbose debug printing during cascade calculations:
[]
= { = "0.18.0", = ["debug-print"] }
References
- Pozar, D. Microwave Engineering (4th ed.) — Friis equation, noise figure, IP3
- Razavi, B. RF Microelectronics (2nd ed.) — dynamic range, SFDR, receiver design
- Noise Figure — Wikipedia
