<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://zeptoblog.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://zeptoblog.com/" rel="alternate" type="text/html" /><updated>2026-06-20T09:40:25+00:00</updated><id>https://zeptoblog.com/feed.xml</id><title type="html">Zeptoblog</title><subtitle>An electrical engineering blog about circuits, systems, signal processing and other related technical bits and bytes.</subtitle><entry><title type="html">Compiling ngspice for macOS on Apple Silicon</title><link href="https://zeptoblog.com/2026/06/20/compiling-ngspice-for-macos-on-apple-silicon.html" rel="alternate" type="text/html" title="Compiling ngspice for macOS on Apple Silicon" /><published>2026-06-20T00:00:00+00:00</published><updated>2026-06-20T00:00:00+00:00</updated><id>https://zeptoblog.com/2026/06/20/compiling-ngspice-for-macos-on-apple-silicon</id><content type="html" xml:base="https://zeptoblog.com/2026/06/20/compiling-ngspice-for-macos-on-apple-silicon.html"><![CDATA[<p>ngspice is the circuit simulator behind most open-source analog flows. Recently, I’ve been trying to set such a flow up on an Apple silicon laptop. Getting ngspice running was the first step in getting such a flow up and running on macOS.</p>

<p>The least resistance path to get ngspice working on macOS is <code class="language-plaintext highlighter-rouge">brew install ngspice</code>. However, the version shipped lags ngspice releases and lacks certain features such as OSDI support for Verilog-A models and OpenMP support for multi-threaded operation.</p>

<p>This article walks through the steps for compiling ngspice from source on Apple Silicon, the dependencies and the <code class="language-plaintext highlighter-rouge">./configure</code> options worth knowing about. This has been tested with ngspice-46 on macOS Tahoe 26.5.1 on a MacBook Pro with the M4 Pro chip.</p>

<h1 id="prerequisites">Prerequisites</h1>

<p>The first prerequisite is that the <strong>Xcode command line tools</strong> are installed. This is where we get <code class="language-plaintext highlighter-rouge">clang</code>, <code class="language-plaintext highlighter-rouge">make</code> and header files. Make sure you have run <code class="language-plaintext highlighter-rouge">xcode-select --install</code> first if you haven’t already.</p>

<p>Next, <strong>Homebrew</strong> needs to be in place. We’re going to use it to get a bunch of libraries the ngspice build depends on. You can install it by using the bootstrap script <code class="language-plaintext highlighter-rouge">/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</code>. The rest of the instructions here assume that Homebrew is installed under its default path for Apple Silicon of <code class="language-plaintext highlighter-rouge">/opt/homebrew</code>.</p>

<h1 id="installing-build-dependencies">Installing build dependencies</h1>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>bison flex readline ncurses libomp
brew <span class="nb">install</span> <span class="nt">--cask</span> xquartz
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">flex</code>/<code class="language-plaintext highlighter-rouge">bison</code> are the lexer/parser combination required for ngspice’s grammar engine. The libraries shipped by macOS are too old, and we get the new ones from homebrew. <code class="language-plaintext highlighter-rouge">readline</code> and <code class="language-plaintext highlighter-rouge">ncurses</code> are required to handle the interactive command line and terminal interactions. <code class="language-plaintext highlighter-rouge">libomp</code> is the OpenMP runtime, and is required for multi-core operation.</p>

<p><code class="language-plaintext highlighter-rouge">xquartz</code> is the big one amongst these. It’s a pre-built cask, providing the X11 runtime and installs to <code class="language-plaintext highlighter-rouge">/opt/X11</code>. It’s required to build ngspice with the <code class="language-plaintext highlighter-rouge">--with-x</code> configuration that enables the internal waveform viewer. It’s entirely possible to skip this (and <code class="language-plaintext highlighter-rouge">freetype</code> which enables font rendering for X11) if you intend to use ngspice only in batch mode.</p>

<p>If you ever want to install directly from the git repository, you’ll also need the following additional packages.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>autoconf automake libtool
</code></pre></div></div>

<h1 id="getting-the-ngspice-source">Getting the ngspice source</h1>

<p>You can download ngspice from its <a href="https://sourceforge.net/projects/ngspice/files/ng-spice-rework/">SourceForge</a> repository.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-L</span> <span class="nt">-o</span> ngspice-46.tar.gz <span class="s2">"https://sourceforge.net/projects/ngspice/files/ng-spice-rework/46/ngspice-46.tar.gz/download"</span>
<span class="nb">tar </span>xzf ngspice-46.tar.gz <span class="o">&amp;&amp;</span> <span class="nb">cd </span>ngspice-46
</code></pre></div></div>

<h1 id="configuring-the-build">Configuring the build</h1>

<p>The first step in configuring is to set the environment variables for the compiler and linker flags so they refer to the dependencies we just installed. Homebrew packages aren’t on the default compiler search path, so <code class="language-plaintext highlighter-rouge">CPPFLAGS</code>/<code class="language-plaintext highlighter-rouge">LDFLAGS</code> point clang at them and <code class="language-plaintext highlighter-rouge">PATH</code> makes sure the right flex/bison executables run first.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"/opt/homebrew/opt/bison/bin:/opt/homebrew/opt/flex/bin:</span><span class="nv">$PATH</span><span class="s2">"</span>
<span class="nb">export </span><span class="nv">CPPFLAGS</span><span class="o">=</span><span class="s2">"-I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/ncurses/include -I/opt/homebrew/opt/libomp/include -I/opt/X11/include/freetype2"</span>
<span class="nb">export </span><span class="nv">LDFLAGS</span><span class="o">=</span><span class="s2">"-L/opt/homebrew/opt/libomp/lib -lomp -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/ncurses/lib -L/opt/X11/lib"</span>
</code></pre></div></div>
<p>The tarball used comes with a pre-made <code class="language-plaintext highlighter-rouge">./configure</code>, so we do not need to use <code class="language-plaintext highlighter-rouge">autogen.sh</code> like we would if we were building directly from the repository. There are a few different options for the ngspice configuration:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">--with-x</code> - builds the X11 waveform viewer. Good for observing simulation results directly from the interactive shell</li>
  <li><code class="language-plaintext highlighter-rouge">--enable-xspice</code> - enables the XSPICE extension: event-driven/mixed-signal simulation and code-model plugin system</li>
  <li><code class="language-plaintext highlighter-rouge">--enable-cider</code> - enables the CIDER extension: a coupled numerical device simulator, allowing the simulation of critical devices using physical/numerical models</li>
  <li><code class="language-plaintext highlighter-rouge">--enable-osdi</code> - enables the OSDI loader, an interface to load in Verilog-A compiled models at runtime</li>
  <li><code class="language-plaintext highlighter-rouge">--enable-predictor</code> - enables the predictor algorithm that estimates the next time-step’s solution to speed up transient simulation</li>
  <li><code class="language-plaintext highlighter-rouge">--enable-pss</code> - enables periodic steady-state analysis</li>
  <li><code class="language-plaintext highlighter-rouge">--enable-openmp</code> - enables OpenMP multi-threading for parallel matrix/device evaluation</li>
  <li><code class="language-plaintext highlighter-rouge">--with-readline=yes</code> - uses GNU readline for command-line history and line editing in the interactive shell</li>
  <li><code class="language-plaintext highlighter-rouge">--disable-debug</code> and <code class="language-plaintext highlighter-rouge">CFLAGS="-O2"</code> - drops debug symbols for a faster optimized build and sets the compiler optimization level to the standard <code class="language-plaintext highlighter-rouge">-O2</code></li>
</ul>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure <span class="se">\</span>
  <span class="nt">--with-x</span> <span class="se">\</span>
  <span class="nt">--enable-xspice</span> <span class="se">\</span>
  <span class="nt">--enable-cider</span> <span class="se">\</span>
  <span class="nt">--enable-osdi</span> <span class="se">\</span>
  <span class="nt">--enable-predictor</span> <span class="se">\</span>
  <span class="nt">--enable-pss</span> <span class="se">\</span>
  <span class="nt">--with-readline</span><span class="o">=</span><span class="nb">yes</span> <span class="se">\</span>
  <span class="nt">--enable-openmp</span> <span class="se">\</span>
  <span class="nt">--disable-debug</span> <span class="se">\</span>
  <span class="nt">--prefix</span><span class="o">=</span><span class="s2">"/opt/ngspice"</span> <span class="se">\</span>
  <span class="nv">CFLAGS</span><span class="o">=</span><span class="s2">"-O2"</span>
</code></pre></div></div>

<p>If you’d like to install it under a different directory than the specified <code class="language-plaintext highlighter-rouge">/opt/ngspice/</code> you can change the prefix option.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure <span class="se">\</span>
  ...
  <span class="nt">--prefix</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.local/ngspice"</span> <span class="se">\</span>
  ...
</code></pre></div></div>

<h1 id="compiling-and-installing">Compiling and installing</h1>

<p>After configuring, you can compile ngspice (in parallel) by:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make <span class="nt">-j</span><span class="si">$(</span>sysctl <span class="nt">-n</span> hw.ncpu<span class="si">)</span>
</code></pre></div></div>

<p>You can then install it into the prefix location by:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>make <span class="nb">install</span>
</code></pre></div></div>

<p>The sudo will only be required if you’re installing in a system area.</p>

<p>Make sure that the installation directory is in your path:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">:/opt/ngspice/bin"</span>
</code></pre></div></div>

<h1 id="verifying-the-installation">Verifying the installation</h1>

<p>Run <code class="language-plaintext highlighter-rouge">ngspice --version</code> and you should see:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ ngspice <span class="nt">--version</span>
<span class="k">******</span>
<span class="k">**</span> ngspice-46 : Circuit level simulation program
<span class="k">**</span> Compiled with KLU Direct Linear Solver
<span class="k">**</span> The U. C. Berkeley CAD Group
<span class="k">**</span> Copyright 1985-1994, Regents of the University of California.
<span class="k">**</span> Copyright 2001-2025, The ngspice team.
<span class="k">**</span> Please get your ngspice manual from https://ngspice.sourceforge.io/docs.html
<span class="k">**</span> Please file your bug-reports at http://ngspice.sourceforge.net/bugrep.html
<span class="k">******</span>
</code></pre></div></div>

<p>Better yet, you can launch ngspice and use the <code class="language-plaintext highlighter-rouge">version -f</code> command inside the ngspice shell to display the extensions:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ngspice 1 -&gt; version -f
******
** ngspice-46 : Circuit level simulation program
** Compiled with KLU Direct Linear Solver
** The U. C. Berkeley CAD Group
** Copyright 1985-1994, Regents of the University of California.
** Copyright 2001-2025, The ngspice team.
** Please get your ngspice manual from https://ngspice.sourceforge.io/docs.html
** Please file your bug-reports at http://ngspice.sourceforge.net/bugrep.html
**
** CIDER 1.b1 (CODECS simulator) included
** XSPICE extensions included
** Relevant compilation options (refer to user's manual):
** OpenMP multithreading for BSIM3, BSIM4 enabled
** --enable-predictor
**
******
</code></pre></div></div>

<h1 id="wrapping-up">Wrapping up</h1>

<p>With that, you have a feature-complete ngspice built from source — OSDI, OpenMP, XSPICE and the X11 viewer all compiled in, none of which Homebrew gives you. This is the engine the rest of the flow plugs into: OSDI lets you load Verilog-A compact models (compiled with OpenVAF), OpenMP puts the extra cores to work, and the interactive viewer gives you a quick look at results without leaving the shell. I’ll try to cover most of these in a follow-up post.</p>]]></content><author><name></name></author><category term="analog" /><category term="eda" /><category term="macos" /><summary type="html"><![CDATA[ngspice is the circuit simulator behind most open-source analog flows. Recently, I’ve been trying to set such a flow up on an Apple silicon laptop. Getting ngspice running was the first step in getting such a flow up and running on macOS.]]></summary></entry><entry><title type="html">IHP SG13G2 Analog Flow on WSL - Fedora 43 Update</title><link href="https://zeptoblog.com/2025/12/06/ihp-sg13g2-analog-flow-on-wsl-fedora-43-update.html" rel="alternate" type="text/html" title="IHP SG13G2 Analog Flow on WSL - Fedora 43 Update" /><published>2025-12-06T00:00:00+00:00</published><updated>2025-12-06T00:00:00+00:00</updated><id>https://zeptoblog.com/2025/12/06/ihp-sg13g2-analog-flow-on-wsl-fedora-43-update</id><content type="html" xml:base="https://zeptoblog.com/2025/12/06/ihp-sg13g2-analog-flow-on-wsl-fedora-43-update.html"><![CDATA[<p>I previously had published an article about setting up the analog flow for open-source circuit design tools on WSL, using the default Ubuntu distribution that was available and the SkyWater 130nm PDK.</p>

<p>The Ubuntu version I was using (22.04) was getting too old for development work and I desperately needed an upgrade. It was a pleasant surprise to see that WSL now supported Fedora out of the box, which has been my go-to choice for a distro for a while. I like using the bleeding-edge versions of the development packages, and things breaking occasionally makes life interesting.</p>

<p>This tutorial will provide a guide to install and bring-up the basic toolkit. The installation paths will look like:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">~/analog</code> - root directory for all work</li>
  <li><code class="language-plaintext highlighter-rouge">~/analog/tools</code> - open source EDA software
    <ul>
      <li><code class="language-plaintext highlighter-rouge">~/analog/tools/bin</code> - binaries</li>
      <li><code class="language-plaintext highlighter-rouge">~/analog/tools/src</code> - source files</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">~/analog/pdk</code> - pdk installation</li>
  <li><code class="language-plaintext highlighter-rouge">~/analog/wa</code> - workarea directories for development</li>
</ul>

<h1 id="installation">Installation</h1>

<h2 id="wsl">WSL</h2>

<p>Installing Fedorra under WSL in Windows 11 is pretty straightforward.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wsl <span class="nt">--install</span> FedoraLinux-43
</code></pre></div></div>
<p>You can now use the newly installed WSL distribution by:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wsl ~
</code></pre></div></div>

<p>It’s a good idea to upgrade the packages before continuing any further.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf upgrade
</code></pre></div></div>

<h2 id="tools">Tools</h2>

<p>The toolset is the same as before.</p>

<ul>
  <li>Schematic editor: <code class="language-plaintext highlighter-rouge">xschem</code></li>
  <li>Simulator: <code class="language-plaintext highlighter-rouge">ngspice</code></li>
  <li>VerilogA Model Interface: <code class="language-plaintext highlighter-rouge">openvaf</code></li>
  <li>Layout: <del><code class="language-plaintext highlighter-rouge">magic</code></del> &amp; <code class="language-plaintext highlighter-rouge">klayout</code></li>
</ul>

<h3 id="development-environment">Development Environment</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>gcc g++ gdb make
</code></pre></div></div>
<h3 id="xschem">xschem</h3>

<p>Install prerequisites:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>flex bison
<span class="nb">sudo </span>dnf <span class="nb">install </span>libX11-devel libXrender-devel libXpm-devel
<span class="nb">sudo </span>dnf <span class="nb">install </span>tcl8-devel tk8-devel
<span class="nb">sudo </span>dnf <span class="nb">install </span>libjpeg-turbo-devel
<span class="nb">sudo </span>dnf <span class="nb">install </span>xterm <span class="c"># required for the simulate command</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">tcl8-devel</code> and <code class="language-plaintext highlighter-rouge">tk8-devel</code> packages are chosen for compatibilty with <code class="language-plaintext highlighter-rouge">magic</code>, the Tcl 9.0 versions of these packages (<code class="language-plaintext highlighter-rouge">tcl-devel</code> and <code class="language-plaintext highlighter-rouge">tk-devel</code>) seemed to have a lot of issues with <code class="language-plaintext highlighter-rouge">magic</code> scripts; altough both tools compile and run with the latest versions as well.</p>

<p>Build and install <code class="language-plaintext highlighter-rouge">xschem</code> from the GitHub repository:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/analog/tools/src/xschem<span class="p">;</span> <span class="nb">cd</span> ~/analog/tools/src/xschem
git clone https://github.com/StefanSchippers/xschem.git ./
<span class="c"># build and install</span>
./configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/analog/tools
make <span class="nt">-j</span> 16
make <span class="nb">install</span>
</code></pre></div></div>

<h3 id="ngspice">ngspice</h3>

<p>Install prerequisites:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>autoconf libtool automake
<span class="nb">sudo </span>dnf <span class="nb">install </span>libXaw-devel
</code></pre></div></div>

<p>Build and install <code class="language-plaintext highlighter-rouge">ngspice</code> from the SourceForge repository:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/analog/tools/src/ngspice<span class="p">;</span> <span class="nb">cd</span> ~/analog/tools/src/ngspice
git clone https://git.code.sf.net/p/ngspice/ngspice ./
<span class="c"># build and install</span>
./autogen.sh
<span class="nb">mkdir </span>release<span class="p">;</span> <span class="nb">cd </span>release
../configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/analog/tools <span class="nt">--with-x</span> <span class="nt">--enable-cider</span> <span class="nt">--with-readline</span><span class="o">=</span><span class="nb">yes</span> <span class="nt">--enable-predictor</span> <span class="nt">--enable-openmp</span> <span class="nt">--enable-osdi</span> <span class="nt">--enable-pss</span>
make <span class="nt">-j</span> 16
make <span class="nb">install</span>
</code></pre></div></div>

<h3 id="openvaf">openvaf</h3>

<p>OpenVAF is a Verilog-A compiler that compiles a restricted subset of the language into a OSDI (Open Source Device Interface) model; which can be loaded by the circuit simulators. The IHP SG13G2 PDK requires <code class="language-plaintext highlighter-rouge">openvaf</code> to compile the PSP 103 MOSFET, MOS varactor and 3-terminal resistor models.</p>

<p>Compiling OpenVAF is a challenge. Also, it’s development seems to have gone through a break and a revival, which makes compiling it from source a bit of a higher challenge.</p>

<p>For now, simply download the pre-compiled binary from <a href="https://fides.fe.uni-lj.si/openvaf/download/">here</a>. I chose the latest 0.3 version (<a href="https://fides.fe.uni-lj.si/openvaf/download/openvaf-reloaded-osdi_0.3-31-gf47d557-linux_x64.tar.gz"><code class="language-plaintext highlighter-rouge">	openvaf-reloaded-osdi_0.3-31-gf47d557-linux_x64.tar.gz</code></a> to be exact) and copied the binary to <code class="language-plaintext highlighter-rouge">$HOME/analog/tools/bin</code>.</p>

<h3 id="magic">magic</h3>

<p><code class="language-plaintext highlighter-rouge">magic</code> main branch does not compile successfully for Fedora 43. <code class="language-plaintext highlighter-rouge">termio.h</code> was removed from glibc 2.42 (April 2025) after ~37 years when it was deprecated by the standardization of <code class="language-plaintext highlighter-rouge">termios.h</code> in 1998. Thankfully, the replacements only constitute a small patch, which I hope will be merged to the main branch soon.</p>

<p>I have not detailed the changes here, as the developers seem to be aware fixes are under discussion <a href="https://github.com/RTimothyEdwards/magic/issues/470">here</a>.</p>

<p>Install prerequisites:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>mesa-libGL-devel mesa-libGLU-devel
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> ~/analog/tools/src/magic<span class="p">;</span> <span class="nb">cd</span> ~/analog/tools/src/magic
git clone https://github.com/RTimothyEdwards/magic ./
</code></pre></div></div>

<h4 id="patching-for-fedora-43">Patching for Fedora 43</h4>

<p>Compiling directly seems to cause some issues with old-style function declarations, where their prototypes are declared without the arguments. You need to change the <code class="language-plaintext highlighter-rouge">configure</code> script to the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>( CFLAGS=${CFLAGS:-"-g -std=gnu89"}; export CFLAGS; cd scripts ; ./configure "$@" )
</code></pre></div></div>

<p>Afterwards, you can build and install <code class="language-plaintext highlighter-rouge">magic</code>:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># build and install</span>
./configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/analog/tools
make <span class="nt">-j</span> 16
make <span class="nb">install</span>
</code></pre></div></div>

<h2 id="klayout">klayout</h2>

<p>Install prerequisites:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install </span>qt6-qtbase-devel qt6-qt5compat-devel
<span class="nb">sudo </span>dnf <span class="nb">install </span>qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel
<span class="nb">sudo </span>dnf <span class="nb">install </span>ruby-devel python-devel
<span class="nb">sudo </span>dnf <span class="nb">install </span>libgit2-devel
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> ~/analog/tools/klayout<span class="p">;</span> <span class="nb">cd</span> ~/analog/tools/klayout
git clone https://github.com/KLayout/klayout ./
<span class="c"># build - this will take a while</span>
./build.sh <span class="nt">-option</span> <span class="nt">-j16</span>
</code></pre></div></div>

<p>klayout places all its executables and libraries under a <code class="language-plaintext highlighter-rouge">bin-release</code> folder.
Instead of copying these to <code class="language-plaintext highlighter-rouge">~/analog/tools/bin</code> we’re going to directly refer to them by prepending these to <code class="language-plaintext highlighter-rouge">PATH</code> and <code class="language-plaintext highlighter-rouge">LD_LIBRARY_PATH</code>.</p>

<p>klayout also requires the <code class="language-plaintext highlighter-rouge">gdsfactory</code> python package to run the pcells defined in the pdk.
Install these packages to the default python instance via:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>gdsfactory
pip <span class="nb">install</span> <span class="nt">--upgrade</span> attrs
</code></pre></div></div>

<h2 id="pdk---ihp-sg13g2">PDK - IHP SG13G2</h2>

<p>The IHP 130nm BiCMOS Open Source PDK is available at https://github.com/IHP-GmbH/IHP-Open-PDK. Unlike SKY130 in the open_pdks flow, the PDK does not need to be built and can be used directly after cloning the repository.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/analog/pdk/IHP-Open-PDK<span class="p">;</span> <span class="nb">cd</span> ~/analog/pdk/IHP-Open-PDK
git clone https://github.com/IHP-GmbH/IHP-Open-PDK.git ./
</code></pre></div></div>

<p>After cloning, you also need to populate all the submodules the repository is referencing via:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git submodule update <span class="nt">--init</span> <span class="nt">--recursive</span>
</code></pre></div></div>

<h1 id="workarea-setup">Workarea Setup</h1>

<p>Create a simple workarea:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/analog/wa/dev<span class="p">;</span> <span class="nb">cd</span> ~/analog/wa/dev
</code></pre></div></div>

<p>Create a <code class="language-plaintext highlighter-rouge">source.sh</code> file in this workarea. Source this before running any of the tooling.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># add the tools to the path</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$HOME</span>/analog/tools/bin:<span class="nv">$PATH</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$HOME</span>/analog/tools/src/klayout/bin-release:<span class="nv">$PATH</span>
<span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="nv">$HOME</span>/analog/tools/src/klayout/bin-release:<span class="nv">$LD_LIBRARY_PATH</span>
<span class="c"># some useful environment variables</span>
<span class="nb">export </span><span class="nv">PDK_ROOT</span><span class="o">=</span><span class="nv">$HOME</span>/analog/pdk/share/pdk
<span class="nb">export </span><span class="nv">PDK</span><span class="o">=</span>ihp-sg13g2
<span class="nb">export </span><span class="nv">WA_ROOT</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>
</code></pre></div></div>

<h2 id="xschem-1">xschem</h2>

<p>The PDK comes with an install script that does a couple of things: compile the Verilog-A model libraries and copy the required <code class="language-plaintext highlighter-rouge">xschemrc</code> to the current workarea, and the <code class="language-plaintext highlighter-rouge">.spiceinit</code> file to <code class="language-plaintext highlighter-rouge">$HOME/</code>.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$PDK_ROOT</span>/<span class="nv">$PDK</span>/libs.tech/xschem/install.py
</code></pre></div></div>

<p>I’m not a big fan of cluttering my <code class="language-plaintext highlighter-rouge">$HOME/</code> (unlike in real life), so another option is to place the <code class="language-plaintext highlighter-rouge">.spiceinit</code> file under <code class="language-plaintext highlighter-rouge">$WA_ROOT/simulations/.spiceinit</code>.</p>

<p>You can now start <code class="language-plaintext highlighter-rouge">xschem</code> and the example simulations should work.</p>

<figure><img src="/assets/ihp-sg13g2-analog-flow-on-wsl-fedora-43-update/xschem-screenshot.png" alt="xschem start page" />
  <figcaption>xschem start page</figcaption>
</figure>
<figure><img src="/assets/ihp-sg13g2-analog-flow-on-wsl-fedora-43-update/xschem-sim-screenshot.png" alt="xschem + ngspice simulation environment" />
  <figcaption>xschem + ngspice simulation environment</figcaption>
</figure>

<h3 id="klayout-1">klayout</h3>

<p>You can launch klayout in editor mode by:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>klayout <span class="nt">-e</span>
</code></pre></div></div>

<p>When launching klayout for the first time, you need to import the technology files.
To do this:</p>

<ol>
  <li>Run <em>Tools -&gt; Manage Technologies</em></li>
  <li>Right click on the technology list and choose <em>Import technology</em>
    <ol>
      <li>Choose <code class="language-plaintext highlighter-rouge">$PDK_ROOT/$PDK/libs.tech/klayout/tech/sg13g2.lyt</code></li>
      <li>“sg13g2 - IHP SiGe 130nm Technology” should appear in the technology list</li>
    </ol>
  </li>
  <li>Restart klayout. It should now come with the <em>SG13G2 PDK</em> menu loaded by default.</li>
</ol>]]></content><author><name></name></author><category term="analog" /><summary type="html"><![CDATA[I previously had published an article about setting up the analog flow for open-source circuit design tools on WSL, using the default Ubuntu distribution that was available and the SkyWater 130nm PDK.]]></summary></entry><entry><title type="html">Setting up a DIY NAS Server</title><link href="https://zeptoblog.com/2025/08/10/setting-up-a-diy-nas-server.html" rel="alternate" type="text/html" title="Setting up a DIY NAS Server" /><published>2025-08-10T00:00:00+00:00</published><updated>2025-08-10T00:00:00+00:00</updated><id>https://zeptoblog.com/2025/08/10/setting-up-a-diy-nas-server</id><content type="html" xml:base="https://zeptoblog.com/2025/08/10/setting-up-a-diy-nas-server.html"><![CDATA[<p>I’ve recently upgraded my desktop PC at home to a more modern system. This made my previous desktop redundant but it continued to serve as a media server with its meager 8TB storage until a fateful PSU fan failure. After that day, it sat inactive in my room and we only watched Netflix. On a fateful day, almost two years after the failure, I realized that I was running out of space in the 2 TB SSD in my “new” desktop and decided I needed a solution where I could store data long-term. Instead of buying an expensive and inflexible off-the shelf NAS, it made sense to re-purpose the old beast to a DIY NAS server.</p>

<p>I had a few requirements for this storage solution:</p>

<ul>
  <li>I wanted to use smaller drives in a RAID array instead of a single large-capacity one to safeguard against disk failures.</li>
  <li>I wanted to abstract away the hardware to easily extend the storage in the future.</li>
  <li>I wanted peak performance, rather than enterprise level flexibility.</li>
  <li>I wanted ease of access for the files on the system from various systems in my home.</li>
</ul>

<p>In order to achieve these, I decided to build a Linux system that ran RAID, LVM, an XFS file system and a Samba file server. I had set up something very similar for a laboratory build years ago, so I thought it would be quite straightforward. It wasn’t. The setup procedure is documented in the rest of this article.</p>

<h1 id="raid-array">RAID Array</h1>

<p>I wanted to go for a RAID solution to protect against hardware failures. The reasonable options were RAID1, RAID5 or RAID6 arrays. I found the best compromise between disk usage and safety to be the RAID5 array, which uses a single parity disk to safeguard against a single drive failure.</p>

<p>Before setting up RAID, we first need to create partitions in the drives that will be used to build the array.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ fdisk /dev/sda
</code></pre></div></div>
<p>Inside <code class="language-plaintext highlighter-rouge">fdisk</code>, follow these steps:</p>
<ol>
  <li>Press <code class="language-plaintext highlighter-rouge">g</code> to create a new empty GPT partition table.</li>
  <li>Press <code class="language-plaintext highlighter-rouge">n</code> to create a new partition. Press Enter through the prompts to accept the defaults, which will use the entire disk.</li>
  <li>Press <code class="language-plaintext highlighter-rouge">t</code> to change the partition type.</li>
  <li>Type <code class="language-plaintext highlighter-rouge">raid</code> (for “Linux RAID”) and press Enter.</li>
  <li>Press <code class="language-plaintext highlighter-rouge">w</code> to write the changes to the disk and exit.</li>
</ol>

<p>Repeat this for all disks you want in the array. In my case, I was building with three drives: <code class="language-plaintext highlighter-rouge">/dev/sda</code>, <code class="language-plaintext highlighter-rouge">/dev/sdb</code> and <code class="language-plaintext highlighter-rouge">/dev/sdc</code>. You should be able to verify that these partitions have been created by seeing the <code class="language-plaintext highlighter-rouge">/dev/sd[abc]1</code> appear.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mdadm --create /dev/md0 --level=5 --raid-devices=3 /dev/sda1 /dev/sdb1 /dev/sdc1
</code></pre></div></div>

<p>You can now see the RAID array status by:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mdadm --detail /dev/md0
/dev/md0:
           Version : 1.2
     Creation Time : Sun Aug  3 15:04:36 2025
        Raid Level : raid5
        Array Size : 15627786240 (14.55 TiB 16.00 TB)
...
       Update Time : Mon Aug  4 00:04:07 2025
             State : clean, degraded, recovering
    Active Devices : 2
   Working Devices : 3
    Failed Devices : 0
     Spare Devices : 1
...
    Rebuild Status : 78% complete

              Name : fedora:0  (local to host fedora)
              UUID : c89b4882:2eb89e2e:b46483e8:52939c5c
            Events : 6190

    Number   Major   Minor   RaidDevice State
       0       8        1        0      active sync   /dev/sda1
       1       8       17        1      active sync   /dev/sdb1
       2       8       49        2      spare rebuilding   /dev/sdc1
</code></pre></div></div>
<p>At this point the array is ready to use, but it’s in a <code class="language-plaintext highlighter-rouge">clean, degraded, recovering</code> state. This means that it will not perform until the rebuilding process is finished, and the data on it will not be safe, as a drive failure at this stage can cause a total loss.</p>

<p>The rebuilding process will take <em>long</em>: in my case, it was over 10 hours. During this time the disks will see heavy access and it is a good idea to ensure that they have good ventilation. Halfway through the process, I realized the temperature of my disks had climbed to 60°C because I had all the case fans disconnected.</p>

<p>There are a couple more steps to ensure that the RAID array goes online with the system. I am not sure how much of the following is required, as there are reports that Fedora actually is able to detect the array and bring it online without any intervention. However, running the below worked for me.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mdadm --detail --scan | sudo tee /etc/mdadm.conf
➜  ~ sudo dracut -f
</code></pre></div></div>

<h1 id="lvm">LVM</h1>

<p>The next layer in the storage process is the <strong>L</strong>ogical <strong>V</strong>olume <strong>M</strong>anager. LVM allows a flexible way to manage disk space, acting as a layer of abstraction between the physical disks and actual partitions the operating system uses. It has three components:</p>

<ol>
  <li><strong>P</strong>hysical <strong>V</strong>olumes: PVs are the actual disks or partitions, or in our case, the RAID array.</li>
  <li><strong>V</strong>olume <strong>G</strong>roups: VGs are the storage pools that combine one or more PVs into a large storage space.</li>
  <li><strong>L</strong>ogical <strong>V</strong>olumes: LVs are the actual virtual partitions that get formatted with a file system and mounted to be used by the operating system.</li>
</ol>

<p>The benefit of using LVM is the flexibility it provides beyond the physical constraints of the actual disks. In this case, we might want to a completely new RAID array and have LVs that span across both; or perhaps move an LV from one RAID array to another.</p>

<p>For the flexibility it provides, it is also extremely easy to set up. First label the disks to be used as PVs:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo pvcreate /dev/md0
</code></pre></div></div>

<p>Then, to create the VG, which will only contain <code class="language-plaintext highlighter-rouge">/dev/md0</code> for now:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo vgcreate vg_storage /dev/md0
</code></pre></div></div>

<p>Finally, to create the LV that spans across the entire free space on <code class="language-plaintext highlighter-rouge">/dev/md0</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo lvcreate -l 100%FREE -n lv_storage vg_storage
</code></pre></div></div>

<p>This should make the LV now available at <code class="language-plaintext highlighter-rouge">/dev/vg_storage/lv_storage</code> and <code class="language-plaintext highlighter-rouge">/dev/mapper/vg-storage-lv_storage</code>. At this point, this is a partition, very much like a <code class="language-plaintext highlighter-rouge">/dev/sda1</code> that we can format and mount.</p>

<h1 id="xfs-file-system">XFS File system</h1>

<p>Finally, we get to format our virtual partition and mount it. An important choice here is the file system. The <code class="language-plaintext highlighter-rouge">ext4</code> file system is a good choice, but <code class="language-plaintext highlighter-rouge">xfs</code> is reported to have a slight edge on performance in RAID arrays, due to its awareness of the underlying RAID geometry. The important parameters to know while creating an <code class="language-plaintext highlighter-rouge">xfs</code> file system are the <strong>Stripe Unit</strong>(su) and <strong>Stripe Width</strong>(sw). We use the information from the RAID array to get these:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mdadm --detail /dev/md0

/dev/md0:
           Version : 1.2
     Creation Time : Sun Aug  3 15:04:36 2025
        Raid Level : raid5
...
        Chunk Size : 512K
...
    Number   Major   Minor   RaidDevice State
       0       8        1        0      active sync   /dev/sda1
       1       8       17        1      active sync   /dev/sdb1
       3       8       49        2      active sync   /dev/sdc1
</code></pre></div></div>

<ol>
  <li><strong>Stripe Unit</strong>(<code class="language-plaintext highlighter-rouge">su</code>) is the <strong>Chunk Size</strong>. In our case, this is <code class="language-plaintext highlighter-rouge">512K</code>.</li>
  <li><strong>Stripe Width</strong>(<code class="language-plaintext highlighter-rouge">sw</code>) is a multiplier of <code class="language-plaintext highlighter-rouge">su</code>, and is the number of data disks in a RAID device. The <em>data disks</em> exclude the parity disks, which take up one disk in a RAID5 configuration. In our case, this is 2.</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mkfs.xfs -f -d su=512k,sw=2 /dev/vg_storage/lv_storage
</code></pre></div></div>

<p>Unfortunately, this selection of the RAID geometry is a one-time process during file system creation. Extending the RAID array with another disk will change the geometry (<code class="language-plaintext highlighter-rouge">sw</code> will increase to 3) and the underlying file system will not be optimally sized for it.</p>

<h1 id="mounting-the-file-system">Mounting the File system</h1>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo blkid /dev/vg_storage/lv_storage
/dev/vg_storage/lv_storage: UUID="582846f1-c651-4794-a1d4-064ca3fc8550" BLOCK_SIZE="4096" TYPE="xfs"
</code></pre></div></div>

<p>Add this UUID as a mount point to <code class="language-plaintext highlighter-rouge">/etc/fstab</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#
# /etc/fstab
# Created by anaconda on Sun Aug  3 00:57:19 2025
...
UUID=582846f1-c651-4794-a1d4-064ca3fc8550 /mnt/storage xfs defaults,noatime 0 2
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">noatime</code> is an parameter telling the system not to update the access time of the files stored. This offers a slight performance boost.</p>

<p>Finally, execute all the mounts in the <code class="language-plaintext highlighter-rouge">/etc/fstab</code> by running:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mount -a
</code></pre></div></div>

<h1 id="samba-server">Samba Server</h1>

<p>The last piece of the puzzle is the Samba server. With this, the Windows machines on the network can access the files hosted on the Linux NAS. I wanted to set up two types of shares, a public one anyone in my home network could access; and a private one that would require authentication.</p>

<h2 id="samba-installation">Samba Installation</h2>
<p>Setting up Samba is fairly forward. Install the packages, enable it through the firewall and we’re done.</p>

<p>First install Samba by:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo dnf install samba
</code></pre></div></div>

<p>Enable Samba through the firewall.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo firewall-cmd --get-active-zone
FedoraWorkstation (default)
  interfaces: eno1

➜  ~ sudo firewall-cmd --permanent --zone=FedoraWorkstation --add-service=samba
➜  ~ sudo firewall-cmd --reload
</code></pre></div></div>

<h2 id="public-share">Public Share</h2>

<p>I created the public share under <code class="language-plaintext highlighter-rouge">/mnt/storage/public</code> and created a new <code class="language-plaintext highlighter-rouge">sharepub</code> group, so that the members of this group will have access to the share under Linux as well.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mdkir /mnt/share/public
➜  ~ sudo chown -R nobody:sharepub /mnt/share/public
➜  ~ sudo chmod -R 2775 /mnt/storage/public
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">chmod</code> sets the <code class="language-plaintext highlighter-rouge">setgid</code> flag for the directory, meaning that all files and directories created under it will inherit the group ownership rules.</p>

<p>Edit the Samba configuration file at <code class="language-plaintext highlighter-rouge">/etc/samba/smb.conf</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
[global]
        workgroup = SAMBA
        security = user
...
        map to guest = bad user
...
[public]
        comment = public
        path = /mnt/storage/public
        public = yes
        writable = yes
        browsable = yes
        guest ok = yes
        read only = no
        create mask = 0664
        directory mask = 0775
        force user = nobody
        force group = sharepub
</code></pre></div></div>

<p>Here we define the <code class="language-plaintext highlighter-rouge">[public]</code> share (which will be accessed from Windows clients at <code class="language-plaintext highlighter-rouge">\\ip-address\public</code>) at <code class="language-plaintext highlighter-rouge">path = /mnt/storage/public</code> in the Linux file system. The <code class="language-plaintext highlighter-rouge">guest ok = yes</code> statement tells Samba that we do not need an user to authenticate to be able to access the share. The rest are fairly self explanatory, allowing users to browse, read and write to the folder.</p>

<p>The <code class="language-plaintext highlighter-rouge">create mask = 0664</code> and <code class="language-plaintext highlighter-rouge">directory mask = 0775</code> ensures all users can read these files, but only members of the <code class="language-plaintext highlighter-rouge">sharepub</code> group are allowed to edit them. The <code class="language-plaintext highlighter-rouge">force user = nobody</code> and  <code class="language-plaintext highlighter-rouge">force group = sharepub</code> statements sets the user and the group of newly created files;</p>

<p>The final step on the Linux side is to ensure that SELinux does not block the Samba daemon from accessing the shared folder. Run the following to tag the hierarchy with the <code class="language-plaintext highlighter-rouge">samba_share_t</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo semanage fcontext -a -t samba_share_t "/mnt/storage/public(/.*)?"
➜  ~ sudo restorecon -Rv /mnt/storage/public
</code></pre></div></div>
<p>Restart the samba service:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo systemctl restart smb nmb
</code></pre></div></div>

<p>Finally, on the Windows side, there might be a Group Policy Setting that blocks users to access the public share without authentication. From <code class="language-plaintext highlighter-rouge">gpedit.msc</code> You need to go to <code class="language-plaintext highlighter-rouge">Computer Configuration -&gt; Administrative Templates -&gt; Network -&gt; Lanman Workstation</code> and enable <code class="language-plaintext highlighter-rouge">Enable insecure guest logons</code>.</p>

<p>After following these steps, you should be able to navigate to ``\ip-adress\public` and access the drive, without being prompted for a username or password.</p>

<h2 id="private-share">Private Share</h2>

<p>I created the private share under <code class="language-plaintext highlighter-rouge">/mnt/storage/private</code> and created a new <code class="language-plaintext highlighter-rouge">shareprv</code> group.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo mdkir /mnt/share/private
➜  ~ sudo chown -R cgurleyuk:shareprv /mnt/share/private
➜  ~ sudo chmod -R 2770 /mnt/storage/private
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">/etc/samba/smb.conf</code> is modified to define the new private storage area:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[private]
        comment = private
        path = /mnt/storage/private
        read only = no
        browsable = yes
        writable = yes
        guest ok = no
        valid users = cgurleyuk @shareprv
        create mask = 0660
        directory mask = 0770
</code></pre></div></div>

<p>Here we define <code class="language-plaintext highlighter-rouge">valid users = cgurleyuk @shareprv</code> meaning that the user <code class="language-plaintext highlighter-rouge">cgurleyuk</code> and members of the group <code class="language-plaintext highlighter-rouge">shareprv</code> will be able to access the share. Compared to the public share, the create and directory masks have <code class="language-plaintext highlighter-rouge">0660</code> and <code class="language-plaintext highlighter-rouge">0770</code>, disabling public access on the Linux side.</p>

<p>Finally, to remove SELinux restrictions the new hierarchy is tagged with <code class="language-plaintext highlighter-rouge">samba_share_t</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~  sudo semanage fcontext -a -t samba_share_t "/mnt/storage/private(/.*)?"
➜  ~  sudo restorecon -Rv /mnt/storage/public
</code></pre></div></div>

<p>Restart the samba service:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜  ~ sudo systemctl restart smb nmb
</code></pre></div></div>

<p>After following these steps, you should be able to navigate to ``\ip-adress\private` and access the drive after being prompted for a username and password.</p>

<h2 id="troubleshooting">Troubleshooting</h2>

<p>There are a couple of issues I ran into while trying to access these shares from the Windows server. Firstly, make sure that the <code class="language-plaintext highlighter-rouge">Credential Manager</code> on Windows does not have a login stored. If you have a login stored already, you might run into the issue that Windows prompts for a password each time you try to access the public share as well.</p>

<p>Also, if you already log in as an anonymous user, and then try to access the private drive; you might run into authentication issues as well. Windows establishes a connection to the public share using the anonymous <em>guest</em> user; but since the private share is on the same server and you try to login with a different user account, Windows will reject opening the second connection. This especially is problematic if you map a network drive as the <em>guest</em> user, as there is no way to remove the connection other than simply removing the network drive.</p>

<p>In order to resolve this issue, you need to remove all network drive mappings; clear cached credentials and finally delete all active network connections by:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>net use * /delete
</code></pre></div></div>

<p>After this, you need to login to the private share first, and give your username and password. With the above Samba setup; you should be able to access both the private and public shares with the authenticated user.</p>]]></content><author><name></name></author><category term="home-automation" /><summary type="html"><![CDATA[I’ve recently upgraded my desktop PC at home to a more modern system. This made my previous desktop redundant but it continued to serve as a media server with its meager 8TB storage until a fateful PSU fan failure. After that day, it sat inactive in my room and we only watched Netflix. On a fateful day, almost two years after the failure, I realized that I was running out of space in the 2 TB SSD in my “new” desktop and decided I needed a solution where I could store data long-term. Instead of buying an expensive and inflexible off-the shelf NAS, it made sense to re-purpose the old beast to a DIY NAS server.]]></summary></entry><entry><title type="html">DC Operating Point Annotation on SKY130A and xschem</title><link href="https://zeptoblog.com/2025/01/12/dc-operating-point-annotation-on-sky130a-and-xschem.html" rel="alternate" type="text/html" title="DC Operating Point Annotation on SKY130A and xschem" /><published>2025-01-12T00:00:00+00:00</published><updated>2025-01-12T00:00:00+00:00</updated><id>https://zeptoblog.com/2025/01/12/dc-operating-point-annotation-on-sky130a-and-xschem</id><content type="html" xml:base="https://zeptoblog.com/2025/01/12/dc-operating-point-annotation-on-sky130a-and-xschem.html"><![CDATA[<p>A large majority of analog design time takes place with the DC operating point analysis. Once the bias points for a circuit have been established correctly, it becomes largely trivial to get it to the desired performance. Observing the steady-state node voltages and the operating points of transistors via their small-signal parameters gives complete</p>

<p>The SKY130A open-source analog flow, with ngspice and xschem, supports DC annotations. However, I’ve found that these are largely limited to node voltages, which can be somewhat limiting. A few additional steps are required to get complete information about the operating points of transistors and here I’ll try to explain how to complete the picture.</p>

<h1 id="saving-mos-transistor-operating-points">Saving MOS Transistor Operating Points</h1>

<p>In the SKY130A PDK, MOS transistors are defined as macromodels: parametrizable subcircuits that instantiate the actual transistor device internally. This approach seems to have been chosen to enable automatic model selection via width/length and also to conduct parameter calculations (mainly related to mismatch). A typical model definition looks like the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*  Nmos Model
.subckt  sky130_fd_pr__nfet_01v8  d g s b  mult=1
.param  l = 1 w = 1 nf = 1 ad = 0 as = 0 ...
Msky130_fd_pr__nfet_01v8  d g s b nshort_model l = {l} w = {w} ad = {ad} as = {as} ...

.model nshort_model.1 nmos
+ level = 54 lmin = 8E-6 lmax = 2.02E-5 wmin = 7E-5
+ wmax = 1.01E-3 version = 4.5
...

.model nshort_model.2 nmos
+ level = 54 lmin = 4E-6 lmax = 8E-6 wmin = 7E-6
+ wmax = 1.01E-3 version = 4.5
...

.ends sky130_fd_pr__nfet_01v8

</code></pre></div></div>

<p>Here the line <code class="language-plaintext highlighter-rouge">.subckt  sky130_fd_pr__nfet_01v8  d g s b  mult=1 ...</code> defines the subcircuit for the transistor and the line <code class="language-plaintext highlighter-rouge">Msky130_fd_pr__nfet_01v8  d g s b nshort_model l = {l} w = {w} ...</code> defines the transistor instantiation itself. What follows are numbered model definitions starting with <code class="language-plaintext highlighter-rouge">.model nshort_model.1 nmos</code> and featuring the <code class="language-plaintext highlighter-rouge">lmin, lmax, wmin, wmax</code> parameters to define the geometry range the model is valid for.</p>

<p>When this device is netlisted through xschem, it will express that it is a subcircuit instantiation by prefixing it with an <code class="language-plaintext highlighter-rouge">X</code>. A typical device instantiation will look like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>XM3 von vip vtail vss sky130_fd_pr__nfet_01v8 L=0.5 W=10 nf=1 ...
</code></pre></div></div>

<p>ngspice does not save the evaluated operating point information for these models (such as id, gm, vdsat) by default. It has to be instructed to save these parameters if you want to annotate them. This is achieved by a save command in the netlist:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.save @m.XDUT.XM3.msky130_fd_pr__nfet_01v8[id]
.save @m.XDUT.XM3.msky130_fd_pr__nfet_01v8[gm]
...
</code></pre></div></div>

<p>The format of the save command generally follows: <code class="language-plaintext highlighter-rouge">.save @m.{path}.msky130_fd_pr__{model}[{param}]</code>. Here <code class="language-plaintext highlighter-rouge">{path}</code> refers to the full path to the device subcircuit. In the above example, the device is <code class="language-plaintext highlighter-rouge">XM3</code> which is in an amplifier <code class="language-plaintext highlighter-rouge">XDUT</code>, so the full path is <code class="language-plaintext highlighter-rouge">XDUT.XM3</code>. The <code class="language-plaintext highlighter-rouge">{model}</code> refers to the actual model of the device, and in the example, it is <code class="language-plaintext highlighter-rouge">nfet_01v8</code>. Generally, the model name is printed on the symbol in xschem. Finally, <code class="language-plaintext highlighter-rouge">{param}</code> is the operating point information to be saved.</p>

<p>When the netlist is run with these save commands, running a <code class="language-plaintext highlighter-rouge">print all</code> command will reveal that the operating point information is saved for the device:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ngspice 533 -&gt; print all
...
@m.xdut.xm3.msky130_fd_pr__nfet_01v8[id] = 9.579547e-6
@m.xdut.xm3.msky130_fd_pr__nfet_01v8[gm] = 2.409997e-4
...
</code></pre></div></div>

<h2 id="automating-save-commands">Automating .save Commands</h2>

<p>It can get quite unwieldy to write <code class="language-plaintext highlighter-rouge">.save</code> commands for a large number of devices. A simple Python script makes it easy to generate all the save commands for all transistors in a netlist. It reads in a <code class="language-plaintext highlighter-rouge">.spice</code> netlist file and generates a <code class="language-plaintext highlighter-rouge">.save</code> file that has all the save commands for all devices and the specified parameters.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">block</span> <span class="o">=</span> <span class="sh">'</span><span class="s">amp</span><span class="sh">'</span>
<span class="n">testbench</span> <span class="o">=</span> <span class="sh">'</span><span class="s">dc</span><span class="sh">'</span>
<span class="n">subcircuit</span> <span class="o">=</span> <span class="sh">'</span><span class="s">xdut</span><span class="sh">'</span>
<span class="n">params</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">gm</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">gds</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">vdsat</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">vth</span><span class="sh">'</span><span class="p">]</span>

<span class="n">fname_spice</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">'</span><span class="si">{</span><span class="n">block</span><span class="si">}</span><span class="s">/simulation/tb_</span><span class="si">{</span><span class="n">block</span><span class="si">}</span><span class="s">_</span><span class="si">{</span><span class="n">testbench</span><span class="si">}</span><span class="s">.spice</span><span class="sh">'</span>
<span class="n">fname_save</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">'</span><span class="si">{</span><span class="n">block</span><span class="si">}</span><span class="s">/simulation/tb_</span><span class="si">{</span><span class="n">block</span><span class="si">}</span><span class="s">_</span><span class="si">{</span><span class="n">testbench</span><span class="si">}</span><span class="s">.save</span><span class="sh">'</span>

<span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">fname_spice</span><span class="p">,</span> <span class="sh">'</span><span class="s">r</span><span class="sh">'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f_spice</span><span class="p">:</span>
    <span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="n">fname_save</span><span class="p">,</span> <span class="sh">'</span><span class="s">w</span><span class="sh">'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f_save</span><span class="p">:</span>
        <span class="n">lines</span> <span class="o">=</span> <span class="n">f_spice</span><span class="p">.</span><span class="nf">readlines</span><span class="p">()</span>

        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">line</span><span class="p">.</span><span class="nf">startswith</span><span class="p">(</span><span class="sh">'</span><span class="s">XM</span><span class="sh">'</span><span class="p">):</span>
                <span class="n">tokens</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="nf">split</span><span class="p">()</span>
                <span class="n">name</span> <span class="o">=</span> <span class="n">tokens</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
                <span class="n">model</span> <span class="o">=</span> <span class="n">tokens</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span>

                <span class="k">for</span> <span class="n">param</span> <span class="ow">in</span> <span class="n">params</span><span class="p">:</span>
                    <span class="n">f_save</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">.save @m.</span><span class="si">{</span><span class="n">subcircuit</span><span class="si">}</span><span class="s">.</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="sh">'</span>
                                 <span class="sa">f</span><span class="sh">'</span><span class="s">.m</span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s">[</span><span class="si">{</span><span class="n">param</span><span class="si">}</span><span class="s">]</span><span class="se">\n</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div></div>

<p>Unfortunately, this script is quite limited in scope. It only works for transistors instantiated within a single subcircuit (named via the <code class="language-plaintext highlighter-rouge">subcircuit = 'xdut'</code>) line. A more generic version could traverse the entire SPICE netlist, build a tree out of all the subcircuit instantiations and populate the <code class="language-plaintext highlighter-rouge">.save</code> file.</p>

<p>Finally, this <code class="language-plaintext highlighter-rouge">.save</code> file can be included in your netlist via an <code class="language-plaintext highlighter-rouge">.include tb_{block}_{testbench}.save</code> command, keeping the actual netlist definition uncluttered.</p>

<h1 id="dc-operating-point-annotation-in-xschem">DC Operating Point Annotation in xschem</h1>

<p>Default symbols for SKY130A devices in xschem already come with text boxes that annotate some parameters like id, gm, vds and vgs. These are quite useful ss it is but an extension with some additional small-signal parameters gives  more complete information about the device’s operating condition.</p>

<p>This can be achieved by editing the device symbol file through xschem (even though you’ll need write permissions to the pdk area) or directly editing the text file under <code class="language-plaintext highlighter-rouge">$PDK_ROOT/$PDK/libs.tech/xschem/sky130_fd_pr/{device}.sym</code>. These symbols will already have some examples that can be extended, but the general format for the contents of a text box looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcleval(vgs=[to_eng \{@#1:spice_get_voltage - @#2:spice_get_voltage \}]
vds=[to_eng \{@#0:spice_get_voltage - @#2:spice_get_voltage \}]
vdsat=[to_eng [ngspice::get_node v(\\@m.$\{path\}@spiceprefix@name\\.msky130_fd_pr__@model\\\\\\[vdsat\\\\])]] 
gds=[to_eng [ngspice::get_node \\@m.$\{path\}@spiceprefix@name\\.msky130_fd_pr__@model\\\\\\[gds\\\\]]] )
</code></pre></div></div>

<p>When the schematic is annotated, these boxes get evaluated and pull the operating point information saved in the raw file. This setup looks like this in xschem, and is what I currently use:</p>

<figure><img src="/assets/dc-operating-point-annotation-on-sky130a-and-xschem/xschem-screenshot.png" alt="xschem DC annotation of transistor operating points" />
  <figcaption>xschem DC annotation of transistor operating points</figcaption>
</figure>]]></content><author><name></name></author><category term="analog" /><summary type="html"><![CDATA[A large majority of analog design time takes place with the DC operating point analysis. Once the bias points for a circuit have been established correctly, it becomes largely trivial to get it to the desired performance. Observing the steady-state node voltages and the operating points of transistors via their small-signal parameters gives complete]]></summary></entry><entry><title type="html">SKY130A Analog Flow on WSL</title><link href="https://zeptoblog.com/2025/01/04/sky130a-analog-flow-on-wsl.html" rel="alternate" type="text/html" title="SKY130A Analog Flow on WSL" /><published>2025-01-04T00:00:00+00:00</published><updated>2025-01-04T00:00:00+00:00</updated><id>https://zeptoblog.com/2025/01/04/sky130a-analog-flow-on-wsl</id><content type="html" xml:base="https://zeptoblog.com/2025/01/04/sky130a-analog-flow-on-wsl.html"><![CDATA[<p>This tutorial serves as documentation on the installation procedure for the SKY130A PDK and the required tooling for analog design under Windows Subsystem for Linux (WSL).
The tools tend to come with a bunch of bugs.
Therefore, in this tutorial, I’ve opted to go for their bleeding edge, rather than older distro versions.
The software will be installed under <code class="language-plaintext highlighter-rouge">~/tools</code> and the pdk will be installed under <code class="language-plaintext highlighter-rouge">~/pdk</code>.</p>

<h1 id="wsl">WSL</h1>

<p>Use a Windows terminal to run:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wsl <span class="nt">--install</span> <span class="nt">-d</span> Ubuntu-24.04
wsl
</code></pre></div></div>

<p>This will drop you into the Linux terminal.
You can now update the installed distribution by running:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt upgrade
</code></pre></div></div>

<h1 id="tools">Tools</h1>

<p>Currently the tools used are:</p>

<ul>
  <li>Schematic editor: xschem</li>
  <li>Simulator: ngspice</li>
  <li>Layout: magic &amp; klayout</li>
</ul>

<h2 id="development-environment-installation">Development Environment Installation</h2>

<p>Since most of the tools are going to be built from source, we first need to install basic build tooling</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>gcc g++ gdb make
</code></pre></div></div>

<p><strong>TODO:</strong> There might be missing items here.</p>

<h2 id="xschem">xschem</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> ~/tools/xschem<span class="p">;</span> <span class="nb">cd</span> ~/tools/xschem
git clone https://github.com/StefanSchippers/xschem.git ./
<span class="c"># install prerequisites</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>tcl-dev tk-dev libx11-dev 
<span class="nb">sudo </span>apt <span class="nb">install </span>libxrender-dev libx11-xcb-dev 
<span class="nb">sudo </span>apt <span class="nb">install </span>libcairo2-dev bison libxpm-dev libjpeg-dev
<span class="c"># build and install</span>
./configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/tools
make
make <span class="nb">install</span>
</code></pre></div></div>

<h2 id="ngspice">ngspice</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> ~/tools/ngspice<span class="p">;</span> <span class="nb">cd</span> ~/tools/ngspice
git clone https://git.code.sf.net/p/ngspice/ngspice ./
<span class="c"># install prerequisites</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>autoconf libtool automake 
<span class="nb">sudo </span>apt <span class="nb">install </span>libxaw7-dev bison flex libreadline-dev
<span class="c"># build and install</span>
./autoconf.sh
<span class="nb">mkdir </span>release<span class="p">;</span> <span class="nb">cd </span>release
../configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/tools <span class="nt">--with-x</span> <span class="nt">--enable-cider</span> <span class="nt">--enable-predictor</span> <span class="nt">--enable-openmp</span> <span class="nt">--enable-osdi</span> <span class="nt">--enable-pss</span>
make <span class="nt">-j</span> 16
make <span class="nb">install</span>
</code></pre></div></div>

<h2 id="magic">magic</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> ~/tools/magic<span class="p">;</span> <span class="nb">cd</span> ~/tools/magic
git clone https://github.com/RTimothyEdwards/magic ./
<span class="c"># install prerequisites</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>m4 python3 libx11-dev tcl-dev tk-dev 
<span class="nb">sudo </span>apt <span class="nb">install </span>libcairo2-dev mesa-common-dev 
<span class="nb">sudo </span>apt <span class="nb">install </span>libgl-dev libglu1-mesa-dev zlib1g-dev
<span class="c"># build and install</span>
./configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/tools
make <span class="nt">-j</span> 16
make <span class="nb">install</span>
</code></pre></div></div>

<h2 id="klayout">klayout</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> ~/tools/klayout<span class="p">;</span> <span class="nb">cd</span> ~/tools/klayout
git clone https://github.com/KLayout/klayout ./
<span class="c"># install prerequisites</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>qt5-default qttools5-dev libqt5xmlpatterns5-dev
<span class="nb">sudo </span>apt <span class="nb">install </span>qtmultimedia5-dev libqt5multimediawidgets5 libqt5svg5-dev
<span class="nb">sudo </span>apt <span class="nb">install </span>ruby ruby-dev
<span class="nb">sudo </span>apt <span class="nb">install </span>python3 python3-dev
<span class="nb">sudo </span>apt <span class="nb">install </span>zlib1g-dev
<span class="nb">sudo </span>apt <span class="nb">install </span>libgit2-dev
<span class="c"># build - this will take a while</span>
./build.sh
</code></pre></div></div>

<p>klayout places all its executables and libraries under a <code class="language-plaintext highlighter-rouge">bin-release</code> folder.
Instead of copying these to <code class="language-plaintext highlighter-rouge">~/tools/bin</code> we’re going to directly refer to them by adding the build artifacts to <code class="language-plaintext highlighter-rouge">PATH</code> and <code class="language-plaintext highlighter-rouge">LD_LIBRARY_PATH</code>.</p>

<p>klayout also requires the <code class="language-plaintext highlighter-rouge">gdsfactory</code> python package to run the pcells defined in the pdk.
Install these packages to the default python instance via:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>gdsfactory
pip <span class="nb">install</span> <span class="nt">--upgrade</span> attrs
</code></pre></div></div>

<h1 id="pdk">PDK</h1>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mdkir ~/pdk
mkidr ~/pdk/open_pdks<span class="p">;</span> <span class="nb">cd</span> ~/pdk/open_pdks
git clone https://github.com/RTimothyEdwards/open_pdks ./
<span class="c"># build and install</span>
./configure <span class="nt">--prefix</span><span class="o">=</span><span class="nv">$HOME</span>/pdk <span class="nt">--enable-sky130-pdk</span> <span class="nt">--with-sky130-variants</span><span class="o">=</span>A
make <span class="nt">-j</span> 16
make <span class="nb">install</span>
</code></pre></div></div>

<h1 id="workarea-setup">Workarea Setup</h1>

<h2 id="mvp-experimentation-workarea">MVP Experimentation Workarea</h2>

<p>Create a simple workarea</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> ~/wa<span class="p">;</span> <span class="nb">cd</span> ~/wa
</code></pre></div></div>

<p>Create a <code class="language-plaintext highlighter-rouge">source.sh</code> file in this workarea. We source this before running any of the tooling.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># add the tooling to the path</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$HOME</span>/tools/bin:<span class="nv">$PATH</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$HOME</span>/tools/klayout/bin-release:<span class="nv">$PATH</span>
<span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="nv">$HOME</span>/tools/klayout/bin-release:<span class="nv">$LD_LIBRARY_PATH</span>
<span class="c"># define some useful environment variables</span>
<span class="nb">export </span><span class="nv">PDK_ROOT</span><span class="o">=</span><span class="nv">$HOME</span>/pdk/share/pdk
<span class="nb">export </span><span class="nv">PDK</span><span class="o">=</span>sky130A
<span class="nb">export </span><span class="nv">WA_ROOT</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>
</code></pre></div></div>

<h3 id="xschem-1">xschem</h3>

<p>Copy <code class="language-plaintext highlighter-rouge">xschemrc</code> into the local workarea and modify it to look for the correct location for the SKY130A libraries.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> <span class="nv">$HOME</span>/pdk/share/pdk/sky130A/libs.tech/xschem/xschemrc ./
append XSCHEM_LIBRARY_PATH :<span class="k">${</span><span class="nv">PDK_ROOT</span><span class="k">}</span>/<span class="k">${</span><span class="nv">PDK</span><span class="k">}</span>/libs.tech/xschem
</code></pre></div></div>

<p>You can now start the schematic editor by:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xschem
</code></pre></div></div>

<h3 id="magic-1">magic</h3>

<p>Copy the <code class="language-plaintext highlighter-rouge">sky130A.magicrc</code> file into the local workarea:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> <span class="nv">$PDK_ROOT</span>/<span class="nv">$PDK</span>/libs.tech/magic/sky130A.magicrc ./.magicrc
</code></pre></div></div>

<p>You can now start the layout editor by:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>magic
</code></pre></div></div>

<h3 id="klayout-1">klayout</h3>

<p>You can launch klayout in editor mode by:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>klayout <span class="nt">-e</span>
</code></pre></div></div>

<p>When launching klayout for the first time, you need to import the technology files.
To do this:</p>

<ol>
  <li>Run <em>Tools -&gt; Technology Manager</em></li>
  <li>Right click on the technology list and choose <em>Import technology</em>
    <ol>
      <li>Choose <code class="language-plaintext highlighter-rouge">~/pdk/share/pdk/sky130A/libs.tech/klayout/tech/sky130A.lyt</code></li>
      <li>sky130 - SkyWater 130nm technology should appear in the technology list</li>
    </ol>
  </li>
  <li>Change the paths to point to the correct locations
    <ol>
      <li>Change <em>Base path</em> to <code class="language-plaintext highlighter-rouge">~/pdk/share/pdk/sky130A/libs.tech/klayout</code></li>
      <li>Change <em>Layer properties</em> to <code class="language-plaintext highlighter-rouge">tech/sky130A.lyp</code></li>
    </ol>
  </li>
  <li>Restart klayout. It should now come with the <em>Efabless sky130</em> menu loaded by default.</li>
</ol>]]></content><author><name></name></author><category term="analog" /><summary type="html"><![CDATA[This tutorial serves as documentation on the installation procedure for the SKY130A PDK and the required tooling for analog design under Windows Subsystem for Linux (WSL). The tools tend to come with a bunch of bugs. Therefore, in this tutorial, I’ve opted to go for their bleeding edge, rather than older distro versions. The software will be installed under ~/tools and the pdk will be installed under ~/pdk.]]></summary></entry><entry><title type="html">Colored Noise: Frequency-Domain Filtering</title><link href="https://zeptoblog.com/2024/04/21/colored-noise-frequency-domain-filtering.html" rel="alternate" type="text/html" title="Colored Noise: Frequency-Domain Filtering" /><published>2024-04-21T00:00:00+00:00</published><updated>2024-04-21T00:00:00+00:00</updated><id>https://zeptoblog.com/2024/04/21/colored-noise-frequency-domain-filtering</id><content type="html" xml:base="https://zeptoblog.com/2024/04/21/colored-noise-frequency-domain-filtering.html"><![CDATA[<p>Noise signals that display specific frequency spectra are attributed colors, an
allusion to the color of visible light showing the same. Like white light,
white noise has uniform power spectral density, containing the same power in all
frequency intervals. Pink noise has a power spectral density inversely
proportional to frequency, showing a \(1/f\) spectrum and a pinkish hue in the
visible spectrum. Brown(ian) noise is named as such because it’s the product of
Brownian motion, but actually, it is colored red due to its amplified
low-frequency content.</p>

<p>Methods to generate colored noise sources for time-domain simulations generally
involve generating white noise and using a filter to produce the desired
spectrum. Some of these time-domain filters can be trivial to build. A simple
accumulator will be enough to approximate the \(1/f^2\) spectra. However,
synthesizing time-domain filters that produce \(1/f\) spectra is much less
trivial. Generally, filter stages show 20 dB/dec response, and it’s not clear
how to combine these to build the 10 dB/dec spectra of pink noise.</p>

<p>This post presents a method that uses frequency-domain processing to generate
colored noise sources to avoid this challenge altogether <a href="#1">[1]</a>. The idea
is to manipulate the white noise signal to the desired shape in the frequency
domain via a shaping function. The discrete Fourier transform (DFT) and its
inverse are used to bring the signal back and forth, and all the processing is
done in the frequency domain. The method presented is supposed to be a precursor
to an post tackling the challenge of developing time-domain filters to generate
\(1/f\) noise.</p>

<h1 id="the-method">The Method</h1>

<p>The algorithm for frequency domain filtering is quite straightforward.
Take the DFT of a white noise signal to get its frequency domain representation,
multiply it with the desired shaping function, and use the IDFT to return the signal
to the time domain. Using Python and NumPy, it can be implemented as follows:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">generateColoredNoise</span><span class="p">(</span><span class="n">n_pts</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
                         <span class="n">exp</span><span class="p">:</span> <span class="nb">float</span>
                         <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">NDArray</span><span class="p">[</span><span class="n">Shape</span><span class="p">[</span><span class="sh">'</span><span class="s">Any</span><span class="sh">'</span><span class="p">],</span> <span class="n">ComplexFloating</span><span class="p">]:</span>
    <span class="c1"># generate white noise with 0 mean
</span>    <span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="nf">randn</span><span class="p">(</span><span class="n">n_pts</span><span class="p">)</span>
    <span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">np</span><span class="p">.</span><span class="nf">mean</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>

    <span class="c1"># generate the shaping function for the desired exponent
</span>    <span class="n">f</span> <span class="o">=</span> <span class="nf">generateShapingFunction</span><span class="p">(</span><span class="n">n_pts</span><span class="p">,</span> <span class="n">exp</span><span class="p">)</span>

    <span class="c1"># frequency domain filtering
</span>    <span class="n">x_f</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
    <span class="n">y_f</span> <span class="o">=</span> <span class="n">x_f</span><span class="o">*</span><span class="n">f</span>
    <span class="n">y</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">ifft</span><span class="p">(</span><span class="n">y_f</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">y</span>
</code></pre></div></div>

<p>Running the above a few times and averaging the results shows the expected
frequency-domain shape for different exponents. In the figure below, noise for
each exponent was generated for 1000 runs and the magnitude of their outputs
was averaged to show their spectra clearly. The signals have also been scaled
to have the same approximate power, which makes them easy to compare in the time
domain.</p>

<figure><img src="/assets/colored-noise/output-frequency-domain.png" alt="Colored noise signals in the frequency domain" />
  <figcaption>Colored noise signals in the frequency domain</figcaption>
</figure>

<p>The figure below shows one of the runs in the time domain. Because of the
scaling, the signals with the higher exponents tend to show much less
high-frequency content and have approximately the same magnitude.</p>

<figure><img src="/assets/colored-noise/output-time-domain.png" alt="Colored noise signals in the time domain" />
  <figcaption>Colored noise signals in the time domain</figcaption>
</figure>

<p>The only missing piece of the puzzle is generating the shaping function.</p>

<h2 id="the-shaping-function">The Shaping Function</h2>

<p>DFTs of real signals are symmetric around the Nyquist frequency. This property
has to be maintained after shaping in the frequency domain to generate real,
time-domain signals. The implication is that the shaping function must also 
be symmetric around \(f_s/2\). The function below generates these shaping
functions for different powers of \(1/f\).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">generateShapingFunction</span><span class="p">(</span><span class="n">n_pts</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
                            <span class="n">exp</span><span class="p">:</span> <span class="nb">float</span>
                            <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">NDArray</span><span class="p">[</span><span class="n">Shape</span><span class="p">[</span><span class="sh">'</span><span class="s">Any</span><span class="sh">'</span><span class="p">],</span> <span class="n">Float</span><span class="p">]:</span>
    <span class="n">f</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">n_pts</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nf">int</span><span class="p">(</span><span class="n">n_pts</span><span class="o">/</span><span class="mi">2</span><span class="p">)</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
        <span class="n">f</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="o">/</span><span class="n">i</span><span class="o">**</span><span class="p">(</span><span class="n">exp</span><span class="o">/</span><span class="mi">2</span><span class="p">)</span>
        <span class="n">f</span><span class="p">[</span><span class="n">n_pts</span><span class="o">-</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">f</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>

    <span class="k">return</span> <span class="n">f</span>
</code></pre></div></div>

<p>The figure below shows the gain of the generated functions in log-log space. The
different slopes correspond to different exponents. The dashed line shows the
Nyquist frequency, and the functions are symmetric with respect to it.</p>

<figure><img src="/assets/colored-noise/shaping-functions.png" alt="Frequency-domain shaping functions" />
  <figcaption>Frequency-domain shaping functions</figcaption>
</figure>

<p>If this property were not satisfied, the resulting output of the IDFT would have
significant imaginary content. Since the goal is to generate real-valued noise
signals, this is undesirable. We can discard the imaginary component of the
signal, but removing this data would make the magnitude response deviate from
the desired shape. Even with the above implementation, there will still be some
imaginary values after the IDFT, but their magnitude will be small enough to
ignore and maintain the spectra. The imaginary output with symmetric shaping
functions is attributed to the numerical resolution of the DFT/IDFT operations.</p>

<h1 id="comparison-with-time-domain-filtering">Comparison with Time-Domain Filtering</h1>
<p>We can use the trivial case to compare the method to its time-domain equivalent.
The time-domain filter for a \(1/f^2\) response can be implemented with a
simple accumulator, implemented with one delay element and an adder.</p>

<p>The accumulator is driven with the same input signal used for the
frequency-domain filter. The figure below compares the magnitude of their
outputs’ PSD and their over-time behavior. The PSD magnitude shows a similar
\(1/f^2\) response for both, whereas the time-domain signals look different.
The two signals in the time domain have approximately the same amplitude, but
the similarities end there.</p>

<figure><img src="/assets/colored-noise/fd-td-comparison.png" alt="Comparison of the filters' PSD magnitude (100 run average) and over time behavior" />
  <figcaption>Comparison of the filters’ PSD magnitude (100 run average) and over time behavior</figcaption>
</figure>

<p>The missing piece of information can explain these differences: the phase of the
signals. Comparing the transfer functions of both filters in the frequency
domain reveals the source of the difference. The transfer function of the
frequency-domain filter is the shaping function itself. The transfer function of
the accumulator in the z-domain is:</p>

\[H(z) = \frac{z^{-1}}{1-z^{-1}}\]

<p>The magnitude of its transfer function in the frequency domain then can be
written as:</p>

\[\left|H(j\omega)\right| = \left|\frac{e^{-j\omega T_s}}{1-e^{-j\omega T_s}}\right|\]

<p>The magnitude and phase responses for these transfer functions are plotted in
the figure below.  The accumulator shows a 90-degree phase shift at DC, which
drops to 180 degrees at around \(f_s/2\). On the other hand, the
frequency-domain filter shows a 0-degree phase shift across the entire band.
Different frequency components are delayed differently for the accumulator,
whereas the frequency-domain filter retains the phase of its input.</p>

<figure><img src="/assets/colored-noise/transfer-functions.png" alt="Transfer functions for the time-domain and the frequency-domain filters" />
  <figcaption>Transfer functions of the time-domain and frequency-domain filters</figcaption>
</figure>

<p>The second, and the less significant, difference originates from the time-domain
accumulator’s behavior around frequencies close to \(f_s/2\). The accumulator
does not maintain a \(1/f^2\) slope after its unity-gain frequency at
\(f_s/6\), and the magnitude of its transfer function is 0.5 at \(f_s/2\).</p>

<h1 id="issues-with-frequency-domain-filtering">Issues with Frequency-Domain Filtering</h1>

<p>One might argue that frequency-domain filtering and time-domain filtering are
equivalent. One could take the IDFT of the shaping function to get its impulse
response and build a convolutional filter with it. Such an approach is
entirely correct. The only difference between the two would be that the
implementation is more efficient when utilizing the FFT.</p>

<p>The most prominent issue with this method is that it requires precomputing the
entire noise signal. All samples that will be used must be computed before the
start of the simulation and stored in memory.  It is possible to quickly run
into issues in long simulations requiring several such signals. A time-domain
filter will generally require much less memory but might be computationally
demanding during simulation.</p>

<p>A related problem is that simulation time must be known a priori to generate
colored noise via frequency-domain filtering. The generated signals should be
long enough to cover the entire length of the simulation. It is not trivial to
stitch two signals generated via this method and achieve the same
frequency-domain behavior due to windowing effects.  The technique works well on
noise signals that do not show specific features over time. However, utilizing
it for generic filtering applications runs into windowing issues <a href="#2">[2]</a>
that result in poor time domain responses.</p>

<p>The last one is not necessarily a unique problem to frequency-domain filtering.
The simulation time must be estimated while synthesizing time-domain filters to
find the number of stages that maintain the spectra to low frequencies.
However, it’s much easier to overdesign since time-domain filters have
significantly smaller memory requirements.</p>

<h1 id="the-code">The Code</h1>

<p>You can find the code used to generate the plots on <a href="https://gist.github.com/cgurleyuk/ea112c1dbfdbd0bc198a6da4a8ce81b9">GitHub</a>.</p>

<h1 id="references">References</h1>

<p><a id="1">[1]</a> <a href="https://scicomp.stackexchange.com/questions/18987/algorithm-for-high-quality-1-f-noise">Algorithm for high quality 1/f noise?</a> on <a href="https://scicomp.stackexchange.com/questions/18987/algorithm-for-high-quality-1-f-noise">Computational Science StackExchange</a></p>

<p><a id="2">[2]</a> <a href="https://dsp.stackexchange.com/questions/6220/why-is-it-a-bad-idea-to-filter-by-zeroing-out-fft-bins">Why is it a bad idea to filter by zeroing out FFT bins?</a> on <a href="https://dsp.stackexchange.com/questions/6220/why-is-it-a-bad-idea-to-filter-by-zeroing-out-fft-bins">Signal Processing StackExchange</a></p>]]></content><author><name></name></author><category term="signal-processing" /><summary type="html"><![CDATA[Noise signals that display specific frequency spectra are attributed colors, an allusion to the color of visible light showing the same. Like white light, white noise has uniform power spectral density, containing the same power in all frequency intervals. Pink noise has a power spectral density inversely proportional to frequency, showing a \(1/f\) spectrum and a pinkish hue in the visible spectrum. Brown(ian) noise is named as such because it’s the product of Brownian motion, but actually, it is colored red due to its amplified low-frequency content.]]></summary></entry><entry><title type="html">Mixed-Mode Y/Z-Parameters</title><link href="https://zeptoblog.com/2024/03/09/mixed-mode-yz-parameters.html" rel="alternate" type="text/html" title="Mixed-Mode Y/Z-Parameters" /><published>2024-03-09T00:00:00+00:00</published><updated>2024-03-09T00:00:00+00:00</updated><id>https://zeptoblog.com/2024/03/09/mixed-mode-yz-parameters</id><content type="html" xml:base="https://zeptoblog.com/2024/03/09/mixed-mode-yz-parameters.html"><![CDATA[<p>Mixed-mode S-parameters are used extensively by singal integrity engineers, and
a lot of resources are available that document the conversion from their
single-ended versions. The same is not the case for mixed-mode Y/Z parameters.
These less frequently used versions come in useful in certain applications.</p>

<p>One example I came across is the simulation of an inductor for an LC oscillator
in an electromagnetic simulator.  Usually, the simulator exports single-ended
Y-parameters for the two inductor terminals referenced to ground.  However, what
we care about is the behavior of differential inductance in the LC tank.  So,
the output needs to be transformed to mixed-mode to do any further analysis.</p>

<p>This article shows the derivation of mixed-mode Y/Z-parameters from their
single-ended counterparts.  It starts with a brief introduction to
Y/Z-parameters followed by an analysis conducted via defining the differential
and common-mode drive conditions for the two-port. At the end, a couple of
example circuits demonstrate the expressions derived.</p>

<h1 id="single-ended-yz-parameters">Single-Ended Y/Z-Parameters</h1>
<p>Y/Z-parameters are used to define the behavior of linear electrical networks by
specifying the relationship between the voltages and currents of its ports.</p>

<p>Y-parameters (also known as conductance parameters) relate the independent
variables of port voltages to dependent variables of port currents. To derive
the Y-parameters, we drive each port with a voltage source, while shorting all
other ports and measure the currents into each port.</p>

<figure>
<img src="/assets/mixed-mode-yz-parameters/y-parameters.svg" alt="Y-parameter characterization for a
two-port" />
  <figcaption>Y-parameter characterization for a two-port</figcaption>
</figure>

<p>The Y-parameters 2-port are then defined using these measurements as the
following.</p>

\[\begin{array}{c c} Y_{11} \triangleq \left.\frac{I_1}{V_1}\right|_{V_2 = 0} &amp;
Y_{12} \triangleq \left.\frac{I_1}{V_2}\right|_{V_1 = 0} \\ Y_{21} \triangleq
\left.\frac{I_2}{V_1}\right|_{V_2 = 0} &amp; Y_{22} \triangleq
\left.\frac{I_2}{V_2}\right|_{V_1 = 0} \end{array}\]

<p>These can be represented in matrix form as below.</p>

\[\begin{align} \begin{bmatrix} I_1 \\ I_2 \end {bmatrix} &amp;= \begin{bmatrix}
Y_{11} &amp; Y_{12} \\ Y_{21} &amp; Y_{22} \end{bmatrix} \begin{bmatrix} V_1 \\ V_2
\end{bmatrix} \\ \mathbf{I} &amp;= \mathbf{Y}\cdot\mathbf{V} \end{align}\]

<p>Similarly, Z-parameters (impedance parameters) relate the independent variables
of port currents to dependent variables of port voltages.  This, in turn, means
that we drive each port with an independent current source, while leaving open
and measuring the voltage at every other port.</p>

<figure>
<img src="/assets/mixed-mode-yz-parameters/z-parameters.svg" alt="Z-parameter characterization for a
two-port" />
  <figcaption>Z-parameter characterization for a two-port</figcaption>
</figure>

<p>The Z-parameters for a 2-port can then be defined as the following.</p>

\[\begin{array}{c c} Z_{11} \triangleq \left.\frac{V_1}{I_1}\right|_{I_2 = 0} &amp;
Z_{12} \triangleq \left.\frac{V_1}{I_2}\right|_{I_1 = 0} \\ Z_{21} \triangleq
\left.\frac{V_2}{I_1}\right|_{I_2 = 0} &amp; Z_{22} \triangleq
\left.\frac{V_2}{I_2}\right|_{I_1 = 0} \end{array}\]

<p>These can be represented in matrix form as below.</p>

\[\begin{align} \begin{bmatrix} V_1 \\ V_2 \end {bmatrix} &amp;= \begin{bmatrix}
Z_{11} &amp; Z_{12} \\ Z_{21} &amp; Z_{22} \end{bmatrix} \begin{bmatrix} I_1 \\ I_2
\end{bmatrix} \\ \mathbf{V} &amp;= \mathbf{Z} \cdot \mathbf{I} \end{align}\]

<p>Both sets of parameters completely describe the behavior of the linear circuit.
They can also be converted from one to the other via matrix inversion.</p>

\[\begin{align} \mathbf{Y} &amp;= \mathbf{Z}^{-1} \end{align}\]

<p>Naturally, this requires the matrices to be invertible. There are some circuits
for which the matrices will not be invertable, which implies that the other set
of parameters tend to infinity.</p>

<h1 id="mixed-mode-yz-parameters">Mixed-Mode Y/Z-Parameters</h1>

<p>The label <em>mixed-mode</em> refers to the combination of and differential- and
common-mode signaling over a pair of ports.  A differential transmitter uses a
balanced interface to send the singal and its inverse across a two-conductor
channel, and a differential receiver responds to the voltage difference between
the two conductors. In such a system, external interference appears in-phase on
the two lines and the corruption of the differential-mode signal is avoided.  As
an additional benefit, the signal amplitude is doubled, which results in higher
signal-to-noise ratio.</p>

<figure><img src="/assets/mixed-mode-yz-parameters/mixed-mode-y-parameters.svg" alt="Two-port driven with differential- and common-mode voltage
sources" />
  <figcaption>Two-port driven with differential- and common-mode sources</figcaption>
</figure>

<p>The differential- and common-mode voltages and currents in the case of a
two-port driven with .  The differential-mode is the out-of-phase (and generally
the desirable) signal component, and the common-mode is the in-phase (and
generally the parasitic) component.</p>

\[\begin{array} {c c} V_{DM} = V_1 - V_2 &amp; V_{CM} = \frac{V_1 + V_2}{2} \\
I_{DM} = \frac{I_1 - I_2}{2} &amp; I_{CM} = I_1 + I_2 \end{array}\]

<p>The voltage and current expressions for the ports are as follows.</p>

\[\begin{array} {c c} V_1 = \frac{V_{DM}}{2} + V_{CM} &amp; V_2 = -\frac{V_{DM}}{2}
+ V_{CM} \\ I_1 = I_{DM} + \frac{I_{CM}}{2} &amp; I_2 = -I_{DM}+\frac{I_{CM}}{2}
\end{array}\]

<p>The expressions for the voltages are familiar, but the mixed-mode currents might
be slightly confusing.  The differential-mode current circulates into the first
port and out of the second. The common-mode current is the total current going
into both ports and coming out of the reference node.</p>

<h2 id="mixed-mode-y-parameters">Mixed-Mode Y-Parameters</h2>

<p>Similar to their single-ended counterparts, mixed-mode Y-parameters relate these
differential- and common-mode currents (dependent variables) to voltages
(independent variables).</p>

\[\begin{array}{c c} Y_{DD} \triangleq
\left.\frac{I_{DM}}{V_{DM}}\right|_{V_{CM} = 0} &amp; Y_{DC} \triangleq
\left.\frac{I_{DM}}{V_{CM}}\right|_{V_{DM} = 0} \\ Y_{CD} \triangleq
\left.\frac{I_{CM}}{V_{DM}}\right|_{V_{CM} = 0} &amp; Y_{CC} \triangleq
\left.\frac{I_{CM}}{V_{CM}}\right|_{V_{DM} = 0} \end{array}\]

<p>In matrix form, mixed-mode Y-paramaters are as below.</p>

\[\begin{bmatrix} I_{DM} \\ I_{CM} \end {bmatrix} = \begin{bmatrix} Y_{DD} &amp;
Y_{DC} \\ Y_{CD} &amp; Y_{CC} \end{bmatrix} \begin{bmatrix} V_{DM} \\ V_{CM}
\end{bmatrix}\]

<h3 id="converting-single-ended-to-mixed-mode">Converting Single-Ended to Mixed-Mode</h3>

<p>Two conditions have to be considered to convert the single-ended Y-parameters to
mixed-mode.  In the first condition the ports are driven with oppsite polarity
voltage sources.  \( V_{CM} \) is set to zero, and the circuit is excited with
\( V_{DM} \) only.</p>

\[\begin{array}{l} V_1 = -V_2 = V \\ V_{DM} = V_1 - V_2 = 2V \\ V_{CM} =
\frac{V_1 + V_2}{2} = 0 \end{array}\]

<p>We can then derive \( I_1 \) and \( I_2 \) from the single ended
Y-parameters.</p>

\[\begin{array}{l} I_1 = \left(Y_{11} - Y_{12}\right) V \\ I_2 = \left(Y_{21} -
Y_{22}\right) V \end{array}\]

<p>Then, the differential and common-mode currents can be derived and \( V_{DM}
\) can be substituted in for \( V \) to arrive at the final expressions.</p>

\[\begin{array}{l} I_{DM} = \frac{I_1 - I_2}{2} = \frac{Y_{11} - Y_{12} -
Y_{21} + Y_{22}}{2} V = \frac{Y_{11} - Y_{12} - Y_{21} + Y_{22}}{4} V_{DM} \\
I_{CM} = I_1 + I_2 = \left(Y_{11} - Y_{12} + Y_{21} - Y_{22}\right) V =
\frac{Y_{11} - Y_{12} + Y_{21} - Y_{22}}{2} V_{DM} \end{array}\]

<p>In the second condition, the ports are driven with the same polarity voltage
sources.  \( V_{DM} \) is set to zero, and the circuit is excited with \(
V_{CM} \) only.</p>

\[\begin{array}{l} V_1 = V_2 = V \\ V_{DM} = V_1 - V_2 = 0 \\ V_{CM} =
\frac{V_1 + V_2}{2} = V \end{array}\]

<p>Similarly, given the voltages at the ports under common-mode drive, we derive
\( I_1 \) and \( I_2 \) from the single-ended Y-parameters.</p>

\[\begin{array}{l} I_1 = \left(Y_{11} + Y_{12}\right) V \\ I_2 = \left(Y_{21} +
Y_{22}\right) V \\ \end{array}\]

<p>Then, we derive the differential and common-mode currents under common-mode
drive, and convert the variable V to the common-mode voltage \( V_{CM} \).</p>

\[\begin{array}{l} I_{DM} = \frac{I_1 - I_2}{2} = \frac{Y_{11} + Y_{12} -
Y_{21} - Y_{22}}{2} V = \frac{Y_{11} + Y_{12} - Y_{21} - Y_{22}}{2} V_{CM} \\
I_{CM} = I_1 + I_2 = \left(Y_{11} + Y_{12} + Y_{21} + Y_{22}\right) V =
\left(Y_{11} + Y_{12} + Y_{21} + Y_{22}\right) V_{CM} \end{array}\]

<p>We can combine these expressions with their differential-mode drive counterparts
in matrix form to arrive at the mixed-mode Y-parameters.</p>

\[\begin{bmatrix} I_{DM} \\ I_{CM} \end {bmatrix} = \begin{bmatrix} 
\frac{Y_{11} - Y_{12} - Y_{21} + Y_{22}}{4} &amp; \frac{Y_{11} + Y_{12} - Y_{21} -
Y_{22}}{2} \\ \frac{Y_{11} - Y_{12} + Y_{21} - Y_{22}}{2} &amp;  Y_{11} + Y_{12} +
Y_{21} + Y_{22} \end{bmatrix} \begin{bmatrix} V_{DM} \\ V_{CM} \end{bmatrix}\]

<h2 id="mixed-mode-z-parameters">Mixed-Mode Z-parameters</h2>

<p>Mixed-mode Z-parameters relate the differential and common-mode voltages
(dependent variable) to currents (independent variable).</p>

\[\begin{array}{c c} Z_{DD} \triangleq
\left.\frac{V_{DM}}{I_{DM}}\right|_{V_{CM} = 0} &amp; Z_{DC} \triangleq
\left.\frac{V_{DM}}{I_{CM}}\right|_{V_{DM} = 0} \\ Z_{CD} \triangleq
\left.\frac{V_{CM}}{I_{DM}}\right|_{V_{CM} = 0} &amp; Z_{CC} \triangleq
\left.\frac{V_{CM}}{I_{CM}}\right|_{V_{DM} = 0} \end{array}\]

<p>In matrix form, mixed-mode Z-paramaters are as below.</p>

\[\begin{bmatrix} V_{DM} \\ V_{CM} \end {bmatrix} = \begin{bmatrix} Z_{DD} &amp;
Z_{DC} \\ Z_{CD} &amp; Z_{CC} \end{bmatrix} \begin{bmatrix} I_{DM} \\ I_{CM}
\end{bmatrix}\]

<h3 id="converting-single-ended-to-mixed-mode-1">Converting Single-Ended to Mixed-Mode</h3>

<p>Similar to the analysis for the Y-parameters, there are two conditions of
consideration.  In the first condition, the two-port terminals are driven with
current sources with opposite polarity.  This excites the circuit with
\(I_{DM}\) while setting \(I_{CM}\) to zero.</p>

<figure>
<img src="/assets/mixed-mode-yz-parameters/mixed-mode-z-parameters-differential.svg" alt="Two-port driven with differential-mode current
sources" />
  <figcaption>Two-port driven with differential-mode current sources</figcaption>
</figure>

<p>In this case, the port currents are as below.</p>

\[I_1 = -I_2 = I_{DM}\]

<p>The port voltages, \(V_1\) and \(V_2\), then can be calculated from the
single-ended parameters.</p>

\[\begin{align} V_1 &amp;= (Z_{11} - Z_{12})I_{DM} \\ V_2 &amp;= (Z_{21} -
Z_{22})I_{DM} \end{align}\]

<p>We can then derive the differential- and common-mode voltages for the ports.</p>

\[\begin{align} V_{DM} &amp;= V_1 - V_2 = (Z_{11} - Z_{12} - Z_{21} + Z_{22})
I_{DM} \\ V_{CM} &amp;= \frac{V_1 + V_2}{2} = \frac{Z_{11} - Z_{12} - Z_{21} +
Z_{22}}{2} I_{DM} \end{align}\]

<p>For the scond condition, the two-port terminals are driven with current sources
with the same polarity.  This excites the circuit with \(I_{CM}\) while
setting \(I_{DM}\) to zero.</p>

<figure>
<img src="/assets/mixed-mode-yz-parameters/mixed-mode-z-parameters-common.svg" alt="Two-port driven with common-mode current
sources" />
  <figcaption>Two-port driven with common-mode current sources</figcaption>
</figure>

\[I_1 = I_2 = I_{CM}/2\]

<p>The port voltages, \(V_1\) and \(V_2\), then can be calculated from the
single-ended parameters.</p>

\[\begin{align} V_1 &amp;= (Z_{11} + Z_{12})\frac{I_{CM}}{2} \\ V_2 &amp;= (Z_{21} +
Z_{22})\frac{I_{CM}}{2} \end{align}\]

<p>We can then finally derive the remaining differential- and common-mode voltages
for the ports.</p>

\[\begin{align} V_{DM} &amp;= V_1 - V_2 = \frac{Z_{11} + Z_{12} - Z_{21} -
Z_{22}}{2} I_{CM} \\ V_{CM} &amp;= \frac{V_1 + V_2}{2} = \frac{Z_{11} + Z_{12} +
Z_{21} + Z_{22}}{4} I_{CM} \end{align}\]

<p>We can combine these expressions with their differential-mode drive counterparts
in matrix form to arrive at the mixed-mode Z-parameters.</p>

\[\begin{bmatrix} V_{DM} \\ V_{CM} \end {bmatrix} = \begin{bmatrix} Z_{11} -
Z_{12} - Z_{21} + Z_{22} &amp; \frac{Z_{11} + Z_{12} - Z_{21} - Z_{22}}{2} \\
\frac{Z_{11} + Z_{12} - Z_{21} - Z_{22}}{2} &amp;  \frac{Z_{11} + Z_{12} + Z_{21} +
Z_{22}}{4} \end{bmatrix} \begin{bmatrix} I_{DM} \\ I_{CM} \end{bmatrix}\]

<h1 id="examples">Examples</h1>

<h2 id="single-ended-termination">Single-Ended Termination</h2>

<figure><img src="/assets/mixed-mode-yz-parameters/single-ended-termination.svg" alt="Single-ended
resistive
termination" />
  <figcaption>Single-ended resistive termination</figcaption>
</figure>

<p>The Z-parameters for the differential termination is as follows.</p>

\[\begin{bmatrix} V_1 \\ V_2 \end {bmatrix} = \begin{bmatrix} R &amp; 0 \\ 0 &amp; R
\end{bmatrix} \begin{bmatrix} I_1 \\ I_2 \end{bmatrix}\]

<p>Using the expressions above, these can be converted to mixed-mode Z-parameters.</p>

\[\begin{bmatrix} V_1 \\ V_2 \end {bmatrix} = \begin{bmatrix} 2R &amp; 0 \\ 0 &amp;
\frac{R}{2} \end{bmatrix} \begin{bmatrix} I_1 \\ I_2 \end{bmatrix}\]

<p>The differential impedance seen into the differential port is doubled and the
common-mode impedance is halved.  In single-ended systems that use 50Ω
characteristic impedance, the differential impedance is (as expected) 100Ω, and
the common-mode impedance is 25Ω.</p>

<h2 id="differential-termination">Differential Termination</h2>

<p>We could just as well terminate the system differentially, as follows:</p>

<figure><img src="/assets/mixed-mode-yz-parameters/differential-termination.svg" alt="Differential
resistive
termination" />
  <figcaption>Differential resistive termination</figcaption>
</figure>

<p>The Y-parameters for the differential termination is below. Notice how similar
this looks to the MNA stamp of a resistor.</p>

\[\begin{bmatrix} I_1 \\ I_2 \end {bmatrix} = \begin{bmatrix} G &amp; -G \\ -G &amp; G
\end{bmatrix} \begin{bmatrix} V_1 \\ V_2 \end{bmatrix}\]

<p>The mixed-mode Y-parameters for the differential resistor is:</p>

\[\begin{bmatrix} I_{DM} \\ I_{CM} \end {bmatrix} = \begin{bmatrix} G &amp; 0\\ 0 &amp;
0 \end{bmatrix} \begin{bmatrix} V_{DM} \\ V_{CM} \end{bmatrix}\]

<p>The common-mode input conductance of the two-port is zero, implying an infinite
common-mode input impedance. For the same reason, the Y-parameters cannot be
converted to Z-parameters with the above expression as the determinant of the
Y-parameter matrix is zero.</p>]]></content><author><name></name></author><category term="analog" /><summary type="html"><![CDATA[Mixed-mode S-parameters are used extensively by singal integrity engineers, and a lot of resources are available that document the conversion from their single-ended versions. The same is not the case for mixed-mode Y/Z parameters. These less frequently used versions come in useful in certain applications.]]></summary></entry></feed>