LC Lowpass Filter

This example demonstrates designing a simple LC lowpass filter, simulating its S-parameters using Qucsator, and plotting the results with GLMakie.

Circuit Design

A Pi-type lowpass filter uses shunt capacitors to ground and a series inductor. This creates a 3rd-order filter with a well-defined cutoff frequency.

using CircuitSim
using GLMakie

# Create circuit
circ = Circuit()

# Ports
port1 = ACPowerSource("P1", port_num=1, impedance=50.0)
port2 = ACPowerSource("P2", port_num=2, impedance=50.0)
add_component!(circ, port1)
add_component!(circ, port2)

# Components
L1 = Inductor("L1", inductance=80e-9)    # 80 nH
C1 = Capacitor("C1", capacitance=32e-12)  # 32 pF
C2 = Capacitor("C2", capacitance=32e-12)  # 32 pF
add_component!(circ, L1)
add_component!(circ, C1)
add_component!(circ, C2)

# Ground
gnd = Ground("GND")
add_component!(circ, gnd);
Ground("GND", 0)

Connecting Components

# Shunt capacitor C1 from Port1 to ground
@connect circ port1.nplus C1.n1
@connect circ C1.n2 gnd

# Series inductor from Port1 to Port2
@connect circ port1.nplus L1.n1
@connect circ L1.n2 port2.nplus

# Shunt capacitor C2 from Port2 to ground
@connect circ L1.n2 C2.n1
@connect circ C2.n2 gnd

# Port ground connections
@connect circ port1.nminus gnd
@connect circ port2.nminus gnd

S-Parameter Simulation

Simulate S-parameters from 1 MHz to 1 GHz.

# Define S-parameter analysis
sparam = SParameterAnalysis(start=1e6, stop=1e9, points=201,
    sweep_type="linear",
    z0=50.0
)

# Run simulation - returns typed SParameterResult directly
sp_result = simulate_qucsator(circ, sparam)

println("S-parameter simulation completed:")
println("  Number of ports: ", sp_result.num_ports)
println("  Frequency points: ", length(sp_result.frequencies_Hz))
println("  Reference impedance: ", sp_result.z0_Ohm, " Ω")
S-parameter simulation completed:
  Number of ports: 2
  Frequency points: 201
  Reference impedance: 50.0 Ω

Plotting Results

Extract S-parameters from the typed result structure.

# Get frequency vector in MHz
freq_mhz = sp_result.frequencies_Hz ./ 1e6

# Extract S-parameters from typed result
s11_complex = sp_result.s_matrix[(1,1)]
s21_complex = sp_result.s_matrix[(2,1)]

# Convert to dB
s11_db = 20 .* log10.(abs.(s11_complex))
s21_db = 20 .* log10.(abs.(s21_complex))

S-Parameter Magnitude

fig = Figure(size=(900, 600), fontsize=14)

ax1 = Axis(fig[1, 1],
    xlabel = "Frequency [MHz]",
    ylabel = "Magnitude [dB]",
    title = "LC Lowpass Filter (L=80nH, C=32pF)",
)

lines!(ax1, freq_mhz, s21_db, label="S₂₁ (Insertion Loss)", linewidth=2)
lines!(ax1, freq_mhz, s11_db, label="S₁₁ (Return Loss)", linewidth=2)

ylims!(ax1, -50, 5)
xlims!(ax1, 0, 1000)
axislegend(ax1, position=:lb)

fig
Example block output

AC Analysis

For AC voltage analysis, we use an AC voltage source at the input and a resistive load at the output. We add voltage probes to measure at both nodes.

# Create a new circuit for AC analysis (can't mix ACPowerSource with ACVoltageSource)
circ_ac = Circuit()

# AC voltage source at input
v_source = ACVoltageSource("Vin", ac_magnitude=1.0)  # 1V AC source
add_component!(circ_ac, v_source)

# Output load resistor
r_load = Resistor("Rload", resistance=50.0)  # 50Ω load
add_component!(circ_ac, r_load)

# Same filter components
L1_ac = Inductor("L1", inductance=80e-9)
C1_ac = Capacitor("C1", capacitance=32e-12)
C2_ac = Capacitor("C2", capacitance=32e-12)
add_component!(circ_ac, L1_ac)
add_component!(circ_ac, C1_ac)
add_component!(circ_ac, C2_ac)

# Ground
gnd_ac = Ground("GND")
add_component!(circ_ac, gnd_ac)

# Voltage probes
v_in_probe = VoltageProbe("Vin_probe")
v_out_probe = VoltageProbe("Vout_probe")
add_component!(circ_ac, v_in_probe)
add_component!(circ_ac, v_out_probe)

# Connect filter: Vsource -> C1||L1 -> C2||Rload
@connect circ_ac v_source.nplus C1_ac.n1
@connect circ_ac C1_ac.n2 gnd_ac.n
@connect circ_ac v_source.nplus L1_ac.n1
@connect circ_ac L1_ac.n2 C2_ac.n1
@connect circ_ac C2_ac.n1 r_load.n1
@connect circ_ac C2_ac.n2 gnd_ac.n
@connect circ_ac r_load.n2 gnd_ac.n
@connect circ_ac v_source.nminus gnd_ac.n

# Connect probes
@connect circ_ac v_source.nplus v_in_probe.n1
@connect circ_ac gnd_ac.n v_in_probe.n2
@connect circ_ac r_load.n1 v_out_probe.n1
@connect circ_ac gnd_ac.n v_out_probe.n2

# AC analysis from 1 MHz to 1 GHz
ac_analysis = ACAnalysis(start=1e6, stop=1e9, points=201, sweep_type="linear")
ac_result = simulate_qucsator(circ_ac, ac_analysis)

println("AC analysis completed:")
println("  Frequency points: ", length(ac_result.frequencies_Hz))
AC analysis completed:
  Frequency points: 201

Voltage Response

Compare input and output voltages to see the filter's attenuation.

# Get frequency vector
freq_ac_mhz = ac_result.frequencies_Hz ./ 1e6

# Get voltages from probes (already complex vectors)
v_in = ac_result.voltages["Vin_probe"]
v_out = ac_result.voltages["Vout_probe"]

# Convert to dB (magnitude)
v_in_db = 20 .* log10.(abs.(v_in))
v_out_db = 20 .* log10.(abs.(v_out))
fig2 = Figure(size=(900, 600), fontsize=14)

ax2 = Axis(fig2[1, 1],
    xlabel = "Frequency [MHz]",
    ylabel = "Voltage Magnitude [dB]",
    title = "AC Voltage Response",
)

lines!(ax2, freq_ac_mhz, v_in_db, label="Input Voltage", linewidth=2)
lines!(ax2, freq_ac_mhz, v_out_db, label="Output Voltage", linewidth=2)

xlims!(ax2, 0, 1000)
axislegend(ax2, position=:lb)

fig2
Example block output

The AC analysis shows the same frequency response as the S-parameter analysis, but expressed as node voltages rather than scattering parameters.