Skip to content

Harmonic Balance

S-parameters describe only small-signal behavior. For large-signal design — power amplifiers, mixers, or any circuit where compression and mixing products matter — COBRA can optimize against a Harmonic Balance (.HB) analysis instead of, or together with, the .AC sweep.

HB returns the steady-state spectrum at a chosen point in the circuit: how much power sits at which frequency. That makes it the basis for output-power, gain, and isolation goals.

flowchart LR
    A[Netlist with .HB] --> B[Xyce Harmonic Balance]
    B --> C[.HB.FD.csv / .prn phasors]
    C --> D[Spectrum at analysis point]
    D --> E[Power / Gain / Isolation goals]
    D --> F[Live spectrum plot]

Netlist Requirements

A Qucs-S schematic containing a Harmonic Balance simulation block exports the three directives COBRA needs:

.options hbint numfreq=3 STARTUPPERIODS=2
.HB 130G
.PRINT hb format=csv I(VOut) v(Out) v(Vdd) v(ip) v(on) v(op) v(sub)
Directive Meaning
.HB Fundamental frequencies. A single token is single-tone; several tokens make the analysis multi-tone.
.options hbint numfreq=… Harmonics per fundamental. A list such as numfreq=4,40 assigns a different count to each tone.
.PRINT hb … Signals written to the result file — this determines which analysis points are available.

Single-tone is appropriate whenever one signal is amplified:

.HB 130G

Multi-tone is required when signals actually mix, because Xyce then solves for all products \(m \cdot f_1 + n \cdot f_2\):

.HB 95E9 10E9

Tip

Both the .HB frequencies and the .options hbint values become editable fields under Simulation Parameters once a netlist is loaded, so harmonic settings can be adjusted without returning to the schematic.

Analysis Points

Output power needs a voltage and a current. The Qucs-S convention is a labelled node with a 0 V source acting as a current probe in series with it:

VOut Out _net6 DC 0

This produces the pair V(Out) and I(VOut). COBRA reads the .PRINT hb line and offers every node that has both halves of such a pair:

from cobra.spice_sim.netlist_parsers.xyce_netlist_parser import XyceNetlistParser

netlist = XyceNetlistParser().parse_file("examples/netlists/Mixer/mixer_hb.cir")
netlist.probe_nodes
# ['IF_neg', 'IF_pos', 'LO_CM', 'LO_in', 'LO_neg', 'LO_pos', 'Out',
#  'RF_CM', 'RF_M_n', 'RF_M_p', 'RF_in', 'RF_neg', 'RF_pos']

In the GUI this list populates the Analysis Point dropdown in the configuration panel. The selected node is what both the HB design parameters and the spectrum plot refer to.

Warning

A node that is only printed as a voltage cannot carry a power or gain goal, because the probe current is missing. Add the corresponding current probe in Qucs-S if a node needs to be evaluated.

Power and Gain

Xyce stores HB results as two-sided phasors, so every spectral line holds amplitude / 2. The apparent power at an analysis point is therefore:

\[S = V_\mathrm{rms} \cdot I_\mathrm{rms} = 2 \cdot \lvert V_\mathrm{pk} \cdot I_\mathrm{pk} \rvert\]
\[P[\mathrm{dBm}] = 10 \cdot \log_{10}\!\left(\frac{2 \cdot \lvert V_\mathrm{pk} \cdot I_\mathrm{pk} \rvert}{1\,\mathrm{mW}}\right)\]

The DC line has no negative-frequency twin and holds the DC value itself, so its power is \(\lvert V \cdot I \rvert\): with a 0 V probe in the supply, Power_dBm[Vdd] over 0-0.1GHz is the DC supply power under the HB drive (the bias current changes with drive, so it is not the quiescent power).

Because the real probe current is used instead of an assumed load resistance, the result stays valid for arbitrary and reactive loads, not just 50 Ω.

Gain additionally needs the drive level. It is derived from the SIN source of the port nominated as the input, using its amplitude \(A\) and port impedance \(z_0\) from the netlist:

\[P_\mathrm{avail} = \frac{A^2}{8 \cdot z_0} \qquad\qquad G[\mathrm{dB}] = P_\mathrm{out}[\mathrm{dBm}] - P_\mathrm{in}[\mathrm{dBm}]\]

For the port line P2 _net28 0 port=1 z0=100 AC 0.089442719 SIN 0 0.089442719 130G this yields \(P_\mathrm{in} = 10\,\mu\mathrm{W} = -20\) dBm.

Design Goals

Loading a netlist with an HB analysis adds three families of design parameters, named after the nodes and ports actually present in the circuit:

Parameter Description
Power_dBm[<node>] Output power in dBm at the analysis point
Gain_dB[<port>@<node>] Transducer gain in dB at <node>, referred to the drive level of input port <port>
Isolation_dB[<node>] Margin in dB between the target line at <node> and the strongest other line (see below)

A goal targets one spectral line by giving a single frequency, or a band by giving a range:

Power_dBm[Out] > 10 dB   @ 35ghz

Single frequency in the goal dialog, or a bare 35ghz in a config. The nearest spectral line is selected, matching scikit-rf slicing behavior.

Power_dBm[Out] < -30 dB  @ 100-160ghz

Every HB line inside the band contributes to the penalty — useful for suppressing unwanted mixing products.

Isolation

Suppressing spurs with absolute Power_dBm caps means re-tuning every cap whenever the wanted signal moves. Isolation_dB[<node>] states the requirement the way a datasheet does — as a margin:

Isolation_dB[Out] > 30 dB  @ 35ghz

The goal's frequency range names the wanted line; every other line in the spectrum is a spur. The value is

\[\mathrm{Isolation[dB]} = P_\mathrm{target} - \max_{f \neq f_\mathrm{target}} P(f)\]

so one goal replaces a cap per spur and keeps holding as the design changes: raising conversion gain by 3 dB does not suddenly satisfy it.

Note

DC is excluded from the spur search. The 0 Hz bin carries the operating point, not a mixing product, and would otherwise dominate every comparison.

A frequency range is required — without a target line there is nothing to measure against, and the goal is rejected before the run starts. Where the range spans several bins, the weakest of them is taken as the target, so the margin is the worst case.

Scripting Example

from cobra.optimizers.design_goal import DesignGoal
from cobra.optimizers.design_goal_collection import (
    make_gain_db,
    make_isolation_db,
    make_power_dbm,
)
from cobra.spice_sim.netlist_parsers.xyce_netlist_parser import XyceNetlistParser

netlist = XyceNetlistParser().parse_file("examples/netlists/Mixer/mixer_hb.cir")
node = "Out"
port = netlist.port_sources["P1"]          # {'z0': 100.0, 'sin_amplitude': 0.28284271, ...}

goals = [
    # IF output power at the 35 GHz difference frequency
    DesignGoal(
        parameter=make_power_dbm(node),
        frequency_range="35ghz",
        min_value=-20.0,
    ),
    # Conversion gain referred to the LO/RF drive on P1
    DesignGoal(
        parameter=make_gain_db("P1", port["sin_amplitude"], port["z0"], node),
        frequency_range="35ghz",
        min_value=6.0,
    ),
    # Every other line at least 30 dB below the 35 GHz IF
    DesignGoal(
        parameter=make_isolation_db(node),
        frequency_range="35ghz",
        min_value=30.0,
    ),
]

Combining HB and S-Parameter Goals

AC and HB goals may be mixed in one run. COBRA determines which analyses the goals require, generates one netlist per analysis type, runs them, and combines the resulting penalties into a single loss:

\[L = \sum_i w_i \cdot p_i\]

If a goal needs an analysis the netlist does not contain, the missing directive is injected automatically — including a matching .PRINT HB format=csv line, without which Xyce would produce no HB output at all.

Note

This is what makes a typical mixer specification expressible in one optimization: S11 matching from the .AC sweep, conversion gain and mixing-product isolation from the .HB spectrum.

Spectrum Visualization

During optimization the visualization panel plots the spectrum at the selected analysis point as a stem plot, updated every iteration.

Quantity Requires Y axis
Power V(node) and I(Vnode) dBm
Gain V(node), I(Vnode) and an input port dB
Voltage V(node) dBV
Current I(Vnode) dBmA
  • Fundamentals are drawn in a distinct color from the remaining lines.
  • Clicking a line toggles a marker on it, labelled with its frequency, value, and harmonic index — H2 for single-tone, or the mixing-product decomposition such as 2f1-f2 for multi-tone. Clicking a marked line again removes its marker, and the click snaps to the nearest line, so it need not land exactly on one.
  • When the run produces more than one of the S-parameter, HB and transient plots, a selector switches the left plot between them. When only one is active, that plot is shown and the selector is disabled.

The mixing-product labels are the practical tool for isolation work: they identify which unwanted product a given line belongs to while the optimizer is running.

Worked Example: Mixer

examples/netlists/Mixer/mixer_hb.cir is a downconverting mixer:

Signal Frequency
RF input 130 GHz
LO input 95 GHz
IF output (target) 35 GHz

The simulation does not use 130 GHz and 95 GHz as fundamentals. It uses .HB 95E9 10E9 with numfreq=4,40, so the coarse 95 GHz tone and a fine 10 GHz grid together resolve 130 GHz and 35 GHz with enough mixing products. The lower fundamental carries far more harmonics precisely because the frequency resolution there is not oversampled.

Alongside the wanted difference frequency, the sum frequency (225 GHz) and higher products such as 2f_LO - f_RF appear. Constraining them is an isolation goal: every line other than the target should sit a defined margin below it.

Result Files

HB runs add the frequency-domain table to the timestamped results folder:

results/<timestamp>_<name>/
└── trials/trial_<n>/
    ├── <netlist>.HB.FD.csv     # or .HB.FD.prn, depending on the .PRINT format
    └── …

Columns follow the Xyce convention FREQ, Re(V(OUT)), Im(V(OUT)), Re(I(VOUT)), Im(I(VOUT)), … and contain both the negative and positive halves of the two-sided spectrum. COBRA evaluates the non-negative half.

Limitations

Note

A transient (.TRAN) analysis yields the same spectrum through an FFT of the settled waveform, with TRAN:-prefixed goals; see Transient Analysis.

  • A node needs both a voltage label and a current probe to support power or gain goals.
  • Which frequencies are mixed, and how many harmonics per fundamental are considered, depends on the circuit and must be specified by the user — the tool cannot infer them.