├── content ├── lna │ ├── content │ ├── _fig_smith_chart.qmd │ ├── _fig_lna_block.qmd │ ├── _fig_lna_inductive_degeneration_zin.qmd │ ├── _fig_lna_naive_noise_input.qmd │ ├── _fig_lna_shunt_fb.qmd │ ├── _fig_lna_naive_noise.qmd │ ├── _fig_lna_cg.qmd │ ├── _fig_lna_inductive_degeneration.qmd │ ├── _fig_lna_naive.qmd │ └── _fig_lna_inductive_degeneration_detailed.qmd ├── pll │ ├── content │ ├── _fig_pd_xor_diagram.qmd │ ├── _fig_pll_dco_dac.qmd │ ├── _fig_pfd_diagram3.qmd │ ├── _fig_pfd_diagram1.qmd │ ├── _fig_pfd_diagram2.qmd │ ├── _fig_ref_doubler.qmd │ ├── _fig_pfd_diagram4.qmd │ ├── _fig_pll_dsm_first_order.qmd │ ├── _fig_dco_switched_varactor.qmd │ ├── _fig_pll_tdc.qmd │ ├── _fig_pll_block_diagram_s_domain.qmd │ ├── _fig_pll_block_diagram.qmd │ ├── _fig_adpll_block_diagram.qmd │ ├── _fig_cp_circuit.qmd │ ├── _fig_pll_dsm_digital_first_order.qmd │ ├── _fig_cp_circuit_w_offset.qmd │ ├── _fig_pll_frac_n_block_diagram.qmd │ ├── _fig_pfd_implementation.qmd │ ├── _fig_pll_frac_n_block_diagram_retiming.qmd │ ├── _fig_pll_dsm_ntf_plot.qmd │ ├── _fig_pll_phase_noise.qmd │ └── _fig_pfd_cp_pll.qmd ├── trx │ ├── content │ ├── _fig_trx_supersimple.qmd │ ├── _fig_rx_demodulation.qmd │ ├── _fig_trx_tdd.qmd │ ├── _fig_tx_modulation.qmd │ ├── _fig_trx_fdd.qmd │ ├── _fig_trx_superheterodyne.qmd │ ├── _fig_trx_block_diagram.qmd │ └── _fig_trx_lowif.qmd ├── intro │ ├── content │ ├── _fig_wireless_system.qmd │ ├── _fig_free_space_path_loss.qmd │ ├── _fig_rf_design_considerations.qmd │ └── _fig_rf_multidisciplinary.qmd ├── mixer │ ├── content │ ├── _fig_mixer_square.qmd │ ├── _fig_mixer_block.qmd │ ├── _fig_switch_mixer.qmd │ ├── _fig_iqgen_phase_diagram.qmd │ ├── _fig_logen_25_waveforms.qmd │ ├── _fig_cmos_mixer_single.qmd │ ├── _fig_rc_cr_phase_shift.qmd │ ├── _fig_diode_mixer.qmd │ ├── _fig_iq_divider.qmd │ ├── _fig_cmos_mixer_double.qmd │ ├── _fig_logen_dll.qmd │ ├── _fig_npath_filter.qmd │ ├── _fig_rx_mixer.qmd │ ├── _fig_logen_25.qmd │ ├── _fig_polyphase_filter.qmd │ ├── _fig_tank_bandpass.qmd │ └── _fig_gilbert_mixer.qmd ├── poweramp │ ├── content │ ├── _fig_poweramp_basic.qmd │ ├── _fig_poweramp_twostage.qmd │ └── _fig_poweramp_differential.qmd ├── fundamentals │ ├── content │ ├── _fig_noise_matched_system.qmd │ ├── _fig_noise_block.qmd │ ├── _fig_iip3_cascade.qmd │ ├── _fig_noise_cascade.qmd │ ├── _fig_harmonics.qmd │ ├── _fig_ofdm_transceiver.qmd │ ├── _fig_gaussian_pulse.qmd │ ├── _fig_1db_compression.qmd │ ├── _fig_im3_tones.qmd │ ├── _fig_raised_cosine_pulse.qmd │ └── _fig_im3_power_sweep.qmd ├── oscillator │ ├── content │ ├── _fig_oscillator_block.qmd │ ├── _fig_oscillator_model.qmd │ ├── _fig_ring_oscillator.qmd │ ├── _fig_lc_oscillator_noise.qmd │ ├── _fig_crystal_equivalent_circuit.qmd │ ├── _fig_lc_oscillator.qmd │ ├── _fig_cross_coupled_pair.qmd │ ├── _fig_neg_r_single_ended.qmd │ ├── _fig_switched_capacitor.qmd │ ├── _fig_oscillator_lc_nmos.qmd │ ├── _fig_crystal_oscillator.qmd │ ├── _fig_oscillator_lc_pmos.qmd │ ├── _fig_oscillator_lc_cmos.qmd │ └── _fig_phase_noise_spectrum.qmd └── _macros.qmd ├── requirements.txt ├── CITATION.cff ├── .gitignore ├── clean.sh ├── _quarto.yml ├── .github └── workflows │ └── quarto-publish.yml ├── index.qmd ├── README.md └── rfic.qmd /content/lna/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/pll/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/trx/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/intro/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/mixer/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/poweramp/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/fundamentals/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /content/oscillator/content: -------------------------------------------------------------------------------- 1 | ../../content -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | schemdraw[svgmath] 3 | ziamath 4 | numpy 5 | pandas 6 | matplotlib 7 | pygmid 8 | pysmithchart 9 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this material, please cite it as below." 3 | authors: 4 | - family-names: Pretl 5 | given-names: Harald 6 | orcid: https://orcid.org/0000-0003-1519-076X 7 | title: "Radio-Frequency Integrated Circuits" 8 | date-released: 2025-11-09 9 | url: https://iic-jku.github.io/radio-frequency-integrated-circuits 10 | identifiers: 11 | - type: doi 12 | value: 10.5281/zenodo.17563943 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.fdb_latexmk 3 | *.html 4 | *.quarto_ipynb 5 | _freeze 6 | figures/_fig_*_files 7 | index_files 8 | site_libs 9 | /.quarto/ 10 | __pycache__ 11 | _site 12 | *.aux 13 | *.log 14 | *.pdf 15 | *.toc 16 | *.tex 17 | *.fls 18 | **/*.quarto_ipynb 19 | /rfic_files 20 | /content/fundamentals/_*_files 21 | /content/intro/_*_files 22 | /content/lna/_*_files 23 | /content/mixer/_*_files 24 | /content/oscillator/_*_files 25 | /content/pll/_*_files 26 | /content/poweramp/_*_files 27 | /content/trx/_*_files 28 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ------------------------------------------------- 3 | # Clean all auxiliary files from the render process 4 | # ------------------------------------------------- 5 | # SPDX-FileCopyrightText: 2025 Harald Pretl 6 | # Johannes Kepler University, Institute for Integrated Circuits 7 | # SPDX-License-Identifier: Apache-2.0 8 | 9 | rm -rf _freeze 10 | rm -rf _manuscript 11 | rm -rf *.html 12 | rm -f index.fdb_latexmk 13 | rm -rf content/*_files 14 | rm -rf content/*.html 15 | rm -rf figures/*_files 16 | rm -rf figures/*.html 17 | rm -rf index_files 18 | -------------------------------------------------------------------------------- /_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | var: 5 | url-repo: https://github.com/iic-jku/radio-frequency-integrated-circuits # to call this value : {{< meta var.url-repo >}} 6 | 7 | website: 8 | title: "Radio-Frequency Integrated Circuits" 9 | navbar: 10 | background: "#447099" 11 | search: true 12 | left: 13 | - icon: house 14 | href: index.qmd 15 | text: "About" 16 | - href: rfic.qmd 17 | text: "Course Material" 18 | - icon: bi-filetype-pdf 19 | text: PDF 20 | href: rfic.pdf 21 | right: 22 | - icon: github 23 | href: "{{< meta var.url-repo >}}" 24 | target: "_blank" 25 | 26 | page-footer: 27 | right: | 28 | Website built with [Quarto](https://quarto.org/){.external target="_blank"} 29 | left: | 30 | [Source code]({{< meta var.url-repo >}}){.external target="_blank"} 31 | execute: 32 | freeze: auto 33 | 34 | format: 35 | html: 36 | theme: 37 | light: cosmo 38 | dark: solar 39 | fig-align: center 40 | lang: en-US 41 | lightbox: true 42 | -------------------------------------------------------------------------------- /.github/workflows/quarto-publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: main 5 | 6 | name: quarto-publish 7 | 8 | jobs: 9 | build-deploy: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - name: Install APT dependencies 15 | run: | 16 | sudo apt update 17 | sudo apt -y install librsvg2-bin 18 | 19 | - name: Check out repository 20 | uses: actions/checkout@v4 21 | with: 22 | submodules: recursive 23 | 24 | - name: Set up Quarto 25 | uses: quarto-dev/quarto-actions/setup@v2 26 | with: 27 | tinytex: true 28 | 29 | - name: Install Python and Dependencies 30 | uses: actions/setup-python@v4 31 | with: 32 | python-version: '3.x' 33 | cache: 'pip' 34 | - run: pip install -r requirements.txt 35 | 36 | - name: Render and Publish 37 | uses: quarto-dev/quarto-actions/publish@v2 38 | with: 39 | target: gh-pages 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /content/oscillator/_fig_oscillator_block.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-oscillator-symbol 19 | #| echo: false 20 | #| fig-cap: "Oscillator symbol." 21 | #| 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | dsp.Oscillator() 32 | dsp.Arrow().length(d.unit/2).label(r'$\omega_\mathrm{LO}(t) = \omega_0$', 'right') 33 | ``` 34 | -------------------------------------------------------------------------------- /index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | pagetitle: "Radio-Frequency Integrated Circuits" 3 | --- 4 | 5 | # Radio-Frequency Integrated Circuits 6 | 7 | [![Quarto Publish](https://github.com/iic-jku/radio-frequency-integrated-circuits/actions/workflows/quarto-publish.yml/badge.svg?branch=main)](https://github.com/iic-jku/radio-frequency-integrated-circuits/actions/workflows/quarto-publish.yml) 8 | 9 | **(c) 2025 Harald Pretl and co-authors, Department for Integrated Circuits (ICD), Johannes Kepler University, Linz (JKU)** 10 | 11 | This is the material for a graduate-level radio-frequency integrated circuit course, held at JKU under course number 336.023 ("VO Integrierte Hochfrequenz-Schaltungstechnik"). Follow this [link to access the material](https://iic-jku.github.io/radio-frequency-integrated-circuits/rfic.html). 12 | 13 | All course material is made publicly available and shared under the Apache-2.0 license. 14 | 15 | **We happily accept [pull requests](https://github.com/iic-jku/radio-frequency-integrated-circuits/pulls) to fix typos or add content! If you want to discuss something that is not clear, please [open an issue](https://github.com/iic-jku/radio-frequency-integrated-circuits/issues/new)!** 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Radio-Frequency Integrated Circuits 2 | 3 | [![Quarto Publish](https://github.com/iic-jku/radio-frequency-integrated-circuits/actions/workflows/quarto-publish.yml/badge.svg?branch=main)](https://github.com/iic-jku/radio-frequency-integrated-circuits/actions/workflows/quarto-publish.yml) 4 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17563943.svg)](https://doi.org/10.5281/zenodo.17563943) 5 | 6 | **(c) 2025 Harald Pretl and co-authors, Department for Integrated Circuits (ICD), Johannes Kepler University, Linz (JKU)** 7 | 8 | This is the material for a graduate-level radio-frequency integrated circuit course, held at JKU under course number 336.023 ("VO Integrierte Hochfrequenz-Schaltungstechnik"). Follow this [link to access the material](https://iic-jku.github.io/radio-frequency-integrated-circuits/rfic.html). 9 | 10 | All course material is made publicly available and shared under the Apache-2.0 license. 11 | 12 | **We happily accept [pull requests](https://github.com/iic-jku/radio-frequency-integrated-circuits/pulls) to fix typos or add content! If you want to discuss something that is not clear, please [open an issue](https://github.com/iic-jku/radio-frequency-integrated-circuits/issues/new)!** 13 | -------------------------------------------------------------------------------- /content/lna/_fig_smith_chart.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-smith-chart 19 | #| echo: false 20 | #| fig-cap: "Smith chart showing constant resistance and reactance circles for impedance matching in RF circuits." 21 | 22 | import matplotlib.pyplot as plt 23 | import pysmithchart 24 | 25 | # Create a subplot that will use the Smith projection and include the minor grid 26 | plt.figure(figsize=(6,6)) 27 | plt.subplot(1, 1, 1, projection="smith", axes_impedance=50, grid_minor_enable=True) 28 | 29 | plt.plot([50 + 0j], markersize=10) 30 | plt.show() 31 | ``` 32 | -------------------------------------------------------------------------------- /content/mixer/_fig_mixer_square.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-mixer-block 19 | #| echo: false 20 | #| fig-cap: "Mixer block diagram." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{in}(t)$', 'left') 31 | mix1 = dsp.Mixer() 32 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{out}(t)$', 'right') 33 | dsp.Arrow().at(mix1.S).down(d.unit/2).label(r'$s_\mathrm{LO}(t)$', 'left').reverse() 34 | ``` 35 | -------------------------------------------------------------------------------- /content/pll/_fig_pd_xor_diagram.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pd-xor-diagram 19 | #| echo: false 20 | #| fig-cap: "Input and output waveforms of an XOR-based phase-detector." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'f_ref', 'wave': '01...0...1.'}, 33 | {'name': 'f_fb', 'wave': '0.1...0...1'}, 34 | {}, 35 | {'name': 'pd_out', 'wave': '010..10..10'} 36 | ] 37 | }) 38 | ``` 39 | -------------------------------------------------------------------------------- /content/oscillator/_fig_oscillator_model.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-oscillator-model 19 | #| echo: false 20 | #| fig-cap: "A model of a VCO as a perfect integrator for the excess phase in the $s$-domain." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Arrow().right(d.unit/2).label(r'$V_\mathrm{tune}(s)$', 'left') 31 | dsp.Square().label(r'$\frac{K_\mathrm{VCO}}{s}$', loc='center') 32 | dsp.Arrow().length(d.unit/2).label(r'$\varphi_\mathrm{VCO}(s)$', 'right') 33 | ``` 34 | -------------------------------------------------------------------------------- /content/mixer/_fig_mixer_block.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-mixer-square 19 | #| echo: false 20 | #| fig-cap: "A squarer as a nonlinear mixer." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{in}(t)$', 'left') 31 | mix1 = dsp.Square().label(r'$(\cdot)^2$', loc='center') 32 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{out}(t)$', 'right') 33 | dsp.Arrow().at(mix1.S).down(d.unit/2).label(r'$s_\mathrm{LO}(t)$', 'left').reverse() 34 | ``` 35 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_dco_dac.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-dco-dac 19 | #| echo: false 20 | #| fig-cap: "Digitally controlled oscillator using a DAC." 21 | #| 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | elm.DataBusLine().right(d.unit/2).label('frequency\ncontrol\nword', 'left') 32 | dsp.Dac().label('DAC') 33 | elm.Line().right(d.unit/2) 34 | dsp.Filter(response='lp') 35 | elm.Line().right(d.unit/2) 36 | dsp.Oscillator() 37 | dsp.Arrow().length(d.unit/2).label(r'$\omega_\mathrm{vco}$', 'right') 38 | ``` 39 | -------------------------------------------------------------------------------- /content/pll/_fig_pfd_diagram3.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pfd-diagram3 19 | #| echo: false 20 | #| fig-cap: "VCO feedback and reference signal are aligned in phase and frequency." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'f_ref', 'wave': '0.1...0...1'}, 33 | {'name': 'f_fb', 'wave': '0.1...0...1'}, 34 | {}, 35 | {'name': 'up', 'wave': '0.0.......0'}, 36 | {'name': 'down', 'wave': '0.0.......0'} 37 | ] 38 | }) 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /content/pll/_fig_pfd_diagram1.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pfd-diagram1 19 | #| echo: false 20 | #| fig-cap: "Reference leading the VCO feedback signal, frequencies are already aligned." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'f_ref', 'wave': '01...0...1.'}, 33 | {'name': 'f_fb', 'wave': '0.1...0...1'}, 34 | {}, 35 | {'name': 'up', 'wave': '010......10'}, 36 | {'name': 'down', 'wave': '0..........'} 37 | ] 38 | }) 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /content/pll/_fig_pfd_diagram2.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pfd-diagram2 19 | #| echo: false 20 | #| fig-cap: "VCO feedback signal leading the reference, frequencies are already aligned." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'f_ref', 'wave': '0..1...0...'}, 33 | {'name': 'f_fb', 'wave': '01...0...1.'}, 34 | {}, 35 | {'name': 'up', 'wave': '0..........'}, 36 | {'name': 'down', 'wave': '01.0.....1.'} 37 | ] 38 | }) 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /content/trx/_fig_trx_supersimple.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-trx-supersimple 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a super simple TX and RX." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Oscillator() 31 | dsp.Arrow().right(d.unit/2) 32 | dsp.Amp() 33 | dsp.Line().right(d.unit/2) 34 | elm.Antenna() 35 | 36 | d.move(dx=d.unit*2, dy=0) 37 | 38 | elm.Antenna() 39 | dsp.Arrow().right(d.unit/2) 40 | dsp.Filter(response='bp') 41 | dsp.Arrow().right(d.unit/2) 42 | dsp.Demod() 43 | ``` 44 | -------------------------------------------------------------------------------- /content/oscillator/_fig_ring_oscillator.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-ring-oscillator 19 | #| echo: false 20 | #| fig-cap: "Three stage single-ended ring oscillator." 21 | #| 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | import schemdraw.logic as lgc 26 | 27 | sd.svgconfig.svg2 = False 28 | 29 | with sd.Drawing(canvas='svg') as d: 30 | d.config(unit=2, fontsize=14) 31 | 32 | iv1 = lgc.Not() 33 | lgc.Not() 34 | lgc.Not() 35 | elm.Line().right(d.unit/4).dot() 36 | d.push() 37 | dsp.Arrow().length(d.unit/2).label(r'$\omega_0$', 'right') 38 | d.pop() 39 | elm.Line().down(d.unit*3/4) 40 | elm.Line().left(d.unit*3.5) 41 | elm.Line().up(d.unit*3/4) 42 | elm.Line().right().to(iv1.in1) 43 | ``` 44 | -------------------------------------------------------------------------------- /content/oscillator/_fig_lc_oscillator_noise.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lc-oscillator-noise-model 19 | #| echo: false 20 | #| fig-cap: "LC parallel tank with oscillator in steady-state operation." 21 | #| 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | L1 = elm.Inductor2(loops=2).label('$s L$').down() 32 | elm.Line().right(d.unit).dot() 33 | C1 = elm.Capacitor().label('$1 / s C$').up().dot() 34 | elm.Line().left().tox(L1.start) 35 | elm.Line().at(C1.start).right(d.unit).dot(open=True) 36 | elm.Gap().label(('',r'$Z_\mathrm{tank}(s)$','')).up() 37 | elm.Line().left(d.unit).idot(open=True) 38 | ``` 39 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_noise_matched_system.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-noise-matched-system 19 | #| echo: false 20 | #| fig-cap: "A noise-matched system with source and load impedances." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | sd.svgconfig.svg2 = False 24 | with sd.Drawing(canvas='svg') as d: 25 | d.config(unit=2) 26 | d.config(fontsize=16) 27 | 28 | elm.Ground() 29 | Vn = elm.SourceSin().up().label(r'$\overline{V_\mathrm{n,s}^2}$') 30 | elm.Line().right().length(d.unit*0.5) 31 | Rs = elm.Resistor().right().label(r'$R_\mathrm{s}$') 32 | 33 | elm.Line().right().length(d.unit*0.5) 34 | elm.Dot(open=True) 35 | elm.Line().right().length(d.unit*0.5) 36 | 37 | Rl = elm.Resistor().down().label(r'$R_\mathrm{load}$') 38 | elm.Ground() 39 | ``` 40 | -------------------------------------------------------------------------------- /content/mixer/_fig_switch_mixer.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-switch-mixer 19 | #| echo: false 20 | #| fig-cap: "A switch as a time-variant mixer." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Line().right(d.unit/2).label(r'$s_\mathrm{in}(t)$', 'left') 31 | mix1 = elm.Switch(contacts=False) 32 | elm.Line().right(d.unit/4).dot() 33 | d.push() 34 | elm.Resistor().down() 35 | elm.Ground() 36 | d.pop() 37 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{out}(t)$', 'right') 38 | d.move(dx=-d.unit*1.25, dy=d.unit/4) 39 | dsp.Arrow().up(d.unit/2).label(r'$s_\mathrm{LO}(t)$', 'right').reverse() 40 | ``` 41 | -------------------------------------------------------------------------------- /content/mixer/_fig_iqgen_phase_diagram.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-ff-phase-diagram 19 | #| echo: false 20 | #| fig-cap: "Input and output waveforms of I/Q generation with a divide-by-2." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'lo2_p', 'wave': '010101010'}, 33 | {'name': 'lo2_n', 'wave': '101010101'}, 34 | {}, 35 | {'name': 'lo_ip', 'wave': '01.0.1.0.'}, 36 | {'name': 'lo_qp', 'wave': '0.1.0.1.0'}, 37 | {'name': 'lo_in', 'wave': '10.1.0.1.'}, 38 | {'name': 'lo_qn', 'wave': '1.0.1.0.1'} 39 | ] 40 | }) 41 | ``` 42 | -------------------------------------------------------------------------------- /content/mixer/_fig_logen_25_waveforms.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-logen-25-waveforms 19 | #| echo: false 20 | #| fig-cap: "Input and output waveforms of I/Q generation with a divide-by-2 and 25% duty cycle generation." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'lo2_p', 'wave': '010101010'}, 33 | {'name': 'lo2_n', 'wave': '101010101'}, 34 | {}, 35 | {'name': 'lo_ip', 'wave': '010..10..'}, 36 | {'name': 'lo_qp', 'wave': '0.10..10.'}, 37 | {'name': 'lo_in', 'wave': '0..10..10'}, 38 | {'name': 'lo_qn', 'wave': '10..10..1'} 39 | ] 40 | }) 41 | ``` 42 | -------------------------------------------------------------------------------- /rfic.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Radio-Frequency Integrated Circuits 3 | author: 4 | - name: Harald Pretl 5 | orcid: 0000-0003-1519-076X 6 | email: harald.pretl@jku.at 7 | corresponding: true 8 | roles: "Lead author" 9 | affiliation: 10 | - name: Johannes Kepler University 11 | city: Linz 12 | state: Austria 13 | url: https://www.jku.at/en/institute-for-integrated-circuits-and-quantum-computing 14 | date: last-modified 15 | license: "Apache-2.0 license" 16 | copyright: 17 | holder: "Harald Pretl and co-authors, Johannes Kepler University." 18 | year: 2025 19 | citation: 20 | doi: "10.5281/zenodo.17563943" 21 | url: https://iic-jku.github.io/radio-frequency-integrated-circuits 22 | bibliography: references.bib 23 | format: 24 | html: 25 | number-sections: true 26 | toc: true 27 | toc-expand: 2 28 | typst: 29 | toc: true 30 | toc-depth: 3 31 | number-sections: true 32 | section-numbering: 1.1.1 33 | lang: en 34 | fig-align: center 35 | bib: references.bib 36 | papersize: a4 37 | --- 38 | 39 | {{< include /content/_macros.qmd >}} 40 | {{< include /content/intro/_sec_intro.qmd >}} 41 | {{< include /content/fundamentals/_sec_fundamentals.qmd >}} 42 | {{< include /content/trx/_sec_trx.qmd >}} 43 | {{< include /content/lna/_sec_lna.qmd >}} 44 | {{< include /content/mixer/_sec_mixer.qmd >}} 45 | {{< include /content/oscillator/_sec_oscillator.qmd >}} 46 | {{< include /content/pll/_sec_pll.qmd >}} 47 | {{< include /content/poweramp/_sec_poweramp.qmd >}} 48 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_block.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-block 19 | #| echo: false 20 | #| fig-cap: "Block diagram of an LNA. Typically, the LNA input is impedance matched to 50 Ω, while the output is often not matched if the LNA is kept on chip. Often, the LNA gain is adjustable to allow for gain control in the receiver depending on the signal conditions. The LNA also might have a low-power bypass mode to reduce the power consumption of the LNA for sufficiently strong signals." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Line().right(d.unit/2).label(r'$\text{RF}_\mathrm{in}$', 'left').idot(open=True) 31 | dsp.Amp() 32 | dsp.Line().right(d.unit/2).label(r'$\text{RF}_\mathrm{out}$', 'right').dot(open=True) 33 | ``` 34 | -------------------------------------------------------------------------------- /content/pll/_fig_ref_doubler.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-ref-doubler 19 | #| echo: false 20 | #| fig-cap: "Implementation of reference frequency doubler." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | import schemdraw.logic as lgc 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | elm.Line().right(d.unit/2).dot().idot(open=True).label(r'$f_\mathrm{ref}$', 'left') 32 | d.push() 33 | elm.Line().right(d.unit*2) 34 | xor1 = lgc.Xor().anchor('in1').drop('out') 35 | elm.Line().right(d.unit/2).dot(open=True).label(r'$f_\mathrm{double} = 2 f_\mathrm{ref}$', 'right') 36 | d.pop() 37 | elm.Line().down(d.unit/2) 38 | elm.Line().right(d.unit/2) 39 | dsp.Square().label(r'$T_\mathrm{d}$') 40 | elm.Line().right(d.unit/2) 41 | elm.Wire('|-').to(xor1.in2) 42 | ``` 43 | -------------------------------------------------------------------------------- /content/pll/_fig_pfd_diagram4.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pfd-diagram4 19 | #| echo: false 20 | #| fig-cap: "VCO feedback and reference signal have different frequencies (the VCO frequency is too high). There will be some periods with a wrong output (as shown here in the first cycle), but on average the phase-frequency detector output will indicate that the VCO frequency must be decreased." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | lgc.TimingDiagram( 31 | {'signal': [ 32 | {'name': 'f_ref', 'wave': '01...0...1.'}, 33 | {'name': 'f_fb', 'wave': '0.1.0.1.0.1'}, 34 | {}, 35 | {'name': 'up', 'wave': '010........'}, 36 | {'name': 'down', 'wave': '0.....1..0.'} 37 | ] 38 | }) 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_inductive_degeneration_zin.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-inductive-degeneration-zin 19 | #| echo: false 20 | #| fig-cap: "Equivalent small-signal circuit of the input stage around $M_1$." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Dot(open=True).label(r'$V_\mathrm{x}$', loc='left') 31 | wire1 = elm.Line().right().length(d.unit) 32 | elm.CurrentLabelInline(ofst=0).at(wire1).label(r'$I_\mathrm{x}$') 33 | elm.Capacitor().down().label(r'$C_\mathrm{gs}$') 34 | elm.Line().right().length(d.unit).dot() 35 | d.push() 36 | elm.ResistorIEC().down().label(r'$Z_\mathrm{deg}$') 37 | elm.Ground() 38 | 39 | d.pop() 40 | elm.SourceI().up().label(r'$g_\mathrm{m} V_\mathrm{gs}$', loc='bottom').reverse().dot(open=True) 41 | ``` 42 | -------------------------------------------------------------------------------- /content/oscillator/_fig_crystal_equivalent_circuit.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-crystal-equivalent-circuit 19 | #| echo: false 20 | #| fig-cap: "Quartz crystal equivalent circuit." 21 | #| 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | elm.Crystal().right().idot(open=True).dot(open=True) 32 | d.move(dx = d.unit, dy=0) 33 | 34 | elm.Line().right(d.unit/2).idot(open=True).dot() 35 | Rm = elm.Resistor().right().label(r'$R_\mathrm{m}$') 36 | Lm = elm.Inductor2(loops=2).right().label(r'$L_\mathrm{m}$') 37 | Cm = elm.Capacitor().right().label(r'$C_\mathrm{m}$').dot() 38 | elm.Line().right(d.unit/2).dot(open=True) 39 | elm.Line().down(d.unit*3/4).at(Cm.end) 40 | elm.Capacitor().left().label(r'$C_0$').tox(Rm.start) 41 | elm.Line().up().to(Rm.start) 42 | ``` 43 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_noise_block.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-noise-block 19 | #| echo: false 20 | #| fig-cap: "A noise-matched system with source and load impedances and a noisy circuit block." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2) 28 | d.config(fontsize=16) 29 | 30 | elm.Ground() 31 | Vn = elm.SourceSin().up().label(r'$S_\mathrm{in}$') 32 | Rs = elm.Resistor().right().label(r'$R_\mathrm{s}$') 33 | 34 | elm.Line().right().length(d.unit*0.25) 35 | elm.Dot(open=True) 36 | elm.Line().right().length(d.unit*0.5) 37 | 38 | dsp.Box().label('Noisy Circuit') 39 | 40 | elm.Line().right().length(d.unit*0.5) 41 | elm.Dot(open=True) 42 | elm.Line().right().length(d.unit*0.5) 43 | 44 | Rl = elm.Resistor().down().label(r'$R_\mathrm{load}$') 45 | elm.Ground() 46 | ``` 47 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_dsm_first_order.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-dsm-first-order 19 | #| echo: false 20 | #| fig-cap: "A first-order continuous time delta-sigma modulator." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Arrow().right(d.unit/2).label(r'$X(s)$', 'left') 31 | sum1 = dsp.Sum() 32 | elm.Arrow().right(d.unit/2) 33 | dsp.Square().label(r'$\frac{1}{sT}$') 34 | elm.Arrow().right(d.unit/2) 35 | sum2 = dsp.Sum() 36 | elm.Line().right(d.unit/2).dot() 37 | d.push() 38 | elm.Arrow().right(d.unit/2).label(r'$Y(s)$', 'right') 39 | d.pop() 40 | elm.Line().down(d.unit) 41 | elm.Line().left().tox(sum1.S) 42 | elm.Arrow().up().to(sum1.S).label('--', 'bottom') 43 | elm.Arrow().up(d.unit/2).at(sum2.N).reverse().label(r'$Q(s)$', 'right') 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /content/mixer/_fig_cmos_mixer_single.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-cmos-single-mixer 19 | #| echo: false 20 | #| fig-cap: "A MOSFET as a switch used as a mixer, with ac-coupled LO signal." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | import schemdraw.logic as lgc 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | elm.Line().right(d.unit/2).label(r'$v_\mathrm{in}(t)$', 'left') 32 | M1 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 33 | elm.Arrow().right(d.unit).label(r'$v_\mathrm{out}(t)$', 'right') 34 | elm.Line().up(d.unit/4).dot().at(M1.gate) 35 | d.push() 36 | elm.Resistor().right().dot(open=True).label(r'$V_\mathrm{bias}$', loc='right') 37 | d.pop() 38 | elm.Capacitor().up(d.unit) 39 | lgc.Buf().up(d.unit).reverse().dot(open=True).label(r'$v_\mathrm{LO}(t)$', loc='right') 40 | ``` 41 | -------------------------------------------------------------------------------- /content/mixer/_fig_rc_cr_phase_shift.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-rc-cr-phase-shift 19 | #| echo: false 20 | #| fig-cap: "An RC/CR IQ generation network." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | 24 | sd.svgconfig.svg2 = False 25 | 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2, fontsize=14) 28 | 29 | elm.Line().right(d.unit/2).idot(open=True).label(r'$V_\mathrm{in}$', 'left').dot() 30 | 31 | d.push() 32 | elm.Line().up(d.unit) 33 | elm.Capacitor().right().label(r'$C$').dot() 34 | d.push() 35 | elm.Resistor().down().label(r'$R$') 36 | elm.Ground() 37 | d.pop() 38 | elm.Line().right(d.unit/2).label(r'$V_\mathrm{out,Q}$', 'right').dot(open=True) 39 | 40 | d.pop() 41 | 42 | elm.Line().down(d.unit) 43 | elm.Resistor().right().label(r'$R$').dot() 44 | d.push() 45 | elm.Capacitor().down().label(r'$C$') 46 | elm.Ground() 47 | d.pop() 48 | elm.Line().right(d.unit/2).label(r'$V_\mathrm{out,I}$', 'right').dot(open=True) 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /content/intro/_fig_wireless_system.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-simple-wireless-system 19 | #| echo: false 20 | #| fig-cap: "The block diagram of a simple wireless system." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | sd.svgconfig.svg2 = False 25 | with sd.Drawing(canvas='svg') as d: 26 | d.config(unit=2) 27 | d.config(fontsize=16) 28 | 29 | elm.Dot(open=True).label(r'data_in') 30 | elm.Line().right().length(d.unit*0.5) 31 | dsp.Square().label('TX') 32 | elm.Line().right().length(d.unit*0.5) 33 | dsp.Amp() 34 | elm.Line().right().length(d.unit*0.5) 35 | elm.Antenna() 36 | 37 | d.move(dx=1) 38 | elm.Arrow().right().label('EM wave').color('blue') 39 | d.move(dx=1) 40 | 41 | elm.Antenna() 42 | elm.Line().right().length(d.unit*0.5) 43 | dsp.Filter(response='bp') 44 | elm.Line().right().length(d.unit*0.5) 45 | dsp.Square().label('RX') 46 | elm.Line().right().length(d.unit*0.5) 47 | elm.Dot(open=True).label(r'data_out') 48 | ``` 49 | -------------------------------------------------------------------------------- /content/trx/_fig_rx_demodulation.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-rx-demodulation 19 | #| echo: false 20 | #| fig-cap: "RX demodulator." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Line().right(d.unit/4).label(r'$s_\mathrm{RF}(t)$', 'left') 31 | dsp.Line().right(d.unit/4).dot() 32 | 33 | d.push() 34 | dsp.Line().up(d.unit) 35 | dsp.Arrow().right(d.unit/2) 36 | mix1 = dsp.Mixer() 37 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{I}(t)$', 'right') 38 | dsp.Arrow().at(mix1.S).down(d.unit/4).label(r'$\cos(\omega_\mathrm{c} t)$', 'left').reverse() 39 | d.pop() 40 | 41 | dsp.Line().down(d.unit) 42 | dsp.Arrow().right(d.unit/2) 43 | mix2 = dsp.Mixer() 44 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{Q}(t)$', 'right') 45 | dsp.Arrow().at(mix2.N).up(d.unit/4).label(r'$-\sin(\omega_\mathrm{c} t)$', 'right').reverse() 46 | ``` 47 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_iip3_cascade.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-iip3-cascade 19 | #| echo: false 20 | #| fig-cap: "Block cascade for IIP3 calculation showing multiple stages with gains and individual IIP3 values." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2) 28 | d.config(fontsize=14) 29 | 30 | elm.Dot(open=True).label(r'$x(t)$') 31 | elm.Line().right().length(d.unit*0.5) 32 | dsp.Box().label(r'$G_1$, $\text{IIP3}_1$') 33 | 34 | elm.Line().right().length(d.unit*0.5) 35 | elm.Dot(open=True) 36 | elm.Line().right().length(d.unit*0.5) 37 | 38 | dsp.Box().label(r'$G_2$, $\text{IIP3}_2$') 39 | 40 | elm.Line().right().length(d.unit*0.5) 41 | elm.Dot(open=True) 42 | elm.Line().right().length(d.unit*0.5) 43 | 44 | dsp.Box().label(r'$G_3$, $\text{IIP3}_3$') 45 | 46 | elm.Line().right().length(d.unit*0.5) 47 | elm.Dot(open=True).label(r'$y(t)$') 48 | ``` -------------------------------------------------------------------------------- /content/pll/_fig_dco_switched_varactor.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-dco-switched-varactor 19 | #| echo: false 20 | #| fig-cap: "A switched differential varactor used for fine frequency control in a DCO." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | M1 = elm.AnalogNFet(offset_gate=False, arrow=False).theta(180) 31 | elm.Line().at(M1.gate).left(d.unit/4).dot(open=True) 32 | elm.Line().at(M1.drain).right(d.unit/2).dot() 33 | elm.Line().right(d.unit/2) 34 | M2 = elm.AnalogNFet(offset_gate=False, arrow=False).anchor('drain').reverse().theta(180) 35 | elm.Line().at(M2.gate).right(d.unit/4).dot(open=True) 36 | elm.Line().at(M1.source).right(d.unit/2).dot() 37 | pos = d.here 38 | elm.Line().right(d.unit/2) 39 | 40 | elm.Line().at(pos).down(d.unit) 41 | 42 | lgc.Not().down().reverse() 43 | elm.Dot(open=True).label('$S$', loc='bottom') 44 | ``` 45 | -------------------------------------------------------------------------------- /content/trx/_fig_trx_tdd.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-trx-tdd 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a TDD RF front-end." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Antenna() 31 | dsp.Line().right(d.unit/2) 32 | dsp.Filter(response='bp').label(r'$f_\mathrm{RX} = f_\mathrm{TX}$', loc='top', ofst=0.15) 33 | dsp.Line().right(d.unit/2) 34 | 35 | sw = elm.SwitchSpdt2(contacts=False) 36 | dsp.Line().up(d.unit).at(sw.b).length(d.unit/2) 37 | dsp.Arrow().right(d.unit/2).label(r'RX') 38 | dsp.Amp() 39 | dsp.Arrow().right(d.unit/2).linestyle('--').label(r'$s_\mathrm{RX}$', 'right') 40 | 41 | dsp.Line().down(d.unit).at(sw.c).length(d.unit/2).reverse() 42 | dsp.Line().right(d.unit/2).label(r'TX') 43 | dsp.Amp().reverse() 44 | dsp.Arrow().right(d.unit/2).reverse().linestyle('--').label(r'$s_\mathrm{TX}$', 'right') 45 | 46 | ``` 47 | -------------------------------------------------------------------------------- /content/trx/_fig_tx_modulation.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-tx-modulation 19 | #| echo: false 20 | #| fig-cap: "TX modulator." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | d.push() 31 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{I}(t)$', 'left') 32 | mix1 = dsp.Mixer() 33 | dsp.Line().right(d.unit/2) 34 | dsp.Line().down(d.unit*3/4) 35 | sum1 = dsp.Sum() 36 | dsp.Arrow().at(sum1.N).right(d.unit/2).label(r'$s_\mathrm{RF}(t)$', 'right') 37 | dsp.Arrow().at(mix1.S).down(d.unit/4).label(r'$\cos(\omega_\mathrm{c} t)$', 'left').reverse() 38 | d.pop() 39 | 40 | d.move(dx=0, dy=-2*d.unit) 41 | 42 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{Q}(t)$', 'left') 43 | mix2 = dsp.Mixer() 44 | dsp.Line().right(d.unit/2) 45 | dsp.Line().up(d.unit*3/4) 46 | dsp.Arrow().at(mix2.N).up(d.unit/4).label(r'$-\sin(\omega_\mathrm{c} t)$', 'right').reverse() 47 | ``` 48 | -------------------------------------------------------------------------------- /content/poweramp/_fig_poweramp_basic.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-poweramp-basic 19 | #| echo: false 20 | #| fig-cap: "A basic single stage single-ended common-source power amplifier with input and output matching." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Line().right(d.unit/2).idot(open=True).label(r'$s_\mathrm{in}$', loc='left') 31 | dsp.Square().label('Input\nmatch', loc='top', ofst=0.2) 32 | elm.Line().right(d.unit/2) 33 | M1 = elm.AnalogNFet(offset_gate=False).anchor('gate').drop('source').label('$M_1$', loc='right').reverse() 34 | elm.Ground() 35 | elm.Line().up(d.unit/4).at(M1.drain).dot() 36 | d.push() 37 | elm.Line().right(d.unit/2) 38 | dsp.Square().label('Output\nmatch', loc='top', ofst=0.2) 39 | elm.Line().right(d.unit/2).dot(open=True).label(r'$s_\mathrm{out}$', loc='right') 40 | d.pop() 41 | elm.Inductor2().up().label(r'RF choke') 42 | elm.Vdd().label(r'$V_\mathrm{DD}$', loc='top') 43 | ``` 44 | -------------------------------------------------------------------------------- /content/oscillator/_fig_lc_oscillator.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lc-oscillator 19 | #| echo: false 20 | #| fig-cap: "LC parallel tank with connected negative resistance forming an LC oscillator." 21 | #| 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | L1 = elm.Inductor2(loops=2).label('$L$').down() 32 | elm.Line().right(d.unit).dot() 33 | C1 = elm.Capacitor().label('$C$').up().dot() 34 | d.push() 35 | elm.Line().left().tox(L1.start) 36 | d.pop() 37 | elm.Line().right(d.unit).dot() 38 | elm.Resistor().label(r'$R_\mathrm{p}$').down().dot() 39 | d.push() 40 | elm.Line().left().tox(C1.start) 41 | d.pop() 42 | elm.Line().right(d.unit).dot(open=True) 43 | d.push() 44 | elm.Gap().label(('–',r'$V_\mathrm{osc}$','+')).up() 45 | d.pop() 46 | elm.Line().right(d.unit) 47 | elm.Resistor().label(r'$-R_\mathrm{amp}$', loc='bottom').up().color('blue') 48 | elm.Line().left(d.unit).dot(open=True) 49 | elm.Line().left(d.unit) 50 | ``` 51 | -------------------------------------------------------------------------------- /content/trx/_fig_trx_fdd.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-trx-fdd 19 | #| echo: false 20 | #| fig-cap: "Block diagram of an FDD RF front-end." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Antenna() 31 | dsp.Line().right(d.unit/2) 32 | d.push() 33 | d.move(dx=0, dy=d.unit/4) 34 | dpx1 = dsp.Filter(response='bp').label(r'$f_\mathrm{RX}$', loc='right', ofst=0.15) 35 | dsp.Line().up(d.unit).at(dpx1.N).length(d.unit/2) 36 | dsp.Arrow().right(d.unit/2).label(r'RX') 37 | dsp.Amp() 38 | dsp.Arrow().right(d.unit/2).linestyle('--').label(r'$s_\mathrm{RX}$', 'right') 39 | d.pop() 40 | 41 | d.move(dx=0, dy=-d.unit/4) 42 | dpx2 = dsp.Filter(response='bp').label(r'$f_\mathrm{TX}$', loc='right', ofst=0.15) 43 | dsp.Arrow().down(d.unit).at(dpx2.S).length(d.unit/2).reverse() 44 | dsp.Line().right(d.unit/2).label(r'TX') 45 | dsp.Amp().reverse() 46 | dsp.Arrow().right(d.unit/2).reverse().linestyle('--').label(r'$s_\mathrm{TX}$', 'right') 47 | ``` 48 | -------------------------------------------------------------------------------- /content/mixer/_fig_diode_mixer.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-diode-mixer 19 | #| echo: false 20 | #| fig-cap: "A diode mixer." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{in}(t)$', 'left') 31 | dsp.Filter(response='bp') 32 | dsp.Line().right(d.unit/4) 33 | dsp.Line().down(d.unit/2).dot() 34 | d.push() 35 | dsp.Line().down(d.unit/2) 36 | dsp.Line().left(d.unit/4) 37 | dsp.Filter(response='bp') 38 | dsp.Arrow().left(d.unit/2).label(r'$s_\mathrm{LO}(t)$', 'left').reverse() 39 | d.pop() 40 | 41 | dsp.Line().right(d.unit/2).dot() 42 | 43 | d.push() 44 | dsp.Line().right(d.unit/2) 45 | dsp.Filter(response='bp') 46 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{out}(t)$', 'right') 47 | d.pop() 48 | 49 | d.push() 50 | elm.Diode().down() 51 | elm.Ground() 52 | d.pop() 53 | dsp.Line().up(d.unit/4) 54 | elm.Inductor2().up().label('RF choke', loc='bottom') 55 | elm.Dot(open=True).label(r'$V_\mathrm{bias}$', 'top') 56 | ``` 57 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_tdc.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-tdc 19 | #| echo: false 20 | #| fig-cap: "Basic TDC implementation as a delay line with parallel capture flip-flops. The TDC delay resolution is limited two inverter delays." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | sd.svgconfig.svg2 = False 25 | 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2, fontsize=14) 28 | 29 | elm.Line().right(d.unit/2).idot(open=True).label(r'$f_\mathrm{ref}$', loc='left') 30 | 31 | N_STAGES = 4 32 | DFF = {} 33 | pos = {} 34 | for i in range(4): 35 | elm.Dot() 36 | d.push() 37 | elm.Line().down(d.unit) 38 | elm.Line().right(d.unit/4) 39 | DFF[i] = elm.DFlipFlop().anchor('D') 40 | elm.Line().left(d.unit/4).at(DFF[i].CLK) 41 | elm.Line().down(d.unit).dot() 42 | pos[i] = d.here 43 | d.pop() 44 | lgc.Buf().right(d.unit*2) 45 | 46 | elm.DotDotDot() 47 | 48 | elm.Line().at(pos[N_STAGES-1]).to(pos[0]) 49 | elm.Line().at(pos[N_STAGES-1]).right(d.unit*2) 50 | elm.DotDotDot() 51 | elm.Line().at(pos[0]).left(d.unit/2).dot(open=True).label(r'$f_\mathrm{fb}$', loc='left') 52 | ``` 53 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_block_diagram_s_domain.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-block-diagram-s-domain 19 | #| echo: false 20 | #| fig-cap: "Laplace domain model of a PLL. Note that input and output signals are (excess) phase in rad." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Arrow().right(d.unit/2).label(r'$\varphi_\mathrm{ref}(s)$', 'left') 31 | pfd = dsp.Square().label(r'$K_\mathrm{PD}$', loc='center') 32 | dsp.Arrow().length(d.unit).label(r'$V_\mathrm{PD}(s)$', 'top') 33 | dsp.Square().label(r'$H_\mathrm{LP}$', loc='center') 34 | dsp.Arrow().length(d.unit).label(r'$V_\mathrm{tune}(s)$', 'top') 35 | dsp.Square().label(r'$\frac{K_\mathrm{VCO}}{s}$', loc='center') 36 | dsp.Line().right(d.unit/2).dot() 37 | d.push() 38 | dsp.Arrow().right(d.unit/2).label(r'$\varphi_\mathrm{out}(s)$', 'right') 39 | d.pop() 40 | dsp.Line().down(d.unit) 41 | dsp.Arrow().left(d.unit*1.5) 42 | dsp.Square().label(':N', loc='center') 43 | dsp.Line().tox(pfd.S).label(r'$\varphi_\mathrm{out}(s)/N$', 'top') 44 | dsp.Arrow().up().to(pfd.S) 45 | ``` 46 | -------------------------------------------------------------------------------- /content/oscillator/_fig_cross_coupled_pair.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-cross-coupled-pair 19 | #| echo: false 20 | #| fig-cap: "A cross-coupled differential pair." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$g_\mathrm{m1}$', loc='right').reverse().drop('source').theta(0) 31 | elm.Line().down(d.unit/4) 32 | elm.Line().left(d.unit*1.5).dot() 33 | d.push() 34 | elm.SourceI().down() 35 | elm.Ground() 36 | d.pop() 37 | elm.Line().left(d.unit*1.5) 38 | elm.Line().up(d.unit/4) 39 | M2 = elm.AnalogNFet(offset_gate=False).label(r'$g_\mathrm{m2}$', loc='left').anchor('source').theta(0) 40 | 41 | elm.Line().up(d.unit/4).at(M1.drain).dot() 42 | d.push() 43 | l1 = elm.Line().up(d.unit/2).dot(open=True) 44 | d.pop() 45 | elm.Wire('z').to(M2.gate) 46 | 47 | elm.Line().up(d.unit/4).at(M2.drain).dot() 48 | d.push() 49 | l2 = elm.Line().up(d.unit/2).dot(open=True) 50 | d.pop() 51 | elm.Wire('z').to(M1.gate) 52 | 53 | elm.Gap().label(('',r'$Z_\mathrm{amp}$','')).at(l1.end).to(l2.end) 54 | ``` 55 | -------------------------------------------------------------------------------- /content/oscillator/_fig_neg_r_single_ended.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-neg-r-single-ended 19 | #| echo: false 20 | #| fig-cap: "Circuit diagram of a single-ended negative resistance implementation." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$g_\mathrm{m}$', loc='right').reverse().anchor('source').drop('gate').theta(0) 31 | l1 = elm.Line().up(d.unit/4).dot().at(M1.drain) 32 | elm.Line().left(d.unit/2).dot(open=True).label(r'$Z_\mathrm{in}$', loc='left', ofst=(+d.unit/4,-d.unit*0)) 33 | 34 | elm.Line().left(d.unit/2).dot().at(M1.gate).label('1', color='blue', loc='top') 35 | d.push() 36 | elm.Line().up(d.unit/4).dot(open=True) 37 | d.pop() 38 | C1 = elm.Capacitor().label('$C_1$').down() 39 | elm.Line().right().tox(M1.source).dot().label('2', color='blue', loc='bottom') 40 | d.push() 41 | elm.Line().up().to(M1.source) 42 | d.pop() 43 | elm.Line().right(d.unit) 44 | C2 = elm.Capacitor().label('$C_2$').up().toy(l1.end) 45 | elm.Line().left().tox(l1.start).label('3', color='blue', loc='top') 46 | ``` 47 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_block_diagram.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-block-diagram 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a PLL. A reference phase signal is compared to the phase of a VCO output signal in a phase detector. The phase error is low-pass filtered and used to tune the VCO frequency." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Oscillator() 31 | dsp.Arrow().right(d.unit).label(r'$f_\mathrm{ref}$', 'top') 32 | pfd = dsp.Square().label(r'PD', loc='center') 33 | dsp.Arrow().length(d.unit).label('phase\nerror', 'top') 34 | dsp.Filter(response='lp') 35 | dsp.Arrow().length(d.unit).label('tuning\nvoltage', 'top') 36 | dsp.Oscillator().label('VCO', loc='top', ofst=(0, 0.2)) 37 | dsp.Line().right(d.unit/2).dot() 38 | d.push() 39 | dsp.Arrow().right(d.unit/2).label(r'$f_\mathrm{out}$', 'right') 40 | d.pop() 41 | dsp.Line().down(d.unit) 42 | dsp.Arrow().left(d.unit*1.5) 43 | dsp.Square().label(':N', loc='center') 44 | dsp.Line().tox(pfd.S).label(r'$f_\mathrm{fb} = f_\mathrm{out} / N$', 'top') 45 | dsp.Arrow().up().to(pfd.S) 46 | ``` 47 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_noise_cascade.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-noise-cascade 19 | #| echo: false 20 | #| fig-cap: "Block cascade for noise factor calculation showing multiple stages with gains and individual noise factors." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2) 28 | d.config(fontsize=14) 29 | 30 | elm.Ground() 31 | Vn = elm.SourceSin().up().label(r'$S_\mathrm{in}$') 32 | Rs = elm.Resistor().right().label(r'$R_\mathrm{s}$') 33 | 34 | elm.Line().right().length(d.unit*0.25) 35 | elm.Dot(open=True) 36 | elm.Line().right().length(d.unit*0.5) 37 | 38 | dsp.Box().label(r'$G_1$, $F_1$') 39 | 40 | elm.Line().right().length(d.unit*0.5) 41 | elm.Dot(open=True) 42 | elm.Line().right().length(d.unit*0.5) 43 | 44 | dsp.Box().label(r'$G_2$, $F_2$') 45 | 46 | elm.Line().right().length(d.unit*0.5) 47 | elm.Dot(open=True) 48 | elm.Line().right().length(d.unit*0.5) 49 | 50 | dsp.Box().label(r'$G_3$, $F_3$') 51 | 52 | elm.Line().right().length(d.unit*0.5) 53 | elm.Dot(open=True) 54 | elm.Line().right().length(d.unit*0.5) 55 | Rl = elm.Resistor().down().label(r'$R_\mathrm{load}$') 56 | elm.Ground() 57 | ``` 58 | -------------------------------------------------------------------------------- /content/mixer/_fig_iq_divider.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-iq-divider 19 | #| echo: false 20 | #| fig-cap: "I/Q generation with a divide-by-2." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | 24 | sd.svgconfig.svg2 = False 25 | 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2, fontsize=14) 28 | 29 | d.push() 30 | elm.Line().right(d.unit/2).idot(open=True).label(r'$v_\mathrm{lo2,p}$', 'left') 31 | ff1 = elm.intcircuits.DFlipFlop().anchor('CLK') 32 | d.pop() 33 | 34 | d.move(dx=0, dy=-d.unit*2) 35 | elm.Line().right(d.unit/2).idot(open=True).label(r'$v_\mathrm{lo2,n}$', 'left') 36 | ff2 = elm.intcircuits.DFlipFlop().anchor('CLK') 37 | 38 | elm.Line().at(ff1.Q).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,ip}$', 'right') 39 | elm.Line().at(ff1.Qbar).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,in}$', 'right') 40 | elm.Line().at(ff2.Q).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,qp}$', 'right') 41 | elm.Line().at(ff2.Qbar).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,qn}$', 'right') 42 | 43 | elm.Line().at(ff1.Qbar).idot().up(d.unit*1.5) 44 | elm.Line().left(d.unit*1.5) 45 | elm.Line().down().to(ff1.D) 46 | 47 | elm.Line().at(ff2.Qbar).idot().up(d.unit*1.5) 48 | elm.Line().left(d.unit*1.5) 49 | elm.Line().down().to(ff2.D) 50 | ``` 51 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_naive_noise_input.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-naive-noise-input 19 | #| echo: false 20 | #| fig-cap: "Eqivalent circuit to calculate the output noise from the input." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Ground() 31 | elm.SourceSin().up().label(r'$4 k T R_\mathrm{s}$').color('blue') 32 | elm.Resistor().right().label(r'$R_\mathrm{s}$') 33 | elm.Line().right().length(d.unit/4).dot(open=True) 34 | elm.Line().right().length(d.unit/2).dot() 35 | d.push() 36 | Rp = elm.Resistor().down().label(r'$R_\mathrm{p}$') 37 | gnd1 = elm.Ground() 38 | d.pop() 39 | elm.Line().right().length(d.unit/2) 40 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('gate').drop('source') 41 | elm.Line().down().toy(Rp.end) 42 | elm.Ground() 43 | 44 | # load 45 | elm.Line().at(M1.drain).right().length(d.unit/2).label(r'$\overline{V_\mathrm{n,out,s}^2}$', loc='right').dot(open=True).idot() 46 | Rd = elm.Resistor().at(M1.drain).up().label(r'$R_\mathrm{D}$') 47 | elm.Line().at((Rd.end[0]-d.unit/4, Rd.end[1])).to((Rd.end[0]+d.unit/4, Rd.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 48 | ``` 49 | -------------------------------------------------------------------------------- /content/pll/_fig_adpll_block_diagram.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-adpll-block-diagram 19 | #| echo: false 20 | #| fig-cap: "Block diagram of an all-digital PLL. A reference phase signal is compared to the phase of a VCO output signal in a time-to-digital converter. The phase error word is digitally low-pass filtered and used to tune the DCO frequency via a frequency control word." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Oscillator() 31 | dsp.Arrow().right(d.unit).label(r'$f_\mathrm{ref}$', 'top') 32 | pfd = dsp.Square().label(r'TDC', loc='center') 33 | elm.DataBusLine().length(d.unit).label('phase error\nword', 'top') 34 | dsp.Filter(response='lp') 35 | elm.DataBusLine().length(d.unit).label('tuning\nword', 'top') 36 | dsp.Oscillator().label('DCO', loc='top', ofst=(0, 0.2)) 37 | dsp.Line().right(d.unit/2).dot() 38 | d.push() 39 | dsp.Arrow().right(d.unit/2).label(r'$f_\mathrm{out}$', 'right') 40 | d.pop() 41 | dsp.Line().down(d.unit) 42 | dsp.Arrow().left(d.unit*1.5) 43 | dsp.Square().label(':N', loc='center') 44 | dsp.Line().tox(pfd.S).label(r'$f_\mathrm{fb} = f_\mathrm{out} / N$', 'top') 45 | dsp.Arrow().up().to(pfd.S) 46 | ``` 47 | -------------------------------------------------------------------------------- /content/pll/_fig_cp_circuit.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-cp-implementation 19 | #| echo: false 20 | #| fig-cap: "Charge pump consisting of two matched current sources and switches. The resistor in series with the capacitor introduces a zero." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Vdd() 31 | elm.SourceI().down().label(r'$I_\mathrm{CP}$') 32 | S1 = elm.Switch(contacts=False).down().dot().flip().dot() 33 | S2 = elm.Switch(contacts=False).down().flip().reverse() 34 | elm.SourceI().down().label(r'$I_\mathrm{CP}$') 35 | elm.Ground() 36 | 37 | elm.Line().right(d.unit).at(S1.end).dot() 38 | d.push() 39 | elm.Line().right(d.unit/2).dot(open=True).label(r'$V_\mathrm{LF}$', loc='right') 40 | d.pop() 41 | elm.Resistor().down().label(r'$R_\mathrm{int}$').color('blue') 42 | elm.Capacitor().down().label(r'$C_\mathrm{int}$') 43 | elm.Ground() 44 | 45 | d.here = S1.end 46 | d.move(dx=-d.unit/4, dy=d.unit/2) 47 | elm.Arrow().left(d.unit/2).reverse().dot(open=True).label('UP', 'left') 48 | 49 | d.here = S2.start 50 | d.move(dx=-d.unit/4, dy=-d.unit/2) 51 | elm.Arrow().left(d.unit/2).reverse().dot(open=True).label('DOWN', 'left') 52 | ``` 53 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_shunt_fb.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-shunt-fb 19 | #| echo: false 20 | #| fig-cap: "A shunt-feedback LNA." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Ground() 31 | Sin = elm.SourceSin().up().label(r'$v_\mathrm{in}$') 32 | elm.Resistor().right().label(r'$R_\mathrm{s}=50\,\Omega$') 33 | elm.Line().right().length(d.unit/4).dot(open=True) 34 | elm.Line().right().length(d.unit/2).dot() 35 | 36 | d.push() 37 | elm.Line().up().length(d.unit*3/4) 38 | Zf = elm.ResistorIEC().right().label(r'$Z_\mathrm{F}$') 39 | d.pop() 40 | elm.Line().right().length(d.unit*3/4) 41 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('gate').drop('source') 42 | elm.Line().down().toy(Sin.start) 43 | elm.Ground() 44 | 45 | # load 46 | elm.Line().up().at(M1.drain).toy(Zf.end).dot() 47 | d.push() 48 | elm.Line().left().to(Zf.end) 49 | d.pop() 50 | d.push() 51 | elm.Line().right().length(d.unit/2).label(r'$v_\mathrm{out}$', loc='right').dot(open=True) 52 | d.pop() 53 | Zl = elm.ResistorIEC().up().label(r'$Z_\mathrm{L}$') 54 | 55 | elm.Line().at((Zl.end[0]-d.unit/4, Zl.end[1])).to((Zl.end[0]+d.unit/4, Zl.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 56 | ``` 57 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_dsm_digital_first_order.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-dsm-digital-first-order 19 | #| echo: false 20 | #| fig-cap: "A first-order digital delta-sigma modulator." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Line().right(d.unit/4).label(r'$x[n]$', 'left') 31 | elm.DataBusLine().right(d.unit/2).label(r'$N$', 'top') 32 | sum1 = dsp.Sum() 33 | elm.DataBusLine().right(d.unit).label(r'$N+1$', 'top') 34 | sum2 = dsp.Sum() 35 | elm.DataBusLine().right(d.unit).label(r'$N+2$', 'top') 36 | dsp.Square().label(r'$z^{-1}$') 37 | elm.DataBusLine().right(d.unit).label(r'$N+2$', 'top').dot() 38 | d.push() 39 | elm.DataBusLine().right(d.unit).label(r'$N+2$', 'top') 40 | sum3 = dsp.Sum().color('red') 41 | elm.DataBusLine().right(d.unit).label('MSB', 'top').dot() 42 | pos = d.here 43 | elm.Arrow().right(d.unit/2).label(r'$y[n]$', 'right') 44 | d.pop() 45 | elm.Line().down(d.unit) 46 | elm.DataBusLine().left().tox(sum2.S).label(r'$N+1$', 'top') 47 | elm.Arrow().up().to(sum2.S) 48 | elm.Line().down(d.unit*1.5).at(pos) 49 | elm.Line().left().tox(sum1.S) 50 | elm.Arrow().up().to(sum1.S).label('--', 'bottom') 51 | elm.Arrow().up(d.unit/2).at(sum3.N).reverse().label(r'$q[n]$', 'right').color('red') 52 | ``` 53 | -------------------------------------------------------------------------------- /content/pll/_fig_cp_circuit_w_offset.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-cp-offset 19 | #| echo: false 20 | #| fig-cap: "Charge pump with offset current for use in a fractional-N Type-II PLL." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Vdd() 31 | elm.SourceI().down().label(r'$I_\mathrm{CP}$') 32 | S1 = elm.Switch(contacts=False).down().dot().flip().dot() 33 | S2 = elm.Switch(contacts=False).down().flip().reverse() 34 | icp = elm.SourceI().down().label(r'$I_\mathrm{CP}$') 35 | elm.Ground() 36 | 37 | elm.Line().right(d.unit).at(S1.end).dot() 38 | d.push() 39 | elm.SourceI().down().toy(icp.end).label(r'$I_\mathrm{offset}$') 40 | elm.Ground() 41 | d.pop() 42 | elm.Line().right(d.unit) 43 | d.push() 44 | elm.Line().right(d.unit/2).dot(open=True).label(r'$V_\mathrm{LF}$', loc='right') 45 | d.pop() 46 | elm.Resistor().down().label(r'$R_\mathrm{int}$') 47 | elm.Capacitor().down().label(r'$C_\mathrm{int}$') 48 | elm.Ground() 49 | 50 | d.here = S1.end 51 | d.move(dx=-d.unit/4, dy=d.unit/2) 52 | elm.Arrow().left(d.unit/2).reverse().dot(open=True).label('UP', 'left') 53 | 54 | d.here = S2.start 55 | d.move(dx=-d.unit/4, dy=-d.unit/2) 56 | elm.Arrow().left(d.unit/2).reverse().dot(open=True).label('DOWN', 'left') 57 | ``` 58 | -------------------------------------------------------------------------------- /content/oscillator/_fig_switched_capacitor.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-switched-capacitor 19 | #| echo: false 20 | #| fig-cap: "An switched capacitor for use in an oscillator switched capacitor tuning bank. The bias resistors tie the drain/source nodes to ground during turn on of $M_1$ (for low on resistance), while they tie the drain/source nodes to VDD during turn off of $M_1$ to prevent accidential turn on of the drain/source to bulk diodes of $M_1$." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Capacitor().label('$C$', loc='top').right().idot(open=True).dot() 31 | l1 = elm.Line().right(d.unit/2) 32 | M1 = elm.AnalogNFet(offset_gate=False, arrow=False).label('$M_1$', loc='right').reverse().anchor('source').theta(270).reverse().drop('drain') 33 | l2 = elm.Line().right(d.unit/2) 34 | elm.Capacitor().label('$C$', loc='top').right().idot().dot(open=True) 35 | 36 | R1 = elm.Resistor().at(l1.start).down(d.unit*1.5).label(r'$R_\mathrm{bias}$', loc='top') 37 | R2 = elm.Resistor().at(l2.end).down(d.unit*1.5).label(r'$R_\mathrm{bias}$', loc='bottom') 38 | elm.Line().at(R1.end).right().tox(M1.gate).dot() 39 | d.push() 40 | lgc.Not().up().to(M1.gate) 41 | d.pop() 42 | d.push() 43 | elm.Line().to(R2.end) 44 | d.pop() 45 | lgc.Not().down().reverse() 46 | elm.Dot(open=True).label('$S$', loc='bottom') 47 | ``` 48 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_harmonics.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | negative WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-harmonics 19 | #| echo: false 20 | #| fig-cap: "Single-tone test showing created harmonics at 2ω and 3ω." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Create figure with single subplot 26 | fig, ax = plt.subplots(1, 1) 27 | 28 | # Define frequencies (for example) 29 | f1 = 2.40 # GHz 30 | 31 | # Calculate harmonics 32 | f_harmonic_2 = 2*f1 33 | f_harmonic_3 = 3*f1 34 | 35 | # Frequency domain spectrum 36 | frequencies = [f1, f_harmonic_2, f_harmonic_3] 37 | freq_labels = ['ω', '2ω', '3ω'] 38 | colors = ['blue', 'red', 'red'] 39 | amplitudes = [1.0, 0.3, 0.1] # Relative amplitudes 40 | line_styles = ['-', '--', '--'] 41 | 42 | # Plot spectral lines 43 | for i, (freq, amp, color, style, label) in enumerate(zip(frequencies, amplitudes, colors, line_styles, freq_labels)): 44 | ax.vlines(freq, 0, amp, colors=color, linestyles=style, linewidth=3, alpha=0.8) 45 | ax.plot(freq, amp, 'o', color=color, markersize=8, markerfacecolor=color, alpha=0.8) 46 | 47 | # Add frequency labels 48 | if 'harmonic' not in label: # Fundamental tones 49 | ax.text(freq, amp + 0.1, label, ha='center', va='bottom', color=color) 50 | else: # Harmonics 51 | ax.text(freq, amp + 0.1, f'{label}\n(IM3)', ha='center', va='bottom', color=color) 52 | 53 | # Frequency domain formatting 54 | ax.set_xlabel('Frequency (GHz)') 55 | ax.set_ylabel('Magnitude') 56 | ax.grid(True, alpha=0.3) 57 | ax.set_xlim(f1 - f1/2, f_harmonic_3 + f1/2) 58 | ax.set_ylim(0, 1.2) 59 | 60 | plt.show() 61 | ``` 62 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_naive_noise.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-naive-noise 19 | #| echo: false 20 | #| fig-cap: "Equivalent circuit of resistively matched common-source LNA." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Ground() 31 | src1 = elm.SourceSin().up().label(r'$4 k T (R_\mathrm{s} \parallel R_\mathrm{p})$').color('blue') 32 | elm.Line().right().length(d.unit/2) 33 | elm.Resistor().right().label(r'$R_\mathrm{s} \parallel R_\mathrm{p}$') 34 | elm.Line().right().length(d.unit/2) 35 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('gate').drop('source') 36 | elm.Line().down().toy(src1.start) 37 | elm.Ground() 38 | 39 | # load 40 | R1 = elm.Resistor().up().label(r'$R_\mathrm{D}$').at(M1.drain) 41 | elm.Line().right().length(d.unit*3/4).dot().at(M1.drain).idot() 42 | d.push() 43 | elm.Line().right().length(d.unit/2).label(r'$\overline{V_\mathrm{n,out}^2}$', loc='right').dot(open=True) 44 | d.pop() 45 | d.push() 46 | elm.SourceI().down().label(r'$4 k T \gamma g_\mathrm{m}$', loc='bottom').color('blue') 47 | elm.Line().down().toy(src1.start) 48 | elm.Ground() 49 | d.pop() 50 | N1 = elm.SourceI().up().label(r'$4 k T / R_\mathrm{D}$', loc='bottom').color('blue') 51 | 52 | elm.Line().at((R1.end[0]-d.unit/4, R1.end[1])).to((N1.end[0]+d.unit/4, N1.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 53 | ``` 54 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_cg.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-cg 19 | #| echo: false 20 | #| fig-cap: "Circuit diagram of a common-gate LNA." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Ground() 31 | elm.SourceSin().up().label(r'$v_\mathrm{in}$') 32 | elm.Resistor().right().label(r'$R_\mathrm{s}=50\,\Omega$') 33 | elm.Line().right().length(d.unit/4).dot(open=True) 34 | elm.Line().right().length(d.unit*1.5).dot() 35 | d.push() 36 | elm.Line().up().length(d.unit/4) 37 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('source').drop('gate').theta(0) 38 | elm.Line().left().length(d.unit/4).dot(open=True).label(r'$V_\mathrm{bias}$', loc='left') 39 | d.pop() 40 | elm.SourceI().down().label(r'$I_\mathrm{bias}$') 41 | elm.Ground() 42 | 43 | # load 44 | elm.Line().up().at(M1.drain).length(d.unit/4).dot() 45 | d.push() 46 | elm.Resistor().up().label(r'$R_\mathrm{D}$') 47 | d.pop() 48 | d.push() 49 | elm.Line().left().length(d.unit/2) 50 | L1 = elm.Inductor2(loops=2).up().label(r'$L$') 51 | d.pop() 52 | elm.Line().right().length(d.unit/2).dot() 53 | d.push() 54 | elm.Line().right().length(d.unit/2).label(r'$v_\mathrm{out}$', loc='right').dot(open=True) 55 | d.pop() 56 | C1 = elm.Capacitor().up().label(r'$C$') 57 | elm.Line().at((L1.end[0]-d.unit/4, L1.end[1])).to((C1.end[0]+d.unit/4, C1.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 58 | ``` 59 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_frac_n_block_diagram.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-frac-n-block 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a fractional-N PLL. The VCO signal is divided by a sequence of integer divider values div[k] with (on average) is given by $N.f$, where $N$ is the integer part of the divider value, and $f$ the fractional part of the divider." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Oscillator() 31 | dsp.Arrow().right(d.unit).label(r'$f_\mathrm{ref}$', 'top') 32 | pfd = dsp.Square().label('PFD\nCP', loc='center') 33 | dsp.Arrow().length(d.unit).label('phase\nerror', 'top') 34 | dsp.Filter(response='lp') 35 | dsp.Arrow().length(d.unit).label('tuning\nvoltage', 'top') 36 | dsp.Oscillator().label('VCO', loc='top', ofst=(0, 0.2)) 37 | dsp.Line().right(d.unit/2).dot() 38 | d.push() 39 | dsp.Arrow().right(d.unit/2).label(r'$f_\mathrm{out}$', 'right') 40 | d.pop() 41 | dsp.Line().down(d.unit) 42 | dsp.Arrow().left(d.unit*1.5) 43 | mmd = dsp.Square().label('MMD', loc='center') 44 | dsp.Line().tox(pfd.S).label(r'$f_\mathrm{out} / \text{div}[k-1]$', 'top').dot() 45 | d.push() 46 | dsp.Arrow().up().to(pfd.S) 47 | d.pop() 48 | elm.Line().down(d.unit*3/4) 49 | elm.Arrow().right(d.unit/2) 50 | dsm = dsp.Square().label('DSM') 51 | elm.Line().tox(mmd.S).label(r'$\text{div}[k]$') 52 | elm.Arrow().up().to(mmd.N) 53 | elm.Arrow().down(d.unit/4).at(dsm.S).reverse().label(r'$N.f$', 'left') 54 | ``` 55 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_inductive_degeneration.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-inductive-degeneration 19 | #| echo: false 20 | #| fig-cap: "A common-source MOSFET stage with degeneration impedance." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | #elm.Ground() 31 | src1 = elm.SourceSin().up().label(r'$v_\mathrm{in}$') 32 | elm.Resistor().right().label(r'$R_\mathrm{s}=50\,\Omega$') 33 | elm.Line().right().length(d.unit/4) 34 | elm.Dot(open=True).label(r'$v_\mathrm{x}$', loc='top') 35 | elm.Line().right().length(d.unit/2) 36 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('gate').drop('source') 37 | Zdeg = elm.ResistorIEC().down().label(r'$Z_\mathrm{deg}$') 38 | elm.Ground() 39 | 40 | # load 41 | elm.Line().up().at(M1.drain).length(d.unit/4).dot() 42 | d.push() 43 | elm.Resistor().up().label(r'$R_\mathrm{D}$') 44 | d.pop() 45 | d.push() 46 | elm.Line().left().length(d.unit/2) 47 | L1 = elm.Inductor2(loops=2).up().label(r'$L$') 48 | d.pop() 49 | elm.Line().right().length(d.unit/2).dot() 50 | d.push() 51 | elm.Line().right().length(d.unit/2).label(r'$v_\mathrm{out}$', loc='right').dot(open=True) 52 | d.pop() 53 | C1 = elm.Capacitor().up().label(r'$C$') 54 | elm.Line().at((L1.end[0]-d.unit/4, L1.end[1])).to((C1.end[0]+d.unit/4, C1.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 55 | 56 | # complete ground of input 57 | elm.Line().at(src1.start).down().toy(Zdeg.end) 58 | elm.Ground() 59 | ``` 60 | -------------------------------------------------------------------------------- /content/poweramp/_fig_poweramp_twostage.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-poweramp-twostage 19 | #| echo: false 20 | #| fig-cap: "A basic two-stage single-ended common-source power amplifier with input and output matching." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Line().right(d.unit/2).idot(open=True).label(r'$s_\mathrm{in}$', loc='left') 31 | dsp.Square().label('Input\nmatch', loc='top', ofst=0.2) 32 | elm.Line().right(d.unit/2) 33 | M1 = elm.AnalogNFet(offset_gate=False).anchor('gate').drop('source').label('$M_1$', loc='right').reverse() 34 | elm.Ground() 35 | elm.Line().up(d.unit/4).at(M1.drain).dot() 36 | d.push() 37 | elm.Line().right(d.unit/2) 38 | dsp.Square().label('Interstage\nmatch', loc='top', ofst=0.2) 39 | l1 = elm.Line().right(d.unit/2) 40 | d.pop() 41 | elm.Inductor2().up().label(r'RF choke') 42 | elm.Vdd().label(r'$V_\mathrm{DD}$', loc='top') 43 | 44 | elm.Line().at(l1.end).down().toy(M1.gate) 45 | elm.Line().right(d.unit/2) 46 | M2 = elm.AnalogNFet(offset_gate=False).anchor('gate').drop('source').label('$M_2$', loc='right').reverse() 47 | elm.Ground() 48 | elm.Line().up(d.unit/4).at(M2.drain).dot() 49 | d.push() 50 | elm.Line().right(d.unit/2) 51 | dsp.Square().label('Output\nmatch', loc='top', ofst=0.2) 52 | l2 = elm.Line().right(d.unit/2).dot(open=True).label(r'$s_\mathrm{out}$', loc='right') 53 | d.pop() 54 | elm.Inductor2().up().label(r'RF choke') 55 | elm.Vdd().label(r'$V_\mathrm{DD}$', loc='top') 56 | 57 | ``` 58 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_naive.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-naive 19 | #| echo: false 20 | #| fig-cap: "A simple LNA with resistive input matching and a tank circuit as a load (biasing details are omitted). The LNA is driven by a 50 Ω source." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Ground() 31 | elm.SourceSin().up().label(r'$v_\mathrm{in}$') 32 | elm.Resistor().right().label(r'$R_\mathrm{s}=50\,\Omega$') 33 | elm.Line().right().length(d.unit/4).dot(open=True) 34 | elm.Line().right().length(d.unit/2).dot() 35 | d.push() 36 | Rp = elm.Resistor().down().label(r'$R_\mathrm{p}$') 37 | gnd1 = elm.Ground() 38 | d.pop() 39 | elm.Line().right().length(d.unit/2) 40 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('gate').drop('source') 41 | elm.Line().down().toy(Rp.end) 42 | elm.Ground() 43 | 44 | # load 45 | elm.Line().up().at(M1.drain).length(d.unit/4).dot() 46 | d.push() 47 | elm.Resistor().up().label(r'$R_\mathrm{D}$') 48 | d.pop() 49 | d.push() 50 | elm.Line().left().length(d.unit/2) 51 | L1 = elm.Inductor2(loops=2).up().label(r'$L$') 52 | d.pop() 53 | elm.Line().right().length(d.unit/2).dot() 54 | d.push() 55 | elm.Line().right().length(d.unit/2).label(r'$v_\mathrm{out}$', loc='right').dot(open=True) 56 | d.pop() 57 | C1 = elm.Capacitor().up().label(r'$C$') 58 | elm.Line().at((L1.end[0]-d.unit/4, L1.end[1])).to((C1.end[0]+d.unit/4, C1.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 59 | ``` 60 | -------------------------------------------------------------------------------- /content/oscillator/_fig_oscillator_lc_nmos.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-oscillator-lc-nmos 19 | #| echo: false 20 | #| fig-cap: "An LC differential oscillator using an NMOS cross-coupled differential pair. $L_1$ and $L_2$ are usually implemented as a single on-chip spiral inductor with a center tap." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | # cross-coupled NMOS pair 31 | M1 = elm.AnalogNFet(offset_gate=False).label('$M_1$', loc='right').reverse().drop('source').theta(0) 32 | elm.Line().down(d.unit/4) 33 | elm.Line().left(d.unit*1.5).dot() 34 | pos1 = d.here 35 | d.push() 36 | Ibias = elm.SourceI().down() 37 | elm.Ground() 38 | d.pop() 39 | elm.Line().left(d.unit*1.5) 40 | elm.Line().up(d.unit/4) 41 | M2 = elm.AnalogNFet(offset_gate=False).label('$M_2$', loc='left').anchor('source').theta(0) 42 | 43 | elm.Line().up(d.unit/4).at(M1.drain).dot() 44 | d.push() 45 | l1 = elm.Line().up(d.unit/2).dot() 46 | d.pop() 47 | elm.Wire('z').to(M2.gate) 48 | 49 | elm.Line().up(d.unit/4).at(M2.drain).dot() 50 | d.push() 51 | l2 = elm.Line().up(d.unit/2).dot() 52 | d.pop() 53 | elm.Wire('z').to(M1.gate) 54 | 55 | # LC tank 56 | elm.CapacitorVar().at(l1.end).to(l2.end).label('$C$', loc='top').flip().reverse() 57 | l3 = elm.Line().up(d.unit).at(l1.end) 58 | l4 = elm.Line().up(d.unit).at(l2.end) 59 | elm.Inductor2(loops=2).at(l3.end).tox(pos1).label('$L_1$', loc='bottom').dot() 60 | elm.Vdd() 61 | elm.Inductor2(loops=2).to(l4.end).label('$L_2$', loc='bottom') 62 | ``` 63 | -------------------------------------------------------------------------------- /content/oscillator/_fig_crystal_oscillator.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-crystal-oscillator 19 | #| echo: false 20 | #| fig-cap: "Circuit diagram of a single-ended Pierce crystal oscillator operating the quartz between series and parallel resonance where it acts as a large high-Q inductor. Note that the quartz crystal has no dc path, hence we need a high-ohmic bias resistor to connect M1 into a diode configuration." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | M1 = elm.AnalogNFet(offset_gate=False).label('$M_1$', loc='right').reverse().anchor('source').drop('gate').theta(0) 31 | l1 = elm.Line().up(d.unit/2).dot().at(M1.drain) 32 | l3 = elm.Line().down(d.unit/4).at(M1.source) 33 | elm.Ground() 34 | l4 = elm.Line().left(d.unit*3/4).dot().at(M1.gate) 35 | elm.Line().up().toy(l1.end) 36 | Rb = elm.Resistor().right().to(l1.end).label(r'$R_\mathrm{bias} \gg$') 37 | 38 | l2 = elm.Line().up(d.unit*3/4).dot().at(l1.end) 39 | elm.SourceI().up().label(r'$I_\mathrm{bias}$').reverse() 40 | elm.Vdd().label(r'$V_\mathrm{DD}$') 41 | 42 | elm.Line().right(d.unit).at(l2.end).dot() 43 | d.push() 44 | elm.Line().right(d.unit/2).dot(open=True).label(r'$V_\mathrm{out}$', loc='right') 45 | d.pop() 46 | C2 = elm.Capacitor().down().toy(l3.end).label(r'$C_2$') 47 | elm.Ground() 48 | 49 | elm.Line().left(d.unit*2).at(l2.end) 50 | elm.Crystal().down().toy(M1.gate).dot() 51 | d.push() 52 | elm.Line().right().to(l4.end) 53 | d.pop() 54 | C1 = elm.Capacitor().down().toy(l3.end).label(r'$C_1$') 55 | elm.Ground() 56 | ``` 57 | -------------------------------------------------------------------------------- /content/_macros.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2024-2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ::: {.hidden} 18 | \newcommand{\VDD}[1][]{V_\mathrm{DD#1}} 19 | \newcommand{\VSS}[1][]{V_\mathrm{SS#1}} 20 | \newcommand{\VGS}[1][]{V_\mathrm{GS#1}} 21 | \newcommand{\Vgs}[1][]{V_\mathrm{gs#1}} 22 | \newcommand{\VDS}[1][]{V_\mathrm{DS#1}} 23 | \newcommand{\Vds}[1][]{V_\mathrm{ds#1}} 24 | \newcommand{\VSB}[1][]{V_\mathrm{SB#1}} 25 | \newcommand{\Vsb}[1][]{V_\mathrm{sb#1}} 26 | \newcommand{\VGB}[1][]{V_\mathrm{GB#1}} 27 | \newcommand{\vgs}[1][]{v_\mathrm{gs#1}} 28 | \newcommand{\vds}[1][]{v_\mathrm{ds#1}} 29 | \newcommand{\Vth}[1][]{V_\mathrm{th#1}} 30 | \newcommand{\gm}[1][]{g_\mathrm{m#1}} 31 | \newcommand{\gmb}[1][]{g_\mathrm{mb#1}} 32 | \newcommand{\gmid}{g_\mathrm{m}/I_\mathrm{D}} 33 | \newcommand{\gmgds}{g_\mathrm{m}/g_\mathrm{ds}} 34 | \newcommand{\gmCgg}{g_\mathrm{m}/C_\mathrm{gg}} 35 | \newcommand{\gds}[1][]{g_\mathrm{ds#1}} 36 | \newcommand{\CGG}[1][]{C_\mathrm{GG#1}} 37 | \newcommand{\Cgg}[1][]{C_\mathrm{gg#1}} 38 | \newcommand{\CGS}[1][]{C_\mathrm{GS#1}} 39 | \newcommand{\Cgs}[1][]{C_\mathrm{gs#1}} 40 | \newcommand{\Cox}[1][]{C'_\mathrm{ox#1}} 41 | \newcommand{\CGB}[1][]{C_\mathrm{GB#1}} 42 | \newcommand{\Cgb}[1][]{C_\mathrm{gb#1}} 43 | \newcommand{\CGD}[1][]{C_\mathrm{GD#1}} 44 | \newcommand{\Cgd}[1][]{C_\mathrm{gd#1}} 45 | \newcommand{\CDG}[1][]{C_\mathrm{DG#1}} 46 | \newcommand{\Cdg}[1][]{C_\mathrm{gd#1}} 47 | \newcommand{\CDB}[1][]{C_\mathrm{DB#1}} 48 | \newcommand{\Cdb}[1][]{C_\mathrm{db#1}} 49 | \newcommand{\CSB}[1][]{C_\mathrm{SB#1}} 50 | \newcommand{\Csb}[1][]{C_\mathrm{sb#1}} 51 | \newcommand{\ID}[1][]{I_\mathrm{D#1}} 52 | \newcommand{\Id}[1][]{I_\mathrm{d#1}} 53 | \newcommand{\id}[1][]{i_\mathrm{d#1}} 54 | \newcommand{\fco}{f_\mathrm{co}} 55 | \newcommand{\VBE}[1][]{V_\mathrm{BE#1}} 56 | \newcommand{\phn}[1][]{\mathcal{L}\left\{#1\right\}} 57 | ::: 58 | -------------------------------------------------------------------------------- /content/pll/_fig_pfd_implementation.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pfd-implementation 19 | #| echo: false 20 | #| fig-cap: "Implementation of a phase-frequency detector." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | import schemdraw.logic as lgc 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | DFF1 = elm.Ic(pins=[elm.IcPin(name='>', side='left'), elm.IcPin(name='D', side='left'), 32 | elm.IcPin(name='Q', side='right'), elm.IcPin(name='R', side='bottom')], 33 | pinspacing=1).hold() 34 | d.move(dx=0, dy=-d.unit) 35 | DFF2 = elm.Ic(pins=[elm.IcPin(name='>', side='left'), elm.IcPin(name='D', side='left'), 36 | elm.IcPin(name='Q', side='right'), elm.IcPin(name='R', side='bottom')], 37 | pinspacing=1).hold().flip() 38 | 39 | elm.Line().left(d.unit/4).at(DFF1['D']).label('1', 'left') 40 | elm.Line().left(d.unit/4).at(DFF2['D']).label('1', 'left') 41 | elm.Line().left(d.unit/2).at(DFF1['>']).dot(open=True).label(r'$f_\mathrm{ref}$', 'left') 42 | elm.Line().left(d.unit/2).at(DFF2['>']).dot(open=True).label(r'$f_\mathrm{fb}$', 'left') 43 | 44 | elm.Line().at(DFF1['R']).to(DFF2['R']) 45 | 46 | elm.Line().right(d.unit/2).at(DFF1['Q']).dot() 47 | pos1 = d.here 48 | elm.Line().right(d.unit/2).dot(open=True).label('UP', 'right') 49 | elm.Line().right(d.unit/2).at(DFF2['Q']).dot() 50 | pos2 = d.here 51 | elm.Line().right(d.unit/2).dot(open=True).label('DOWN', 'right') 52 | 53 | AND1 = lgc.And().at((d.unit,-d.unit/2)).reverse() 54 | elm.Wire('|-').at(pos1).to(AND1.in1) 55 | elm.Wire('|-').at(pos2).to(AND1.in2) 56 | elm.Line().at(AND1.out).left().tox(DFF1['R']).dot() 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_ofdm_transceiver.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-ofdm-transceiver 19 | #| echo: false 20 | #| fig-cap: "OFDM transmission system block diagram showing transmitter and receiver processing chains." 21 | 22 | import schemdraw as sd 23 | import schemdraw.elements as elm 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | elm.Dot(open=True).label(r'Bitrate $T_\mathrm{b}$', loc='left') 32 | dsp.Arrow().right(d.unit/2) 33 | dsp.Square().label('S/P') 34 | dsp.Arrow().right(d.unit/2).label(r'$T_\mathrm{b} / N$') 35 | dsp.Square().label('QAM\nMap') 36 | dsp.Arrow().right(d.unit/2) 37 | dsp.Square().label('IFFT') 38 | dsp.Arrow().right(d.unit/2) 39 | dsp.Square().label('P/S') 40 | dsp.Arrow().right(d.unit/2) 41 | dsp.Square().label('Add\nCP') 42 | dsp.Arrow().right(d.unit/2) 43 | dsp.Square().label('Radio') 44 | dsp.Line().right(d.unit/2) 45 | elm.Antenna() 46 | 47 | # Move down for receiver chain 48 | d.move(dx=-d.unit*7, dy=-d.unit*1.5) 49 | 50 | # Receiver chain (bottom row) 51 | elm.Antenna() 52 | dsp.Arrow().right(d.unit/2) 53 | dsp.Square().label('Radio') 54 | dsp.Arrow().right(d.unit/2) 55 | dsp.Square().label('Rem.\nCP') 56 | dsp.Arrow().right(d.unit/2) 57 | dsp.Square().label('S/P') 58 | dsp.Arrow().right(d.unit/2) 59 | dsp.Square().label('FFT') 60 | dsp.Arrow().right(d.unit/2) 61 | dsp.Square().label('EQ') 62 | dsp.Arrow().right(d.unit/2) 63 | dsp.Square().label('QAM\nDem.') 64 | dsp.Arrow().right(d.unit/2).label(r'$T_\mathrm{b} / N$') 65 | dsp.Square().label('P/S') 66 | dsp.Arrow().right(d.unit/2) 67 | elm.Dot(open=True).label(r'Bitrate $T_\mathrm{b}$', loc='right') 68 | ``` 69 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_frac_n_block_diagram_retiming.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-frac-n-block-retiming 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a fractional-N PLL including a retiming flip flop to reduce the MMD-related jitter." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | dsp.Oscillator() 31 | dsp.Arrow().right(d.unit).label(r'$f_\mathrm{ref}$', 'top') 32 | pfd = dsp.Square().label('PFD\nCP', loc='center') 33 | elm.Line().right(d.unit/2).dot() 34 | pos2 = d.here 35 | dsp.Arrow().length(d.unit/2) 36 | dsp.Filter(response='lp') 37 | dsp.Arrow().length(d.unit) 38 | dsp.Oscillator().label('VCO', loc='top', ofst=(0, 0.2)) 39 | dsp.Line().right(d.unit/2).dot() 40 | d.push() 41 | dsp.Arrow().right(d.unit/2).label(r'$f_\mathrm{out}$', 'right') 42 | d.pop() 43 | dsp.Line().down(d.unit*1).dot() 44 | pos1 = d.here 45 | dsp.Line().down(d.unit/2) 46 | dsp.Arrow().left(d.unit/2) 47 | mmd = dsp.Square().label('MMD', loc='center') 48 | dsp.Arrow().left(d.unit/2) 49 | ff = dsp.Square().label('FF', loc='center') 50 | dsp.Line().tox(pfd.S).label(r'$f_\mathrm{out} / \text{div}[k-1]$', 'top').dot() 51 | d.push() 52 | dsp.Arrow().up().to(pfd.S) 53 | d.pop() 54 | elm.Line().down(d.unit*3/4) 55 | elm.Arrow().right(d.unit/2) 56 | dsm = dsp.Square().label('DSM') 57 | elm.Line().tox(mmd.S).label(r'$\text{div}[k]$') 58 | elm.Arrow().up().to(mmd.N) 59 | elm.Arrow().down(d.unit/4).at(dsm.S).reverse().label(r'$N.f$', 'left') 60 | 61 | elm.Line().at(pos1).left().tox(ff.S) 62 | elm.Arrow().down().to(ff.S) 63 | 64 | elm.Line().at(pos2).down(d.unit/2) 65 | dsp.Square().label('offset') 66 | ``` 67 | -------------------------------------------------------------------------------- /content/oscillator/_fig_oscillator_lc_pmos.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-oscillator-lc-pmos 19 | #| echo: false 20 | #| fig-cap: "An LC differential oscillator using a PMOS cross-coupled differential pair. $L_1$ and $L_2$ are usually implemented as a single on-chip spiral inductor with a center tap." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | # Start from VDD at top 31 | elm.Vdd() 32 | Ibias = elm.SourceI().down().dot() 33 | pos_vdd = d.here 34 | 35 | # cross-coupled PMOS pair (at top) 36 | d.push() 37 | elm.Line().left(d.unit*1.5) 38 | elm.Line().down(d.unit/4) 39 | M1 = elm.AnalogPFet(offset_gate=False).label('$M_1$', loc='left').theta(0) 40 | d.pop() 41 | elm.Line().right(d.unit*1.5) 42 | elm.Line().down(d.unit/4) 43 | M2 = elm.AnalogPFet(offset_gate=False).label('$M_2$', loc='right').anchor('source').theta(0).reverse() 44 | 45 | # Cross-coupling connections for PMOS 46 | elm.Line().down(d.unit/4).at(M1.drain).dot() 47 | d.push() 48 | l1 = elm.Line().down(d.unit/2).dot() 49 | d.pop() 50 | elm.Wire('z').to(M2.gate) 51 | 52 | elm.Line().down(d.unit/4).at(M2.drain).dot() 53 | d.push() 54 | l2 = elm.Line().down(d.unit/2).dot() 55 | d.pop() 56 | elm.Wire('z').to(M1.gate) 57 | 58 | # LC tank (now at bottom) 59 | elm.CapacitorVar().at(l1.end).to(l2.end).label('$C$', loc='bottom') 60 | l3 = elm.Line().down(d.unit).at(l1.end) 61 | l4 = elm.Line().down(d.unit).at(l2.end) 62 | elm.Inductor2(loops=2).at(l3.end).tox(pos_vdd).label('$L_1$', loc='top').dot() 63 | pos_center = d.here 64 | d.push() 65 | elm.Ground() 66 | d.pop() 67 | elm.Inductor2(loops=2).to(l4.end).label('$L_2$', loc='top') 68 | ``` 69 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_dsm_ntf_plot.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-dsm-ntf-plot 19 | #| echo: false 20 | #| fig-cap: "NTF H(f) of a first-order delta-sigma modulator showing the high-pass noise shaping characteristic. The quantization noise is suppressed at low frequencies and increases towards the Nyquist frequency. An exemplary loop filter bandwidth of 300 kHz for a 40 MHz reference frequency is indicated." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define frequency range 26 | f_s = 40e6 # Sampling frequency in Hz (40 MHz) 27 | T_s = 1 / f_s # Sampling period 28 | 29 | # Normalized frequency range (0 to Nyquist frequency) 30 | f_norm = np.linspace(1e-4, 0.5, 1000) # Normalized frequency (avoid f=0) 31 | f = f_norm * f_s # Actual frequency in Hz 32 | 33 | # Calculate H(f) for first-order DSM 34 | # From NTF = 1 - z^(-1), with z = exp(j*2*pi*f*T_s) 35 | # |NTF(f)|^2 = |1 - exp(-j*2*pi*f*T_s)|^2 = 2(1 - cos(2*pi*f*T_s)) 36 | # Therefore: |H(f)| = sqrt(2(1 - cos(2*pi*f*T_s))) 37 | H_f_magnitude = np.sqrt(2 * (1 - np.cos(2 * np.pi * f_norm))) 38 | 39 | # Convert to dB 40 | H_f_dB = 20 * np.log10(H_f_magnitude) 41 | 42 | # For low frequencies, approximate: H(f) ≈ 2*pi*f*T_s = 2*pi*f_norm 43 | # This gives a 20 dB/decade slope 44 | f_low = f_norm[f_norm < 0.1] 45 | H_approx_dB = 20 * np.log10(2 * np.pi * f_low) 46 | 47 | # Create the plot 48 | plt.figure() 49 | plt.plot(f_norm, H_f_magnitude, 'b-', linewidth=2, label='First-order DSM NTF |H(f)|') 50 | 51 | # Add vertical line for loop filter bandwidth (100 kHz) 52 | f_loop_bw = 300e3 # Loop filter bandwidth in Hz 53 | f_loop_norm = f_loop_bw / f_s # Normalized loop filter bandwidth 54 | plt.axvline(f_loop_norm, color='red', linestyle=':', linewidth=2, label='Loop filter BW (300 kHz)') 55 | 56 | # Formatting 57 | plt.xlabel('Normalized frequency (f/f_s)') 58 | plt.ylabel('|H(f)| (linear scale)') 59 | plt.grid(True, alpha=0.3) 60 | plt.xlim(0, 0.5) 61 | plt.ylim(0, 2) 62 | 63 | plt.tight_layout() 64 | plt.show() 65 | ``` -------------------------------------------------------------------------------- /content/mixer/_fig_cmos_mixer_double.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-cmos-double-mixer 19 | #| echo: false 20 | #| fig-cap: "A fully-differential double-balanced MOSFET mixer." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | 24 | sd.svgconfig.svg2 = False 25 | 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2, fontsize=14) 28 | 29 | pos1 = d.here 30 | elm.Line().right(d.unit/2).label(r'$v_\mathrm{in,p}$', 'left').dot().idot(open=True) 31 | pos2 = d.here 32 | elm.Line().right(d.unit/2) 33 | M1 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 34 | elm.Line().right(d.unit*1.5).dot() 35 | elm.Line().right(d.unit).dot(open=True).label(r'$v_\mathrm{out,p}$', 'right') 36 | elm.Line().up(d.unit/2).dot(open=True).at(M1.gate).label(r'$v_\mathrm{LO,p}$', 'right') 37 | 38 | d.here = pos1 39 | d.move(dx=0, dy=-d.unit*2) 40 | elm.Line().right(d.unit/2).label(r'$v_\mathrm{in,n}$', 'left').dot().idot(open=True) 41 | pos3 = d.here 42 | elm.Line().right(d.unit/2) 43 | M2 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 44 | elm.Line().right(d.unit*2).dot() 45 | elm.Line().right(d.unit/2).dot(open=True).label(r'$v_\mathrm{out,n}$', 'right') 46 | elm.Line().at(M1.gate).to(M2.gate) 47 | 48 | d.here = pos2 49 | elm.Line().down(d.unit/2) 50 | elm.Line().right(d.unit*1.5) 51 | M3 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 52 | elm.Line().right(d.unit) 53 | elm.Line().down(d.unit*1.5) 54 | elm.Line().up(d.unit).dot(open=True).at(M3.gate).label(r'$v_\mathrm{LO,n}$', 'right') 55 | 56 | d.here = pos3 57 | elm.Line().up(d.unit/2) 58 | elm.Line().right(d.unit*1.5) 59 | M4 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 60 | elm.Line().right(d.unit/2) 61 | elm.Line().up(d.unit*1.5) 62 | elm.Line().at(M3.gate).to(M4.gate) 63 | ``` 64 | -------------------------------------------------------------------------------- /content/mixer/_fig_logen_dll.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-logen-dll 19 | #| echo: false 20 | #| fig-cap: "LO multiphase generation by delay-locked loop (DLL). An extension to more than four phases is straightforward." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | import schemdraw.dsp as dsp 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | elm.Line().right(d.unit/2).idot(open=True).dot().label(r'$\text{LO}_\mathrm{in}$', 'left') 32 | pos1 = d.here 33 | elm.Line().right(d.unit) 34 | vg1 = dsp.VGA() 35 | ph1 = elm.Line().right(d.unit/2).dot() 36 | elm.Line().right(d.unit/2) 37 | vg2 = dsp.VGA() 38 | ph2 = elm.Line().right(d.unit/2).dot() 39 | elm.Line().right(d.unit/2) 40 | vg3 = dsp.VGA() 41 | ph3 = elm.Line().right(d.unit/2).dot() 42 | elm.Line().right(d.unit/2) 43 | vg4 = dsp.VGA() 44 | ph4 = elm.Line().right(d.unit/2).dot() 45 | 46 | 47 | d.here=pos1 48 | 49 | elm.Line().up(d.unit) 50 | elm.Arrow().right(d.unit/4) 51 | pd = dsp.Square().label('PD', 'center') 52 | 53 | elm.Line().at(ph4.end).up(d.unit*1.5) 54 | elm.Line().left().tox(pd.N) 55 | elm.Arrow().down().to(pd.N) 56 | 57 | elm.Line().down(d.unit/4).at(pd.S) 58 | l2 = elm.Line().right().tox(vg4.tune) 59 | 60 | elm.Line().at(vg1.tune).up().toy(l2.start).dot() 61 | elm.Line().at(vg2.tune).up().toy(l2.start).dot() 62 | elm.Line().at(vg3.tune).up().toy(l2.start).dot() 63 | elm.Line().at(vg4.tune).up().toy(l2.start) 64 | 65 | elm.Line().down(d.unit/2).at(ph1.end).dot(open=True).label(r'$\text{LO}_\mathrm{0}$', 'left') 66 | elm.Line().down(d.unit/2).at(ph2.end).dot(open=True).label(r'$\text{LO}_\mathrm{90}$', 'left') 67 | elm.Line().down(d.unit/2).at(ph3.end).dot(open=True).label(r'$\text{LO}_\mathrm{180}$', 'left') 68 | elm.Line().down(d.unit/2).at(ph4.end).dot(open=True).label(r'$\text{LO}_\mathrm{270}$', 'left') 69 | ``` 70 | -------------------------------------------------------------------------------- /content/mixer/_fig_npath_filter.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-npath-filter 19 | #| echo: false 20 | #| fig-cap: "A 4-phase N-path filter." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | 24 | sd.svgconfig.svg2 = False 25 | 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2, fontsize=14) 28 | 29 | elm.Line().right(d.unit/2).idot(open=True).label(r'$Z_\mathrm{in}$', 'left').dot() 30 | d.push() 31 | elm.Line().up(d.unit*3/4).dot() 32 | d.push() 33 | elm.Line().up(d.unit*1.5) 34 | elm.Line().right(d.unit/2) 35 | M1 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 36 | elm.Line().right(d.unit/4) 37 | elm.Capacitor().down().length(d.unit*3/4).label(r'$C$') 38 | elm.Ground() 39 | elm.Line().up(d.unit/4).at(M1.gate).label(r'$\text{LO}_{0}$', 'right').dot(open=True) 40 | d.pop() 41 | 42 | elm.Line().right(d.unit/2) 43 | M2 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 44 | elm.Line().right(d.unit/4) 45 | elm.Capacitor().down().length(d.unit*3/4).label(r'$C$') 46 | elm.Ground() 47 | elm.Line().up(d.unit/4).at(M2.gate).label(r'$\text{LO}_{90}$', 'right').dot(open=True) 48 | 49 | d.pop() 50 | 51 | elm.Line().down(d.unit*3/4).dot() 52 | d.push() 53 | elm.Line().down(d.unit*1.5) 54 | elm.Line().right(d.unit/2) 55 | M3 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 56 | elm.Line().right(d.unit/4) 57 | elm.Capacitor().down().length(d.unit*3/4).label(r'$C$') 58 | elm.Ground() 59 | elm.Line().up(d.unit/4).at(M3.gate).label(r'$\text{LO}_{270}$', 'right').dot(open=True) 60 | d.pop() 61 | 62 | elm.Line().right(d.unit/2) 63 | M4 = elm.AnalogNFet(offset_gate=False).reverse().anchor('source').drop('drain').theta(270) 64 | elm.Line().right(d.unit/4) 65 | elm.Capacitor().down().length(d.unit*3/4).label(r'$C$') 66 | elm.Ground() 67 | elm.Line().up(d.unit/4).at(M4.gate).label(r'$\text{LO}_{180}$', 'right').dot(open=True) 68 | ``` 69 | -------------------------------------------------------------------------------- /content/intro/_fig_free_space_path_loss.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-free-space-path-loss 19 | #| echo: false 20 | #| fig-cap: "Free space path loss vs. distance for different frequencies (1 GHz, 10 GHz, and 100 GHz)." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define distance range in kilometers 26 | distance_km = np.linspace(1, 100, 200) 27 | 28 | # Define frequencies in Hz 29 | frequencies = { 30 | '1 GHz': 1e9, 31 | '10 GHz': 10e9, 32 | '100 GHz': 100e9 33 | } 34 | 35 | # Speed of light in m/s 36 | c = 299792458 37 | 38 | # Function to calculate Free Space Path Loss (FSPL) in dB 39 | def free_space_path_loss_db(distance_km, frequency_hz): 40 | """ 41 | Calculate Free Space Path Loss in dB 42 | FSPL(dB) = 20*log10(d) + 20*log10(f) + 20*log10(4π/c) 43 | where: 44 | - d is distance in meters 45 | - f is frequency in Hz 46 | - c is speed of light in m/s 47 | """ 48 | distance_m = distance_km * 1000 # Convert km to m 49 | fspl_db = (20 * np.log10(distance_m) + 50 | 20 * np.log10(frequency_hz) + 51 | 20 * np.log10(4 * np.pi / c)) 52 | return fspl_db 53 | 54 | # Create the plot 55 | fig, ax = plt.subplots(1, 1) 56 | 57 | # Colors for different frequencies 58 | colors = ['green', 'blue', 'red'] 59 | 60 | # Plot FSPL for each frequency 61 | for i, (freq_label, frequency) in enumerate(frequencies.items()): 62 | fspl = free_space_path_loss_db(distance_km, frequency) 63 | ax.plot(distance_km, fspl, color=colors[i], linewidth=2.5, 64 | label=f'{freq_label}') 65 | 66 | # Formatting 67 | ax.set_xlabel('Distance (km)') 68 | ax.set_ylabel('Free Space Path Loss (dB)') 69 | ax.set_xscale('log') 70 | ax.grid(True, alpha=0.3) 71 | ax.legend(loc='lower right') 72 | 73 | # Set x-axis to show plain numbers instead of scientific notation 74 | from matplotlib.ticker import ScalarFormatter 75 | ax.xaxis.set_major_formatter(ScalarFormatter()) 76 | ax.xaxis.get_major_formatter().set_scientific(False) 77 | 78 | # Set axis limits 79 | ax.set_xlim(1, 100) 80 | ax.set_ylim(90, 170) 81 | 82 | plt.show() 83 | ``` 84 | -------------------------------------------------------------------------------- /content/mixer/_fig_rx_mixer.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-rx-mixer-simplified 19 | #| echo: false 20 | #| fig-cap: "An RX front-end using a current-mode (passive) mixer." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | elm.Line().right(d.unit/2).label(r'$s_\mathrm{RX}(t)$', 'left') 31 | dsp.Amp() 32 | elm.Line().right(d.unit/2) 33 | mix1 = dsp.Mixer() 34 | elm.Line().at(mix1.NE).right(d.unit/2) 35 | elm.Line().up(d.unit/8).dot() 36 | d.push() 37 | elm.Capacitor().up(d.unit*3/4) 38 | elm.Ground().flip() 39 | d.pop() 40 | elm.Line().right(d.unit*3/4).dot() 41 | pos1 = d.here 42 | elm.Line().right(d.unit/2) 43 | op1 = elm.Opamp().anchor('in1') 44 | dsp.Arrow().at(mix1.S).down(d.unit/2).label(r'$s_\mathrm{LO}(t)$', 'left').reverse() 45 | 46 | elm.Line().at(mix1.SE).right(d.unit/2) 47 | elm.Line().down(d.unit/8).dot() 48 | d.push() 49 | elm.Capacitor().down(d.unit*3/4) 50 | elm.Ground() 51 | d.pop() 52 | elm.Line().right(d.unit*3/4).dot() 53 | pos2 = d.here 54 | elm.Line().right(d.unit/2) 55 | 56 | elm.Line().at(op1.n2).right(d.unit/2).dot() 57 | pos3 = d.here 58 | elm.Line().right(d.unit/2).dot(open=True).label(r'$v_\mathrm{out,p}(t)$', 'right') 59 | elm.Line().at(op1.n1).right(d.unit/2).dot() 60 | pos4 = d.here 61 | elm.Line().right(d.unit/2).dot(open=True).label(r'$v_\mathrm{out,n}(t)$', 'right') 62 | 63 | d.here = pos1 64 | elm.Line().up(d.unit/2) 65 | R1 = elm.Resistor().right().tox(pos3) 66 | elm.Line().down().toy(pos3) 67 | elm.Line().up(d.unit/2).at(R1.start).idot() 68 | elm.Capacitor().right(d.unit/2).tox(pos3) 69 | elm.Line().down().to(R1.end).dot() 70 | 71 | d.here = pos2 72 | elm.Line().down(d.unit/2) 73 | R2 = elm.Resistor().right().tox(pos3) 74 | elm.Line().up().toy(pos4) 75 | elm.Line().down(d.unit/2).at(R2.start).idot() 76 | elm.Capacitor().right(d.unit/2).tox(pos3) 77 | elm.Line().up().to(R2.end).dot() 78 | ``` 79 | -------------------------------------------------------------------------------- /content/oscillator/_fig_oscillator_lc_cmos.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-oscillator-lc-cmos 19 | #| echo: false 20 | #| fig-cap: "An LC differential oscillator using a PMOS and an NMOS cross-coupled differential pair." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | # Start from VDD at top 31 | elm.Vdd() 32 | 33 | # cross-coupled PMOS pair (at top) 34 | d.push() 35 | elm.Line().left(d.unit*1.5).idot() 36 | elm.Line().down(d.unit/4) 37 | M1 = elm.AnalogPFet(offset_gate=False).label('$M_1$', loc='left').theta(0) 38 | d.pop() 39 | elm.Line().right(d.unit*1.5) 40 | elm.Line().down(d.unit/4) 41 | M2 = elm.AnalogPFet(offset_gate=False).label('$M_2$', loc='right').anchor('source').theta(0).reverse() 42 | 43 | # Cross-coupling connections for PMOS 44 | elm.Line().down(d.unit/4).at(M1.drain).dot() 45 | d.push() 46 | l1 = elm.Line().down(d.unit/2).dot() 47 | d.pop() 48 | elm.Wire('z').to(M2.gate) 49 | 50 | elm.Line().down(d.unit/4).at(M2.drain).dot() 51 | d.push() 52 | l2 = elm.Line().down(d.unit/2).dot() 53 | d.pop() 54 | elm.Wire('z').to(M1.gate) 55 | 56 | # LC tank 57 | elm.CapacitorVar().at(l1.end).to(l2.end).label('$C$', loc='bottom') 58 | l3 = elm.Line().down(d.unit).at(l1.end).dot() 59 | l4 = elm.Line().down(d.unit).at(l2.end).dot() 60 | elm.Inductor2(loops=2).at(l3.end).to(l4.end).label('$L$', loc='top') 61 | 62 | # cross-coupled NMOS pair 63 | l5 = elm.Line().down(d.unit/2).at(l4.end).dot() 64 | elm.Line().down(d.unit/4) 65 | M4 = elm.AnalogNFet(offset_gate=False).label('$M_4$', loc='right').reverse().drop('source').theta(0) 66 | elm.Line().down(d.unit/4) 67 | elm.Line().left(d.unit*1.5).dot() 68 | elm.Ground() 69 | elm.Line().left(d.unit*1.5) 70 | elm.Line().up(d.unit/4) 71 | M3 = elm.AnalogNFet(offset_gate=False).label('$M_3$', loc='left').anchor('source').theta(0).drop('drain') 72 | elm.Line().up(d.unit/4).dot() 73 | d.push() 74 | elm.Wire('z').to(M4.gate) 75 | d.pop() 76 | elm.Line().up().to(l3.end) 77 | elm.Wire('z').at(l5.end).to(M3.gate) 78 | ``` 79 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_gaussian_pulse.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-gaussian-pulse 19 | #| echo: false 20 | #| fig-cap: "Gaussian pulse shaping in time and frequency domain for different bandwidth-time products BT." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Create figure with subplots 26 | fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 5)) 27 | 28 | # Parameters 29 | T = 1.0 # Symbol period 30 | fs = 100 / T # Sampling frequency (high for smooth curves) 31 | t_span = 4 * T # Time span to plot 32 | f_span = 6 / T # Frequency span to plot 33 | 34 | # Time and frequency vectors 35 | t = np.linspace(-t_span/2, t_span/2, int(fs * t_span)) 36 | f = np.linspace(-f_span, f_span, 1000) 37 | 38 | # BT products to plot (bandwidth-time products) 39 | BT_values = [0.3, 0.5] 40 | colors = ['green', 'blue'] 41 | linestyles = ['-', '-'] 42 | 43 | # Plot time domain responses 44 | for i, (BT, color) in enumerate(zip(BT_values, colors)): 45 | # Gaussian pulse in time domain 46 | # For BT product: B = BT/T 47 | B = BT / T # 3dB bandwidth 48 | alpha = np.sqrt(np.log(2)/2) * T/BT 49 | h_t = np.sqrt(np.pi)/alpha * np.exp(- (np.pi * t / alpha)**2) 50 | 51 | ax1.plot(t/T, h_t, color=color, linewidth=2.5, 52 | label=f'BT = {BT}', linestyle=linestyles[i]) 53 | 54 | # Plot frequency domain responses 55 | for i, (BT, color) in enumerate(zip(BT_values, colors)): 56 | # Gaussian spectrum in frequency domain 57 | B = BT / T # 3dB bandwidth 58 | alpha = np.sqrt(np.log(2)/2) * T/BT 59 | H_f = np.exp(-alpha**2 * f**2) 60 | 61 | # Convert to dB (20*log10), handle zero values 62 | H_f_dB = 20 * np.log10(np.maximum(H_f, 1e-10)) 63 | ax2.plot(f * T, H_f_dB, color=color, linewidth=2.5, 64 | label=f'BT = {BT}', linestyle=linestyles[i]) 65 | 66 | # Format time domain plot 67 | ax1.set_xlabel('Time (t/T)') 68 | ax1.set_ylabel('Amplitude h(t)') 69 | ax1.grid(True, alpha=0.3) 70 | ax1.legend(loc='upper right') 71 | ax1.set_xlim(-3, 3) 72 | ax1.set_ylim(0, 2) 73 | 74 | # Format frequency domain plot 75 | ax2.set_xlabel('Normalized Frequency (fT)') 76 | ax2.set_ylabel('Magnitude |H(f)| (dB)') 77 | ax2.grid(True, alpha=0.3) 78 | ax2.legend(loc='upper right') 79 | ax2.set_xlim(-3, 3) 80 | ax2.set_ylim(-60, 5) 81 | 82 | plt.tight_layout() 83 | plt.show() 84 | ``` -------------------------------------------------------------------------------- /content/fundamentals/_fig_1db_compression.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-1db-compression-point 19 | #| echo: false 20 | #| fig-cap: "1dB compression point test showing input vs output power relationship and the definition of P1dB." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define input power range (in dBm) 26 | Pin_dBm = np.linspace(-30, 10, 1000) 27 | Pin_linear = 10**(Pin_dBm/10) # Convert to linear scale (mW) 28 | 29 | # Circuit parameters 30 | alpha1 = 100 # Linear coefficient 31 | alpha3 = -0.2 * alpha1 # Third-order coefficient (negative for compression) 32 | 33 | # Calculate output using nonlinear model (simplified) 34 | # For single tone: Pout = G * Pin * (1 + 3/4 * alpha3/alpha1 * Pin) 35 | # Approximation for demonstration 36 | compression_factor = 1 + 0.75 * (alpha3/alpha1) * Pin_linear 37 | Pout_linear = alpha1 * Pin_linear * compression_factor 38 | Pout_dBm = 10 * np.log10(np.abs(Pout_linear)) 39 | 40 | # Ideal linear response 41 | Pout_ideal_dBm = Pin_dBm + 10*np.log10(alpha1) 42 | 43 | # Find 1dB compression point 44 | gain_actual = Pout_dBm - Pin_dBm 45 | gain_compression = 10*np.log10(alpha1) - gain_actual 46 | idx_1dB = np.argmin(np.abs(gain_compression - 1.0)) 47 | P1dB_in = Pin_dBm[idx_1dB] 48 | P1dB_out = Pout_dBm[idx_1dB] 49 | 50 | # Create the plot 51 | fig, ax = plt.subplots(1, 1) 52 | 53 | # Plot ideal and actual responses 54 | ax.plot(Pin_dBm, Pout_ideal_dBm, 'b--', linewidth=2, label='Ideal Linear Response', alpha=0.7) 55 | ax.plot(Pin_dBm, Pout_dBm, 'r-', linewidth=2.5, label='Actual Nonlinear Response') 56 | 57 | # Mark 1dB compression point 58 | ax.plot(P1dB_in, P1dB_out, 'ko', markersize=8, markerfacecolor='black', 59 | markeredgewidth=2, markeredgecolor='black', label=f'P1dB = {P1dB_in:.1f} dBm') 60 | 61 | # Add vertical and horizontal lines for P1dB 62 | ax.axvline(P1dB_in, color='gray', linestyle=':', alpha=0.7) 63 | ax.axhline(P1dB_out, color='gray', linestyle=':', alpha=0.7) 64 | 65 | # Formatting 66 | ax.set_xlabel('Input Power (dBm)') 67 | ax.set_ylabel('Output Power (dBm)') 68 | #ax.set_title('1dB Compression Point Test', fontsize=14, fontweight='bold') 69 | ax.grid(True, alpha=0.3) 70 | ax.legend(loc='lower right', fontsize=11) 71 | 72 | # Set axis limits for better visualization 73 | ax.set_xlim(-25, 5) 74 | ax.set_ylim(-10, 25) 75 | 76 | plt.show() 77 | ``` 78 | -------------------------------------------------------------------------------- /content/lna/_fig_lna_inductive_degeneration_detailed.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-lna-inductive-degeneration-detailed 19 | #| echo: false 20 | #| fig-cap: "An (almost complete) common-source MOSFET stage with degeneration impedance and cascode." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | #elm.Ground() 31 | src1 = elm.SourceSin().up().label(r'$v_\mathrm{in}$') 32 | elm.Resistor().right().label(r'$R_\mathrm{s}$') 33 | elm.Line().right().length(d.unit/4) 34 | elm.Dot(open=True) 35 | elm.Inductor2(loops=2).right().label(r'$L_\mathrm{match}$') 36 | elm.Capacitor().right().label('dc block') 37 | elm.Line().right().length(d.unit/4).dot() 38 | d.push() 39 | elm.Line().right().length(d.unit/2) 40 | M1 = elm.AnalogNFet(offset_gate=False).label(r'$M_1$', loc='right').reverse().anchor('gate').drop('source') 41 | Ldeg = elm.Inductor2(loops=2).down().label(r'$L_\mathrm{deg}$') 42 | elm.Ground() 43 | 44 | d.pop() 45 | elm.Resistor().down().label(r'$R_\mathrm{bias}$') 46 | elm.Line().down().length(d.unit/4).dot(open=True).label(r'$V_\mathrm{bias1}$', loc='left') 47 | 48 | M2 = elm.AnalogNFet(offset_gate=False).right().at(M1.drain).label(r'$M_2$', loc='right').anchor('source').drop('drain').reverse() 49 | elm.Line().left().length(d.unit/4).at(M2.gate).dot(open=True).label(r'$V_\mathrm{bias2}$', loc='left') 50 | 51 | # load 52 | elm.Line().up().at(M2.drain).length(d.unit/4).dot() 53 | d.push() 54 | elm.Resistor().up().label(r'$R_\mathrm{D}$') 55 | d.pop() 56 | d.push() 57 | elm.Line().left().length(d.unit*3/4) 58 | L1 = elm.Inductor2(loops=2).up().label(r'$L_\mathrm{D}$') 59 | d.pop() 60 | elm.Line().right().length(d.unit*3/4).dot() 61 | d.push() 62 | elm.Line().right().length(d.unit/2).label(r'$v_\mathrm{out}$', loc='right').dot(open=True) 63 | d.pop() 64 | C1 = elm.Capacitor().up().label(r'$C_\mathrm{D}$') 65 | elm.Line().at((L1.end[0]-d.unit/4, L1.end[1])).to((C1.end[0]+d.unit/4, C1.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 66 | 67 | # complete ground of input 68 | elm.Line().at(src1.start).down().toy(Ldeg.end) 69 | elm.Ground() 70 | ``` 71 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_im3_tones.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | negative WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-im3-tones 19 | #| echo: false 20 | #| fig-cap: "Two-tone test showing fundamental frequencies ω₁, ω₂ and third-order intermodulation products (IM3) at 2ω₁-ω₂ and 2ω₂-ω₁." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Create figure with single subplot 26 | fig, ax = plt.subplots(1, 1) 27 | 28 | # Define frequencies (for example) 29 | f1 = 2.40 # GHz 30 | f2 = 2.42 # GHz 31 | df = f2 - f1 # Frequency spacing 32 | 33 | # Calculate IM3 frequencies 34 | f_im3_low = 2*f1 - f2 35 | f_im3_high = 2*f2 - f1 36 | 37 | # Frequency domain spectrum 38 | frequencies = [f_im3_low, f1, f2, f_im3_high] 39 | freq_labels = ['2ω₁-ω₂', 'ω₁', 'ω₂', '2ω₂-ω₁'] 40 | colors = ['red', 'blue', 'blue', 'red'] 41 | amplitudes = [0.3, 1.0, 0.8, 0.3] # Relative amplitudes 42 | line_styles = ['--', '-', '-', '--'] 43 | 44 | # Plot spectral lines 45 | for i, (freq, amp, color, style, label) in enumerate(zip(frequencies, amplitudes, colors, line_styles, freq_labels)): 46 | ax.vlines(freq, 0, amp, colors=color, linestyles=style, linewidth=3, alpha=0.8) 47 | ax.plot(freq, amp, 'o', color=color, markersize=8, markerfacecolor=color, alpha=0.8) 48 | 49 | # Add frequency labels 50 | if 'IM3' not in label: # Fundamental tones 51 | ax.text(freq, amp + 0.1, label, ha='center', va='bottom', color=color) 52 | else: # IM3 products 53 | ax.text(freq, amp + 0.1, f'{label}\n(IM3)', ha='center', va='bottom', color=color) 54 | 55 | # Frequency domain formatting 56 | ax.set_xlabel('Frequency (GHz)') 57 | ax.set_ylabel('Magnitude') 58 | ax.grid(True, alpha=0.3) 59 | ax.set_xlim(f_im3_low - 0.02, f_im3_high + 0.02) 60 | ax.set_ylim(0, 1.2) 61 | 62 | # Add frequency spacing annotations 63 | ax.annotate('', xy=(f1, 0.1), xytext=(f2, 0.1), 64 | arrowprops=dict(arrowstyle='<->', color='black', lw=1.5)) 65 | ax.text((f1+f2)/2, 0.2, f'Δω', ha='center', va='top') 66 | 67 | ax.annotate('', xy=(f_im3_low, 0.1), xytext=(f1, 0.1), 68 | arrowprops=dict(arrowstyle='<->', color='black', lw=1.5)) 69 | ax.text((f1+f_im3_low)/2, 0.2, f'Δω', ha='center', va='top') 70 | 71 | ax.annotate('', xy=(f_im3_high, 0.1), xytext=(f2, 0.1), 72 | arrowprops=dict(arrowstyle='<->', color='black', lw=1.5)) 73 | ax.text((f2+f_im3_high)/2, 0.2, f'Δω', ha='center', va='top') 74 | 75 | plt.show() 76 | ``` 77 | -------------------------------------------------------------------------------- /content/poweramp/_fig_poweramp_differential.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-poweramp-differential 19 | #| echo: false 20 | #| fig-cap: "A generic differential power amplifier with cascode stages for peak voltage handling. Inductors $L1$, $L_2$, and $L_3$ are implemented as a transformer, and form the load and output matching network together with $C_1$ and $C_2$. Note that the load transformer allows a differential to single-ended conversion, as well as a dc block." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | # draw transistors 31 | elm.Line().right(d.unit/4).idot(open=True).label(r'$s_\mathrm{in,p}$', loc='left') 32 | M1 = elm.AnalogNFet(offset_gate=False).anchor('gate').drop('source').label('$M_1$', loc='right').reverse() 33 | elm.Ground() 34 | M2 = elm.AnalogNFet(offset_gate=False).at(M1.drain).anchor('source').drop('gate').label('$M_2$', loc='left') 35 | elm.Line().right(d.unit*2).label(r'$V_\mathrm{bias}$', loc='top') 36 | M4 = elm.AnalogNFet(offset_gate=False).anchor('gate').drop('source').label('$M_4$', loc='right').reverse() 37 | M3 = elm.AnalogNFet(offset_gate=False).anchor('drain').drop('source').label('$M_3$', loc='left') 38 | elm.Ground() 39 | elm.Line().at(M3.gate).right(d.unit/4).dot(open=True).label(r'$s_\mathrm{in,n}$', loc='right') 40 | 41 | # draw load 42 | l1 = elm.Line().up(d.unit/2).at(M2.drain).dot() 43 | l2 = elm.Line().up(d.unit/2).at(M4.drain).dot() 44 | elm.Capacitor().at(l1.end).to(l2.end).label('$C_1$', loc='top') 45 | l3 = elm.Line().up(d.unit).at(l1.end) 46 | l4 = elm.Line().up(d.unit).at(l2.end) 47 | l5 = elm.Line().up(d.unit/2).at(l3.end) 48 | l6 = elm.Line().up(d.unit/2).at(l4.end) 49 | elm.Inductor2(loops=2).at(l5.end).right(d.unit*1.5).label('$L_1$', loc='bottom').dot() 50 | elm.Vdd().label(r'$V_\mathrm{DD}$', loc='top') 51 | elm.Inductor2(loops=2).to(l6.end).label('$L_2$', loc='bottom') 52 | 53 | d.here=l3.end 54 | d.move(dx=d.unit, dy=0) 55 | lout = elm.Inductor2(loops=2).right().label('$L_3$', loc='bottom') 56 | elm.Ground().at(lout.start) 57 | elm.Line().at(lout.end).right(d.unit*1.5).dot() 58 | d.push() 59 | elm.Line().right(d.unit/2).dot(open=True).label(r'$s_\mathrm{out}$', loc='right') 60 | d.pop() 61 | elm.Capacitor().down().label('$C_2$', loc='top') 62 | elm.Ground() 63 | ``` 64 | -------------------------------------------------------------------------------- /content/mixer/_fig_logen_25.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-logen-25 19 | #| echo: false 20 | #| fig-cap: "I/Q generation with a divide-by-2 and 25% duty cycle generation." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.logic as lgc 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | d.push() 31 | elm.Line().right(d.unit/2).idot(open=True).label(r'$v_\mathrm{lo2,p}$', 'left') 32 | ff1 = elm.intcircuits.DFlipFlop().anchor('CLK') 33 | d.pop() 34 | 35 | d.move(dx=0, dy=-d.unit*2) 36 | elm.Line().right(d.unit/2).idot(open=True).label(r'$v_\mathrm{lo2,n}$', 'left') 37 | ff2 = elm.intcircuits.DFlipFlop().anchor('CLK') 38 | 39 | #elm.Line().at(ff1.Q).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,ip}$', 'right') 40 | #elm.Line().at(ff1.Qbar).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,in}$', 'right') 41 | #elm.Line().at(ff2.Q).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,qp}$', 'right') 42 | #elm.Line().at(ff2.Qbar).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,qn}$', 'right') 43 | 44 | elm.Line().at(ff1.Qbar).idot().up(d.unit*1.5) 45 | elm.Line().left(d.unit*1.5) 46 | elm.Line().down().to(ff1.D) 47 | 48 | elm.Line().at(ff2.Qbar).idot().up(d.unit*1.5) 49 | elm.Line().left(d.unit*1.5) 50 | elm.Line().down().to(ff2.D) 51 | 52 | elm.Line().at(ff1.Q).right(d.unit*1.25) 53 | g1 = lgc.And().anchor('in1') 54 | elm.Line().at(g1.out).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,ip}$', 'right') 55 | elm.Line().at(g1.in2).left(d.unit*4/4) 56 | elm.Line().down().toy(ff2.Qbar).dot() 57 | 58 | elm.Line().at(ff2.Q).right(d.unit*1.25) 59 | g2 = lgc.And().anchor('in1') 60 | elm.Line().at(g2.out).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,qp}$', 'right') 61 | elm.Line().at(g2.in2).left(d.unit*2/4) 62 | elm.Line().up().toy(ff1.Q).dot() 63 | 64 | elm.Line().at(ff1.Qbar).right(d.unit*1.25) 65 | g3 = lgc.And().anchor('in1') 66 | elm.Line().at(g3.out).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,in}$', 'right') 67 | elm.Line().at(g3.in2).left(d.unit*3/4) 68 | elm.Line().down().toy(ff2.Q).dot() 69 | 70 | elm.Line().at(ff2.Qbar).right(d.unit*1.25) 71 | g4 = lgc.And().anchor('in1') 72 | elm.Line().at(g4.out).right(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,qn}$', 'right') 73 | elm.Line().at(g4.in2).left(d.unit*1/4) 74 | elm.Line().up().toy(ff1.Qbar).dot() 75 | ``` 76 | -------------------------------------------------------------------------------- /content/intro/_fig_rf_design_considerations.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-rf-design-considerations 19 | #| echo: false 20 | #| fig-cap: "RFIC require careful design considerations and trade-offs (adapted from [@Razavi_RFIC])." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | import matplotlib.patches as patches 25 | 26 | # Create figure and axis 27 | fig, ax = plt.subplots(1, 1, figsize=(5, 5)) 28 | 29 | # Define the disciplines around RF Design 30 | disciplines = [ 31 | 'Noise', 'Power', 'Frequency', 'Linearity', 'Gain', 'Supply\nVoltage' 32 | ] 33 | 34 | # Number of disciplines 35 | n_disciplines = len(disciplines) 36 | 37 | # Center position 38 | center_x, center_y = 0, 0 39 | 40 | # Radius for discipline positioning 41 | radius = 12 42 | 43 | # Calculate angles for each discipline (evenly distributed) 44 | angles = np.linspace(0, 2*np.pi, n_disciplines, endpoint=False) 45 | 46 | # Position disciplines and add double-sided arrows between adjacent ones 47 | discipline_positions = [] 48 | for i, (angle, discipline) in enumerate(zip(angles, disciplines)): 49 | # Calculate position 50 | x = center_x + radius * np.cos(angle) 51 | y = center_y + radius * np.sin(angle) 52 | discipline_positions.append((x, y)) 53 | 54 | # Add discipline text centered around circle points 55 | ax.text(x, y, discipline, ha='center', va='center') 56 | 57 | # Draw double-sided arrows between adjacent disciplines 58 | for i in range(n_disciplines): 59 | # Current and next discipline positions (wrapping around) 60 | x1, y1 = discipline_positions[i] 61 | x2, y2 = discipline_positions[(i + 1) % n_disciplines] 62 | 63 | # Calculate direction vector 64 | dx = x2 - x1 65 | dy = y2 - y1 66 | length = np.sqrt(dx**2 + dy**2) 67 | 68 | # Normalize direction vector 69 | unit_dx = dx / length 70 | unit_dy = dy / length 71 | 72 | # Move arrow endpoints closer to text (reduce gap from text edge) 73 | text_offset = 4 # Distance from text center to arrow tip 74 | arrow_start_x = x1 + text_offset * unit_dx 75 | arrow_start_y = y1 + text_offset * unit_dy 76 | arrow_end_x = x2 - text_offset * unit_dx 77 | arrow_end_y = y2 - text_offset * unit_dy 78 | 79 | # Draw double-sided arrow closer to text 80 | ax.annotate('', xy=(arrow_end_x, arrow_end_y), 81 | xytext=(arrow_start_x, arrow_start_y), 82 | arrowprops=dict(arrowstyle='<->', color='black', lw=1.5)) 83 | 84 | # Set axis properties 85 | ax.set_xlim(-radius*1.5, radius*1.5) 86 | ax.set_ylim(-radius*1.5, radius*1.5) 87 | ax.set_aspect('equal') 88 | ax.axis('off') # Hide axes for cleaner look 89 | 90 | plt.show() 91 | ``` 92 | -------------------------------------------------------------------------------- /content/oscillator/_fig_phase_noise_spectrum.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-phase-noise-spectrum 19 | #| echo: false 20 | #| fig-cap: "Phase noise spectrum of a typical LC oscillator showing characteristic 1/f³ and 1/f² slopes vs. frequency offset from carrier. The flicker noise corner (where the 1/f³ region transitions to the 1/f² region) is around 10 kHz, and the thermal noise floor onset is marked at 10 MHz." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define frequency offset range (Hz) 26 | f_offset = np.logspace(3, 8, 1000) # 1 kHz to 100 MHz 27 | 28 | # Phase noise components - simplified approach 29 | # 1/f³ region (close-in phase noise, dominated by flicker noise) 30 | L_1f3 = -70 - 30 * np.log10(f_offset / 1e3) # -70 dBc/Hz @ 1 kHz with 1/f³ slope 31 | 32 | # 1/f² region (thermal noise region) 33 | L_1f2 = -120 - 20 * np.log10(f_offset / 1e5) # -120 dBc/Hz @ 100 kHz with 1/f² slope 34 | 35 | # Flat noise floor (thermal + quantization noise) 36 | L_floor = -160 * np.ones_like(f_offset) # -160 dBc/Hz noise floor 37 | 38 | # Combine phase noise sources correctly in linear domain 39 | # Convert dBc/Hz to linear, add the noise powers, then convert back to dBc/Hz 40 | 41 | # Convert log (dBc/Hz) to linear scale 42 | L_1f3_linear = 10**(L_1f3 / 10) # Convert dBc/Hz to linear 43 | L_1f2_linear = 10**(L_1f2 / 10) # Convert dBc/Hz to linear 44 | L_floor_linear = 10**(L_floor / 10) # Convert dBc/Hz to linear 45 | 46 | # Add all noise sources in linear domain (RSS - Root Sum Square) 47 | L_total_linear = L_1f3_linear + L_1f2_linear + L_floor_linear 48 | 49 | # Convert back to logarithmic scale (dBc/Hz) 50 | L_total = 10 * np.log10(L_total_linear) 51 | 52 | # Create the plot 53 | fig, ax = plt.subplots(1, 1) 54 | 55 | # Plot phase noise spectrum 56 | ax.semilogx(f_offset, L_total, 'b-', linewidth=3, label='Total Phase Noise') 57 | 58 | # Plot individual components (dashed lines) 59 | ax.semilogx(f_offset, L_1f3, 'r--', linewidth=2, alpha=0.7, label='1/f³ region (flicker)') 60 | ax.semilogx(f_offset, L_1f2, 'g--', linewidth=2, alpha=0.7, label='1/f² region (thermal)') 61 | ax.semilogx(f_offset, L_floor, 'm--', linewidth=2, alpha=0.7, label='Noise floor') 62 | 63 | # Mark characteristic frequencies 64 | ax.axvline(1e4, color='gray', linestyle=':', alpha=0.7, linewidth=1) 65 | ax.axvline(1e6, color='gray', linestyle=':', alpha=0.7, linewidth=1) 66 | 67 | # Formatting 68 | ax.set_xlabel('Frequency Offset from Carrier (Hz)') 69 | ax.set_ylabel('Phase Noise L(f) (dBc/Hz)') 70 | ax.grid(True, alpha=0.3, which='both') 71 | ax.legend(loc='upper right') 72 | 73 | # Set axis limits 74 | ax.set_xlim(1e3, 1e8) 75 | ax.set_ylim(-180, -60) 76 | 77 | plt.tight_layout() 78 | plt.show() 79 | ``` 80 | -------------------------------------------------------------------------------- /content/mixer/_fig_polyphase_filter.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-polyphase-filter 19 | #| echo: false 20 | #| fig-cap: "A two-stage polyphase network." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | 24 | sd.svgconfig.svg2 = False 25 | 26 | with sd.Drawing(canvas='svg') as d: 27 | d.config(unit=2, fontsize=14) 28 | 29 | # stage 1 30 | 31 | R1 = elm.Resistor().right().label(r'$R_1$').idot() 32 | d.move(dx=-d.unit, dy=-d.unit) 33 | R2 = elm.Resistor().right().label(r'$R_1$').dot().idot() 34 | d.move(dx=-d.unit, dy=-d.unit) 35 | R3 = elm.Resistor().right().label(r'$R_1$').dot().idot() 36 | d.move(dx=-d.unit, dy=-d.unit) 37 | R4 = elm.Resistor().right().label(r'$R_1$').dot().idot() 38 | 39 | C1 = elm.Capacitor().at(R1.start).to(R2.end).label(r'$C_1$') 40 | C2 = elm.Capacitor().at(R2.start).to(R3.end).label(r'$C_1$') 41 | C3 = elm.Capacitor().at(R3.start).to(R4.end).label(r'$C_1$') 42 | C4 = elm.Capacitor().at(R4.start).to((R4.end[0], R4.end[1]-d.unit)).label(r'$C_1$') 43 | 44 | elm.Line().right(d.unit/2) 45 | elm.Line().up(d.unit*4).dot() 46 | 47 | # stage 2 48 | 49 | d.here = R1.end 50 | d.move(dx=d.unit, dy=0) 51 | 52 | R5 = elm.Resistor().right().label(r'$R_2$').idot() 53 | d.move(dx=-d.unit, dy=-d.unit) 54 | R6 = elm.Resistor().right().label(r'$R_2$').dot().idot() 55 | d.move(dx=-d.unit, dy=-d.unit) 56 | R7 = elm.Resistor().right().label(r'$R_2$').dot().idot() 57 | d.move(dx=-d.unit, dy=-d.unit) 58 | R8 = elm.Resistor().right().label(r'$R_2$').dot().idot() 59 | 60 | C5 = elm.Capacitor().at(R5.start).to(R6.end).label(r'$C_2$') 61 | C6 = elm.Capacitor().at(R6.start).to(R7.end).label(r'$C_2$') 62 | C7 = elm.Capacitor().at(R7.start).to(R8.end).label(r'$C_2$') 63 | C8 = elm.Capacitor().at(R8.start).to((R8.end[0], R8.end[1]-d.unit)).label(r'$C_2$') 64 | 65 | elm.Line().right(d.unit/2) 66 | elm.Line().up(d.unit*4).dot() 67 | 68 | # input connections 69 | 70 | elm.Line().left(d.unit/2).dot().at(R1.start) 71 | elm.Line().left(d.unit/2).label(r'$V_\mathrm{in,0}$', 'left').dot(open=True) 72 | elm.Line().left(d.unit/2).at(R2.start) 73 | elm.Line().up(d.unit) 74 | 75 | elm.Line().left(d.unit/2).dot().at(R3.start) 76 | elm.Line().left(d.unit/2).label(r'$V_\mathrm{in,180}$', 'left').dot(open=True) 77 | elm.Line().left(d.unit/2).at(R4.start) 78 | elm.Line().up(d.unit) 79 | 80 | # interconnections 81 | 82 | elm.Line().at(R1.end).to(R5.start) 83 | elm.Line().at(R2.end).to(R6.start) 84 | elm.Line().at(R3.end).to(R7.start) 85 | elm.Line().at(R4.end).to(R8.start) 86 | 87 | # output connections 88 | 89 | elm.Line().right(d.unit).at(R5.end).label(r'$V_\mathrm{out,0}$', 'right').dot(open=True) 90 | elm.Line().right(d.unit).at(R6.end).label(r'$V_\mathrm{out,90}$', 'right').dot(open=True) 91 | elm.Line().right(d.unit).at(R7.end).label(r'$V_\mathrm{out,180}$', 'right').dot(open=True) 92 | elm.Line().right(d.unit).at(R8.end).label(r'$V_\mathrm{out,270}$', 'right').dot(open=True) 93 | ``` 94 | -------------------------------------------------------------------------------- /content/mixer/_fig_tank_bandpass.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-tank-bandpass 19 | #| echo: false 20 | #| fig-cap: "First-order bandpass behavior and impedance of an N-path filter." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define frequency range around resonant frequency 26 | f0 = 1.0 # Normalized resonant frequency 27 | f = np.linspace(0.1, 2.0, 1000) 28 | omega = 2 * np.pi * f 29 | 30 | # Tank circuit parameters (normalized) 31 | omega0 = 2 * np.pi * f0 32 | Q = 10 # Quality factor for demonstration 33 | Rsw = 1.0 # Switch resistance (normalized) 34 | 35 | # First-order bandpass impedance (magnitude) 36 | # |Z| = Rsw * Q / sqrt(1 + Q^2 * (omega/omega0 - omega0/omega)^2) 37 | # At resonance, |Z| = Q * Rsw 38 | frequency_ratio = omega / omega0 39 | delta_f = frequency_ratio - 1/frequency_ratio 40 | Z_magnitude = (Q * Rsw) / np.sqrt(1 + (Q * delta_f)**2) 41 | 42 | # Peak impedance value (5.3 * Rsw) 43 | Z_peak = 5.3 * Rsw 44 | Q_actual = Z_peak / Rsw # This gives us Q = 5.3 45 | 46 | # Recalculate with actual Q 47 | Z_magnitude = (Q_actual * Rsw) / np.sqrt(1 + (Q_actual * delta_f)**2) 48 | 49 | # Find 3dB bandwidth points 50 | Z_3dB = Z_peak / np.sqrt(2) 51 | idx_3dB_lower = np.argmin(np.abs(Z_magnitude[f < f0] - Z_3dB)) 52 | idx_3dB_upper = np.argmin(np.abs(Z_magnitude[f > f0] - Z_3dB)) + len(f[f < f0]) 53 | f_3dB_lower = f[idx_3dB_lower] 54 | f_3dB_upper = f[idx_3dB_upper] 55 | bandwidth = f_3dB_upper - f_3dB_lower 56 | 57 | # Create the plot 58 | fig, ax = plt.subplots(1, 1) 59 | 60 | # Plot impedance magnitude 61 | ax.plot(f, Z_magnitude, 'b-', linewidth=2.5, label='|Z(ω)|') 62 | 63 | # Mark peak impedance 64 | ax.plot(f0, Z_peak, 'ro', markersize=8, markerfacecolor='red', 65 | markeredgewidth=2, markeredgecolor='darkred') 66 | ax.annotate(f'5.3×Rsw', xy=(f0, Z_peak), xytext=(f0 + 0.2, Z_peak + 0.3), 67 | color='red', 68 | arrowprops=dict(arrowstyle='->', color='red', lw=1.5)) 69 | 70 | # Mark 3dB bandwidth points 71 | ax.plot([f_3dB_lower, f_3dB_upper], [Z_3dB, Z_3dB], 'go', markersize=6, 72 | markerfacecolor='green', markeredgewidth=1.5, markeredgecolor='darkgreen') 73 | 74 | # Draw bandwidth arrow and label 75 | ax.annotate('', xy=(f_3dB_upper, Z_3dB - 1.2), xytext=(f_3dB_lower, Z_3dB - 1.2), 76 | arrowprops=dict(arrowstyle='<->', color='green', lw=2)) 77 | ax.text((f_3dB_lower + f_3dB_upper)/2, Z_3dB - 1.5, 78 | f'BW ∝ 1/C', 79 | ha='center', va='top', color='green', 80 | bbox=dict(boxstyle='round,pad=0.3', facecolor='lightgreen', alpha=0.7)) 81 | 82 | # Add horizontal dashed line for 3dB level 83 | ax.axhline(y=Z_3dB, color='gray', linestyle='--', alpha=0.6, linewidth=1) 84 | ax.text(0.15, Z_3dB + 0.1, '-3dB', color='gray') 85 | 86 | # Add vertical line at resonance 87 | ax.axvline(x=f0, color='gray', linestyle=':', alpha=0.6, linewidth=1) 88 | 89 | # Formatting 90 | ax.set_xlabel('Frequency (normalized to f₀)') 91 | ax.set_ylabel('Impedance Magnitude |Zin(ω) / Rsw|') 92 | ax.grid(True, alpha=0.3) 93 | 94 | # Set axis limits for better visualization 95 | ax.set_xlim(0.1, 2.0) 96 | ax.set_ylim(0, 6) 97 | 98 | plt.tight_layout() 99 | plt.show() 100 | ``` 101 | -------------------------------------------------------------------------------- /content/intro/_fig_rf_multidisciplinary.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-rf-design-multidisciplinary 19 | #| echo: false 20 | #| fig-cap: "RF design as a multidisciplinary field requiring knowledge from various engineering domains (adapted from [@Razavi_RFIC])." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | import matplotlib.patches as patches 25 | 26 | # Create figure and axis 27 | fig, ax = plt.subplots(1, 1, figsize=(6, 6)) 28 | 29 | # Define the disciplines around RF Design 30 | disciplines = [ 31 | 'Communication\nTheory', 32 | 'Random\nSignals', 33 | 'Transceiver\nArchitectures', 34 | 'CAD Tools\nSimulation', 35 | 'Wireless\nStandards', 36 | 'Multiple\nAccess', 37 | 'Signal\nPropagation', 38 | 'Microwave\nTheory', 39 | 'IC Design' 40 | ] 41 | 42 | # Number of disciplines 43 | n_disciplines = len(disciplines) 44 | 45 | # Center position 46 | center_x, center_y = 0, 0 47 | 48 | # Radius for discipline positioning 49 | radius = 12 50 | 51 | # Calculate angles for each discipline (evenly distributed) 52 | angles = np.linspace(0, 2*np.pi, n_disciplines, endpoint=False) 53 | 54 | # Colors for different disciplines 55 | colors = ['lightblue', 'lightcoral', 'lightgreen', 'lightyellow', 56 | 'lightpink', 'lightcyan', 'lightyellow', 'lavender', 'lightgray', 'lightgoldenrodyellow'] 57 | 58 | # Draw center circle for RF Design 59 | center_circle = patches.Circle((center_x, center_y), 5, 60 | facecolor='orange', linewidth=0, alpha=0.8) 61 | ax.add_patch(center_circle) 62 | ax.text(center_x, center_y, 'RF Design', ha='center', va='center', fontweight='bold') 63 | 64 | # Draw discipline circles and connections 65 | for i, (angle, discipline, color) in enumerate(zip(angles, disciplines, colors)): 66 | # Calculate position 67 | x = center_x + radius * np.cos(angle) 68 | y = center_y + radius * np.sin(angle) 69 | 70 | # Draw discipline circle 71 | discipline_circle = patches.Circle((x, y), 4.5, facecolor=color, linewidth=0, alpha=0.7) 72 | ax.add_patch(discipline_circle) 73 | 74 | # Add discipline text 75 | ax.text(x, y, discipline, ha='center', va='center') 76 | 77 | # Draw connection line from center circle edge to discipline circle edge 78 | dx = x - center_x 79 | dy = y - center_y 80 | norm = np.sqrt(dx**2 + dy**2) 81 | 82 | # Calculate start and end points on circle edges 83 | start_x = center_x + 5 * dx / norm # Edge of center circle (radius 0.8) 84 | start_y = center_y + 5 * dy / norm 85 | end_x = x - 4.5 * dx / norm # Edge of discipline circle (radius 0.6) 86 | end_y = y - 4.5 * dy / norm 87 | 88 | # Draw line connecting the circles 89 | ax.plot([start_x, end_x], [start_y, end_y], 'k-', linewidth=2, alpha=0.6) 90 | 91 | # Add arrow in the middle of the connection line 92 | mid_x = (start_x + end_x) / 2 93 | mid_y = (start_y + end_y) / 2 94 | arrow_dx = 0.15 * dx / norm # Arrow direction and length 95 | arrow_dy = 0.15 * dy / norm 96 | 97 | # Set axis properties 98 | ax.set_xlim(-radius*1.5, radius*1.5) 99 | ax.set_ylim(-radius*1.5, radius*1.5) 100 | ax.set_aspect('equal') 101 | ax.axis('off') # Hide axes for cleaner look 102 | 103 | plt.show() 104 | ``` 105 | -------------------------------------------------------------------------------- /content/mixer/_fig_gilbert_mixer.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-gilbert-mixer 19 | #| echo: false 20 | #| fig-cap: "A Gilbert mixer based on bipolar differential pairs." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | import schemdraw.logic as lgc 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | Q1 = elm.BjtNpn().drop('emitter') 32 | elm.Line().down(d.unit/4) 33 | elm.Line().right(d.unit).dot() 34 | pos1 = d.here 35 | elm.Line().right(d.unit) 36 | elm.Line().up(d.unit/4) 37 | Q2 = elm.BjtNpn().anchor('emitter').theta(0).reverse() 38 | d.here = pos1 39 | Ibias = elm.SourceI().down() 40 | elm.Ground() 41 | elm.Line().left(d.unit/4).dot(open=True).at(Q1.base).label(r'$v_\mathrm{rf,p}$', loc='left') 42 | elm.Line().right(d.unit/4).dot(open=True).at(Q2.base).label(r'$v_\mathrm{rf,n}$', loc='right') 43 | 44 | d.here = Q1.collector 45 | elm.Line().up(d.unit/2).dot() 46 | d.push() 47 | elm.Line().right(d.unit/2) 48 | elm.Line().up(d.unit/4) 49 | Q3 = elm.BjtNpn().anchor('emitter').theta(0).reverse() 50 | d.pop() 51 | elm.Line().left(d.unit/2) 52 | elm.Line().up(d.unit/4) 53 | Q4 = elm.BjtNpn().anchor('emitter').theta(0) 54 | 55 | d.here = Q2.collector 56 | elm.Line().up(d.unit/2).dot() 57 | d.push() 58 | elm.Line().right(d.unit/2) 59 | elm.Line().up(d.unit/4) 60 | Q5 = elm.BjtNpn().anchor('emitter').theta(0).reverse() 61 | d.pop() 62 | elm.Line().left(d.unit/2) 63 | elm.Line().up(d.unit/4) 64 | Q6 = elm.BjtNpn().anchor('emitter').theta(0) 65 | 66 | # load impedance 67 | elm.Line().up(d.unit/4).dot().at(Q4.collector) 68 | d.push() 69 | elm.Line().right().tox(Q6.collector) 70 | elm.Line().down().to(Q6.collector) 71 | d.pop() 72 | elm.Line().up(d.unit/2).dot() 73 | d.push() 74 | elm.Line().right(d.unit/2).dot(open=True).label(r'$v_\mathrm{out,p}$', loc='right') 75 | d.pop() 76 | elm.Line().up(d.unit/4) 77 | Z1 = elm.ResistorIEC().up() 78 | 79 | elm.Line().up(d.unit/2).dot().at(Q5.collector) 80 | d.push() 81 | elm.Line().left().tox(Q3.collector) 82 | elm.Line().down().to(Q3.collector) 83 | d.pop() 84 | elm.Line().up(d.unit/4).dot() 85 | d.push() 86 | elm.Line().left(d.unit/2).dot(open=True).label(r'$v_\mathrm{out,n}$', loc='left') 87 | d.pop() 88 | elm.Line().up(d.unit/4) 89 | Z2 = elm.ResistorIEC().up() 90 | 91 | # lo connections 92 | elm.Line().at(Q3.base).right().tox(Ibias.start).dot() 93 | d.push() 94 | elm.Line().right().to(Q6.base) 95 | d.pop() 96 | elm.Line().down(d.unit/4).dot(open=True).label(r'$v_\mathrm{lo,n}$', loc='left') 97 | 98 | elm.Line().at(Q4.base).left(d.unit/4).dot() 99 | d.push() 100 | elm.Line().left(d.unit/2).dot(open=True).label(r'$v_\mathrm{lo,p}$', loc='left') 101 | d.pop() 102 | elm.Line().down(d.unit) 103 | elm.Line().right(d.unit*4.25) 104 | elm.Line().up(d.unit) 105 | elm.Line().left().to(Q5.base) 106 | 107 | # vdd 108 | elm.Line().at((Z1.end[0]-d.unit/4, Z1.end[1])).to((Z2.end[0]+d.unit/4, Z2.end[1])).label(r'$V_\mathrm{DD}$').linewidth(3) 109 | ``` 110 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_raised_cosine_pulse.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-raised-cosine-pulse 19 | #| echo: false 20 | #| fig-cap: "Raised cosine pulse shaping in time and frequency domain for different roll-off factors α." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Create figure with subplots 26 | fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 5)) 27 | 28 | # Parameters 29 | T = 1.0 # Symbol period 30 | fs = 100 / T # Sampling frequency (high for smooth curves) 31 | t_span = 18 * T # Time span to plot 32 | f_span = 2 / T # Frequency span to plot 33 | 34 | # Time and frequency vectors 35 | t = np.linspace(-t_span/2, t_span/2, int(fs * t_span)) 36 | f = np.linspace(-f_span, f_span, 1000) 37 | 38 | # Roll-off factors to plot 39 | alphas = [0, 0.22] 40 | colors = ['blue', 'green'] 41 | linestyles = ['-', '-'] 42 | 43 | # Plot time domain responses 44 | for i, (alpha, color) in enumerate(zip(alphas, colors)): 45 | # Raised cosine pulse in time domain 46 | # h(t) = (sin(πt/T) / (πt/T)) * (cos(παt/T) / (1 - (2αt/T)²)) 47 | 48 | # Handle special cases to avoid division by zero 49 | h_t = np.zeros_like(t) 50 | 51 | for j, t_val in enumerate(t): 52 | if abs(t_val) < 1e-10: # t ≈ 0 53 | h_t[j] = 1.0 54 | elif alpha > 0 and abs(abs(t_val) - T/(2*alpha)) < 1e-10: # t = ±T/(2α) 55 | h_t[j] = (np.pi/4) * np.sin(np.pi/(2*alpha)) / T 56 | else: 57 | # General case 58 | if abs(t_val) < 1e-10: 59 | sinc_term = 1.0 60 | else: 61 | sinc_term = np.sin(np.pi * t_val / T) / (np.pi * t_val / T) 62 | 63 | cos_term = np.cos(np.pi * alpha * t_val / T) 64 | denom = 1 - (2 * alpha * t_val / T)**2 65 | 66 | if abs(denom) > 1e-10: 67 | h_t[j] = sinc_term * cos_term / denom 68 | else: 69 | h_t[j] = 0 70 | 71 | ax1.plot(t/T, h_t, color=color, linewidth=2.5, 72 | label=f'α = {alpha}', linestyle=linestyles[i]) 73 | 74 | # Plot frequency domain responses 75 | for i, (alpha, color) in enumerate(zip(alphas, colors)): 76 | # Raised cosine spectrum 77 | H_f = np.zeros_like(f) 78 | 79 | for j, f_val in enumerate(f): 80 | f_abs = abs(f_val) 81 | if f_abs <= (1 - alpha) / (2 * T): 82 | H_f[j] = T 83 | elif (1 - alpha) / (2 * T) < f_abs <= (1 + alpha) / (2 * T): 84 | if alpha > 0: 85 | arg = (np.pi * T / alpha) * (f_abs - (1 - alpha) / (2 * T)) 86 | H_f[j] = (T / 2) * (1 + np.cos(arg)) 87 | else: 88 | H_f[j] = 0 89 | else: 90 | H_f[j] = 0 91 | 92 | # Convert to dB (20*log10), handle zero values 93 | H_f_dB = 20 * np.log10(np.maximum(H_f / T, 1e-10)) 94 | ax2.plot(f * T, H_f_dB, color=color, linewidth=2.5, 95 | label=f'α = {alpha}', linestyle=linestyles[i]) 96 | 97 | # Format time domain plot 98 | ax1.set_xlabel('Time (t/T)') 99 | ax1.set_ylabel('Amplitude h(t)') 100 | ax1.grid(True, alpha=0.3) 101 | ax1.legend(loc='upper right') 102 | ax1.set_xlim(-9, 9) 103 | ax1.set_ylim(-0.3, 1.1) 104 | 105 | # Format frequency domain plot 106 | ax2.set_xlabel('Normalized Frequency (fT)') 107 | ax2.set_ylabel('Magnitude |H(f)| (dB)') 108 | ax2.grid(True, alpha=0.3) 109 | ax2.set_xlim(-1, 1) 110 | ax2.set_ylim(-60, 5) 111 | 112 | plt.tight_layout() 113 | plt.show() 114 | ``` 115 | -------------------------------------------------------------------------------- /content/pll/_fig_pll_phase_noise.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-phase-noise 19 | #| echo: false 20 | #| fig-cap: "PLL phase noise contributions at the PLL output showing reference phase noise (increased by 20*log(N) and low-pass shaped), VCO phase noise (high-pass shaped), and total phase noise. The loop bandwidth determines the crossover frequency between reference and VCO phase noise contributions. For effective rejection of the 1/f³ VCO phase noise, a higher-order loop filter (shown is 3rd order) is beneficial." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define frequency offset range (Hz) 26 | f_offset = np.logspace(3, 8, 1000) # 10 Hz to 100 MHz 27 | 28 | # PLL parameters 29 | f_loop = 1e5 # Loop bandwidth = 100 kHz 30 | N = 100 # Division ratio 31 | 32 | # Reference phase noise - unfiltered (free-running reference oscillator) 33 | L_ref_1MHz = -140 # Reference phase noise at 1 MHz in dBc/Hz 34 | L_ref_unfiltered = L_ref_1MHz + 20*np.log10(N) # Scaled by N² (20*log10(N)) 35 | 36 | # Reference phase noise - filtered through PLL (low-pass shaped) 37 | # Use higher order filter for steeper rolloff (3rd order) 38 | H_lp = 1 / (1 + (f_offset/f_loop)**2)**3 # 3rd order low-pass transfer function 39 | L_ref_filtered = L_ref_unfiltered + 10*np.log10(H_lp) 40 | 41 | # VCO phase noise with 1/f³ and 1/f² regions 42 | L_vco_100kHz = -90 # VCO phase noise at 100 kHz in dBc/Hz 43 | f_corner = 1e4 # Corner frequency between 1/f³ and 1/f² regions (10 kHz) 44 | 45 | # Calculate the level at corner frequency for 1/f² region 46 | L_vco_corner_1f2 = L_vco_100kHz - 20*np.log10(f_corner/1e5) # Level at 10 kHz for 1/f² curve 47 | 48 | # 1/f³ region (close-in phase noise, flicker noise) - anchored at corner frequency 49 | L_vco_1f3 = L_vco_corner_1f2 - 30*np.log10(f_offset/f_corner) # 1/f³ slope (30 dB/decade) 50 | 51 | # 1/f² region (thermal noise region) 52 | L_vco_1f2 = L_vco_100kHz - 20*np.log10(f_offset/1e5) # 1/f² slope (20 dB/decade) 53 | 54 | # Combine 1/f³ and 1/f² regions in linear domain 55 | L_vco_1f3_linear = 10**(L_vco_1f3 / 10) 56 | L_vco_1f2_linear = 10**(L_vco_1f2 / 10) 57 | L_vco_combined_linear = L_vco_1f3_linear + L_vco_1f2_linear 58 | L_vco = 10 * np.log10(L_vco_combined_linear) 59 | 60 | # VCO phase noise - high-pass filtered through PLL (inverse of low-pass) 61 | H_hp = (f_offset/f_loop)**6 / (1 + (f_offset/f_loop)**2)**3 # 3rd order high-pass transfer function 62 | L_vco_filtered = L_vco + 10*np.log10(H_hp) 63 | 64 | # Total PLL phase noise - combine filtered reference and filtered VCO noise 65 | L_ref_filtered_linear = 10**(L_ref_filtered / 10) 66 | L_vco_filtered_linear = 10**(L_vco_filtered / 10) 67 | L_total_linear = L_ref_filtered_linear + L_vco_filtered_linear 68 | L_total = 10 * np.log10(L_total_linear) 69 | 70 | # Create the plot 71 | plt.semilogx(f_offset, L_ref_unfiltered * np.ones_like(f_offset), 'b:', linewidth=2, label='Reference phase noise\n(unfiltered)', alpha=0.8) 72 | plt.semilogx(f_offset, L_ref_filtered, 'b--', linewidth=2, label='Reference phase noise\n(low-pass filtered)', alpha=0.8) 73 | plt.semilogx(f_offset, L_vco, 'r:', linewidth=2, label='VCO phase noise\n(unfiltered, 1/f³ + 1/f²)', alpha=0.8) 74 | plt.semilogx(f_offset, L_vco_filtered, 'r--', linewidth=2, label='VCO phase noise\n(high-pass filtered)', alpha=0.8) 75 | plt.semilogx(f_offset, L_total, 'k-', linewidth=3, label='Total PLL phase noise', alpha=0.9) 76 | 77 | # Add vertical line at loop bandwidth 78 | plt.axvline(f_loop, color='gray', linestyle=':', alpha=0.7, linewidth=2) 79 | plt.text(f_loop*1.5, -80, f'Loop BW\n{f_loop/1000:.0f} kHz', fontsize=10, ha='left') 80 | 81 | # Add vertical line at VCO corner frequency 82 | plt.axvline(f_corner, color='red', linestyle=':', alpha=0.5, linewidth=1) 83 | 84 | # Formatting 85 | plt.xlabel('Frequency offset from carrier (Hz)') 86 | plt.ylabel('Phase noise (dBc/Hz)') 87 | plt.grid(True, alpha=0.3) 88 | plt.legend(loc='upper right', fontsize=9) 89 | plt.ylim(-160, -60) 90 | plt.xlim(1e3, 1e8) 91 | 92 | plt.tight_layout() 93 | plt.show() 94 | ``` 95 | -------------------------------------------------------------------------------- /content/fundamentals/_fig_im3_power_sweep.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-im3-power-sweep 19 | #| echo: false 20 | #| fig-cap: "Two-tone IM3 test showing fundamental and IM3 product power vs. input power, with IP3 intercept point definition. Equal input power per tone is assumed." 21 | 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | 25 | # Define input power range per tone (in dBm) 26 | Pin_dBm = np.linspace(-40, 20, 1000) 27 | Pin_linear = 10**(Pin_dBm/10) # Convert to linear scale (mW) 28 | 29 | # Circuit parameters 30 | G_dB = 20 # Small signal gain in dB 31 | G_linear = 10**(G_dB/10) # Linear gain 32 | alpha1 = np.sqrt(G_linear) # Linear coefficient 33 | alpha3 = -0.1 * alpha1 # Third-order coefficient (smaller for realistic IM3) 34 | 35 | # Calculate fundamental output power (per tone) 36 | # For two-tone test: each fundamental experiences compression + cross-modulation 37 | # Simplified model: Pout_fund ≈ G * Pin * (1 + 3/4 * alpha3/alpha1 * Pin_total) 38 | # where Pin_total = 2 * Pin (two equal tones) 39 | Pin_total_linear = 2 * Pin_linear 40 | compression_factor = 1 + 0.75 * (alpha3/alpha1) * Pin_total_linear 41 | Pout_fund_linear = G_linear * Pin_linear * compression_factor 42 | Pout_fund_dBm = 10 * np.log10(np.abs(Pout_fund_linear)) 43 | 44 | # Calculate IM3 output power 45 | # IM3 power is proportional to Pin^3 (third-order effect) 46 | # Simplified: PIM3 ≈ (3/4 * alpha3)^2 * Pin1 * Pin2^2 / alpha1 47 | IM3_coefficient = (0.75 * alpha3)**2 / alpha1 48 | Pout_IM3_linear = np.abs(IM3_coefficient) * Pin_linear * Pin_linear**2 49 | Pout_IM3_dBm = 10 * np.log10(Pout_IM3_linear + 1e-12) # Add small value to avoid log(0) 50 | 51 | # Ideal linear responses (extrapolated) 52 | Pout_fund_ideal_dBm = Pin_dBm + G_dB 53 | Pout_IM3_ideal_dBm = 3 * Pin_dBm + 10 * np.log10(np.abs(IM3_coefficient)) 54 | 55 | # Find IP3 (third-order intercept point) 56 | # IP3 is where extrapolated fundamental and IM3 lines intersect 57 | # This occurs when: Pin + G = 3*Pin + IM3_offset 58 | # Solving: IP3_in = (G - IM3_offset) / 2 59 | IM3_offset = 10 * np.log10(np.abs(IM3_coefficient)) 60 | IP3_in_dBm = (G_dB - IM3_offset) / 2 61 | IP3_out_dBm = IP3_in_dBm + G_dB 62 | 63 | # Create the plot 64 | fig, ax = plt.subplots(1, 1) 65 | 66 | # Plot fundamental tone 67 | valid_fund = (Pout_fund_dBm > -100) & (Pin_dBm <= 5) # Filter values and limit x-axis to 5 dBm 68 | ax.plot(Pin_dBm[valid_fund], Pout_fund_dBm[valid_fund], 'b-', linewidth=2.5, 69 | label='Fundamental (ω₁, ω₂)') 70 | 71 | # Plot IM3 products 72 | valid_IM3 = (Pout_IM3_dBm > -100) & (Pin_dBm <= 0) # Filter IM3 values between -100 and -20 dBm 73 | ax.plot(Pin_dBm[valid_IM3], Pout_IM3_dBm[valid_IM3], 'r-', linewidth=2.5, 74 | label='IM3 (2ω₁-ω₂, 2ω₂-ω₁)') 75 | 76 | # Plot ideal linear extrapolations 77 | ax.plot(Pin_dBm, Pout_fund_ideal_dBm, 'b--', linewidth=2, alpha=0.7, 78 | label='Fundamental (extrapolated)') 79 | ax.plot(Pin_dBm, Pout_IM3_ideal_dBm, 'r--', linewidth=2, alpha=0.7, 80 | label='IM3 (extrapolated)') 81 | 82 | # Mark IP3 point 83 | ax.plot(IP3_in_dBm, IP3_out_dBm, 'ko', markersize=8, markerfacecolor='black', 84 | markeredgewidth=2, markeredgecolor='black') 85 | 86 | # Add vertical and horizontal lines for IP3 87 | ax.axvline(IP3_in_dBm, color='gray', linestyle=':', alpha=0.7, 88 | label=f'IP3 = {IP3_in_dBm:.1f} dBm') 89 | ax.axhline(IP3_out_dBm, color='gray', linestyle=':', alpha=0.7) 90 | 91 | # Add slope annotations 92 | ax.text(-30, -5, '1 dB/dB slope', color='blue', rotation=20, 93 | bbox=dict(boxstyle="round,pad=0.2", facecolor='lightblue', alpha=0.7)) 94 | ax.text(-20, -63, '3 dB/dB slope', color='red', rotation=46, 95 | bbox=dict(boxstyle="round,pad=0.2", facecolor='lightcoral', alpha=0.7)) 96 | 97 | # Formatting 98 | ax.set_xlabel('Input Power per Tone (dBm)') 99 | ax.set_ylabel('Output Power (dBm)') 100 | ax.grid(True, alpha=0.3) 101 | ax.legend(loc='lower right') 102 | 103 | # Set axis limits for better visualization 104 | ax.set_xlim(-40, 20) 105 | ax.set_ylim(-80, 40) 106 | 107 | plt.show() 108 | ``` 109 | -------------------------------------------------------------------------------- /content/pll/_fig_pfd_cp_pll.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-pll-pfd-cp 19 | #| echo: false 20 | #| fig-cap: "Diagram of a PFD-CP PLL. Note the added first-order lowpass which has been added to reduce the CP switching ripple." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | import schemdraw.logic as lgc 25 | 26 | sd.svgconfig.svg2 = False 27 | 28 | with sd.Drawing(canvas='svg') as d: 29 | d.config(unit=2, fontsize=14) 30 | 31 | def draw_cp(pos): 32 | d.here = pos 33 | elm.Vdd() 34 | elm.SourceI().down().label(r'$I_\mathrm{CP}$') 35 | S1 = elm.Switch(contacts=False).down().dot().flip().dot() 36 | S2 = elm.Switch(contacts=False).down().flip().reverse() 37 | elm.SourceI().down().label(r'$I_\mathrm{CP}$') 38 | elm.Ground() 39 | 40 | elm.Line().right(d.unit).at(S1.end).dot() 41 | d.push() 42 | elm.Line().right(d.unit/2) 43 | out_lf = d.here 44 | d.pop() 45 | elm.Resistor().down().label(r'$R_\mathrm{int}$') 46 | elm.Capacitor().down().label(r'$C_\mathrm{int}$') 47 | elm.Ground() 48 | 49 | d.here = S1.end 50 | d.move(dx=-d.unit/4, dy=d.unit/2) 51 | elm.Arrow().left(d.unit/2).reverse() 52 | in1 = d.here 53 | 54 | d.here = S2.start 55 | d.move(dx=-d.unit/4, dy=-d.unit/2) 56 | elm.Arrow().left(d.unit/2).reverse() 57 | in2 = d.here 58 | 59 | return in1, in2, out_lf 60 | 61 | def draw_pfd(pos): 62 | d.here = pos 63 | DFF1 = elm.Ic(pins=[elm.IcPin(name='>', side='left'), elm.IcPin(name='D', side='left'), 64 | elm.IcPin(name='Q', side='right'), elm.IcPin(name='R', side='bottom')], 65 | pinspacing=1).hold() 66 | d.move(dx=0, dy=-d.unit) 67 | DFF2 = elm.Ic(pins=[elm.IcPin(name='>', side='left'), elm.IcPin(name='D', side='left'), 68 | elm.IcPin(name='Q', side='right'), elm.IcPin(name='R', side='bottom')], 69 | pinspacing=1).hold().flip() 70 | 71 | elm.Line().left(d.unit/4).at(DFF1['D']).label('1', 'left') 72 | elm.Line().left(d.unit/4).at(DFF2['D']).label('1', 'left') 73 | elm.Line().left(d.unit/2).at(DFF1['>']) 74 | in_ref = d.here 75 | elm.Line().left(d.unit).at(DFF2['>']) 76 | in_fb = d.here 77 | 78 | elm.Line().at(DFF1['R']).to(DFF2['R']) 79 | 80 | elm.Line().right(d.unit/2).at(DFF1['Q']).dot() 81 | pos1 = d.here 82 | elm.Line().right(d.unit/2) 83 | out1 = d.here 84 | elm.Line().right(d.unit/2).at(DFF2['Q']).dot() 85 | pos2 = d.here 86 | elm.Line().right(d.unit/2) 87 | out2 = d.here 88 | 89 | AND1 = lgc.And().at((d.unit,-d.unit/2)).reverse() 90 | elm.Wire('|-').at(pos1).to(AND1.in1) 91 | elm.Wire('|-').at(pos2).to(AND1.in2) 92 | elm.Line().at(AND1.out).left().tox(DFF1['R']).dot() 93 | 94 | return in_ref, in_fb, out1, out2 95 | 96 | (in_ref, in_fb, out1, out2) = draw_pfd((0,0)) 97 | (in1, in2, out_lf) = draw_cp((d.unit*4,d.unit*1.5)) 98 | 99 | elm.Wire('-|').at(out1).to(in1).label('UP', 'top') 100 | elm.Wire('-|').at(out2).to(in2).label('DOWN', 'bottom') 101 | 102 | d.here = out_lf 103 | elm.Resistor().right().dot().label(r'$R_\mathrm{lp}$', loc='top') 104 | d.push() 105 | elm.Capacitor().down().label(r'$C_\mathrm{lp}$', loc='top') 106 | elm.Ground() 107 | d.pop() 108 | 109 | elm.Line().right(d.unit/2) 110 | 111 | dsp.Oscillator().label('VCO', loc='top', ofst=(0, 0.2)) 112 | dsp.Line().right(d.unit/2).dot() 113 | d.push() 114 | dsp.Line().right(d.unit/2).dot(open=True).label(r'$f_\mathrm{out}$', 'right') 115 | d.pop() 116 | dsp.Line().down(d.unit*3) 117 | dsp.Line().left(d.unit) 118 | dsp.Square().label(':N', loc='center') 119 | dsp.Wire('-|').to(in_fb).label(r'$f_\mathrm{fb} = f_\mathrm{out} / N$', 'bottom') 120 | 121 | elm.Line().at(in_ref).left(d.unit/2).dot(open=True).label(r'$f_\mathrm{ref}$', 'left') 122 | ``` 123 | -------------------------------------------------------------------------------- /content/trx/_fig_trx_superheterodyne.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-trx-superheterodyne 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a super-heterodyne transceiver (TRX) showing the main functional blocks of RX and TX." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | rf_pos=d.here 31 | 32 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{RX}$', 'left') 33 | dsp.Amp() 34 | dsp.Arrow().right(d.unit/4) 35 | dsp.Filter(response='bp').label(r'Image BPF', 'top', ofst=.25) 36 | dsp.Arrow().right(d.unit/4) 37 | rxmix = dsp.Mixer() 38 | dsp.Arrow().right(d.unit/4) 39 | dsp.Filter(response='bp').label(r'IF BPF', 'top', ofst=.25) 40 | dsp.Arrow().right(d.unit/4) 41 | dsp.Amp() 42 | dsp.Line().right(d.unit/4).dot() 43 | 44 | d.push() 45 | dsp.Arrow().down(d.unit/2).at(rxmix.S).reverse().label(r'$f_\mathrm{LO,RX}$') 46 | rxosc = dsp.Oscillator() 47 | dsp.Line().right(d.unit/4).at(rxosc.N) 48 | pll1 = dsp.Square().label('PLL') 49 | d.pop() 50 | 51 | # RX I path 52 | d.push() 53 | dsp.Line().up(d.unit) 54 | dsp.Arrow().right(d.unit/4) 55 | mix1 = dsp.Mixer() 56 | dsp.Arrow().length(d.unit/4) 57 | lpf1 = dsp.Filter(response='lp') 58 | dsp.Arrow().length(d.unit/4) 59 | dsp.Amp() 60 | dsp.Arrow().length(d.unit/2).linestyle('--') 61 | d.pop() 62 | 63 | # RX Q path 64 | d.push() 65 | dsp.Line().down(d.unit) 66 | dsp.Arrow().right(d.unit/4) 67 | mix2 = dsp.Mixer() 68 | dsp.Arrow().length(d.unit/4) 69 | lpf2 = dsp.Filter(response='lp') 70 | dsp.Arrow().length(d.unit/4) 71 | dsp.Amp() 72 | dsp.Arrow().length(d.unit/2).linestyle('--') 73 | d.pop() 74 | 75 | # PLL 76 | d.move(dx=d.unit/4) 77 | iq1 = dsp.Square().label('0/90°') 78 | dsp.Arrow().at(iq1.N).up(d.unit/2) 79 | dsp.Arrow().at(iq1.S).down(d.unit/2) 80 | dsp.Arrow().at(iq1.E).right(d.unit/4).reverse() 81 | osc1 = dsp.Oscillator() 82 | dsp.Line().right(d.unit/4) 83 | pll1 = dsp.Square().label('PLL') 84 | 85 | # Transmitter section (mirrored below) 86 | # Start from beginning of the diagram and move down 87 | d.here = rf_pos 88 | d.move(dx=0, dy=-d.unit*3.5) 89 | 90 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{TX}$', 'left').reverse() 91 | dsp.Amp().reverse() 92 | dsp.Arrow().right(d.unit/4).reverse() 93 | dsp.Filter(response='bp').label(r'Image BPF', 'top', ofst=.25) 94 | dsp.Arrow().right(d.unit/4).reverse() 95 | txmix = dsp.Mixer() 96 | dsp.Arrow().right(d.unit/4).reverse() 97 | dsp.Filter(response='bp').label(r'IF BPF', 'top', ofst=.25) 98 | dsp.Arrow().right(d.unit/4).reverse() 99 | dsp.Amp().reverse() 100 | dsp.Arrow().right(d.unit/4).dot().reverse() 101 | 102 | d.push() 103 | dsp.Arrow().down(d.unit/2).at(txmix.S).reverse().label(r'$f_\mathrm{LO,TX}$') 104 | txosc = dsp.Oscillator() 105 | dsp.Line().right(d.unit/4).at(txosc.N) 106 | pll1 = dsp.Square().label('PLL') 107 | d.pop() 108 | 109 | # TX I path 110 | d.push() 111 | dsp.Line().up(d.unit) 112 | dsp.Line().right(d.unit/4) 113 | mix3 = dsp.Mixer() 114 | dsp.Arrow().length(d.unit/4).reverse() 115 | lpf3 = dsp.Filter(response='lp') 116 | dsp.Arrow().length(d.unit/4).reverse() 117 | dsp.Amp().reverse() 118 | dsp.Arrow().length(d.unit/2).reverse().linestyle('--') 119 | d.pop() 120 | 121 | # TX Q path 122 | d.push() 123 | dsp.Line().down(d.unit) 124 | dsp.Line().right(d.unit/4) 125 | mix4 = dsp.Mixer() 126 | dsp.Arrow().length(d.unit/4).reverse() 127 | lpf4 = dsp.Filter(response='lp') 128 | dsp.Arrow().length(d.unit/4).reverse() 129 | dsp.Amp().reverse() 130 | dsp.Arrow().length(d.unit/2).reverse().linestyle('--') 131 | d.pop() 132 | 133 | # PLL 134 | d.move(dx=d.unit/4) 135 | iq2 = dsp.Square().label('0/90°') 136 | dsp.Arrow().at(iq2.N).up(d.unit/2) 137 | dsp.Arrow().at(iq2.S).down(d.unit/2) 138 | dsp.Arrow().at(iq2.E).right(d.unit/4).reverse() 139 | osc2 = dsp.Oscillator() 140 | dsp.Line().right(d.unit/4) 141 | pll2 = dsp.Square().label('PLL') 142 | 143 | ``` 144 | -------------------------------------------------------------------------------- /content/trx/_fig_trx_block_diagram.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-trx-block 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a typical transceiver (TRX) showing the main functional blocks of RX and TX. The modem provides the digital baseband processing and interfaces to the rest of the system. For implementation options of the RF front-end, see @sec-trx-duplexing." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | rf_pos=d.here 31 | 32 | frontend = dsp.Ic(pins=[dsp.IcPin(side='R'), dsp.IcPin(side='R')], size=(2.5, 8), leadlen=0).label('RF Frontend').anchor('inR2').linestyle(':') 33 | 34 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{RX}$', 'left') 35 | dsp.Amp() 36 | dsp.Line().right(d.unit/4).dot() 37 | 38 | # RX I path 39 | d.push() 40 | dsp.Line().up(d.unit) 41 | dsp.Arrow().right(d.unit/4) 42 | mix1 = dsp.Mixer() 43 | dsp.Arrow().length(d.unit/4) 44 | lpf1 = dsp.Filter(response='lp') 45 | dsp.Arrow().length(d.unit/4) 46 | dsp.Amp() 47 | dsp.Arrow().length(d.unit/4) 48 | adc1 = dsp.Adc().label('ADC') 49 | dsp.Arrow().length(d.unit/4) 50 | dsp.Square().label('DSP') 51 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{RX,I}$', 'right') 52 | 53 | dsp1 = dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], size=(2.5, 12), leadlen=0).anchor('inL2').label('MODEM').linestyle(':') 54 | d.move(dx=2.5, dy=-5.5) 55 | elm.Arrow(double=True).right(d.unit/2).label("user\ndata", 'right') 56 | 57 | d.pop() 58 | 59 | # RX Q path 60 | d.push() 61 | dsp.Line().down(d.unit) 62 | dsp.Arrow().right(d.unit/4) 63 | mix2 = dsp.Mixer() 64 | dsp.Arrow().length(d.unit/4) 65 | lpf2 = dsp.Filter(response='lp') 66 | dsp.Arrow().length(d.unit/4) 67 | dsp.Amp() 68 | dsp.Arrow().length(d.unit/4) 69 | adc2 = dsp.Adc().label('ADC') 70 | dsp.Arrow().length(d.unit/4) 71 | dsp.Square().label('DSP') 72 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{RX,Q}$', 'right') 73 | d.pop() 74 | 75 | # PLL 76 | d.move(dx=d.unit/4) 77 | iq1 = dsp.Square().label('0/90°') 78 | dsp.Arrow().at(iq1.N).up(d.unit/2) 79 | dsp.Arrow().at(iq1.S).down(d.unit/2) 80 | dsp.Arrow().at(iq1.E).right(d.unit/2).reverse().label(r'$f_\mathrm{RX}$') 81 | osc1 = dsp.Oscillator() 82 | dsp.Line().right(d.unit/4) 83 | pll1 = dsp.Square().label('PLL') 84 | dsp.Arrow().right(d.unit/4).reverse().label(r'$f_\mathrm{ref}$', 'right') 85 | 86 | # Transmitter section (mirrored below) 87 | # Start from beginning of the diagram and move down 88 | d.here = rf_pos 89 | d.move(dx=0, dy=-d.unit*3.5) 90 | 91 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{TX}$', 'left').reverse() 92 | dsp.Amp().reverse() 93 | dsp.Arrow().right(d.unit/4).dot().reverse() 94 | 95 | # TX I path 96 | d.push() 97 | dsp.Line().up(d.unit) 98 | dsp.Line().right(d.unit/4) 99 | mix3 = dsp.Mixer() 100 | dsp.Arrow().length(d.unit/4).reverse() 101 | lpf3 = dsp.Filter(response='lp') 102 | dsp.Arrow().length(d.unit/4).reverse() 103 | dsp.Amp().reverse() 104 | dsp.Arrow().length(d.unit/4).reverse() 105 | dac1 = dsp.Adc().label('DAC') 106 | dsp.Arrow().length(d.unit/4).reverse() 107 | dsp.Square().label('DSP') 108 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{TX,I}$', 'right').reverse() 109 | d.pop() 110 | 111 | # TX Q path 112 | d.push() 113 | dsp.Line().down(d.unit) 114 | dsp.Line().right(d.unit/4) 115 | mix4 = dsp.Mixer() 116 | dsp.Arrow().length(d.unit/4).reverse() 117 | lpf4 = dsp.Filter(response='lp') 118 | dsp.Arrow().length(d.unit/4).reverse() 119 | dsp.Amp().reverse() 120 | dsp.Arrow().length(d.unit/4).reverse() 121 | dac2 = dsp.Adc().label('DAC') 122 | dsp.Arrow().length(d.unit/4).reverse() 123 | dsp.Square().label('DSP') 124 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{TX,Q}$', 'right').reverse() 125 | d.pop() 126 | 127 | # PLL 128 | d.move(dx=d.unit/4) 129 | iq2 = dsp.Square().label('0/90°') 130 | dsp.Arrow().at(iq2.N).up(d.unit/2) 131 | dsp.Arrow().at(iq2.S).down(d.unit/2) 132 | dsp.Arrow().at(iq2.E).right(d.unit/2).reverse().label(r'$f_\mathrm{TX}$') 133 | osc2 = dsp.Oscillator() 134 | dsp.Line().right(d.unit/4) 135 | pll2 = dsp.Square().label('PLL') 136 | dsp.Arrow().right(d.unit/4).reverse().label(r'$f_\mathrm{ref}$', 'right') 137 | ``` 138 | -------------------------------------------------------------------------------- /content/trx/_fig_trx_lowif.qmd: -------------------------------------------------------------------------------- 1 | ::: {.content-hidden} 2 | Copyright (C) 2025 Harald Pretl and co-authors (harald.pretl@jku.at) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | ::: 16 | 17 | ```{python} 18 | #| label: fig-trx-lowif 19 | #| echo: false 20 | #| fig-cap: "Block diagram of a low-IF transceiver (TRX) showing the main functional blocks of RX and TX. Note the usage of complex analog and digital baseband filters. Otherwise, the structure is similar to a zero-IF TRX as shown in @fig-trx-block." 21 | import schemdraw as sd 22 | import schemdraw.elements as elm 23 | import schemdraw.dsp as dsp 24 | 25 | sd.svgconfig.svg2 = False 26 | 27 | with sd.Drawing(canvas='svg') as d: 28 | d.config(unit=2, fontsize=14) 29 | 30 | rf_pos=d.here 31 | 32 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{RX}$', 'left') 33 | dsp.Amp() 34 | dsp.Line().right(d.unit/4).dot() 35 | 36 | # RX I path 37 | d.push() 38 | dsp.Line().up(d.unit) 39 | dsp.Arrow().right(d.unit/4) 40 | mix1 = dsp.Mixer() 41 | dsp.Arrow().length(d.unit*2) 42 | dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], size=(1, 5), leadlen=0).anchor('inL2').color('gray') 43 | lpf1 = dsp.Filter(response='bp') 44 | dsp.Arrow().length(d.unit/4) 45 | dsp.Amp() 46 | dsp.Arrow().length(d.unit/4) 47 | adc1 = dsp.Adc().label('ADC') 48 | dsp.Arrow().length(d.unit/4) 49 | dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], size=(1, 5), leadlen=0).anchor('inL2').color('gray') 50 | dsp.Square().label('DSP') 51 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{RX,I}$', 'right') 52 | 53 | dsp1 = dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], size=(2.75, 12), leadlen=0).anchor('inL2').label('MODEM') 54 | 55 | d.pop() 56 | 57 | # RX Q path 58 | d.push() 59 | dsp.Line().down(d.unit) 60 | dsp.Arrow().right(d.unit/4) 61 | mix2 = dsp.Mixer() 62 | dsp.Arrow().length(d.unit*2) 63 | lpf2 = dsp.Filter(response='bp') 64 | dsp.Arrow().length(d.unit/4) 65 | dsp.Amp() 66 | dsp.Arrow().length(d.unit/4) 67 | adc2 = dsp.Adc().label('ADC') 68 | dsp.Arrow().length(d.unit/4) 69 | dsp.Square().label('DSP') 70 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{RX,Q}$', 'right') 71 | d.pop() 72 | 73 | # PLL 74 | d.move(dx=d.unit/4) 75 | iq1 = dsp.Square().label('0/90°') 76 | dsp.Arrow().at(iq1.N).up(d.unit/2) 77 | dsp.Arrow().at(iq1.S).down(d.unit/2) 78 | dsp.Arrow().at(iq1.E).right(d.unit/4).reverse() 79 | osc1 = dsp.Oscillator() 80 | dsp.Line().right(d.unit/4) 81 | pll1 = dsp.Square().label('PLL') 82 | 83 | # Transmitter section (mirrored below) 84 | # Start from beginning of the diagram and move down 85 | d.here = rf_pos 86 | d.move(dx=0, dy=-d.unit*3.5) 87 | 88 | dsp.Arrow().right(d.unit/2).label(r'$s_\mathrm{TX}$', 'left').reverse() 89 | dsp.Amp().reverse() 90 | dsp.Arrow().right(d.unit/4).dot().reverse() 91 | 92 | # TX I path 93 | d.push() 94 | dsp.Line().up(d.unit) 95 | dsp.Line().right(d.unit/4) 96 | mix3 = dsp.Mixer() 97 | dsp.Arrow().length(d.unit*2).reverse() 98 | 99 | dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], size=(1, 5), leadlen=0).anchor('inL2').color('gray') 100 | lpf3 = dsp.Filter(response='bp') 101 | dsp.Arrow().length(d.unit/4).reverse() 102 | dsp.Amp().reverse() 103 | dsp.Arrow().length(d.unit/4).reverse() 104 | dac1 = dsp.Adc().label('DAC') 105 | dsp.Arrow().length(d.unit/4).reverse() 106 | dsp.Ic(pins=[dsp.IcPin(side='L'), dsp.IcPin(side='L'), dsp.IcPin(side='R')], size=(1, 5), leadlen=0).anchor('inL2').color('gray') 107 | dsp.Square().label('DSP') 108 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{TX,I}$', 'right').reverse() 109 | d.pop() 110 | 111 | # TX Q path 112 | d.push() 113 | dsp.Line().down(d.unit) 114 | dsp.Line().right(d.unit/4) 115 | mix4 = dsp.Mixer() 116 | dsp.Arrow().length(d.unit*2).reverse() 117 | lpf4 = dsp.Filter(response='bp') 118 | dsp.Arrow().length(d.unit/4).reverse() 119 | dsp.Amp().reverse() 120 | dsp.Arrow().length(d.unit/4).reverse() 121 | dac2 = dsp.Adc().label('DAC') 122 | dsp.Arrow().length(d.unit/4).reverse() 123 | dsp.Square().label('DSP') 124 | dsp.Arrow().length(d.unit/2).label(r'$s_\mathrm{TX,Q}$', 'right').reverse() 125 | d.pop() 126 | 127 | # PLL 128 | d.move(dx=d.unit/4) 129 | iq2 = dsp.Square().label('0/90°') 130 | dsp.Arrow().at(iq2.N).up(d.unit/2) 131 | dsp.Arrow().at(iq2.S).down(d.unit/2) 132 | dsp.Arrow().at(iq2.E).right(d.unit/4).reverse() 133 | osc2 = dsp.Oscillator() 134 | dsp.Line().right(d.unit/4) 135 | pll2 = dsp.Square().label('PLL') 136 | 137 | ``` 138 | --------------------------------------------------------------------------------