<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Lizard-Spock</title><link href="http://lizard-spock.co.uk/" rel="alternate"/><link href="http://lizard-spock.co.uk/feeds/all.atom.xml" rel="self"/><id>http://lizard-spock.co.uk/</id><updated>2026-09-13T00:00:00+01:00</updated><entry><title>Skill or MCP tool? A rule of thumb for Claude integrations</title><link href="http://lizard-spock.co.uk/choosing-skill-vs-mcp-tool.html" rel="alternate"/><published>2026-09-13T00:00:00+01:00</published><updated>2026-09-13T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-09-13:/choosing-skill-vs-mcp-tool.html</id><summary type="html">&lt;p&gt;A practical rule for deciding whether a new Claude capability should be a skill (prompted procedure) or an MCP tool (deterministic lookup/action), worked out while adding a "daily knot" feature to a SecondBrain morning briefing.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;The question&lt;/h2&gt;
&lt;p&gt;While extending my SecondBrain &lt;code&gt;/morning&lt;/code&gt; briefing, I wanted to add a couple of small daily extras: a "Daily Stoic" quote, and a "daily knot" tip. Both are just: look up today's entry from a fixed, pre-compiled dataset. The question was whether these belonged in the skillbook (as &lt;code&gt;SKILL.md&lt;/code&gt; procedures) or as tools on the SecondBrain MCP server.&lt;/p&gt;
&lt;p&gt;It turned out I'd already answered this once without noticing — &lt;code&gt;get_daily_stoic&lt;/code&gt; already exists as an MCP tool, not a skill. That accident turned out to be the right call, and thinking through &lt;em&gt;why&lt;/em&gt; gave a rule of thumb worth writing down.&lt;/p&gt;
&lt;h2&gt;The distinction: lookup vs. reasoning&lt;/h2&gt;
&lt;p&gt;The deciding factor isn't whether the feature calls an external API. It's whether the work being done is &lt;strong&gt;lookup&lt;/strong&gt; or &lt;strong&gt;reasoning&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCP tool&lt;/strong&gt; — a fixed dataset (say, a JSON or markdown table keyed by date) behind a thin function that does a deterministic lookup. No model judgment is involved in producing the content itself; the same input always returns the same output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skill&lt;/strong&gt; — a procedure the model follows using judgment: formatting rules, what to prioritize, how to phrase things, what to omit. My existing &lt;code&gt;daily-summary&lt;/code&gt; skill is a good example — it reasons over goals, actions, and due dates fresh each time, and that reasoning &lt;em&gt;is&lt;/em&gt; the value it adds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A "daily knot" or "daily quote" is a lookup, not a procedure. Building it as a skill would mean stuffing a reference file into context every time the briefing runs and asking the model to "pick today's entry" — non-deterministic, token-heavy, and re-deriving the same answer every session for no benefit.&lt;/p&gt;
&lt;h2&gt;The practical shape this takes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Curate the data once.&lt;/strong&gt; For something like knots, that's a one-time content pass — pull entries from reference books into a small structured file (&lt;code&gt;knots.json&lt;/code&gt; or similar), living wherever the Stoic dataset already lives on the MCP server. This is content curation, not a recurring skill invocation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add a matching tool.&lt;/strong&gt; A &lt;code&gt;get_daily_knot&lt;/code&gt; tool mirroring &lt;code&gt;get_daily_stoic&lt;/code&gt;'s signature — optional date param in, name/use-case/steps/source out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Let the skill stay thin.&lt;/strong&gt; The &lt;code&gt;/morning&lt;/code&gt; skill just calls both tools and renders the results under a small section, alongside calendar, actions, and reminders. The skill layer keeps doing what it's good at (formatting, prioritization); the MCP layer keeps doing what it's good at (deterministic data serving).&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Why this matters beyond one feature&lt;/h2&gt;
&lt;p&gt;Keeping lookups out of skills has a few compounding benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Determinism.&lt;/strong&gt; A tool call returns the same thing for the same date, every time, regardless of which client or model is asking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reusability.&lt;/strong&gt; An MCP tool is available to &lt;em&gt;any&lt;/em&gt; MCP client, not just a Claude session with the skillbook loaded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cheaper composition.&lt;/strong&gt; No need to re-read a big reference file into context on every invocation — the skill only pays for the small rendered result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The one design question this doesn't answer on its own: should a lookup like this rotate by calendar date (fixed 365-entry cycle, like the Stoic quote) or independently (random-but-non-repeating, needing far fewer entries plus a rotation pointer)? That's a smaller decision, but worth making explicitly before building the dataset — it changes how much content curation is actually required up front.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; if adding the feature means writing a &lt;code&gt;SKILL.md&lt;/code&gt; that just tells the model "here's a list, pick today's one" — it's not a skill. Put the list behind a tool, and let the skill call the tool.&lt;/p&gt;</content><category term="Programming"/><category term="claude"/><category term="mcp"/><category term="skills"/><category term="architecture"/><category term="secondbrain"/></entry><entry><title>Knife Steels Explained: Hardness, Toughness, and Edge Retention</title><link href="http://lizard-spock.co.uk/knife-steels-explained.html" rel="alternate"/><published>2026-09-12T00:00:00+01:00</published><updated>2026-09-12T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-09-12:/knife-steels-explained.html</id><summary type="html">&lt;p&gt;Why stainless resists rust, why carbon steel earned its reputation for strength, and how modern powder steels like Elmax and MagnaCut collapsed the old trade-off, with a look at the steels Leatherman, Opinel, Victorinox, and Fallkniven actually use.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeSteels/01-hero-HQ.png"&gt;&lt;img alt="Bushcraft knife in profile with a magnifier over the edge showing carbide particles in the steel" src="http://lizard-spock.co.uk/images/Outdoor/KnifeSteels/01-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Choosing a grind shapes how a blade cuts, but the steel it's made from decides how long that edge lasts, how it fails under stress, and how much upkeep it needs. This is a follow-up to the piece on knife grinds, looking at steel instead of geometry, and at how the two choices work together for bushcraft and general outdoor use.&lt;/p&gt;
&lt;h2&gt;What actually determines performance&lt;/h2&gt;
&lt;p&gt;A steel's behaviour comes down to its alloy composition (carbon, chromium, vanadium, molybdenum content) and how it's heat treated. Two knives in the "same" steel can behave differently if one is heat treated to a higher hardness than the other. With that caveat, five properties are worth tracking when comparing steels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardness&lt;/strong&gt; – resistance to deforming under load, measured on the Rockwell C scale (HRC). Higher hardness generally supports better edge retention but tends to reduce toughness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge retention / wear resistance&lt;/strong&gt; – how long the edge stays sharp under abrasive use. Driven mainly by hard carbides (vanadium, chromium, molybdenum) embedded in the steel matrix.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Toughness / chip resistance&lt;/strong&gt; – ability to absorb impact and lateral stress without chipping or cracking. Usually trades off against hardness and wear resistance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ease of sharpening&lt;/strong&gt; – how much work it takes to raise a new edge. Steels with dense, hard carbides resist the sharpening stone as much as they resist the material being cut.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corrosion resistance&lt;/strong&gt; – how well the steel resists rust and staining, driven mostly by chromium content (roughly 12%+ for "stainless").&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The science underneath&lt;/h2&gt;
&lt;p&gt;All of the steels here are iron with carbon dissolved in it, plus alloying elements. Heat treatment takes the steel above its transformation temperature so carbon goes into solution, then quenches it fast enough that the carbon has nowhere to go. The result is martensite: a hard, strained crystal structure that gives a blade its hardness. Tempering afterwards relieves some of that strain and trades a little hardness for toughness.&lt;/p&gt;
&lt;p&gt;Whatever carbon does not dissolve into the matrix combines with chromium, vanadium, molybdenum, or niobium to form &lt;strong&gt;carbides&lt;/strong&gt;: ceramic-hard particles suspended in the softer steel around them. Carbides are why a steel resists abrasion, and also why it fights the sharpening stone. Their size and distribution matters as much as how many there are, which is the single idea that explains most of what follows.&lt;/p&gt;
&lt;h3&gt;Why stainless resists rust&lt;/h3&gt;
&lt;p&gt;Chromium does not protect steel by being tough. It protects by oxidising first. Chromium dissolved in the steel matrix reacts with air to form a chromium-oxide film a few nanometres thick, dense and tightly bonded, and that passive layer blocks oxygen and water from reaching the iron underneath. Scratch it and it reforms almost instantly.&lt;/p&gt;
&lt;p&gt;The threshold is around 10.5 to 11% chromium &lt;strong&gt;in solution&lt;/strong&gt;, and that qualifier does the real work. Chromium locked into chromium carbides is not available to form the passive layer. A traditional stainless like 440C carries 17% chromium on the datasheet, but a large share of it is tied up in coarse chromium carbides, so the steel behaves as though it has considerably less. Worse, those carbide particles create local chemistry differences at their boundaries, which is exactly where pitting corrosion starts. High chromium on paper does not automatically mean high corrosion resistance in the hand.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeSteels/03-passive-layer-HQ.png"&gt;&lt;img alt="Cross-section comparing chromium in solution building an intact oxide film against chromium locked in a carbide leaving a gap and a depleted zone" src="http://lizard-spock.co.uk/images/Outdoor/KnifeSteels/03-passive-layer-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Why carbon steel got its reputation for strength&lt;/h3&gt;
&lt;p&gt;Simple carbon steels like 1095 or O1 contain almost no chromium, so nearly all their carbon is either in solution or in fine iron carbides. No coarse chromium carbides means a fine, even microstructure, and a fine microstructure means toughness: the steel can absorb impact and lateral load without a crack finding an easy path through a large brittle particle.&lt;/p&gt;
&lt;p&gt;That is the real basis for the bushcraft preference. Carbon steel takes a very keen edge because there are no large carbides to interrupt the apex, holds up to batoning without chipping, and sharpens quickly on a basic stone because there is little abrasion-resistant material to remove. It is not stronger in any absolute sense. It is tougher and finer-grained at a given hardness, which for tasks involving wood and impact matters more than raw wear resistance.&lt;/p&gt;
&lt;p&gt;The price is rust. With no passive layer, the iron oxidises directly, and a wet knife left in a sheath overnight shows it.&lt;/p&gt;
&lt;h3&gt;Why modern carbon steels cope better with moisture&lt;/h3&gt;
&lt;p&gt;It is worth being precise here: carbon steel has not become stainless. What changed is that the gap between the two families filled in.&lt;/p&gt;
&lt;p&gt;The middle ground is occupied by &lt;strong&gt;semi-stainless&lt;/strong&gt; tool steels such as CPM CruWear and Vanadis 4E, which carry around 7.5% chromium. That is below the passivation threshold, so they will still patina and can still rust, but corrosion happens far more slowly than with 1095, while toughness stays close to carbon-steel levels. Alongside that, powder metallurgy gave even the low-alloy steels a cleaner, finer structure with fewer inclusions to act as corrosion initiation sites, and practical measures help too: a forced patina of stable black iron oxide, nitrided surfaces, and DLC or Cerakote coatings all buy real-world resistance without changing the alloy.&lt;/p&gt;
&lt;p&gt;A modern semi-stainless blade left damp overnight will usually show a grey haze where a 1095 blade would show orange. That is the honest version of the claim.&lt;/p&gt;
&lt;h3&gt;Why modern stainless caught up on strength&lt;/h3&gt;
&lt;p&gt;Three developments closed the gap from the other direction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Powder metallurgy.&lt;/strong&gt; Conventional ingot steel solidifies slowly, and chromium carbides grow coarse as it does, sometimes tens of microns across. In PM production the molten alloy is gas-atomised into fine powder, each droplet freezing almost instantly, then consolidated under heat and pressure. The carbides never get the chance to grow. The same alloy with carbides an order of magnitude smaller is markedly tougher, takes a finer edge, and is easier to sharpen.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeSteels/02-carbides-HQ.png"&gt;&lt;img alt="Two panels at the same magnification, coarse chromium carbides in conventional ingot steel against fine powder metallurgy carbides" src="http://lizard-spock.co.uk/images/Outdoor/KnifeSteels/02-carbides-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Better carbide chemistry.&lt;/strong&gt; Vanadium and niobium carbides are harder than chromium carbides and naturally form much smaller particles. Building wear resistance from vanadium and niobium instead of chromium gives better edge retention &lt;em&gt;and&lt;/em&gt; leaves the chromium free in solution to do corrosion work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MagnaCut as the worked example.&lt;/strong&gt; Larrin Thomas designed CPM MagnaCut around exactly that idea: chromium dropped to about 10.7%, carbon balanced at 1.15% so no chromium carbides form at all, with vanadium, niobium, and 0.2% nitrogen supplying the wear resistance. Every carbide in the finished steel is vanadium or niobium, every atom of chromium is in solution, and the usual pitting initiation sites simply are not there. The result behaves like a tough low-alloy steel that also happens to be properly stainless, which is why it turns up as the reference point in every comparison written since.&lt;/p&gt;
&lt;p&gt;The old triangle of edge retention, toughness, and corrosion resistance has not been abolished. It has been pushed outward, and the trade-offs that remain are milder than they were twenty years ago.&lt;/p&gt;
&lt;h2&gt;Comparison table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Steel&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Hardness (HRC)&lt;/th&gt;
&lt;th&gt;Edge Retention&lt;/th&gt;
&lt;th&gt;Toughness / Chip Resistance&lt;/th&gt;
&lt;th&gt;Ease of Sharpening&lt;/th&gt;
&lt;th&gt;Corrosion Resistance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1095&lt;/td&gt;
&lt;td&gt;High-carbon&lt;/td&gt;
&lt;td&gt;56–58&lt;/td&gt;
&lt;td&gt;Low–Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very Easy&lt;/td&gt;
&lt;td&gt;Low (needs oiling/drying)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O1&lt;/td&gt;
&lt;td&gt;Carbon tool steel&lt;/td&gt;
&lt;td&gt;58–61&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A2&lt;/td&gt;
&lt;td&gt;Carbon tool steel&lt;/td&gt;
&lt;td&gt;58–62&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low–Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;420HC&lt;/td&gt;
&lt;td&gt;Budget stainless&lt;/td&gt;
&lt;td&gt;55–58&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Very Easy&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AUS-8&lt;/td&gt;
&lt;td&gt;Stainless&lt;/td&gt;
&lt;td&gt;57–59&lt;/td&gt;
&lt;td&gt;Low–Medium&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;440C&lt;/td&gt;
&lt;td&gt;Stainless&lt;/td&gt;
&lt;td&gt;56–59&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VG-10&lt;/td&gt;
&lt;td&gt;Stainless&lt;/td&gt;
&lt;td&gt;59–61&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPM S30V&lt;/td&gt;
&lt;td&gt;Premium PM stainless&lt;/td&gt;
&lt;td&gt;58–61&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Moderate–Hard&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPM S35VN&lt;/td&gt;
&lt;td&gt;Premium PM stainless&lt;/td&gt;
&lt;td&gt;58–61&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPM-154&lt;/td&gt;
&lt;td&gt;Premium PM stainless&lt;/td&gt;
&lt;td&gt;58–61&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Elmax&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Premium PM stainless&lt;/td&gt;
&lt;td&gt;58–62&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M390&lt;/td&gt;
&lt;td&gt;Super steel (PM)&lt;/td&gt;
&lt;td&gt;60–62&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPM S90V&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Super steel (PM)&lt;/td&gt;
&lt;td&gt;59–64&lt;/td&gt;
&lt;td&gt;Very High (best-in-class)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Very Hard&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPM MagnaCut&lt;/td&gt;
&lt;td&gt;Modern balanced PM&lt;/td&gt;
&lt;td&gt;60–62&lt;/td&gt;
&lt;td&gt;High–Very High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;PM = powder metallurgy, a manufacturing process that produces a finer, more evenly distributed carbide structure than traditional ingot steel, generally improving toughness and sharpenability for a given hardness.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Notes on the standouts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Elmax&lt;/strong&gt; (Bohler-Uddeholm) is often picked as an all-rounder among premium powder-metallurgy steels: strong wear resistance, good toughness, and excellent corrosion resistance, without being as punishing to sharpen as the very top edge-retention steels. It's a sensible choice for a folder or fixed blade that needs to handle both cutting chores and the odd knock without chipping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPM S90V&lt;/strong&gt; sits at the wear-resistance extreme. Its very high vanadium carbide content gives it edge retention that outperforms most other stainless steels by a wide margin, but that same carbide density makes it slow and demanding to sharpen — diamond or CBN abrasives are close to essential — and it gives up some toughness compared to Elmax or S35VN. It rewards knives that get used hard between sharpenings (hunting, processing) rather than ones sharpened frequently by hand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPM MagnaCut&lt;/strong&gt; is newer and worth flagging here even though it's not in the original list: it was designed specifically to balance toughness, corrosion resistance, and edge retention rather than maximise any single property, and it's becoming a common reference point against older "super steels" like S90V and M390.&lt;/p&gt;
&lt;p&gt;Carbon steels (1095, O1) remain relevant for bushcraft use: lower wear resistance is offset by being genuinely easy to touch up in the field with a simple stone, at the cost of needing more corrosion care.&lt;/p&gt;
&lt;h2&gt;What the familiar brands actually use&lt;/h2&gt;
&lt;p&gt;Four brands most people already own something from, and why each choice makes sense for the knife it is in.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Brand&lt;/th&gt;
&lt;th&gt;Steel&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Hardness (HRC)&lt;/th&gt;
&lt;th&gt;What it optimises for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Leatherman&lt;/td&gt;
&lt;td&gt;420HC&lt;/td&gt;
&lt;td&gt;Budget stainless&lt;/td&gt;
&lt;td&gt;~55–58&lt;/td&gt;
&lt;td&gt;Corrosion resistance, easy field sharpening&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leatherman&lt;/td&gt;
&lt;td&gt;154CM&lt;/td&gt;
&lt;td&gt;Premium stainless&lt;/td&gt;
&lt;td&gt;~58–61&lt;/td&gt;
&lt;td&gt;Edge retention on upgrade models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leatherman&lt;/td&gt;
&lt;td&gt;CPM MagnaCut&lt;/td&gt;
&lt;td&gt;Modern PM stainless&lt;/td&gt;
&lt;td&gt;~60–62&lt;/td&gt;
&lt;td&gt;Everything at once, at flagship price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opinel&lt;/td&gt;
&lt;td&gt;Carbone XC90&lt;/td&gt;
&lt;td&gt;High carbon (~0.9% C)&lt;/td&gt;
&lt;td&gt;~55–57&lt;/td&gt;
&lt;td&gt;Keen edge, effortless sharpening, low cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opinel&lt;/td&gt;
&lt;td&gt;Inox 12C27 Mod&lt;/td&gt;
&lt;td&gt;Stainless (~0.6% C, ~13.5% Cr)&lt;/td&gt;
&lt;td&gt;~56–58&lt;/td&gt;
&lt;td&gt;Rust resistance with no upkeep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Victorinox&lt;/td&gt;
&lt;td&gt;X55CrMo14 (1.4110)&lt;/td&gt;
&lt;td&gt;Martensitic stainless&lt;/td&gt;
&lt;td&gt;~56&lt;/td&gt;
&lt;td&gt;Toughness in thin blades, easy resharpening&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallkniven&lt;/td&gt;
&lt;td&gt;Laminated VG-10&lt;/td&gt;
&lt;td&gt;Stainless core, 420J2 sides&lt;/td&gt;
&lt;td&gt;59 core&lt;/td&gt;
&lt;td&gt;Edge retention plus lateral strength&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallkniven&lt;/td&gt;
&lt;td&gt;3G (SGPS core, VG2 sides)&lt;/td&gt;
&lt;td&gt;Laminated PM stainless&lt;/td&gt;
&lt;td&gt;62 core&lt;/td&gt;
&lt;td&gt;Hard core, chip resistance from soft flanks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallkniven&lt;/td&gt;
&lt;td&gt;CoS (Cobalt Special)&lt;/td&gt;
&lt;td&gt;Laminated cobalt alloy&lt;/td&gt;
&lt;td&gt;~60–61 core&lt;/td&gt;
&lt;td&gt;Fine grain, exceptional sharpening response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallkniven&lt;/td&gt;
&lt;td&gt;Elmax&lt;/td&gt;
&lt;td&gt;Monosteel PM stainless&lt;/td&gt;
&lt;td&gt;61–62&lt;/td&gt;
&lt;td&gt;Wear and corrosion resistance without lamination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Leatherman&lt;/strong&gt; runs a deliberate three-tier ladder. 420HC on the Wave+, Surge, Signal, Wingman, and Sidekick is a low-carbon stainless that no enthusiast would specify for a dedicated knife, and it is the right call here anyway: a multitool lives in a pocket or on a belt in the rain, gets used for whatever is in front of it, and gets sharpened by someone without a diamond plate. Corrosion resistance and easy sharpening beat edge retention in that job. The Skeletool CX and Charge Plus step up to 154CM for people using the blade as their main knife, and the flagship Arc and Wave Alpha carry CPM MagnaCut, which genuinely beats both on all three axes rather than trading one for another. Note that the &lt;em&gt;tool&lt;/em&gt; components, pliers, drivers, and cutters, are a separate engineering problem: they need toughness and fatigue resistance, not edge retention, so blade steel choice tells you nothing about them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Opinel&lt;/strong&gt; offers the clearest carbon-versus-stainless comparison available, because you can buy the same knife either way. Carbone XC90 sits at roughly 0.9% carbon, in the same territory as 1095, and it is a superb value proposition: takes a screaming edge on any stone, sharpens in seconds, patinas within a week of cutting fruit, and rusts if you fold it away wet. The Inox version uses Sandvik 12C27 Modified at about 0.6% carbon and 13.5% chromium, a fine-grained stainless run slightly harder than the carbon version in Opinel's heat treatment. The carbon blade is nicer to sharpen and takes a finer edge; the stainless blade forgives neglect. For a knife that lives in a rucksack or gets handed to a cub, the stainless one is the sensible answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Victorinox&lt;/strong&gt; uses a proprietary variant of X55CrMo14 (material number 1.4110), around 0.52% carbon, 15% chromium, and 0.5% molybdenum, hardened at 1040 °C and tempered to an average of 56 HRC. Enthusiasts sometimes dismiss it as soft, which misses the design brief. Swiss Army knife blades are thin, the knives get used as scissors, screwdrivers, and pry bars by people who will never sharpen them properly, and a harder blade in that geometry would chip rather than roll. At 56 HRC the steel is tough, essentially rustproof in normal use, and can be brought back to sharp on a kitchen steel or the bottom of a mug. It is optimised for recoverability, not retention.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fallkniven&lt;/strong&gt; takes a different route entirely: laminated blades, where a hard high-performance core is hot-rolled between softer stainless outer layers. The classic F1 and similar models use a VG-10 core at 59 HRC between 420J2 sides, which Fallkniven credits with roughly 20% greater lateral strength than the same blade in solid VG-10. The premium 3G version raises the core to Super Gold Powder Steel at 62 HRC flanked by VG2, and the CoS models use a cobalt-alloyed core, about 1.1% carbon, 16% chromium, 2.5% cobalt, at around 60 to 61 HRC. The principle is the same one that makes a Japanese kitchen knife work: put the hardness exactly where the cutting happens and let the softer flanks absorb bending and impact, so the blade can run a harder core than a monosteel of the same geometry would survive.&lt;/p&gt;
&lt;p&gt;Fallkniven also sells several models in &lt;strong&gt;Elmax as a monosteel&lt;/strong&gt;, notably the F1x and S1x in the X-series, hardened to 61–62 HRC, with black tungsten-carbide-coated variants alongside the bare ones. That is the tidiest illustration of the argument in this post: lamination exists to let a hard core sit behind tough flanks, and a powder stainless with fine, evenly distributed vanadium carbides is tough enough at 62 HRC that the blade no longer needs the flanks. Elmax also brings better corrosion resistance than the VG-10 core it replaces. The classic F1 is offered in an Elmax version too, so as with Opinel you can compare construction philosophies in the same knife shape.&lt;/p&gt;
&lt;p&gt;Seen together, the pattern is that none of these brands is choosing a "better" or "worse" steel. Leatherman optimises for neglect, Opinel for sharpenability and price, Victorinox for toughness in thin stock, and Fallkniven for putting maximum hardness at the apex without giving up a robust blade. The steel follows the use case.&lt;/p&gt;
&lt;h2&gt;Pairing steel with grind&lt;/h2&gt;
&lt;p&gt;A high-wear-resistance steel like S90V on a full flat grind suits controlled, precise cutting where the edge needs to last. The same steel on a scandi grind for bushcraft carving work would be harder to maintain in the field than a tougher, easier-sharpening steel like Elmax or CPM-154 — grind and steel should be chosen together against the actual task, not independently.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Next up: a look at sharpening approaches for these different steel families, including when diamond stones or ceramic rods actually earn their keep.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://knifesteelnerds.com/2021/03/25/cpm-magnacut/"&gt;CPM MagnaCut – Knife Steel Nerds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://knifesteelnerds.com/2025/07/14/corrosion-resistance-vs-hardness-in-knife-steels/"&gt;Corrosion resistance vs hardness in knife steels – Knife Steel Nerds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fallkniven.se/en/product-information/steel/"&gt;Fällkniven steel information&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.opinel-usa.com/pages/opinel-carbon-vs-stainless"&gt;Opinel: carbon vs stainless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://knifesteelcomparison.com/elmax-vs-cruwear/"&gt;Elmax vs CruWear comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.slashgear.com/2197012/leatherman-multitool-steel-used/"&gt;Leatherman blade steels overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Outdoor"/><category term="knives"/><category term="steel"/><category term="bushcraft"/><category term="outdoor"/><category term="gear"/><category term="metallurgy"/></entry><entry><title>E-Shape Open Chord Variations</title><link href="http://lizard-spock.co.uk/e-shape-open-chord-variations.html" rel="alternate"/><published>2026-09-01T00:00:00+01:00</published><updated>2026-09-01T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-09-01:/e-shape-open-chord-variations.html</id><summary type="html">&lt;p&gt;Every common variation on the open E chord -- sus4, minor, sevenths, sixth, add9 -- each one a single change from the base shape, with fingering and intervals side by side, and a note on how this shape becomes the movable E-shape barre chord.&lt;/p&gt;</summary><content type="html">&lt;p&gt;E major is the full six-string open shape, and the same shape barred at any fret becomes the movable "E-shape" chord used throughout the &lt;a href="http://lizard-spock.co.uk/diatonic-chords.html"&gt;I-IV-V fretboard map&lt;/a&gt;. This post closes the open-chord series started with &lt;a href="http://lizard-spock.co.uk/a-shape-open-chord-variations.html"&gt;A&lt;/a&gt; and &lt;a href="http://lizard-spock.co.uk/d-shape-open-chord-variations.html"&gt;D&lt;/a&gt;. Each diagram shows the fingering on the left and the intervals that fingering spells on the right.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;Base shape: E major&lt;/h2&gt;
&lt;p&gt;All six strings sound, so with only three distinct notes in the triad there is plenty of doubling. The G string carries the third, and the D string carries a doubled root. Those two strings take almost every variation below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_6f6b755d469c91fc5a1270d9a397d4e3.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Suspended: sus4&lt;/h2&gt;
&lt;p&gt;The G string third moves up one fret to A, the fourth. An open Esus2 is not practical in standard tuning without muting or a stretch that defeats the point of an open chord, so sus4 is the one suspension that stays in the family of easy moves.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_2fdb0b506f19543d9cdce55b76847135.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Minor and minor seventh&lt;/h2&gt;
&lt;p&gt;Em lifts the index finger and the open G string gives the minor third. Em7 then releases the doubled root on the D string as well, adding the flat seventh: one fretted note and five open strings, the simplest full chord shape on the guitar.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_5fc71a97c9ba6313fcc99223e819deb1.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_26af49cf8d568b653abd5a7bfdbca8ff.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Dominant seventh and major seventh&lt;/h2&gt;
&lt;p&gt;E7 makes the same D string move against the major third: release the doubled root to the open D for the flat seventh. It is the I chord in a blues in E (see the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;12-bar blues post&lt;/a&gt;). Emaj7 takes that string down only one fret instead, to D#, a half step below the root.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_b21ef1951edb86e307a181c9021e4fd9.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_6fe6b43f98a28d5ffbd31ac6653b053d.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Sixth and add9&lt;/h2&gt;
&lt;p&gt;E6 is the one variation that leaves both the third and the doubled root alone. It adds the pinky on the B string at the second fret, so a fifth becomes C#, the sixth. Eadd9 goes back to the D string and stretches it up to the fourth fret, turning the doubled root into F#, the ninth.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_d4c1474eb9090a467159d7745d059ba3.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_b5990e6338854fe4a11b60c79d8a1cb9.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Summary table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chord&lt;/th&gt;
&lt;th&gt;Frets (low to high)&lt;/th&gt;
&lt;th&gt;Change from E major&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;022100&lt;/td&gt;
&lt;td&gt;base shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Esus4&lt;/td&gt;
&lt;td&gt;022200&lt;/td&gt;
&lt;td&gt;G string: 3rd -&amp;gt; 4th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Em&lt;/td&gt;
&lt;td&gt;022000&lt;/td&gt;
&lt;td&gt;G string: 3rd -&amp;gt; b3 (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Em7&lt;/td&gt;
&lt;td&gt;020000&lt;/td&gt;
&lt;td&gt;Em, plus D string: doubled root -&amp;gt; b7 (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E7&lt;/td&gt;
&lt;td&gt;020100&lt;/td&gt;
&lt;td&gt;D string: doubled root -&amp;gt; b7 (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emaj7&lt;/td&gt;
&lt;td&gt;021100&lt;/td&gt;
&lt;td&gt;D string: doubled root -&amp;gt; maj7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E6&lt;/td&gt;
&lt;td&gt;022120&lt;/td&gt;
&lt;td&gt;B string: 5th -&amp;gt; 6th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eadd9&lt;/td&gt;
&lt;td&gt;024100&lt;/td&gt;
&lt;td&gt;D string: doubled root -&amp;gt; 9th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;From open shape to movable barre&lt;/h2&gt;
&lt;p&gt;Barre the whole E major shape at any fret and the root moves with it while every relationship above stays intact. An F major barre at the first fret is the open E shape shifted up one. This is the shape behind the "E-shape root on the 6th string" reference in the I-IV-V fretboard map: learn the variations here and the same finger patterns are available anywhere on the neck once barred.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Chords"/><category term="Open Chords"/><category term="Music Theory"/></entry><entry><title>D-Shape Open Chord Variations</title><link href="http://lizard-spock.co.uk/d-shape-open-chord-variations.html" rel="alternate"/><published>2026-08-31T00:00:00+01:00</published><updated>2026-08-31T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-31:/d-shape-open-chord-variations.html</id><summary type="html">&lt;p&gt;Every common variation on the open D chord -- sus2, sus4, minor, sevenths, sixth, add9 -- each one a single change from the base shape, with fingering and intervals shown side by side.&lt;/p&gt;</summary><content type="html">&lt;p&gt;D major only uses four strings, which makes it the easiest open shape to hear a single finger move clearly against the others. This post works through the same set of variations as the &lt;a href="http://lizard-spock.co.uk/a-shape-open-chord-variations.html"&gt;A-shape post&lt;/a&gt;, applied to D. Each diagram shows the fingering on the left and the intervals that fingering spells on the right.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;Base shape: D major&lt;/h2&gt;
&lt;p&gt;The D shape splits its work neatly across two strings. The top E string carries the third, so every suspension and the minor happen there. The B string carries a doubled root, so every seventh, the sixth and the add9 happen there instead.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_a3e396b4050ee45fd3b91fce6d98842c.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Suspended: sus2 and sus4&lt;/h2&gt;
&lt;p&gt;Both suspensions move the top string alone, the string carrying the third in the base shape. Lift the ring finger and the open E gives the second; slide that same finger up one fret instead and G gives the fourth.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_608af7e69202d96b403222442196a591.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_ca9402cbfdc98a59d5928dc900232679.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Minor and minor seventh&lt;/h2&gt;
&lt;p&gt;Dm takes that same top string down one fret to F, the minor third. Dm7 then moves the B string doubled root down two frets to C, the flat seventh, which puts both fretted notes on the first fret under a single index finger.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_637ba4177ed35eddabcdb20432e59c33.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_8a086006e3583c9adeabce8c9570e0a5.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Dominant seventh and major seventh&lt;/h2&gt;
&lt;p&gt;D7 makes the same B string move against the major third: doubled root down to C, the flat seventh. It is the shape most players learn first for a dominant chord, useful as the V in a blues in G (see the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;12-bar blues post&lt;/a&gt;). Dmaj7 stops one fret higher at C#, a half step below the root.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_8c78a72e6302c8bb3261e0f879683b85.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_f60223591ea0aa5ef9d6f04720957fe9.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Sixth and add9&lt;/h2&gt;
&lt;p&gt;D6 opens the B string, so the doubled root becomes the sixth and the third on the top string stays. Dadd9 moves that same B string the other way, from fret 3 to fret 5, turning the doubled root into the ninth. Both keep the third, which is what separates an add chord from a suspension.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_cc18eb8705aacf492a17751f0d935836.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_3608624f7191286703caea009242a6b9.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Summary table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chord&lt;/th&gt;
&lt;th&gt;Frets (low to high)&lt;/th&gt;
&lt;th&gt;Change from D major&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;xx0232&lt;/td&gt;
&lt;td&gt;base shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dsus2&lt;/td&gt;
&lt;td&gt;xx0230&lt;/td&gt;
&lt;td&gt;top E: 3rd -&amp;gt; 2nd (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dsus4&lt;/td&gt;
&lt;td&gt;xx0233&lt;/td&gt;
&lt;td&gt;top E: 3rd -&amp;gt; 4th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dm&lt;/td&gt;
&lt;td&gt;xx0231&lt;/td&gt;
&lt;td&gt;top E: 3rd -&amp;gt; b3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dm7&lt;/td&gt;
&lt;td&gt;xx0211&lt;/td&gt;
&lt;td&gt;Dm, plus B string: doubled root -&amp;gt; b7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D7&lt;/td&gt;
&lt;td&gt;xx0212&lt;/td&gt;
&lt;td&gt;B string: doubled root -&amp;gt; b7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dmaj7&lt;/td&gt;
&lt;td&gt;xx0222&lt;/td&gt;
&lt;td&gt;B string: doubled root -&amp;gt; maj7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D6&lt;/td&gt;
&lt;td&gt;xx0202&lt;/td&gt;
&lt;td&gt;B string: doubled root -&amp;gt; 6th (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dadd9&lt;/td&gt;
&lt;td&gt;xx0252&lt;/td&gt;
&lt;td&gt;B string: doubled root -&amp;gt; 9th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Next in the series: the &lt;a href="http://lizard-spock.co.uk/e-shape-open-chord-variations.html"&gt;E-shape open chord variations&lt;/a&gt;.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Chords"/><category term="Open Chords"/><category term="Music Theory"/></entry><entry><title>A-Shape Open Chord Variations</title><link href="http://lizard-spock.co.uk/a-shape-open-chord-variations.html" rel="alternate"/><published>2026-08-30T00:00:00+01:00</published><updated>2026-08-30T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-30:/a-shape-open-chord-variations.html</id><summary type="html">&lt;p&gt;Every common variation on the open A chord -- sus2, sus4, minor, sevenths, sixth, add9 -- each one a single change from the base shape, with fingering and intervals shown side by side.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The open A shape is one of the first chords most guitarists learn, and one of the most flexible: almost every common variation is a single finger moving from the base triad. Each diagram below shows the fingering on the left and the intervals that fingering spells on the right.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;Base shape: A major&lt;/h2&gt;
&lt;p&gt;A major is the 1-3-5 triad: root, major third, perfect fifth. Four notes sound across five strings, so two of them are doubled. The B string carries the third, and the G string carries a doubled root. Those two strings are where every variation below happens.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_322e10e71b92dc8189cb1354525ba7a9.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Suspended: sus2 and sus4&lt;/h2&gt;
&lt;p&gt;Suspended chords replace the third with the second or fourth -- no major or minor quality, just tension resolving back to the triad. Both moves happen on the B string. Lift the ring finger and the open B gives the second; slide that same finger up one fret and D gives the fourth.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_b83242cc5f65e95151d4f027242e7001.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_3e9f60574e9a4f2cf941e8b8ffb9d417.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Minor and minor seventh&lt;/h2&gt;
&lt;p&gt;Am takes the B string third down one fret to C, the minor third. Am7 then releases the doubled root on the G string to the open G, adding the flat seventh -- one finger fewer than the major shape, not more.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_c74a03ccf4a77069e57f93e485861194.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_b593d85fc264c1f5279d302791801faf.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Dominant seventh and major seventh&lt;/h2&gt;
&lt;p&gt;A7 makes the same move on the G string but against the major third: lift the middle finger, let the G ring open, and the doubled root becomes the flat seventh. The rest of the A major hand does not move. It is the V chord in a blues in D (see the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;12-bar blues post&lt;/a&gt;). Amaj7 takes that string down only one fret instead, to G#, a half step below the root, for a jazzier and unresolved colour.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_346c52a4b332c26851fe9317f84a86c0.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_2fc66d1748ad62fd42ae201e16c64e19.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Sixth and add9&lt;/h2&gt;
&lt;p&gt;A6 is the one exception to the single-finger rule. Barre the second fret across the D, G, B and top E strings, and the open high E, a fifth, becomes F#, the sixth. Aadd9 goes back to the G string and stretches it up to the fourth fret, turning the doubled root into B, the ninth. Both keep the third, which is what separates an add chord from a suspension.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_e1ac4a2f17162570374a91609ce7208a.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_b1cb03d41a801ce581240b12b249497c.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Summary table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chord&lt;/th&gt;
&lt;th&gt;Frets (low to high)&lt;/th&gt;
&lt;th&gt;Change from A major&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;x02220&lt;/td&gt;
&lt;td&gt;base shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asus2&lt;/td&gt;
&lt;td&gt;x02200&lt;/td&gt;
&lt;td&gt;B string: 3rd -&amp;gt; 2nd (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asus4&lt;/td&gt;
&lt;td&gt;x02230&lt;/td&gt;
&lt;td&gt;B string: 3rd -&amp;gt; 4th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Am&lt;/td&gt;
&lt;td&gt;x02210&lt;/td&gt;
&lt;td&gt;B string: 3rd -&amp;gt; b3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Am7&lt;/td&gt;
&lt;td&gt;x02010&lt;/td&gt;
&lt;td&gt;Am, plus G string: doubled root -&amp;gt; b7 (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A7&lt;/td&gt;
&lt;td&gt;x02020&lt;/td&gt;
&lt;td&gt;G string: doubled root -&amp;gt; b7 (open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amaj7&lt;/td&gt;
&lt;td&gt;x02120&lt;/td&gt;
&lt;td&gt;G string: doubled root -&amp;gt; maj7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A6&lt;/td&gt;
&lt;td&gt;x02222&lt;/td&gt;
&lt;td&gt;top E: 5th -&amp;gt; 6th, barre at fret 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aadd9&lt;/td&gt;
&lt;td&gt;x02420&lt;/td&gt;
&lt;td&gt;G string: doubled root -&amp;gt; 9th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Next in the series: the same treatment for the &lt;a href="http://lizard-spock.co.uk/d-shape-open-chord-variations.html"&gt;D-shape&lt;/a&gt; and &lt;a href="http://lizard-spock.co.uk/e-shape-open-chord-variations.html"&gt;E-shape&lt;/a&gt; open chords.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Chords"/><category term="Open Chords"/><category term="Music Theory"/></entry><entry><title>Supports that peel off: a Bambu Studio profile that stops fighting back</title><link href="http://lizard-spock.co.uk/bambu-easy-remove-support-settings.html" rel="alternate"/><published>2026-08-30T00:00:00+01:00</published><updated>2026-08-30T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-30:/bambu-easy-remove-support-settings.html</id><summary type="html">&lt;p&gt;The nine Bambu Studio support settings I change from stock for a 0.4mm nozzle at 0.16mm layer height, and why each one matters when you are trying to get supports off without damaging the part.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The print finished four hours ago and it is still in the vice. One pair of pliers has already slipped and put a bright crescent scar across the face of the part. The support block is coming off in flakes, and every flake takes a little of the model surface with it.&lt;/p&gt;
&lt;p&gt;Nothing went wrong. The stock support profile is tuned to hold overhangs up reliably, and holding up reliably and letting go cleanly are opposite requirements.&lt;/p&gt;
&lt;p&gt;Below is the profile I have settled on in Bambu Studio, printing PLA on an A1 Mini with a 0.4mm nozzle at 0.16mm layer height. The settings map straight across to OrcaSlicer, which shares the same support engine.&lt;/p&gt;
&lt;h2&gt;The 0.16mm profile&lt;/h2&gt;
&lt;div class="support-values"&gt;
&lt;style&gt;
.support-values table { border-collapse: collapse; width: 100%; margin: 1.2em 0; }
.support-values th, .support-values td { padding: 0.4em 0.9em; text-align: left; border: none; }
.support-values thead th { border-bottom: 2px solid #2D2D2D; font-weight: 600; }
.support-values td:not(:first-child) { font-variant-numeric: tabular-nums; white-space: nowrap; }
.support-values tbody tr:nth-child(3n+1) { background: #FFFFFF; }
.support-values tbody tr:nth-child(3n+2) { background: #F5F2EC; }
.support-values tbody tr:nth-child(3n+3) { background: #EAE3D5; }
.support-values td.was { color: #4A4A4A; }
.support-values td.now { color: #7B35C2; font-weight: 600; }
&lt;/style&gt;
&lt;table&gt;
  &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Setting&lt;/th&gt;&lt;th&gt;Panel&lt;/th&gt;&lt;th&gt;Stock&lt;/th&gt;&lt;th&gt;This profile&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
  &lt;tbody&gt;
  &lt;tr&gt;&lt;td&gt;Enable support&lt;/td&gt;&lt;td&gt;Support&lt;/td&gt;&lt;td class="was"&gt;off&lt;/td&gt;&lt;td class="now"&gt;on&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Style&lt;/td&gt;&lt;td&gt;Support&lt;/td&gt;&lt;td class="was"&gt;Default&lt;/td&gt;&lt;td class="now"&gt;Tree Slim&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;On build plate only&lt;/td&gt;&lt;td&gt;Support&lt;/td&gt;&lt;td class="was"&gt;off&lt;/td&gt;&lt;td class="now"&gt;on&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Support critical regions only&lt;/td&gt;&lt;td&gt;Support&lt;/td&gt;&lt;td class="was"&gt;off&lt;/td&gt;&lt;td class="now"&gt;on&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Support wall loops&lt;/td&gt;&lt;td&gt;Advanced&lt;/td&gt;&lt;td class="was"&gt;-1 (auto)&lt;/td&gt;&lt;td class="now"&gt;0&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Top Z distance&lt;/td&gt;&lt;td&gt;Advanced&lt;/td&gt;&lt;td class="was"&gt;0.16mm&lt;/td&gt;&lt;td class="now"&gt;0.25mm&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Base pattern&lt;/td&gt;&lt;td&gt;Advanced&lt;/td&gt;&lt;td class="was"&gt;Default&lt;/td&gt;&lt;td class="now"&gt;Hollow&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Top interface spacing&lt;/td&gt;&lt;td&gt;Interface&lt;/td&gt;&lt;td class="was"&gt;0.5mm&lt;/td&gt;&lt;td class="now"&gt;0.8mm&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;Support/object XY distance&lt;/td&gt;&lt;td&gt;Interface&lt;/td&gt;&lt;td class="was"&gt;0.35mm&lt;/td&gt;&lt;td class="now"&gt;0.65mm&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Stock values are for the 0.16mm Optimal profile. Any setting absent from the
table is left exactly as Bambu ships it.&lt;/p&gt;
&lt;h2&gt;Top Z distance by layer height&lt;/h2&gt;
&lt;p&gt;Top Z distance is the only value here that depends on layer height. Bambu's own
stock value tracks the layer height until it caps at 0.20mm:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer height&lt;/th&gt;
&lt;th&gt;0.08&lt;/th&gt;
&lt;th&gt;0.12&lt;/th&gt;
&lt;th&gt;0.16&lt;/th&gt;
&lt;th&gt;0.20&lt;/th&gt;
&lt;th&gt;0.24&lt;/th&gt;
&lt;th&gt;0.28&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top Z, stock&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top Z, this profile&lt;/td&gt;
&lt;td&gt;0.17&lt;/td&gt;
&lt;td&gt;0.21&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;0.29&lt;/td&gt;
&lt;td&gt;0.29&lt;/td&gt;
&lt;td&gt;0.29&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The rule is stock plus about 0.09mm. Only the 0.16mm column has been print
tested, so treat the rest as a starting point.&lt;/p&gt;
&lt;h2&gt;Why the gap is the whole game&lt;/h2&gt;
&lt;p&gt;A support touches the model in two places: the top of the column against the
underside of the overhang, and the bottom of the column against whatever it
stands on. Everywhere else it stands in open air. All the effort of removal
goes into those two interfaces, and a gap you set in the slicer governs both.&lt;/p&gt;
&lt;p&gt;At a gap of zero the interface extrusion lands on solid plastic that is still
warm, and it welds. Pliers cannot separate a weld, so the model surface tears
instead of the support.&lt;/p&gt;
&lt;p&gt;At too large a gap the interface has nothing under it, sags into the void, and
the overhang above prints onto a wavy surface. Removal is easy because the
support barely worked.&lt;/p&gt;
&lt;p&gt;Every value below lands between those two failures, or reduces how much support
gets generated at all.&lt;/p&gt;
&lt;h2&gt;What each setting does&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Enable support: on.&lt;/strong&gt; Stock leaves it off, and the rest of the list only
matters once it is on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Style: Tree Slim.&lt;/strong&gt; The type is already tree (auto) at stock, so only the
style changes. Tree supports touch the model at discrete points rather than
under the whole projected footprint, and less contact area is less to break.
Tree Slim keeps the branches thin instead of thickening them into the
plate-filling structures Tree Strong produces. One caveat: Bambu Studio has an
&lt;a href="https://github.com/bambulab/BambuStudio/issues/10553"&gt;open bug&lt;/a&gt; where tree
support ignores a threshold angle below 20 degrees and supports everything at
70 degrees and above instead. Stock threshold is 25 at 0.16mm so this profile
never touches it, but stock falls to 20 at 0.12mm and 15 at 0.08mm. At finer
layer heights, hold the threshold at 20 or above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On build plate only: on.&lt;/strong&gt; Supports landing on the model leave the worst
marks, because you pry against a printed surface rather than against the plate.
Anything that would need a support standing on the model now gets none, so this
is a constraint on how you orient the part.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support critical regions only: on.&lt;/strong&gt; Prunes support down to the places that
genuinely fail without it. Small isolated overhangs bridge fine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support wall loops: 0.&lt;/strong&gt; A wall loop wraps the support body in a continuous
perimeter, turning loose infill lines into a rigid tube. The setting runs from
-1 to 2, where -1 is the stock auto and 0 permits infill-only support wherever
the body stands without a wall. That covers most tree branches, so the support
crumbles under a thumb.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Top Z distance: 0.25mm.&lt;/strong&gt; The air gap under the model, and the number that
decides between a fingernail job and a pliers job. Stock 0.16mm is one whole
layer, which welds. See the table above for other layer heights.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Base pattern: Hollow.&lt;/strong&gt; A sparse support body, again in the service of
weakness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Top interface spacing: 0.8mm.&lt;/strong&gt; The surprise of the set. Stock 0.5mm
approaches a solid sheet directly under the overhang: best surface finish,
hardest thing in the world to remove. At 0.8mm with a 0.4mm nozzle the
interface lines sit roughly a line width apart, so contact with the model above
is a series of ridges rather than a plane. Surface finish drops slightly.
Removal changes character completely, because the break now has somewhere to
start.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support/object XY distance: 0.65mm.&lt;/strong&gt; Horizontal clearance where a support
runs alongside a vertical wall. Too small and the support fuses to the side of
the part. Stock is 0.35mm and most published advice sits at 0.35 to 0.40, so
0.65 is deliberately generous. If your parts have narrow slots that supports
must reach into, come back down towards 0.4.&lt;/p&gt;
&lt;h2&gt;Why 0.25mm is available at all&lt;/h2&gt;
&lt;p&gt;0.25mm is not a multiple of 0.16mm, and that is the point.&lt;/p&gt;
&lt;p&gt;A support printed on the model's layer grid can only leave an air gap of a
whole number of layers. At 0.16mm the choices are 0.16mm, which welds, or
0.32mm, which sags. The value you want sits between them and stays
unreachable. Hence all the tuning advice that ends in "try one layer, then try
two", and why neither ever quite works.&lt;/p&gt;
&lt;p&gt;Independent support layer height removes the constraint, and Bambu turns it on
by default, so it is not on the list above. The support uses its own layer
heights and stops wherever it needs to in order to leave the gap you asked for.
The gap becomes a real dimension in millimetres.&lt;/p&gt;
&lt;p&gt;The option is ignored when the prime tower is enabled. Turn on a multi-colour
print and the supports go back on the model's layer grid, which puts you back
to choosing between 0.16mm and 0.32mm.&lt;/p&gt;
&lt;h2&gt;A part to test it on&lt;/h2&gt;
&lt;p&gt;Tuning a gap 0.01mm at a time needs something small to tune against. A real
part takes hours and only has the overhangs it happens to have.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/models/support-peel-test.3mf"&gt;Download the test coupon (3mf)&lt;/a&gt;, or the
&lt;a href="http://lizard-spock.co.uk/models/support-peel-test.scad"&gt;OpenSCAD source&lt;/a&gt; to change the
dimensions.&lt;/p&gt;
&lt;p&gt;It is an inverted L, 32 by 14 by 22mm, printing in under half an hour:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The arm cantilevers 22mm over nothing, so the slicer supports 308mm² of flat
  ceiling from the plate. That underside is the surface to inspect.&lt;/li&gt;
&lt;li&gt;The post beside the support column puts a vertical wall next to the support,
  where too small an XY distance shows up as fusing.&lt;/li&gt;
&lt;li&gt;At 18mm the column is tall enough to get a fingernail under.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The coupon has a &lt;code&gt;label&lt;/code&gt; parameter in the source. Set it to the value under
test, print one per candidate, and a row of them stays readable a week later.
The 3mf carries geometry only, so apply your own profile after opening.&lt;/p&gt;
&lt;h2&gt;If you change one thing&lt;/h2&gt;
&lt;p&gt;Change the top Z distance, and change it by 0.01mm at a time.&lt;/p&gt;
&lt;p&gt;Every other value shifts the result a little. That one decides whether the
support comes off in your hand or comes off with a scalpel, and on a 0.4mm
nozzle 0.24mm and 0.26mm are genuinely different prints. Get it right for your
filament, then leave the rest alone.&lt;/p&gt;</content><category term="Engineering"/><category term="3D Print"/><category term="Bambu Lab"/><category term="Slicer"/><category term="Supports"/><category term="OrcaSlicer"/></entry><entry><title>Digital Design 03: Metastability</title><link href="http://lizard-spock.co.uk/digital-design-03-metastability.html" rel="alternate"/><published>2026-08-29T00:00:00+01:00</published><updated>2026-08-29T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-29:/digital-design-03-metastability.html</id><summary type="html">&lt;p&gt;Why a flip-flop can stop between the rails, why two gates reading that voltage can disagree with each other, and why the bug that follows never reproduces.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article03/00-metastability-hero-HQ.png"&gt;&lt;img alt="A D-type flip-flop whose output wire splits at a junction and reaches two inverters, the upper one resolving it as 1 and the lower one as 0" src="http://lizard-spock.co.uk/images/DigitalDesign/Article03/00-metastability-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Digital Design Fundamentals | Article 3&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The board runs for six days. On the seventh, the status register reports a state
the state machine cannot reach, the firmware takes a branch that was never meant
to execute, and the watchdog resets the part. Simulation has run the same
sequence ten million times and never produced it. Attaching a scope to the
suspect net makes the fault stop for a fortnight.&lt;/p&gt;
&lt;p&gt;This article explains where that state came from. Two routes lead to it: an
input from another clock domain, and a synchronous path that failed timing
closure. Both end at the same condition, a setup or hold requirement that was
not met at the sampling edge. It is for anyone who writes register transfer
level code and has been told to put two flip-flops on an asynchronous input
without being told what the second flip-flop is for.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A decision with no margin&lt;/h2&gt;
&lt;p&gt;A flip-flop is a decision element with gain. At the sampling edge it captures
the difference between its input and its own switching threshold, then amplifies
that difference until the output reaches a rail.&lt;/p&gt;
&lt;p&gt;The captured difference sets how long the amplification takes. An input that
settled a nanosecond early presents almost the full supply, and one pass through
the internal loop is enough. An input that moved a few picoseconds before the
edge presents a few millivolts, and the same loop needs several passes to build
those millivolts up to a rail.&lt;/p&gt;
&lt;p&gt;That difference has no lower limit. Data crossing the threshold close enough to
the clock edge leaves the flip-flop microvolts to amplify, or less, and the
amplification then starts from microvolts.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article03/01-settling-margin-HQ.png"&gt;&lt;img alt="Output voltage against time for several starting margins, showing that a smaller captured margin takes longer to reach a rail and one curve still sitting at the threshold when the next clock edge reads it" src="http://lizard-spock.co.uk/images/DigitalDesign/Article03/01-settling-margin-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Distance from the threshold grows as &lt;code&gt;V0 e^(t / tau)&lt;/code&gt;, where &lt;code&gt;V0&lt;/code&gt; is the
captured margin and &lt;code&gt;tau&lt;/code&gt; is the settling time constant. Time to reach a rail
therefore depends on the logarithm of the margin, so a margin ten times smaller
costs only about 2.3 more time constants. Waiting buys a great deal and never
buys certainty.&lt;/p&gt;
&lt;p&gt;While the output is still climbing it sits between the rails. That is
metastability. Not a broken circuit, but an amplifier part way through a
decision.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Where the clock edge lands&lt;/h2&gt;
&lt;p&gt;Setup time and hold time describe a window around the clock edge in which the
input has to be stable. An input that changes inside that window is the input
that leaves almost no margin to amplify.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_d8b224b08549af11c760dc10e7812f03.svg"&gt;&lt;/p&gt;
&lt;p&gt;Two situations put a real design in that window. The first is a signal with no
fixed relationship to the sampling clock: another clock domain, a button, a
sensor. The second is a fully synchronous path that failed timing closure, where
static timing analysis has already reported a violated setup or hold
requirement.&lt;/p&gt;
&lt;p&gt;The two look different in a report and identical at the flip-flop. Both are the
same condition, a setup or hold requirement not met at the sampling edge.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;One wire, two answers&lt;/h2&gt;
&lt;p&gt;Settling time is not the only hazard, and the next one does not depend on it at
all. A flip-flop output is a wire, and a wire goes to more than one place.&lt;/p&gt;
&lt;p&gt;While the output sits near mid rail, every gate on that net is reading it. Each
has its own switching threshold, set by the ratio of its pull-up and pull-down
networks, and those thresholds are not identical. Process variation moves them,
different cell types have different ones by design, and different loads mean the
reads do not happen at the same instant.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_2ba665312784b327927f0c147c935239.svg"&gt;&lt;/p&gt;
&lt;p&gt;One node, one instant, two answers. Neither gate is faulty and neither reading
is wrong. The wire did not carry a logic value for them to agree about.&lt;/p&gt;
&lt;p&gt;That is the failure, and metastability by itself is not. A flip-flop sitting at
mid rail with nothing reading it costs some current and resolves. The damage
starts when that voltage reaches two places and the two places disagree.&lt;/p&gt;
&lt;p&gt;Downstream registers then hold values that cannot occur together. A one-hot
state machine ends a cycle with two bits high, so two decodes match at once. A
handshake ends with both ends believing the other holds the token. None of those
states appear in the encoding, in the assertions or in the coverage report. The
register transfer level model cannot represent a wire that two readers disagree
about, so simulation never produces them.&lt;/p&gt;
&lt;p&gt;The bug that results is rare, random, and moves when observed. A scope probe
adds capacitance and shifts the timing. Temperature and supply voltage move
switching thresholds. Rebuilding the design changes placement, so the wire
delays that produced the divergence are gone and a fresh set arrives elsewhere.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Two flip-flops, and why not one&lt;/h2&gt;
&lt;p&gt;The fix follows from the exponential. Give the metastable node a full clock
period to settle before anything reads it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_64f11331eecdb03016bc0300ff1c7e11.svg"&gt;&lt;/p&gt;
&lt;p&gt;The first flip-flop absorbs the risk, and its output goes nowhere except the
second flip-flop, so a divergent read has nothing to diverge into. The
single-load rule matters as much as the second stage. Fanning the first stage
out to anything else recreates the divergence the structure exists to prevent.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Putting a number on it&lt;/h2&gt;
&lt;p&gt;Mean time between failures, or MTBF, follows from the settling time constant and
two frequencies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;MTBF = e^(t_r / tau) / (T0 x f_clk x f_data)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;t_r&lt;/code&gt; is the settling time allowed, &lt;code&gt;T0&lt;/code&gt; is the width of the aperture, &lt;code&gt;f_clk&lt;/code&gt;
is the destination clock frequency and &lt;code&gt;f_data&lt;/code&gt; is the rate of input changes.
Only &lt;code&gt;t_r&lt;/code&gt; sits in the exponent, so it dominates everything else.&lt;/p&gt;
&lt;p&gt;Liberty timing files do not carry &lt;code&gt;tau&lt;/code&gt; or &lt;code&gt;T0&lt;/code&gt;. They model a cell as a
deterministic table of setup, hold and clock-to-Q, and metastability is a
probability distribution with an exponential tail. The parameters have to come
from transistor-level simulation instead. Measuring them on
&lt;code&gt;sky130_fd_sc_hd__dfxtp_1&lt;/code&gt;, the real 24-transistor SkyWater cell, gives this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Corner&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;tau&lt;/th&gt;
&lt;th&gt;T0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ss slow&lt;/td&gt;
&lt;td&gt;45.6 ps&lt;/td&gt;
&lt;td&gt;116.8 ps&lt;/td&gt;
&lt;td&gt;67.4 ps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tt typical&lt;/td&gt;
&lt;td&gt;28.6 ps&lt;/td&gt;
&lt;td&gt;43.8 ps&lt;/td&gt;
&lt;td&gt;492.2 ps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ff fast&lt;/td&gt;
&lt;td&gt;20.5 ps&lt;/td&gt;
&lt;td&gt;21.8 ps&lt;/td&gt;
&lt;td&gt;1872 ps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;tau&lt;/code&gt; varies by 5.4 times across the corners, and it sits in an exponent.
Characterise at the slow corner, because that is where the latch resolves most
slowly.&lt;/p&gt;
&lt;p&gt;Take the slow corner and put the synchroniser somewhere demanding. The
destination clock &lt;code&gt;f_clk&lt;/code&gt; is 400 MHz, so its period is 2.5 ns. The asynchronous
input &lt;code&gt;f_data&lt;/code&gt; changes at 10 MHz. The first stage gets half a period to settle,
and each stage after it adds a full period.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stages&lt;/th&gt;
&lt;th&gt;Settling allowed&lt;/th&gt;
&lt;th&gt;In time constants&lt;/th&gt;
&lt;th&gt;MTBF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.25 ns&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;0.16 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3.75 ns&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;10 years&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;6.25 ns&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;2.0e10 years&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A single flip-flop fails six times a minute. The second stage moves that to
about ten years, which is a product lifetime and not a comfortable margin. The
third stage moves it to twenty billion years. Each stage is worth roughly nine
orders of magnitude, because each one adds 2.5 ns of settling and that is 21
time constants.&lt;/p&gt;
&lt;p&gt;Slow the clock to 100 MHz and the same two-stage synchroniser reports 2.8e43
years. That is the number most designers meet, and it is why two flip-flops is
treated as automatic. The rule is safe at 100 MHz and marginal at 400 MHz, and
nothing about the flip-flop changed.&lt;/p&gt;
&lt;p&gt;Two stages is a convention, not a law. It holds only while &lt;code&gt;tau&lt;/code&gt; stays small
against the clock period. Raise the same cell to 1 GHz and two stages give half
a second, three give under an hour, and it takes five to reach 2500 years. A
130 nm cell has no business running at 1 GHz, and a process built for that speed
has a &lt;code&gt;tau&lt;/code&gt; of a few picoseconds.&lt;/p&gt;
&lt;p&gt;The measurement and the code behind those tables are at
&lt;a href="https://github.com/morganp/flipflop-mtbf"&gt;github.com/morganp/flipflop-mtbf&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;For any signal entering a clock domain from outside it, three questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does it pass through two flip-flops before anything else reads it?&lt;/li&gt;
&lt;li&gt;Does the first of those flip-flops drive exactly one load?&lt;/li&gt;
&lt;li&gt;Does anything else cross alongside it that has to stay consistent with it?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second question is the one this article adds. A synchroniser with a fanned
out first stage looks correct in a schematic review and fails in the same way as
no synchroniser at all.&lt;/p&gt;
&lt;p&gt;The third question is a different problem. Two flip-flops protect one bit, and
they promise nothing about a multi-bit bus, about related control signals
synchronised separately, or about a synchronous path that failed timing closure.
Those need handshakes, Gray coding, or a fix in the path itself.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/digital-design-02-pull-up-and-pull-down-networks.html"&gt;Digital Design 02: Pull-up and pull-down networks&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="Digital Design"/><category term="Electronics"/><category term="CMOS"/><category term="Flip-Flops"/><category term="Metastability"/><category term="Synchronisers"/><category term="CDC"/></entry><entry><title>Multi-tool or separate tools</title><link href="http://lizard-spock.co.uk/multi-tool-or-separate-tools.html" rel="alternate"/><published>2026-08-29T00:00:00+01:00</published><updated>2026-08-29T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-29:/multi-tool-or-separate-tools.html</id><summary type="html">&lt;p&gt;A Knipex Cobra XS, an Opinel No 08 and a bit driver measured against a Leatherman Wave+ on weight, price and capability.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/MultiToolOrSeparateTools/01-hero-HQ.png"&gt;&lt;img alt="Sketch comparison of a Leatherman Wave Plus against a Knipex Cobra XS, an Opinel No 08 and a PB Swiss bit driver" src="http://lizard-spock.co.uk/images/Outdoor/MultiToolOrSeparateTools/01-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The nut on the garden tap has seized again. The Leatherman comes out of the
pocket, the pliers swing open, and the jaws close on air either side of a
22 mm flat. The tool that has ridden in that pocket every day for a year cannot
grip the one thing it was carried for.&lt;/p&gt;
&lt;p&gt;This post compares a Leatherman Wave+ against three separate tools that cover
the same jobs, and asks whether an uncompromised set costs more weight or more
money. It is for anyone who carries a multi-tool daily and has started to
wonder what the alternatives are. The comparison uses published weights and
UK street prices throughout.&lt;/p&gt;
&lt;h2&gt;Where the weight sits&lt;/h2&gt;
&lt;p&gt;A multi-tool is a plier frame with implements stored in it. The Wave+ weighs
241 g, and the plier head, the two handle channels and the pivots account for
most of that. Take the blades, the saw, the file and the scissors out of a
Wave+ and the empty frame still weighs more than a whole Opinel.&lt;/p&gt;
&lt;p&gt;The frame does two jobs at once. It works the pliers, and it stores and
supports every other tool. Both jobs push the design in the same direction:
more steel. The frame needs stiffness to transmit grip force, and it needs
walls thick enough to carry a pivot pin for every folding implement.&lt;/p&gt;
&lt;p&gt;Every implement then pays rent on that frame. The knife blade contributes
perhaps 20 g of steel and carries a share of the frame that lets it fold
away. Counting eighteen tools and dividing 241 g by eighteen
gives a flattering number. The honest division puts most of the mass in
the box rather than in the tools.&lt;/p&gt;
&lt;h2&gt;What folding costs each tool&lt;/h2&gt;
&lt;p&gt;The frame costs weight. Folding costs capability, and it costs it three
separate times.&lt;/p&gt;
&lt;p&gt;The pliers lose leverage. Handle length sets the torque a hand can apply, and
the handles have to be short enough to carry in a pocket. They also have to be
hollow, because the other tools live inside them. The Wave+ jaws will not
reach a 22 mm nut, and plenty of fittings under a sink are larger than that.&lt;/p&gt;
&lt;p&gt;The blade loses grind. A folding blade has to fit inside the handle channel
alongside its neighbours, so its thickness and its profile are set by
clearance rather than by cutting. The Wave+ blade sits in a channel it shares
with a saw and a file. The result cuts, and it does not cut like a knife whose
only design constraint is cutting.&lt;/p&gt;
&lt;p&gt;The bit driver loses grip and bit choice. It folds out in line with the handle,
so the closed tool is held like a stubby screwdriver. The hand closes on a flat
steel channel with two square edges, not on a barrel the palm can bear down on.
Torque stops at the pressure the fingers can pinch. The Wave+ takes
Leatherman's own flattened bits rather than standard 1/4 inch hex, so an
existing bit collection needs the Bit Driver Extender accessory before it fits.
The tool carries one reversible bit, which is two tips.&lt;/p&gt;
&lt;h2&gt;The three-part kit&lt;/h2&gt;
&lt;p&gt;Three tools cover the same three jobs, and none of them shares a handle. The
Opinel folds, but its handle carries one blade and nothing else. Each tool is
built around its own job, with nothing given up to storing the others: solid
handles, a blade shaped only for cutting, a barrel the palm can bear down on.
An uncompromised set costs either weight or money, or it costs neither.&lt;/p&gt;
&lt;h3&gt;Knipex Cobra XS&lt;/h3&gt;
&lt;p&gt;The Cobra XS weighs 62 g and measures 100 x 30 x 10 mm. It costs about £25. The
jaws take pipes to 28 mm diameter and nuts to 24 mm across the flats, which is
larger than the Wave+ manages with handles 65 mm longer.&lt;/p&gt;
&lt;p&gt;Water pump pliers achieve that range by moving the pivot rather than opening
the jaws. A push button steps the pivot along a slotted track, so the handles
stay at a comfortable angle whatever the jaw opening. The jaws self-lock under
load, which means grip force comes from the workpiece pushing back rather than
from the hand squeezing harder.&lt;/p&gt;
&lt;p&gt;The handles are solid, because nothing lives inside them. A solid 100 mm handle
is stronger than a hollow 165 mm one, and it grips larger nuts.&lt;/p&gt;
&lt;h3&gt;Opinel No 08&lt;/h3&gt;
&lt;p&gt;The Opinel No 08 in carbon steel weighs 45 g and costs about £12. The blade is
8.5 cm, the handle is beech, and the Virobloc collar locks the blade open and
closed.&lt;/p&gt;
&lt;p&gt;Nothing about the blade is set by clearance. It is thin behind the edge because
a thin edge cuts, and the taper runs the full height of the blade rather than
stopping at a shoulder. XC90 carbon sharpens on almost anything and takes an
edge in a couple of minutes on a stone.&lt;/p&gt;
&lt;p&gt;The handle matters as much as the steel. A wooden handle fills the palm, warms
to the hand and gives a grip that does not depend on finger pressure. The
alternative is holding two hollow steel channels edge-on while cutting.&lt;/p&gt;
&lt;h3&gt;PB Swiss 6460 Insider&lt;/h3&gt;
&lt;p&gt;The PB Swiss 6460 Insider weighs 89 g including eight 1/4 inch C6 bits stored
in the handle. It costs about £56, and it is the expensive part of the kit.&lt;/p&gt;
&lt;p&gt;The handle is the point. It is a round screwdriver handle the palm can bear
down on, so grip force does not stop at what the fingers can pinch. The bit
magazine slides out of the tail and locks, so the bits travel inside the tool
rather than in a separate pouch. The bits are standard 1/4 inch C6, so hex,
Phillips and Pozidriv come out of any bit set already in the drawer.&lt;/p&gt;
&lt;p&gt;There is no ratchet. That is a deliberate choice and not only a weight saving.
A ratchet adds a pawl, a spring and a direction lever, and each is a part that
can fail under load in a pocket-carried tool. A fixed driver has nothing to
strip.&lt;/p&gt;
&lt;h2&gt;The numbers&lt;/h2&gt;
&lt;p&gt;The weights land close together. The prices do not, because the driver sets
them apart.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/MultiToolOrSeparateTools/02-weight-cost-HQ.png"&gt;&lt;img alt="Bar chart comparing the weight and the price of both kits against a Leatherman Wave Plus" src="http://lizard-spock.co.uk/images/Outdoor/MultiToolOrSeparateTools/02-weight-cost-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Knipex Cobra XS 87 00 100&lt;/td&gt;
&lt;td&gt;62 g&lt;/td&gt;
&lt;td&gt;£25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opinel No 08 carbon&lt;/td&gt;
&lt;td&gt;45 g&lt;/td&gt;
&lt;td&gt;£12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PB Swiss 6460 Insider, 8 bits&lt;/td&gt;
&lt;td&gt;89 g&lt;/td&gt;
&lt;td&gt;£56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kit total&lt;/td&gt;
&lt;td&gt;196 g&lt;/td&gt;
&lt;td&gt;£93&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SabreCut MRSC01K1, 4 pieces&lt;/td&gt;
&lt;td&gt;99 g&lt;/td&gt;
&lt;td&gt;£16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kit total with the SabreCut&lt;/td&gt;
&lt;td&gt;206 g&lt;/td&gt;
&lt;td&gt;£53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leatherman Wave+&lt;/td&gt;
&lt;td&gt;241 g&lt;/td&gt;
&lt;td&gt;£98&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first kit saves 45 g and £5. Neither number matters on its own. A 19%
weight saving is inside the noise of whatever else is in the pocket, and £5 on
a hundred is a rounding error.&lt;/p&gt;
&lt;p&gt;For 45 g and £5 less, the kit grips a 24 mm nut, cuts with a knife-shaped
knife and drives a screw with a handle the palm can push. The
weight of a multi-tool does not buy capability. It buys the convenience of one
object, and the capability pays for it.&lt;/p&gt;
&lt;h2&gt;A cheaper driver&lt;/h2&gt;
&lt;p&gt;The PB Swiss is 60% of the kit price. Pliers and knife together come to £37,
and the driver alone comes to £56.&lt;/p&gt;
&lt;p&gt;The SabreCut MRSC01K1 covers the same job for £15.99. The kit holds a stainless
ratchet of 58 g and 85 mm, a 60 mm magnetic bit holder, PH2 and PZ2 impact bits
and a pouch, and the four pieces together weigh 99 g. The gear is
chrome-molybdenum with 72 teeth, rated to 52 Nm. The body drives in line with
the screw or turns across it as a ratchet.&lt;/p&gt;
&lt;p&gt;Swapping it in brings the kit to 206 g and £53, so the second kit costs 10 g
more and £40 less. It also changes the driver in two ways that pull in opposite
directions. It gains a ratchet, which turns a screw in a recess too tight for a
full swing. It loses the handle, because 85 mm of stainless steel is a body the
fingers turn rather than a barrel the palm bears down on. Hex bits are not in
the box and add about 3.5 g each.&lt;/p&gt;
&lt;p&gt;Neither kit is the right answer for everyone. The PB Swiss kit is the lighter
one and drives the stiffest screws. The SabreCut kit comes in at £45 under the
Wave+, a little over half the price, and reaches into places neither of the
others will.&lt;/p&gt;
&lt;h2&gt;The lighter Leatherman&lt;/h2&gt;
&lt;p&gt;The Wave+ is not the lightest Leatherman that covers these three jobs. The
Skeletool carries pliers, one combination blade and a bit driver, and little
else. It weighs 142 g, measures 100 mm closed, and costs about £76.&lt;/p&gt;
&lt;p&gt;That undercuts both kits. The PB Swiss kit gives away 54 g to it and the
SabreCut kit gives away 64 g. Anyone choosing on weight alone should buy the
Skeletool.&lt;/p&gt;
&lt;p&gt;The three compromises survive the diet. The jaws are needle-nose and narrower
than the ones on the Wave+, so they reach nothing like 24 mm. The blade still
folds into a handle channel. The driver still takes Leatherman's flattened
bits, and the tool carries two double-ended bits, so four tips in total.&lt;/p&gt;
&lt;p&gt;The Skeletool wins on weight and the kit keeps the capability. Price runs the
other way again, since the SabreCut kit at £53 lands £23 under it.&lt;/p&gt;
&lt;h2&gt;What you give up&lt;/h2&gt;
&lt;p&gt;The kit loses four tools outright. There is no saw, no file, no scissors and no
awl. Anyone who uses the Wave+ scissors weekly keeps the Wave+.&lt;/p&gt;
&lt;p&gt;It also costs pocket space in a way the total weight hides. Three objects need
three places, or a pouch, and a pouch is another thing to carry. Three objects
are three things to lose, and the Opinel is the one that walks off.&lt;/p&gt;
&lt;p&gt;Deployment splits two ways. The Wave+ carries its blades on the outside, so a
blade opens one-handed, and the Opinel needs two hands and a twist of the
collar. The pliers go the other way, because the Wave+ handles have to swing
apart before the jaws are usable, and the Cobra XS is already a pair of pliers.
The kit also costs a moment of reaching for the right pocket.&lt;/p&gt;
&lt;h2&gt;When the Wave+ still wins&lt;/h2&gt;
&lt;p&gt;Unpredictable work favours the multi-tool. A day when the next job is unknown
is a day for carrying eighteen adequate tools rather than three good ones.&lt;/p&gt;
&lt;p&gt;Belt carry favours it too. One sheath on a belt is comfortable, and three
pouches are not. Anyone who works from a belt rather than a pocket is already
carrying the frame for free.&lt;/p&gt;
&lt;p&gt;Lending favours it. Handing a colleague a Wave+ solves whatever they have.
Handing them one third of a kit solves a third of it.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;Weigh and price whatever is in the pocket now. Then count the tools actually
used in the last month, not the tools on the packaging.&lt;/p&gt;
&lt;p&gt;If that list is pliers, knife and screwdriver, the frame holding the other
fifteen costs 45 g and £5 against the PB Swiss kit, and 35 g and £45 against
the SabreCut kit. If the
list has scissors on it, keep the Wave+.&lt;/p&gt;</content><category term="Outdoor"/><category term="edc"/><category term="tools"/><category term="knives"/><category term="multitool"/></entry><entry><title>SoC Intermediate 04: RTL synthesis and timing closure</title><link href="http://lizard-spock.co.uk/soc-intermediate-04-rtl-synthesis-and-timing-closure.html" rel="alternate"/><published>2026-08-28T00:00:00+01:00</published><updated>2026-08-28T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-28:/soc-intermediate-04-rtl-synthesis-and-timing-closure.html</id><summary type="html">&lt;p&gt;Why a design can pass sign-off timing and still fail in silicon, and the machinery that decides it: what synthesis optimises, what static timing analysis actually proves, SDC exceptions and the damage they hide, reading a timing report, RTL versus tool fixes, and multi-corner multi-mode closure.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI04/00-closure-hero-HQ.png"&gt;&lt;img alt="An RTL to silicon workflow drawn as connected engineering stations, moving from source code through synthesis, gates, placement, routing and timing reports, with a feedback loop returning to the start" src="http://lizard-spock.co.uk/images/SoC/ArticleI04/00-closure-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Intermediate SoC Design | Article 4 of 10&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The report was green&lt;/h2&gt;
&lt;p&gt;Sign-off timing passed. Worst negative slack across every corner was positive,
by a comfortable margin on setup and a thin margin on hold. It had been the
hardest block in the project, and it was finally done. Somebody put a chart in
the review deck showing the slack climbing out of the red over six weeks.&lt;/p&gt;
&lt;p&gt;The chip came back and mostly worked. In the cold chamber, one part in a few
hundred returned bad data from a configuration register, but only when the
register was written while a particular unrelated interface was active. Nobody
could reproduce it in simulation, because in simulation the design is exactly
as correct as the day it was signed off.&lt;/p&gt;
&lt;p&gt;This article is about synthesis and timing closure: what the tools genuinely
prove, what they only appear to prove, and where the gap between the two
usually opens up. It targets engineers writing register transfer level (RTL)
code that has to meet a frequency target, and anybody who has inherited a
constraint file and is wondering how much of it to trust.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What synthesis is being asked to do&lt;/h2&gt;
&lt;p&gt;Synthesis maps RTL onto standard cells from a technology library: flip-flops
and latches, combinational gates, arithmetic structures, clock gating cells,
and in low-power flows the isolation and retention cells that survive a
powered-down domain.&lt;/p&gt;
&lt;p&gt;It does that mapping while trying to satisfy your constraints and minimise
area, power, and delay. The word to notice is &lt;em&gt;your&lt;/em&gt;. The tool has no
independent notion of what the design is supposed to do at speed. It optimises
against the constraint file, and if the constraint file is wrong it will
produce an efficient, well-optimised implementation of the wrong intent.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_290e9a469cd346dbfaf0af140e17d679.svg"&gt;&lt;/p&gt;
&lt;p&gt;Two stages on that path are marked in purple because they are the only two that
read the constraints. Everything downstream inherits whatever those two were
told.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What static timing analysis proves&lt;/h2&gt;
&lt;p&gt;Static timing analysis (STA) checks every path in the design without
simulating a single input vector. It builds a graph from register clock pins,
combinational arcs through cells, net delays extracted after routing, clock
tree delays and skew, library setup and hold requirements, and your
constraints.&lt;/p&gt;
&lt;p&gt;For a path to work, the data launched by one edge has to arrive and settle
before the edge that captures it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;launch edge + clock-to-Q + data path delay + setup time
    &amp;lt;=
capture edge + useful skew - uncertainty
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both terms above assume the launch and capture flip-flops see the same clock
edge at the same moment, which is what a balanced clock tree is built to
deliver. &lt;a href="http://lizard-spock.co.uk/soc-article-07-clocking-reset-and-power-domains.html"&gt;Article 07 covers clock tree
synthesis&lt;/a&gt;
and the H-tree that equalises the paths. Real trees land within tens of
picoseconds rather than at zero, and useful skew unbalances them deliberately,
which is where the skew term earns its place.&lt;/p&gt;
&lt;p&gt;Assume a balanced tree for the moment, and setup needs only two edges of one
clock:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI04/03-setup-violation-HQ.png"&gt;&lt;img alt="Setup violation drawn as three waveform traces, CLK, launch flop Q and capture flop D, with the data transition landing inside a shaded setup window immediately before the second clock edge" src="http://lizard-spock.co.uk/images/SoC/ArticleI04/03-setup-violation-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hold is the same relationship read from the other end, and it is not a
next-edge check at all. The data must not arrive so early that it overwrites the
value the capture flip-flop is still reading from the edge that just fired.
Launch and capture are the same edge:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI04/04-hold-violation-HQ.png"&gt;&lt;img alt="Hold violation drawn as three waveform traces, CLK, launch flop Q and capture flop D, with the new data arriving inside a shaded hold window that starts at the single clock edge" src="http://lizard-spock.co.uk/images/SoC/ArticleI04/04-hold-violation-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The two failures have opposite personalities, and each is worst at the corner
that is kindest to the other.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Worst at&lt;/th&gt;
&lt;th&gt;Common fixes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Data arrives too late&lt;/td&gt;
&lt;td&gt;Slow silicon, low voltage, high temperature&lt;/td&gt;
&lt;td&gt;Reduce logic depth, resize cells, pipeline, improve placement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hold&lt;/td&gt;
&lt;td&gt;Data arrives too early&lt;/td&gt;
&lt;td&gt;Fast silicon, high voltage, low temperature&lt;/td&gt;
&lt;td&gt;Add delay cells, adjust clock skew, correct the constraint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Setup failures are a performance problem: run the part slower and they go away.
Hold failures are not. A hold violation is broken at every frequency, including
zero, which is why a hold bug in silicon is usually a respin and a setup bug
is usually a de-rated data sheet.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The constraint file is the specification&lt;/h2&gt;
&lt;p&gt;Most ASIC and FPGA flows describe timing intent in Synopsys Design Constraints
(SDC) syntax. A minimal block might start like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# Primary clock: 1 GHz&lt;/span&gt;
&lt;span class="nv"&gt;create_clock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;core_clk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;period&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;core_clk&lt;span class="k"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# External interface timing, relative to that clock&lt;/span&gt;
&lt;span class="nv"&gt;set_input_delay&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;0.250&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;clock&lt;span class="w"&gt; &lt;/span&gt;core_clk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rx_data&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;set_output_delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;clock&lt;span class="w"&gt; &lt;/span&gt;core_clk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tx_data&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Jitter and analysis margin&lt;/span&gt;
&lt;span class="nv"&gt;set_clock_uncertainty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.050&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_clocks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;core_clk&lt;span class="k"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Asynchronous reset assertion is not a functional timing path&lt;/span&gt;
&lt;span class="nv"&gt;set_false_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;reset_n&lt;span class="k"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Five commands, and every one of them is a claim about the world outside the
block. The period claims what the clock generator will produce. The input delay
claims when the upstream block launches. The uncertainty claims how much jitter
the phase-locked loop contributes. The false path claims that reset assertion
never needs to be captured on a specific edge.&lt;/p&gt;
&lt;p&gt;None of those claims are checked by anything. They are inputs.&lt;/p&gt;
&lt;p&gt;That is the reason a constraint file deserves the same review a piece of RTL
gets: an owner, a version history, a comment on every non-obvious line, and a
reviewer who is allowed to ask "how do you know?".&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Exceptions, and the damage they hide&lt;/h2&gt;
&lt;p&gt;Three SDC commands exist to tell STA to relax. All three are legitimate. All
three are also the standard way to make a report turn green without changing a
gate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A false path&lt;/strong&gt; says the path is not real and should not be analysed. A
configuration register that only changes during reset, a scan-mode path that is
inactive functionally, a test observation output nobody samples at speed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A multicycle path&lt;/strong&gt; says the path is real but has more than one clock period
to complete.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# Data launched by stage_a is captured by stage_b two cycles later&lt;/span&gt;
&lt;span class="nv"&gt;set_multicycle_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;setup&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;-from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stage_a_reg&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;-to&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stage_b_reg&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# The hold check has to move with it&lt;/span&gt;
&lt;span class="nv"&gt;set_multicycle_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;hold&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;-from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stage_a_reg&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;-to&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stage_b_reg&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That second command gets left out, and leaving it out is worse than not writing
the exception at all. A setup relaxation without the matching
hold adjustment tells the tool the data may arrive a cycle late, while still
requiring it not to arrive early relative to the original edge. The tool will
happily insert delay to satisfy a hold check that was never the real
requirement, or fail to insert it where it was.&lt;/p&gt;
&lt;p&gt;A multicycle constraint is only true if the destination register genuinely
cannot capture every cycle. That means an enable, and the enable has to be
provably sparse:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_554c2dcda9c10709d730c7ecdf34cead.svg"&gt;&lt;/p&gt;
&lt;p&gt;If a later revision adds a bypass mode that asserts &lt;code&gt;capture_en&lt;/code&gt; every cycle,
the RTL change is one line, the constraint is still in the file, and the timing
report still passes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A clock group&lt;/strong&gt; says two clocks have no known phase relationship:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;set_clock_groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;asynchronous&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;-group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_clocks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cpu_clk&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;-group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_clocks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;periph_clk&lt;span class="k"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is the most misread of the three. It does not make the crossing safe. It
does not add a synchroniser. It tells STA to stop analysing a relationship that
was never meaningful, and the correctness of the crossing is now entirely the
job of clock domain crossing (CDC) analysis and the synchronisers in the RTL.
Declaring the group and skipping the CDC review removes the only check that was
looking.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The gap&lt;/h2&gt;
&lt;p&gt;Static timing analysis never tells you whether the chip works. It tells you
whether the implementation matches the constraints. Those are the same
statement only to the extent that the constraints are true, and nothing in the
flow verifies that they are.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI04/01-map-and-terrain-HQ.png"&gt;&lt;img alt="A constraint file drawn as a simplified map laid over the more complicated terrain of a real chip, with a few places where the map and the terrain disagree marked as gaps" src="http://lizard-spock.co.uk/images/SoC/ArticleI04/01-map-and-terrain-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Read the failure at the top of the article again with that in mind. The
configuration register had a &lt;code&gt;set_false_path&lt;/code&gt; on it, added early, when the
register really was written only during reset. Two years later a feature landed
that reprogrammed it at runtime while the interface was live. The RTL review
covered the new write path. Nothing in the flow re-examined the exception,
because an exception is not code, it produces no warning, and it fails silently
by making a real path invisible.&lt;/p&gt;
&lt;p&gt;That is the shape of nearly every timing bug that reaches silicon. Not a path
the tool got wrong, but a path the tool was told not to look at.&lt;/p&gt;
&lt;p&gt;Which is why the useful question during a closure review is not "what is the
worst negative slack". It is "how many paths are we not analysing, who decided
that, and is the reason still true".&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# Worth running, and reviewing line by line, before every sign-off&lt;/span&gt;
&lt;span class="nv"&gt;report_exceptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;ignored
&lt;span class="nv"&gt;report_disable_timing&lt;/span&gt;
&lt;span class="nv"&gt;report_clock_properties&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;An exception with no comment and no owner should be treated as a bug until
somebody re-derives the argument for it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Reading a report&lt;/h2&gt;
&lt;p&gt;When a path genuinely fails, the report tells you what kind of problem it is,
provided you read past the slack number.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Startpoint&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_decode&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;op_reg&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;u_execute&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;alu_result_reg&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;core_clk&lt;/span&gt;
&lt;span class="n"&gt;Slack&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.083&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="mf"&gt;0.061&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="mf"&gt;0.142&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;compare&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="mf"&gt;0.188&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;operand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="mf"&gt;0.221&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;adder&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="mf"&gt;0.364&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="mf"&gt;0.177&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="mf"&gt;0.043&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="mf"&gt;1.000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The distribution matters more than the total. Here the logic dominates and no
single cell is pathological, which means this is a microarchitecture problem
and cell sizing will not save it. A different failure profile would call for a
different response:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What dominates&lt;/th&gt;
&lt;th&gt;What it usually means&lt;/th&gt;
&lt;th&gt;Where to fix it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Many small logic stages&lt;/td&gt;
&lt;td&gt;Logic depth&lt;/td&gt;
&lt;td&gt;RTL: split the chain, add a stage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One large cell delay&lt;/td&gt;
&lt;td&gt;Weak drive, high fanout&lt;/td&gt;
&lt;td&gt;Tools: sizing, buffering, cloning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing delay&lt;/td&gt;
&lt;td&gt;Placement or floorplan&lt;/td&gt;
&lt;td&gt;Physical: placement constraints, partitioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clock skew or uncertainty&lt;/td&gt;
&lt;td&gt;Clock tree or margin&lt;/td&gt;
&lt;td&gt;Clock tree synthesis, useful skew&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Required time surprises you&lt;/td&gt;
&lt;td&gt;The constraint is wrong&lt;/td&gt;
&lt;td&gt;The SDC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Check that last row first. It is the cheapest to fix and the most embarrassing
to find late. If the required time is not the
number you expected, stop looking at the data path.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Fixing it in RTL&lt;/h2&gt;
&lt;p&gt;The fixes available in RTL are the ones that change how much work sits between
two registers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Register the output of a wide multiplexer rather than the input of the next
  stage's logic.&lt;/li&gt;
&lt;li&gt;Split long add, compare, and select chains, especially where a comparison
  result selects an operand for an addition in the same cycle.&lt;/li&gt;
&lt;li&gt;Avoid the accidental priority encoder. A &lt;code&gt;case&lt;/code&gt; with overlapping conditions
  or a long &lt;code&gt;if / else if&lt;/code&gt; chain in a datapath synthesises into a serial
  structure, and one-hot control costs area but flattens the delay.&lt;/li&gt;
&lt;li&gt;Duplicate a high-fanout control register into local copies near its
  consumers, rather than asking the tool to buffer one signal across the block.&lt;/li&gt;
&lt;li&gt;Move work across an existing stage boundary before adding a new one, since
  the free rebalance is always better than the stage that costs a cycle of
  latency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern behind all five is the same: timing closure is easy when the
microarchitecture already has clean stage boundaries, and the &lt;a href="http://lizard-spock.co.uk/soc-intermediate-03-pipeline-design-and-hazards.html"&gt;earlier article on pipeline
hazards&lt;/a&gt;
is the reason those boundaries are not free to add later.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Fixing it in the tools&lt;/h2&gt;
&lt;p&gt;Implementation tools have their own set of moves: cell sizing, buffer
insertion, logic restructuring, register retiming, placement constraints,
useful skew, and clock tree adjustment.&lt;/p&gt;
&lt;p&gt;They are genuinely powerful, and retiming in particular can rebalance logic
across register boundaries in ways that would be tedious by hand. But they all
work within the structure the RTL gave them. No amount of sizing will fit two
cycles of logic into a one-cycle budget, and a path that needs a
microarchitecture change will keep coming back on every run, slightly
differently each time, consuming a week per iteration.&lt;/p&gt;
&lt;p&gt;The tell is a path that oscillates. Fix it, and it closes, and something
adjacent opens. That is not a tool failure. That is a design that has no slack
anywhere in a region, and the tool is moving a fixed shortage around.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Every corner, every mode&lt;/h2&gt;
&lt;p&gt;A single timing run is one point in a space the chip has to survive all of.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Slow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;silicon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;voltage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;worst&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;
&lt;span class="n"&gt;Fast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;silicon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;voltage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;worst&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;case&lt;/span&gt;
&lt;span class="n"&gt;Functional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retention&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;modes&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;different&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Multiply the process, voltage, and temperature corners by the operating modes
and the analysis views multiply with them. The sign-off question is not "does
the design meet timing", it is "does every mode meet timing at every corner it
can be in".&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI04/02-corners-modes-HQ.png"&gt;&lt;img alt="A grid of process, voltage and temperature corners crossed with operating modes, with the slow hot corner and the fast cold corner marked as the ones that fail first" src="http://lizard-spock.co.uk/images/SoC/ArticleI04/02-corners-modes-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Two practical consequences. First, a corner nobody enabled is a corner nobody
checked, so the list of analysis views is itself a review item. Second,
low-temperature inversion means the fast corner is not simply "everything is
quicker": at modern nodes some cells get slower at low voltage as temperature
drops, so the intuition that cold silicon is fast silicon is not reliable and
the corner list has to come from the library characterisation, not from
reasoning.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The ECO loop&lt;/h2&gt;
&lt;p&gt;Late fixes go in as an engineering change order: a small, surgical patch to a
design that is otherwise finished.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_fb0965470e4d1e56a615bafacba4377f.svg"&gt;&lt;/p&gt;
&lt;p&gt;The purple node carries the risk. An ECO is small in the netlist and unbounded
in what it can invalidate: timing on adjacent paths, logical equivalence
against the RTL, CDC structures, power intent, and any software-visible
behaviour that verification signed off weeks earlier. Every ECO needs the full
check set re-run, and the discipline that makes late ECOs survivable is keeping
them small enough that the re-run is credible.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Closure checklist&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Give the constraint file an owner and a version history, and review it like
   RTL.&lt;/li&gt;
&lt;li&gt;List every exception in the design with a one-line justification and the
   name of the person who made the argument.&lt;/li&gt;
&lt;li&gt;Re-derive those justifications whenever the RTL around them changes, because
   nothing else will.&lt;/li&gt;
&lt;li&gt;Pair every &lt;code&gt;-setup&lt;/code&gt; multicycle with its matching &lt;code&gt;-hold&lt;/code&gt;, and prove the
   capture enable is as sparse as the constraint claims.&lt;/li&gt;
&lt;li&gt;Treat &lt;code&gt;set_clock_groups -asynchronous&lt;/code&gt; as a note to STA, never as a CDC
   solution, and check the synchronisers separately.&lt;/li&gt;
&lt;li&gt;Track worst negative slack and total negative slack per path group, since a
   single bad path and a thousand marginal ones need different responses.&lt;/li&gt;
&lt;li&gt;Confirm the analysis view list covers every corner and mode the part can
   actually be in, including the ones only test or retention can reach.&lt;/li&gt;
&lt;li&gt;Re-run lint, CDC, logical equivalence, and STA after every ECO, without
   exception, however small the patch.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Ask what is not being analysed&lt;/h2&gt;
&lt;p&gt;The number at the top of the timing report tells you how the paths under
analysis are doing. It says nothing at all about the paths that were removed
from analysis, and those are the ones with a history of reaching silicon.&lt;/p&gt;
&lt;p&gt;Before the next sign-off, dump the exception list and read it end to end. On a
mature design it will be longer than you expect, and some fraction of it will
be constraints written by people who have left, against RTL that has since
changed, for reasons nobody recorded. That fraction is your actual risk, and it
is countable in an afternoon.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-intermediate-03-pipeline-design-and-hazards.html"&gt;Article I-03: Pipeline design and hazards&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: Intermediate Article 05, Clock domain crossing techniques&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Electronics"/><category term="RTL"/><category term="Synthesis"/><category term="Static Timing Analysis"/><category term="SDC"/><category term="Timing Closure"/><category term="ASIC"/><category term="FPGA"/></entry><entry><title>Digital Design 02: Pull-up and pull-down networks</title><link href="http://lizard-spock.co.uk/digital-design-02-pull-up-and-pull-down-networks.html" rel="alternate"/><published>2026-08-19T00:00:00+01:00</published><updated>2026-08-19T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-19:/digital-design-02-pull-up-and-pull-down-networks.html</id><summary type="html">&lt;p&gt;Why a static CMOS gate is always inverting, how series and parallel transistor networks turn into NAND and NOR, why NOR2 is the slower of the pair, and where the extra two transistors in an AND gate go.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article02/00-pullup-pulldown-hero-HQ.png"&gt;&lt;img alt="An output wire strung between a supply rail and a ground rail, with a closed switch pulling it up and an open switch below it, and arrows pointing up to the supply and down to ground" src="http://lizard-spock.co.uk/images/DigitalDesign/Article02/00-pullup-pulldown-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Digital Design Fundamentals | Article 2&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The AND gate that was not there&lt;/h2&gt;
&lt;p&gt;You wrote &lt;code&gt;assign y = a &amp;amp; b;&lt;/code&gt;, ran synthesis, and opened the gate-level netlist
to check something unrelated. There is no AND gate in it. There is a NAND2
feeding an inverter.&lt;/p&gt;
&lt;p&gt;Look up both cells in the library databook and it gets stranger. The NAND2 is
four transistors. The AND2, which exists, is six, and it is slower. The
synthesiser did nothing unusual. It used the AND2 cell, and
the AND2 cell &lt;strong&gt;is&lt;/strong&gt; a NAND2 followed by an inverter, because in static CMOS
there is no other way to build one.&lt;/p&gt;
&lt;p&gt;This article is about why. It covers the two transistors as switches, the
pull-down and pull-up networks made from them, why those networks always
produce an inverting function, how NAND and NOR fall out of series and parallel
arrangements, and why the two are not equally good. It targets anyone who has
been handed a standard cell library and wants to know what the numbers in it
mean, and anyone learning digital design who wants gates to stop being symbols
on a page. It follows on from
&lt;a href="http://lizard-spock.co.uk/digital-design-01-mask-sets-and-how-a-transistor-gets-built.html"&gt;Digital Design 01&lt;/a&gt;,
which built the transistors these gates are made of and showed how a gate
voltage forms the channel that makes one conduct.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A 1 is a voltage&lt;/h2&gt;
&lt;p&gt;One thing to settle before any of the schematics make sense: a logic value is
not an abstraction sitting on top of the circuit. It is a voltage on a wire.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A digital 1 is the supply voltage, VDD.&lt;/strong&gt; &lt;strong&gt;A digital 0 is ground, 0 V.&lt;/strong&gt; In
these schematics the rail marked VDD and the value 1 are the same thing, and the
rail marked GND and the value 0 are the same thing. Saying a gate "outputs a 1"
and saying it "connects its output to VDD" describe one event.&lt;/p&gt;
&lt;p&gt;VDD is whatever the process runs at, and it has fallen steadily with each node:
5 V in the 1980s, 3.3 V, 1.8 V, and around 0.7 to 0.9 V on a modern logic
process. The logic does not care about the number. It cares that there are two
rails, that every output can be tied to one of them, and that the receiving gate
can tell which.&lt;/p&gt;
&lt;p&gt;Real circuits leave margin around both values. Anything above roughly 70 per
cent of VDD reads as a 1, anything below roughly 30 per cent reads as a 0, and
the band between is the noise margin. A "weak" or "degraded" level later in this
article means a voltage that has drifted towards that middle band, where the
next gate has to work harder to decide.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Two switches, each good at one thing&lt;/h2&gt;
&lt;p&gt;The metal-oxide-semiconductor field-effect transistor (MOSFET) has three
terminals that matter here: &lt;strong&gt;gate&lt;/strong&gt;, &lt;strong&gt;source&lt;/strong&gt; and &lt;strong&gt;drain&lt;/strong&gt;. Voltage on the
gate controls whether current can flow between source and drain. No current
flows into the gate itself; it is a capacitor plate over an insulator. That is
the whole reason complementary metal-oxide-semiconductor (CMOS) logic can drive
thousands of inputs without a power budget for it.&lt;/p&gt;
&lt;p&gt;There are two flavours, and they are opposites.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;nMOS&lt;/th&gt;
&lt;th&gt;pMOS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conducts when the gate is&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turns on at&lt;/td&gt;
&lt;td&gt;gate above source by the threshold voltage&lt;/td&gt;
&lt;td&gt;gate below source by the threshold voltage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passes a good&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passes a poor&lt;/td&gt;
&lt;td&gt;1, stopping a threshold below the supply&lt;/td&gt;
&lt;td&gt;0, stopping a threshold above ground&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrier&lt;/td&gt;
&lt;td&gt;electrons&lt;/td&gt;
&lt;td&gt;holes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed for the same width&lt;/td&gt;
&lt;td&gt;faster&lt;/td&gt;
&lt;td&gt;slower, by roughly two to three times&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article02/01-mos-switches-HQ.png"&gt;&lt;img alt="Side by side schematics of an nMOS transistor connected between the output node and ground and a pMOS transistor connected between the supply and the output node, annotated with which logic level each one passes well" src="http://lizard-spock.co.uk/images/DigitalDesign/Article02/01-mos-switches-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The two rows in the middle of that table are the ones everything else is built
on.&lt;/p&gt;
&lt;p&gt;Try to pull an output up to the supply through an nMOS. The output rises, and as
it rises the gate-to-source voltage falls, because the source is the rising
node. When the output gets within one threshold voltage of the gate, the
transistor stops conducting. The output sits at roughly the supply minus a
threshold: a 1 that is a few hundred millivolts short of VDD, eating into the
noise margin above and leaking current in the gate it feeds. The pMOS has the mirror-image problem pulling down to ground.&lt;/p&gt;
&lt;p&gt;So the transistors get used the way they are good:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;nMOS transistors connect the output to ground.&lt;/strong&gt; They form the &lt;strong&gt;pull-down
  network&lt;/strong&gt;, and their job is producing a solid 0.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pMOS transistors connect the output to the supply.&lt;/strong&gt; They form the
  &lt;strong&gt;pull-up network&lt;/strong&gt;, and their job is producing a solid 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every static CMOS gate is those two networks, sharing the same inputs, sharing
the same output node, and arranged so exactly one of them conducts for any
combination of inputs. That last property is what "complementary" means, and it
has a consequence: when the output is settled, there is no path
from supply to ground, so a settled gate draws no current beyond leakage. CMOS
burns power when it switches, not when it sits.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The inverter, in full&lt;/h2&gt;
&lt;p&gt;One nMOS, one pMOS, gates tied together as the input, drains tied together as
the output.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article02/02-inverter-HQ.png"&gt;&lt;img alt="A CMOS inverter schematic with a pMOS above and an nMOS below, plus two smaller versions showing which transistor conducts for a low input and for a high input" src="http://lizard-spock.co.uk/images/DigitalDesign/Article02/02-inverter-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Input low: the pMOS conducts and the nMOS is off, so the output is connected to
the supply and pulled to a solid 1. Input high: the nMOS conducts and the pMOS
is off, so the output is connected to ground and pulled to a solid 0. Each
transistor is used only in the direction it is good at, and in neither state is
there a path from supply to ground.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;pMOS&lt;/th&gt;
&lt;th&gt;nMOS&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two practical points come straight out of the picture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The pMOS is drawn wider than the nMOS.&lt;/strong&gt; Holes are slower than electrons by
about a factor of two to three, so a pMOS of the same width delivers less
current and pulls up more slowly than the nMOS pulls down. Cell libraries
compensate by making the pMOS roughly twice the width, which balances rise and
fall times at the cost of area and input capacitance. Look at any standard cell
layout and the top row of transistors is visibly fatter than the bottom row.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Drive strength is width.&lt;/strong&gt; An INVX4 is not a different circuit from an INVX1.
It is the same two transistors, four times as wide, sourcing four times the
current into whatever it drives, and presenting four times the capacitance to
whatever drives it. That trade is the entire content of buffer insertion during
synthesis: pay input capacitance here to gain drive strength there.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Series and parallel are AND and OR&lt;/h2&gt;
&lt;p&gt;Now build a network out of more than one transistor. Two rules, and they are the
whole of it, because a transistor is a switch and switches in series and in
parallel behave the way you would expect.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In series&lt;/strong&gt;: the path conducts only if &lt;strong&gt;both&lt;/strong&gt; transistors conduct.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In parallel&lt;/strong&gt;: the path conducts if &lt;strong&gt;either&lt;/strong&gt; transistor conducts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take the pull-down network, made of nMOS, so each transistor conducts when its
input is high. Then:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pull-down arrangement&lt;/th&gt;
&lt;th&gt;Conducts when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A in series with B&lt;/td&gt;
&lt;td&gt;A AND B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A in parallel with B&lt;/td&gt;
&lt;td&gt;A OR B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And here is the step that decides the shape of every gate that follows. The
pull-down network pulls the output &lt;strong&gt;low&lt;/strong&gt;. So when the network conducts, the
output is 0, and when it does not, the pull-up has it at 1:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pull&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;down&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conducts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The pull-down network computes AND and OR of its inputs, and then the output
node inverts the answer, because conducting means pulling low. There is no
arrangement of nMOS transistors between the output and ground that produces a
non-inverting function. Pulling the output down is the only thing such a network
can do.&lt;/p&gt;
&lt;p&gt;The pull-up network has to be the complement, so that exactly one network
conducts at a time. Since pMOS transistors conduct on a &lt;strong&gt;low&lt;/strong&gt; input, the
complement comes out as the series-parallel dual: wherever the pull-down has
transistors in series, the pull-up has them in parallel, and the other way
round. That is De Morgan's theorem drawn as wiring rather than written as
algebra.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article02/04-series-parallel-HQ.png"&gt;&lt;img alt="Two switch networks, one series pair conducting only when both switches are on and one parallel pair conducting when either is on, with the rule that the pull-up network uses the opposite arrangement" src="http://lizard-spock.co.uk/images/DigitalDesign/Article02/04-series-parallel-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The recipe for any static CMOS gate is now mechanical:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write the function you want as an inverted expression, &lt;code&gt;y = NOT(f)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build &lt;code&gt;f&lt;/code&gt; in nMOS between output and ground: AND becomes series, OR becomes
   parallel.&lt;/li&gt;
&lt;li&gt;Build the dual of that network in pMOS between output and supply: series
   becomes parallel, parallel becomes series.&lt;/li&gt;
&lt;li&gt;Count the transistors. Two per input, always.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;NAND2 and NOR2&lt;/h2&gt;
&lt;p&gt;Apply the recipe to the two simplest cases and the standard cell library appears.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NAND2&lt;/strong&gt; is &lt;code&gt;y = NOT(A AND B)&lt;/code&gt;. AND becomes two nMOS in series to ground. The
dual is two pMOS in parallel to the supply.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOR2&lt;/strong&gt; is &lt;code&gt;y = NOT(A OR B)&lt;/code&gt;. OR becomes two nMOS in parallel to ground. The
dual is two pMOS in series to the supply.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article02/03-nand2-nor2-HQ.png"&gt;&lt;img alt="NAND2 and NOR2 transistor schematics side by side, showing series nMOS with parallel pMOS for the NAND and parallel nMOS with series pMOS for the NOR, with the transistor sizing noted for each" src="http://lizard-spock.co.uk/images/DigitalDesign/Article02/03-nand2-nor2-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;NAND2 pull-down&lt;/th&gt;
&lt;th&gt;NAND2 out&lt;/th&gt;
&lt;th&gt;NOR2 pull-down&lt;/th&gt;
&lt;th&gt;NOR2 out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Four transistors each, symmetric on paper. They are not symmetric in silicon.&lt;/p&gt;
&lt;p&gt;In NAND2 the &lt;strong&gt;slow&lt;/strong&gt; transistors, the pMOS pair, are in parallel, so either one
alone can pull the output up, and each can stay near the minimum practical width
that balances the gate. The nMOS pair is in series, so two channel resistances
add on the pull-down, and each nMOS is roughly doubled in width to compensate.
Electrons are cheap to widen.&lt;/p&gt;
&lt;p&gt;In NOR2 it is the other way round, and the arrangement falls the wrong way. The pMOS pair
is in &lt;strong&gt;series&lt;/strong&gt;, so two already-slow devices add their resistances, and getting
a rise time to match the fall time means widening each of them by something like
four times the nMOS width. That is a bigger cell, more input capacitance
presented to whatever drives it, and a worse delay for the same logical work.&lt;/p&gt;
&lt;p&gt;This is why libraries and synthesis are NAND-heavy. Given a choice between two
logically equivalent structures, the one built from NAND2 and inverters is
usually smaller and faster than the one built from NOR2. It is also why a
four-input NOR is rare, and a four-input NAND uncommon: stacking four pMOS in
series produces a cell whose pull-up is several times slower than its pull-down,
and stacking any four devices runs into the &lt;strong&gt;body effect&lt;/strong&gt;, where transistors part-way up a
stack see a raised source voltage, an effectively higher threshold, and less
current than the one at the bottom. Practical libraries stop at three or four
inputs and let synthesis build trees from there.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Where the AND gate's extra transistors went&lt;/h2&gt;
&lt;p&gt;Back to the netlist that started this. The recipe produces &lt;code&gt;y = NOT(f)&lt;/code&gt;, always.
The output node is pulled low by a conducting network, and there is no way to
arrange switches so that conducting pulls it high while still using nMOS below
and pMOS above. &lt;strong&gt;A single-stage static CMOS gate is inverting by construction.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So AND2 is NAND2 plus an inverter: 4 + 2 = 6 transistors, two stages of delay
rather than one. OR2 is NOR2 plus an inverter, and inherits the NOR2 pull-up
problem as well. A buffer is two inverters, for the same reason. Nothing in the
library is non-inverting for free.&lt;/p&gt;
&lt;p&gt;NAND and NOR are not
"universal gates" because of a tidy algebraic fact about being able to express
everything with them. They are the gates the libraries are built from because
a pull-down network and its dual produce them naturally, and AND and OR
are the compound cells, sold at a two-transistor and one-stage premium. The
Boolean operators you write in a hardware description language are the
abstraction. The inverting cells are the hardware.&lt;/p&gt;
&lt;p&gt;Two habits follow from it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Push the inversions around instead of paying for them.&lt;/strong&gt; De Morgan says a
NAND with inverted inputs is an OR, and a NOR with inverted inputs is an AND.
An inverter that cancels another inverter is free once the netlist is optimised,
and a bubble that moves to a place where something else already inverts costs
nothing at all. Synthesis does this automatically, which is why the netlist so
rarely resembles the RTL structurally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compound functions can stay in one stage.&lt;/strong&gt; The recipe never said the network
had to be two transistors. &lt;code&gt;y = NOT((A AND B) OR C)&lt;/code&gt; is an &lt;strong&gt;and-or-invert&lt;/strong&gt;
gate, AOI21: two nMOS in series for the AND, that pair in parallel with a third
for the OR, and the dual above it. Six transistors, one stage of delay, and it
does the work of an AND2 feeding a NOR2, which would have cost ten
transistors and three stages.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article02/05-aoi21-HQ.png"&gt;&lt;img alt="An AOI21 gate schematic with two nMOS in series in parallel with a third in the pull-down network, and the dual arrangement of pMOS in the pull-up network, six transistors in one stage" src="http://lizard-spock.co.uk/images/DigitalDesign/Article02/05-aoi21-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Libraries carry a whole family of these: AOI21, AOI22, OAI21, OAI221 and more.
Technology mapping during synthesis is largely the business of spotting where
one of them fits, and it is a large part of why hand-instantiated gate-level
logic so seldom beats the tool.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The two things static CMOS is bad at&lt;/h2&gt;
&lt;p&gt;Two functions resist the recipe, and both are common enough that libraries solve
them another way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The multiplexer.&lt;/strong&gt; Selecting between two inputs as a static CMOS gate is an
AOI22 at eight transistors, one inverter to make the complement of the select
and another to restore the polarity: twelve transistors for a function that only
has to connect one of two wires to an output. The
alternative is a &lt;strong&gt;transmission gate&lt;/strong&gt;: an nMOS and a pMOS in parallel, driven
by complementary select signals, acting as a switch that passes a full-strength
signal in either direction. The nMOS covers the low end, the pMOS covers the
high end, and between them the weak-1 and weak-0 problems cancel. Two
transmission gates and an inverter make a clean two-way multiplexer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exclusive-OR.&lt;/strong&gt; In pure static CMOS, XOR needs both inputs and their
complements and lands at ten to twelve transistors. Built from transmission
gates it comes down to six or eight. Since XOR is the core of every adder and
every parity tree, that saving justifies the library carrying special cells for
it, and it is why an adder's transistor count never quite matches the figure the
gate count suggests.&lt;/p&gt;
&lt;p&gt;The catch with transmission gates is that they pass signals rather than drive
them. A chain of them accumulates resistance quadratically and has no gain to
restore a degraded edge, so a buffer has to appear every couple of stages.
Transmission-gate logic is a local optimisation inside a cell, not a style for
building a whole design.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Reading a cell library like a circuit&lt;/h2&gt;
&lt;p&gt;The next time a databook is open, the numbers have physical meanings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transistor count&lt;/strong&gt;, or the area column standing in for it: two per input for
  a simple gate, plus two for every inversion the function needs at the output.
  A six-transistor two-input cell is a compound gate with an inverter on it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input capacitance&lt;/strong&gt; differs per pin on the same cell. In a NAND2 the input
  nearest the output in the nMOS stack switches faster than the one nearest
  ground, which is why the timing model gives per-pin arcs and why synthesis
  cares which signal lands on which pin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A NOR2 that is larger than the NAND2&lt;/strong&gt; of the same drive strength is the
  series pMOS stack, sized up. If a design is NOR-heavy, that is showing up in
  both area and timing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drive strength suffixes&lt;/strong&gt; are transistor width. More output current, more
  input capacitance, no change to the logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rise and fall times that differ&lt;/strong&gt; mean the pull-up and pull-down networks
  are not balanced, either by design or because the cell was optimised for area.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every one of those numbers is a pull-up network and a pull-down network, drawn
as polygons on the masks from the previous article, and the standard cell layout
is where the two articles meet: the p-type row along the top under a supply
rail, the n-type row along the bottom over a ground rail, polysilicon stripes
crossing both to form the gates, and metal 1 stitching the drains together into
the output.&lt;/p&gt;
&lt;p&gt;From here the series splits, and the two branches do not depend on each other.&lt;/p&gt;
&lt;p&gt;One branch wires these cells into a loop and asks what happens when the loop
holds neither a 0 nor a 1: metastability, and the unreproducible bugs that
follow when two gates read the same undecided wire and disagree.&lt;/p&gt;
&lt;p&gt;The other branch keeps the cells combinational and starts combining them:
Boolean algebra as a tool for restructuring logic before it ever reaches a
transistor.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/digital-design-01-mask-sets-and-how-a-transistor-gets-built.html"&gt;Digital Design 01: Mask sets, and how a transistor gets built&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/digital-design-03-metastability.html"&gt;Digital Design 03: Metastability&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="Digital Design"/><category term="Electronics"/><category term="CMOS"/><category term="MOSFET"/><category term="Logic Gates"/><category term="NAND"/><category term="NOR"/><category term="Standard Cells"/><category term="Transistors"/></entry><entry><title>Lashing a tripod</title><link href="http://lizard-spock.co.uk/lashing-a-tripod.html" rel="alternate"/><published>2026-08-19T00:00:00+01:00</published><updated>2026-08-19T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-19:/lashing-a-tripod.html</id><summary type="html">&lt;p&gt;How to tie a clove hitch and a tripod lashing, and the wrap and frapping counts that let three poles open into a tripod.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/LashingATripod/00-tripod-hero-HQ.png"&gt;&lt;img alt="Two lashed tripods standing apart, carrying a horizontal spar between their apexes with kit hung from it" src="http://lizard-spock.co.uk/images/Outdoor/LashingATripod/00-tripod-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Camp gadgets stand on tripods. Three points of contact define a plane, so three feet sit solid on whatever ground they are put on. Add a fourth and one leg can end up on a different plane from the other three, which is the rocking you have met on a pub table or a chair on an uneven floor.&lt;/p&gt;
&lt;p&gt;This post ties three poles into a tripod with one lashing, starting with the clove hitch it begins and ends on.&lt;/p&gt;
&lt;h2&gt;Tying the clove hitch&lt;/h2&gt;
&lt;p&gt;Both ends of the lashing are clove hitches, so that knot comes first. It is two turns round the pole, the second crossing the first, with the working end tucked under the crossing. Tie it in place, on the pole, without needing to reach an end.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take the working end up the front of the pole, over the top, and down the back.&lt;/li&gt;
&lt;li&gt;Bring it out underneath. That is one full turn, and on its own it will slide.&lt;/li&gt;
&lt;li&gt;Cross back over the front and round again the same way, so the second turn lies diagonally across the first.&lt;/li&gt;
&lt;li&gt;Feed the working end under that diagonal.&lt;/li&gt;
&lt;li&gt;Pull both ends until the turns bed down against the pole.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/LashingATripod/01-clove-hitch-HQ.png"&gt;&lt;img alt="Five panels showing a clove hitch tied in place on a horizontal pole: the end taken up the front and round the back, brought out underneath, crossed over and round a second time to form a diagonal, fed under that diagonal, and pulled tight" src="http://lizard-spock.co.uk/images/Outdoor/LashingATripod/01-clove-hitch-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step 4 is the whole knot. The two turns trap each other against the pole, so the harder the lashing pulls, the harder the hitch grips. Miss the tuck and you have a round turn, which shakes loose as soon as you let go.&lt;/p&gt;
&lt;p&gt;The finished hitch has the standing part leaving on one side and the working end on the other. If both come out the same side you have tied a cow hitch, which slides along the pole under load. That is the one check worth making before you start wrapping.&lt;/p&gt;
&lt;p&gt;Where the top of the pole is clear, there is a faster way: make two loops the same way round, stack the second behind the first, and drop both over the end. It gives the identical knot and it works in gloves, but it needs a free end, so it is no use partway along a standing spar.&lt;/p&gt;
&lt;h2&gt;Tying the lashing&lt;/h2&gt;
&lt;p&gt;The tripod lashing goes by three names for the same tie. Scouting books call it a figure-of-eight lashing, riggers call it a gyn lashing, and most people call it a tripod lashing. All three describe a set of turns woven between the poles rather than wrapped around the outside of them.&lt;/p&gt;
&lt;p&gt;A cut pole tapers along its length. The thick end, the one that grew nearest the root, is the &lt;strong&gt;butt&lt;/strong&gt;; the thin end is the tip. Lay all three butts the same way and the bundle comes out fat at one end and thin at the other, so the wraps grip hard on one side and slackly on the other.&lt;/p&gt;
&lt;p&gt;So lay the three poles side by side on the ground, with the middle one reversed end for end. The butts then alternate and the bundle stays even in diameter. Everything happens about 200 mm down from the tops.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Clove hitch&lt;/strong&gt; on the left-hand pole, about 200 mm from the top. Pull it snug and tuck the short tail alongside the standing part so the wraps trap it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Four wrapping turns&lt;/strong&gt;, woven over and under: over the left pole, under the middle, over the right, then back over the right, under the middle, over the left. Keep them loose enough to slip a finger under. This is the part everybody gets wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two frapping turns in each gap.&lt;/strong&gt; Take the working end down between the left and middle poles, around the wraps, and back up. Twice. Then the same in the gap between the middle and right poles. Frapping turns run at right angles to the wraps and pull the whole bundle together sideways.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clove hitch&lt;/strong&gt; on the right-hand pole to finish, tied exactly as the first one, and dress the tail back under the frapping so nothing hangs loose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stand the bundle up&lt;/strong&gt; and walk the two outer legs out. Each pole turns a quarter turn inside the wraps, every wrapping turn shortens as it takes up that rotation, and the lashing pulls itself tight.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/LashingATripod/02-lashing-sequence-HQ.png"&gt;&lt;img alt="Five-panel sequence: clove hitch on the left pole, four woven figure-of-eight wrapping turns, two frapping turns in each gap, the finishing clove hitch on the right pole, and the legs walked open" src="http://lizard-spock.co.uk/images/Outdoor/LashingATripod/02-lashing-sequence-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Outdoor"/><category term="knots"/><category term="bushcraft"/><category term="scouting"/><category term="camp craft"/></entry><entry><title>Reading bark and leaves</title><link href="http://lizard-spock.co.uk/reading-bark-and-leaves.html" rel="alternate"/><published>2026-08-19T00:00:00+01:00</published><updated>2026-08-19T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-19:/reading-bark-and-leaves.html</id><summary type="html">&lt;p&gt;A field key for ten common British trees built on bark and branching rather than leaves, and why the same species can carry two different bark patterns on one trunk.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/00-treeid-hero-HQ.png"&gt;&lt;img alt="A drift of ten overlapping leaves, one from each tree in this post: oak, ash, beech, sycamore, silver birch, hazel, horse chestnut, holly, a Scots pine shoot and a flat yew spray" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/00-treeid-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For half the year a British wood has no leaves on it. From the end of October to the middle of April, the field guide in your pocket is mostly photographs of things that are not currently present. The wood stays perfectly identifiable through January, and the people doing it cannot always tell you how.&lt;/p&gt;
&lt;p&gt;This is a key for ten common trees, built on the features available all year, with leaf shape as confirmation rather than the starting point. It is aimed at anyone who takes a group into a wood and gets asked what that one is.&lt;/p&gt;
&lt;h2&gt;Branching first, leaf second&lt;/h2&gt;
&lt;p&gt;Before looking at a single leaf, look at how the twigs come off the branch. Either they sit in pairs directly opposite each other, or they alternate up the twig one at a time.&lt;/p&gt;
&lt;p&gt;Almost every British tree has alternate branching. The opposite-branching ones are a short list: maples including sycamore and field maple, ash, dogwood, the Caprifoliaceae including elder and honeysuckle, and horse chestnut. Scouting books teach it as &lt;strong&gt;MADCap Horse&lt;/strong&gt;, and it is the single most useful thing to check first, because it removes most of a wood in one look and it works in December.&lt;/p&gt;
&lt;p&gt;Most of that list is shrubs. Dogwood, elder and honeysuckle sit at shoulder height and never form a canopy. So when the opposite branching belongs to a full sized tree, the candidates come down to four: ash, sycamore, field maple and horse chestnut.&lt;/p&gt;
&lt;h2&gt;What the leaf is made of&lt;/h2&gt;
&lt;p&gt;A leaf answers two questions in order. Is the blade one piece or several, and if several, how are they arranged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simple&lt;/strong&gt; leaves are one blade on one stalk. Oak, beech, birch, hazel, holly and sycamore are all simple, however deeply the edge is cut. A sycamore leaf has five lobes but the blade never separates, so it stays one leaf.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compound&lt;/strong&gt; leaves are divided into separate leaflets, each with its own small stalk. Pinnate compound means leaflets ranged along a central stalk like a feather, which is ash. Palmate compound means leaflets radiating from one point like fingers, which is horse chestnut.&lt;/p&gt;
&lt;p&gt;The trap is telling a compound leaf from a twig bearing simple leaves. The test is the bud: a leaflet has no bud where it joins the stalk, and a leaf always has one where it joins the twig. Follow the stalk back until you find a bud, and everything beyond that point is a single leaf.&lt;/p&gt;
&lt;p&gt;Then the margin, which separates the simple leaves from each other. Entire means smooth, serrate means toothed, doubly toothed means large teeth carrying smaller teeth of their own, and lobed means the edge cuts deeply toward the midrib without ever reaching it.&lt;/p&gt;
&lt;h2&gt;Why bark cracks at all&lt;/h2&gt;
&lt;p&gt;A trunk gets thicker every year. A thin sleeve of living tissue, the vascular cambium, lays down new wood on its inner face and new phloem on its outer face, so the wood you can see is only the outside of a stack that has been growing outward for decades.&lt;/p&gt;
&lt;p&gt;Outside that sits the bark, and the outer part of the bark is dead. It is made by a second sleeve, the cork cambium, and once a layer of cork is finished it has no way of growing. So a trunk expanding underneath a rigid dead skin has two options, and different species take different ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beech, holly and hornbeam keep the original cork cambium alive.&lt;/strong&gt; It expands with the trunk year after year, so the surface stays continuous and smooth for the life of the tree. A beech at 200 years old has the same grey skin it had at twenty, stretched.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oak, ash, pine and most other trees abandon it.&lt;/strong&gt; New cork cambiums form in successive arcs deeper inside the bark, and each one cuts off the tissue outside it, which dies. That accumulating dead layer is the rhytidome, and it cannot stretch, so it splits. Where it splits is set by the species, and the pattern is consistent enough to name a tree from thirty metres away.&lt;/p&gt;
&lt;p&gt;Birch does something in between. Its cork cambium makes broad flexible sheets loaded with betulin, the white waterproof compound that gives the bark its colour and lets it burn when soaking wet. The sheets part along the lenticels, the horizontal pores the trunk breathes through, which is why birch peels in horizontal strips and never vertical ones.&lt;/p&gt;
&lt;h2&gt;The ten trees&lt;/h2&gt;
&lt;p&gt;Each plate has the leaf on the left and the bark on the right.&lt;/p&gt;
&lt;h3&gt;Oak&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-01-oak-HQ.png"&gt;&lt;img alt="An oak leaf with rounded lobes beside a panel of grey brown oak bark fissured into short blocks" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-01-oak-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Alternate branching. The leaf is widest above the middle, with four or five rounded lobes a side, almost no stalk, and two small ear shaped lobes where the blade meets the stalk. The bark carries deep vertical fissures cross linked into short blocks. Pedunculate oak has long stalked acorns and near stalkless leaves; sessile oak reverses both.&lt;/p&gt;
&lt;h3&gt;Ash&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-02-ash-HQ.png"&gt;&lt;img alt="A pinnate ash leaf of paired toothed leaflets beside a panel of pale grey bark in a diamond lattice" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-02-ash-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Opposite branching. One leaf is a slender stalk carrying five or six pairs of toothed leaflets and a terminal one, so a single ash leaf can be 300 mm long. The bark is pale grey with shallow ridges meeting in a lattice of diamonds. In winter the buds are sooty black, and nothing else in Britain has them.&lt;/p&gt;
&lt;h3&gt;Beech&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-03-beech-HQ.png"&gt;&lt;img alt="An oval beech leaf with a wavy margin beside a panel of smooth silvery grey beech bark" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-03-beech-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Alternate branching. The leaf is a plain oval with a pointed tip, the margin gently wavy and never toothed, with straight parallel side veins running out to each wave and a fringe of silky hairs when young. The bark is smooth silvery grey and stays smooth for the life of the tree.&lt;/p&gt;
&lt;h3&gt;Sycamore&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-04-sycamore-HQ.png"&gt;&lt;img alt="A five lobed sycamore leaf beside a panel of grey pink bark flaking into irregular plates" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-04-sycamore-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Opposite branching. One blade, five lobes cut about halfway to the centre, coarse blunt teeth, on a long stout stalk. The bark is grey with a pink tinge, flaking away in irregular rounded plates that leave paler patches behind. The seeds are the familiar winged pairs.&lt;/p&gt;
&lt;h3&gt;Silver birch&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-05-silver-birch-HQ.png"&gt;&lt;img alt="A small triangular birch leaf beside a panel of white papery bark with lenticels and black diamonds" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-05-silver-birch-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Alternate branching. A small triangular leaf with a broad base and doubly toothed margin, large teeth carrying smaller teeth of their own. The bark is white and papery, peeling in horizontal strips, marked with dark lenticel lines and breaking into rough black diamonds near the base. The twigs are warty and hairless, which separates it from downy birch.&lt;/p&gt;
&lt;h3&gt;Hazel&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-06-hazel-HQ.png"&gt;&lt;img alt="A round hazel leaf with an abrupt point beside a panel of smooth coppery bark with pale lenticels" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-06-hazel-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Alternate branching. Almost circular, with an abrupt short point, a heart shaped base, and a soft downy surface puckered by impressed veins. The bark is smooth coppery brown with pale horizontal lenticels and no fissures at all. Hazel usually stands multi stemmed from old coppice, and carries catkins from January.&lt;/p&gt;
&lt;h3&gt;Horse chestnut&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-07-horse-chestnut-HQ.png"&gt;&lt;img alt="A palmate compound horse chestnut leaf of seven leaflets beside a panel of grey brown scaly bark" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-07-horse-chestnut-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Opposite branching. Five to seven leaflets radiate from one point at the top of a stout stalk, each widest near its tip. The bark is grey brown and breaks into coarse scaly plates that lift at the edges. The buds are large and sticky. It is not native, and arrived here around 1600.&lt;/p&gt;
&lt;h3&gt;Holly&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-08-holly-HQ.png"&gt;&lt;img alt="A spiny glossy holly leaf with red berries beside a panel of smooth pale grey bark with warty bumps" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-08-holly-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Alternate branching, and evergreen. Stiff, glossy, very dark green, with spines at the crest of each wave in the margin. Those spines are a response to browsing, so above the height a deer can reach the same tree often carries smooth edged leaves with no spines at all. The bark is smooth pale grey with scattered warty bumps. Berries appear on female trees only.&lt;/p&gt;
&lt;h3&gt;Scots pine&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-09-scots-pine-HQ.png"&gt;&lt;img alt="A Scots pine shoot with needles in pairs beside a bark panel that is orange and papery above and grey and fissured below" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-09-scots-pine-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Needles in pairs, 40 to 70 mm, blue green and twisted, each pair joined at the base by a small brown sheath. Counting the needles in a bundle is the test: two means Scots pine, and Britain has no other native pine. The bark runs orange pink and papery high on the trunk and grey brown in deep fissured slabs at the base.&lt;/p&gt;
&lt;h3&gt;Yew&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-10-yew-HQ.png"&gt;&lt;img alt="A flat yew spray with needles in two ranks and red arils beside a panel of red brown flaking bark" src="http://lizard-spock.co.uk/images/Outdoor/ReadingBarkAndLeaves/tree-10-yew-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Needles single and flat, set in two ranks so the whole spray lies flat, and soft enough to run a hand along. The bark is red brown, peeling in thin flakes, over a trunk that is fluted and often made of several fused stems. Yew carries red arils rather than cones, and every part of the tree except the flesh of the aril is poisonous.&lt;/p&gt;
&lt;h2&gt;The reframe&lt;/h2&gt;
&lt;p&gt;The field guides do not say this plainly. Bark pattern is not a portrait of a species. It is the record of how a dead outer layer failed under the strain of the trunk growing, and the species only sets the rule for the failure.&lt;/p&gt;
&lt;p&gt;Age sets how far that rule has run. A young oak is nearly smooth, because it has not yet grown enough girth to break its own bark into blocks. An old silver birch is black and coarsely fissured at the base while it is still white and papery at head height, because the base has been expanding for eighty years and the upper trunk for twenty. Growth rate matters too: the same species on a wet fertile site cracks deeper than one on a dry exposed ridge, because the strain arrives faster.&lt;/p&gt;
&lt;p&gt;So read two places on the same tree, not one. The base and the crown of a Scots pine disagree, and the disagreement is itself the identification.&lt;/p&gt;
&lt;h2&gt;Running the key&lt;/h2&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_0ebc59728f803de30075c1dd23f10673.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Winter, when there is nothing to pick&lt;/h2&gt;
&lt;p&gt;Buds are as diagnostic as leaves and they are on the tree from October. Six of these ten are settled in a second by the bud alone.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tree&lt;/th&gt;
&lt;th&gt;Bud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ash&lt;/td&gt;
&lt;td&gt;sooty black and velvety, in opposite pairs. Nothing else in Britain is black&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beech&lt;/td&gt;
&lt;td&gt;long, slender, sharply pointed, copper coloured, held out at an angle from the twig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horse chestnut&lt;/td&gt;
&lt;td&gt;large, fat, sticky to the touch, red brown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sycamore&lt;/td&gt;
&lt;td&gt;green with dark scale edges, opposite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oak&lt;/td&gt;
&lt;td&gt;small and clustered together at the very tip of the twig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hazel&lt;/td&gt;
&lt;td&gt;small, oval, blunt, green flushed red, and catkins hanging from January&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Silver birch keeps its warty hairless twigs, which separates it from downy birch. Holly, yew and Scots pine keep their leaves, so they need no winter test at all.&lt;/p&gt;
&lt;p&gt;Ash is the sad case. Ash dieback, caused by the fungus &lt;em&gt;Hymenoscyphus fraxineus&lt;/em&gt;, has been through most of the country, and many of the ash you find now are dead or dying with sparse crowns and diamond-shaped lesions on the bark. The black buds are still the giveaway.&lt;/p&gt;
&lt;h2&gt;What the timber is for&lt;/h2&gt;
&lt;p&gt;Naming a tree matters more once you want to cut something from it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ash&lt;/strong&gt; is the handle wood. Straight grained, tough and shock resistant, it takes repeated impact without splitting, which is why axe handles, tent pegs and tool shafts are ash. It steam bends well and seasons quickly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hazel&lt;/strong&gt; is the greenwood staple. It grows multi-stemmed from coppice, cleaves cleanly along its length, and bends almost double when green without breaking. Hurdles, spars, bean poles and walking sticks all come from hazel, and a coppiced stool will keep producing for centuries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sycamore&lt;/strong&gt; is pale, close grained and completely odourless, so it goes into anything that touches food. Spoons, bowls, butter pats and dairy equipment are traditionally sycamore for that reason.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Birch&lt;/strong&gt; turns beautifully and plywoods well, but it rots fast in contact with the ground. The bark is the real prize: peel a loose strip from a fallen trunk and it lights from a spark even after rain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Oak&lt;/strong&gt; is the structural timber, durable outdoors for decades, and it comes with a warning. Oak is full of tannin, tannin reacts with iron, and the reaction both corrodes the fastener and stains the wood black around it. Use brass, copper or stainless in oak, and keep steel clamps off wet oak.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Beech&lt;/strong&gt; turns and steam bends as well as anything, which made it the chair wood of the Chilterns, but it has almost no durability outdoors. Keep it indoors and dry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yew&lt;/strong&gt; gives the classic self bow, because a stave cut across the boundary between heartwood and sapwood gets a compression-strong belly and a tension-strong back in one piece with no glue. Every part of the tree except the flesh of the aril is poisonous, and the dust is an irritant, so wear a mask when you sand it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scots pine&lt;/strong&gt; is the construction softwood, and a resin-soaked stump left in the ground for years becomes fatwood, which shaves into the most reliable natural firelighter available in Britain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Horse chestnut&lt;/strong&gt; and &lt;strong&gt;holly&lt;/strong&gt; are the two to leave standing. Horse chestnut timber is soft and weak with almost no use. Holly is white and dense enough for inlay, and it dyes black as a substitute for ebony, but the tree grows slowly enough that felling one for the sake of it is a waste.&lt;/p&gt;
&lt;h2&gt;The two-minute routine&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Branching. Opposite or alternate. That is your first cut.&lt;/li&gt;
&lt;li&gt;Leaf. One blade or several leaflets, and how the edge is cut.&lt;/li&gt;
&lt;li&gt;Bark, twice. Once at chest height, once as high up the trunk as you can see.&lt;/li&gt;
&lt;li&gt;The ground. Acorns, conkers, nut shells, winged seeds and old leaf litter are all still there in February.&lt;/li&gt;
&lt;li&gt;Buds, if the tree is bare.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Get into the habit of naming the tree before you look at a leaf. It is slower for the first month and much faster afterwards, and it is the only version that works in winter.&lt;/p&gt;</content><category term="Outdoor"/><category term="trees"/><category term="nature"/><category term="bushcraft"/><category term="woodwork"/><category term="scouting"/></entry><entry><title>Digital Design 01: Mask sets, and how a transistor gets built</title><link href="http://lizard-spock.co.uk/digital-design-01-mask-sets-and-how-a-transistor-gets-built.html" rel="alternate"/><published>2026-08-18T00:00:00+01:00</published><updated>2026-08-18T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-18:/digital-design-01-mask-sets-and-how-a-transistor-gets-built.html</id><summary type="html">&lt;p&gt;What a photomask is, the sequence of masks that turns a blank wafer into a MOSFET, why the mask count keeps climbing, the approximate cost of a mask set at each geometry from 250 nm down to 3 nm, and what an A1 metal spin costs against a B0 all-layer respin.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article01/00-mask-hero-HQ.png"&gt;&lt;img alt="Four photomask plates floating in an exploded stack above a silicon wafer, with a single beam of light passing down through them onto the dies below" src="http://lizard-spock.co.uk/images/DigitalDesign/Article01/00-mask-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Digital Design Fundamentals | Article 1&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The forty pence chip&lt;/h2&gt;
&lt;p&gt;A small microcontroller in a reel of three thousand costs about forty pence. It
holds a processor, flash, RAM, a handful of timers and a bootloader, and the
supplier will happily sell you one.&lt;/p&gt;
&lt;p&gt;The set of photomasks used to make it cost somewhere north of a quarter of a
million pounds, paid once, years ago, before a single working part existed.
Nothing about the forty pence recovers that. The forty pence is glass, gas,
electricity, packaging and test, spread over tens of millions of parts. The
quarter of a million was the price of being allowed to start.&lt;/p&gt;
&lt;p&gt;This article is about that gap. It covers the photomask itself, the sequence of
masks that builds a metal-oxide-semiconductor field-effect transistor (MOSFET)
out of a blank wafer, why the number of masks keeps rising, the approximate cost
of a mask set at each process geometry, and what a respin costs when the first
attempt is wrong. It targets anyone
learning digital design who has been told "the transistor is a switch" and
would like to know where the switch comes from, and anyone weighing a custom
chip against a field-programmable gate array (FPGA).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A mask is a stencil, and there is one per patterned layer&lt;/h2&gt;
&lt;p&gt;A chip is not carved. It is built up in layers, and every layer that needs a
pattern gets that pattern from a &lt;strong&gt;photomask&lt;/strong&gt;: a flat plate of fused silica,
usually a 6 inch square a quarter of an inch thick, carrying an opaque chrome
image of the shapes for that one layer. Fused silica is amorphous silicon
dioxide, chosen because it is transparent at the exposure wavelength and barely
expands when the plate warms up. In the trade it is often called a quartz plate,
though it is not quartz: quartz is crystalline, fused silica is not. The mask is
glass. The wafer it prints onto is single-crystal silicon. They are not the same
material.&lt;/p&gt;
&lt;p&gt;Three facts about the mask decide most of the economics that follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It is not the same size as the chip.&lt;/strong&gt; In a modern scanner the mask, called a
&lt;strong&gt;reticle&lt;/strong&gt;, is drawn four times larger than the pattern it prints. The optics
demagnify the image by four on its way to the wafer, so a 1 micrometre chrome
feature on the plate prints a 250 nanometre feature in resist. Making the plate is
therefore easier than making the chip, which is the only reason the plate can
be made at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One exposure covers a small area.&lt;/strong&gt; The reticle field is roughly 26 mm by
33 mm at the wafer. A 300 mm wafer holds a few hundred of those fields, and the
scanner steps across the wafer exposing them one at a time. A die larger than
the reticle field cannot be printed in one shot, which is a hard ceiling on
single-die size and one of the arguments for chiplets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One mask means one layer, one shot.&lt;/strong&gt; The mask has no adjustments. Every
change to the design, however small, on whatever layer, means a new plate for
that layer. This is why a bug found after tape-out is expensive in a way that
software bugs are not.&lt;/p&gt;
&lt;p&gt;The resolution the scanner can print follows the Rayleigh relation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;smallest half-pitch  =  k1 x wavelength / numerical aperture
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;with &lt;code&gt;k1&lt;/code&gt; a process factor around 0.3 in practice. Deep ultraviolet immersion
lithography uses 193 nm light and a numerical aperture of about 1.35, giving
roughly a 40 nm half-pitch in a single exposure. That number is the wall the
industry has been climbing over since 2005, and the way it climbed over is the
reason mask counts exploded. More on that below.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Building an inverter's transistors, one mask at a time&lt;/h2&gt;
&lt;p&gt;The complementary metal-oxide-semiconductor (CMOS) process needs two kinds of
transistor: an &lt;strong&gt;nMOS&lt;/strong&gt;, which conducts when its gate is high, and a &lt;strong&gt;pMOS&lt;/strong&gt;,
which conducts when its gate is low. Both are built simultaneously on the same
wafer, and the sequence below produces one of each.&lt;/p&gt;
&lt;p&gt;The starting material is a polished slice of single-crystal silicon, lightly
doped p-type, 300 mm across and under a millimetre thick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. n-well (mask 1).&lt;/strong&gt; A pMOS needs to sit in n-type silicon. The n-well mask
opens windows where phosphorus or arsenic is implanted and driven in, creating
tubs of n-type material inside the p-type wafer. Every pMOS on the chip lives
in one of these.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Isolation (mask 2).&lt;/strong&gt; Trenches are etched between the regions that will
become transistors and filled with oxide. This is &lt;strong&gt;shallow trench isolation&lt;/strong&gt;,
and it stops neighbouring devices leaking into each other. The mask defines the
&lt;strong&gt;active&lt;/strong&gt; areas, meaning everywhere a trench is not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Gate oxide.&lt;/strong&gt; A thin insulating layer is grown over the whole active area,
a few atoms thick at modern nodes. No mask: it is grown everywhere and removed
later where it is not wanted. This layer is the "oxide" in MOSFET, and the
capacitor it forms lets a voltage on the gate control the silicon
underneath without any current flowing into the gate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Gate (mask 3).&lt;/strong&gt; Polysilicon is deposited over the whole wafer, then
patterned. What survives is the gate electrode. The width of that stripe where
it crosses an active area is the &lt;strong&gt;channel length&lt;/strong&gt;, historically the number a
process is named after: the 130 nm process drew a 130 nm gate.&lt;/p&gt;
&lt;p&gt;This is the most critical mask in the set. Channel length sets drive current and
switching speed, so its dimensional control drives the entire process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Source and drain (masks 4 and 5).&lt;/strong&gt; Dopant is implanted on both sides of
the gate: n-type for the nMOS regions, p-type for the pMOS regions, so two
masks, each blocking the areas the other one implants. The gate itself blocks
the implant from the silicon directly beneath it, so the source and drain end up
self-aligned to the gate rather than aligned by the scanner. That trick,
introduced around 1970, removed alignment tolerance from the most sensitive
dimension on the chip and is the reason the polysilicon gate replaced the
earlier aluminium one.&lt;/p&gt;
&lt;p&gt;At this point there are transistors, and they are connected to nothing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Contacts (mask 6).&lt;/strong&gt; Oxide is deposited over everything and holes are
etched down to each source, drain and gate, then filled with tungsten. These
are the plugs the wiring connects to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. Metal 1 (mask 7), via 1 (mask 8), metal 2 (mask 9), and so on.&lt;/strong&gt; Each
metal layer is a mask, and each layer of vias joining two metals is another
mask. Modern logic processes stack ten to fifteen metal layers: the lowest are
thin and dense for wiring inside a standard cell, the highest are thick and wide
for power distribution and long-distance signals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. Passivation and pads (final masks).&lt;/strong&gt; A protective layer over the whole
die, with openings where bond pads or bumps need to make contact with the
outside world.&lt;/p&gt;
&lt;p&gt;Counting: two masks before the transistors exist, three to make them, one for
contacts, and then two per metal level for the rest. A simple two-metal process
lands around twelve masks. A fifteen-metal modern logic process is already past
forty before any of the complications below.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/DigitalDesign/Article01/01-mosfet-cross-section-HQ.png"&gt;&lt;img alt="Cross-section of a CMOS inverter showing the p-type substrate, n-well, shallow trench isolation, n-plus and p-plus source and drain regions, gate oxide, polysilicon gates, contacts and metal 1, with each feature numbered by the mask that creates it" src="http://lizard-spock.co.uk/images/DigitalDesign/Article01/01-mosfet-cross-section-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;How the switch actually switches&lt;/h2&gt;
&lt;p&gt;The masks have produced a structure. Nothing so far explains why a voltage on a
plate of metal sitting over an insulator should control a current flowing
underneath it, several nanometres away, through material the plate never
touches.&lt;/p&gt;
&lt;p&gt;Start with the device at rest. The source and drain are islands of n-type
silicon in a p-type substrate, which makes two diodes back to back. Put a
voltage across them and one of the two is always reverse biased, so no useful
current flows between source and drain no matter what the drain voltage does.
The transistor is off, and it is off because there is nothing joining the two
islands.&lt;/p&gt;
&lt;p&gt;Now raise the gate. The gate, the oxide and the silicon underneath form a
capacitor: two conductors separated by an insulator. Charge on the gate
therefore pulls an equal and opposite charge to the silicon surface, without a
single electron crossing the oxide.&lt;/p&gt;
&lt;p&gt;That induced charge arrives in two stages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Below the threshold, the surface merely empties.&lt;/strong&gt; A positive gate repels the
holes that a p-type substrate is full of, pushing them down and away. What is
left behind is a depleted layer, carrying no mobile charge at all. There is
still no path from source to drain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Above the threshold, the surface changes type.&lt;/strong&gt; Push harder and the gate
starts attracting electrons rather than just repelling holes. They gather in a
thin sheet at the oxide interface, and where there was p-type silicon there is
now a film of n-type silicon a few nanometres thick. That film is the
&lt;strong&gt;channel&lt;/strong&gt;, and it joins the source island to the drain island. The gate
voltage at which this happens is the &lt;strong&gt;threshold voltage&lt;/strong&gt;, and it is one of the
numbers a process is built around.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Animated cross-section of an nMOS transistor. The gate voltage rises from zero, first pushing holes away from the surface to leave a depleted layer, then pulling electrons to the surface to form an n-type channel joining source to drain, after which electrons drift along the channel" src="http://lizard-spock.co.uk/images/DigitalDesign/Article01/02-channel-formation.svg"&gt;&lt;/p&gt;
&lt;p&gt;Once the channel exists, a voltage between drain and source moves electrons
along it. More gate voltage pulls in more electrons, which lowers the channel
resistance and raises the current. The transistor is not a mechanical contact
being closed. It is a resistor whose value is set by a voltage on a capacitor
plate.&lt;/p&gt;
&lt;p&gt;Four consequences run through everything in this series.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The gate draws no steady current.&lt;/strong&gt; It is one plate of a capacitor, so a
finished logic gate can hold its inputs at a value forever and spend nothing
doing it. Only the transitions cost energy, because each one charges or
discharges that capacitance. This is why complementary metal-oxide-semiconductor
logic displaced the alternatives, and why power scales with switching activity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thin oxide means a stronger grip.&lt;/strong&gt; Bring the plate closer and the same
voltage induces more charge, so the device switches at a lower voltage and
carries more current. Hence gate oxides a handful of atoms thick, and hence the
industry moving to hafnium-based insulators once silicon dioxide could get no
thinner without leaking outright.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Short channels are fast and leaky.&lt;/strong&gt; A shorter channel is a shorter distance
for electrons to cross, so the device is quicker, which is the whole point of
shrinking. It also means the drain sits closer to the source and starts
influencing the channel that the gate is supposed to control alone. Every
structure in the modern device list, from FinFETs to gate-all-around
nanosheets, exists to wrap the gate further around the channel and win that
argument back.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Off is not zero.&lt;/strong&gt; Below the threshold the current falls exponentially rather
than to nothing, so several billion transistors that are all switched off still
add up to a measurable leakage current. Standby battery life is largely a fight
with that number.&lt;/p&gt;
&lt;p&gt;The pMOS works the same way with every sign reversed. It sits in the n-well from
mask 1, its source and drain are p-type, and pulling the gate &lt;strong&gt;below&lt;/strong&gt; its
source attracts holes to the surface to form a p-type channel. An nMOS conducts
when its gate is high; a pMOS conducts when its gate is low.&lt;/p&gt;
&lt;p&gt;That pair of sentences is the entire input to the next article. Two switches,
each closed by the opposite condition, each with an insulated gate that costs
nothing to hold. Everything in a logic library is built by wiring those two
kinds of switch into networks.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What one masking step actually involves&lt;/h2&gt;
&lt;p&gt;Every one of those numbered steps expands into the same loop. The mask count
multiplies its cost and its yield risk.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_7bc80acf6aa7aaf0f9daafc114aa512e.svg"&gt;&lt;/p&gt;
&lt;p&gt;Photoresist is a polymer whose solubility changes where light hits it. Expose,
develop, and the mask pattern now exists in resist on the wafer. Etch, and it
exists in the film underneath. Strip the resist, and the layer is finished.&lt;/p&gt;
&lt;p&gt;Two consequences fall out of the loop.&lt;/p&gt;
&lt;p&gt;A wafer visits the scanner once per mask, and the scanner is the most expensive
tool in the fab. Mask count therefore drives cycle time and wafer cost together,
not just the one-off cost of the plates. A forty-mask flow takes six to eight
weeks of processing; an eighty-mask flow takes three months or more.&lt;/p&gt;
&lt;p&gt;Every step is an opportunity to lose the die. A single particle in one exposure
kills the chips it lands on. With eighty steps, per-step yield has to be
extraordinary before the product of them is usable, which is why fabs are
obsessive about contamination and why defect density, not lithography, usually
limits how large a die can economically be.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why the mask count keeps climbing&lt;/h2&gt;
&lt;p&gt;Single-exposure 193 nm immersion lithography stops at roughly 40 nm half-pitch,
and the industry reached that around the 32 nm node. The features kept
shrinking anyway. Three mechanisms did it, and all three cost masks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multiple patterning.&lt;/strong&gt; Split one dense layer into two or more sparser layers,
each printable on its own, and superimpose them. Litho-etch-litho-etch (LELE)
turns one mask into two, LELELE into three. Self-aligned double and quadruple
patterning (SADP, SAQP) print a sparse grid and use deposited spacers to halve
the pitch, then need &lt;strong&gt;cut masks&lt;/strong&gt; to remove the unwanted segments. A single
logical layer such as metal 1 can consume four or five plates at 10 nm and 7 nm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More structure per transistor.&lt;/strong&gt; A planar transistor is a stripe of poly over
a flat active area. A FinFET is a set of vertical fins, needing a fin-definition
mask and fin-cut masks. A gate-all-around nanosheet device stacks channels
vertically and adds its own patterning steps. Extra masks buy tighter electrostatic
control of the channel, which keeps the switch behaving like a switch at these
dimensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extreme ultraviolet lithography pushes the other way.&lt;/strong&gt; EUV uses 13.5 nm
light prints in a single exposure a pattern that deep ultraviolet needed three
or four exposures to build up, so it removes masks from the critical layers even as
it raises the cost per mask sharply. It also abandons the quartz plate. Nothing
is usefully transparent at 13.5 nm, so an EUV mask is a mirror rather than a
stencil: a low-expansion glass blank, about forty alternating molybdenum and
silicon layers to reflect the light, and a tantalum-based absorber patterned on
top. That construction is a large part of an EUV plate's price. It also explains
why 7 nm and 5 nm did not carry on doubling their mask counts: EUV collapsed several of the worst
multi-patterned layers back into one plate each.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;Typical mask count&lt;/th&gt;
&lt;th&gt;Metal layers&lt;/th&gt;
&lt;th&gt;Masks in the metal stack&lt;/th&gt;
&lt;th&gt;What is driving it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;250 nm&lt;/td&gt;
&lt;td&gt;20 to 24&lt;/td&gt;
&lt;td&gt;4 to 5&lt;/td&gt;
&lt;td&gt;8 to 10&lt;/td&gt;
&lt;td&gt;Planar, aluminium wiring, single exposure everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;180 nm&lt;/td&gt;
&lt;td&gt;24 to 28&lt;/td&gt;
&lt;td&gt;5 to 6&lt;/td&gt;
&lt;td&gt;10 to 12&lt;/td&gt;
&lt;td&gt;More metal layers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;130 nm&lt;/td&gt;
&lt;td&gt;28 to 32&lt;/td&gt;
&lt;td&gt;6 to 8&lt;/td&gt;
&lt;td&gt;12 to 16&lt;/td&gt;
&lt;td&gt;Copper interconnect arrives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90 nm&lt;/td&gt;
&lt;td&gt;32 to 36&lt;/td&gt;
&lt;td&gt;7 to 9&lt;/td&gt;
&lt;td&gt;14 to 18&lt;/td&gt;
&lt;td&gt;Strained silicon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65 nm&lt;/td&gt;
&lt;td&gt;36 to 40&lt;/td&gt;
&lt;td&gt;8 to 10&lt;/td&gt;
&lt;td&gt;16 to 20&lt;/td&gt;
&lt;td&gt;Immersion lithography arrives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 nm&lt;/td&gt;
&lt;td&gt;40 to 45&lt;/td&gt;
&lt;td&gt;9 to 11&lt;/td&gt;
&lt;td&gt;18 to 22&lt;/td&gt;
&lt;td&gt;Immersion at the limit, more resolution enhancement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28 nm&lt;/td&gt;
&lt;td&gt;45 to 50&lt;/td&gt;
&lt;td&gt;10 to 12&lt;/td&gt;
&lt;td&gt;20 to 25&lt;/td&gt;
&lt;td&gt;Last node before multiple patterning is unavoidable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 nm&lt;/td&gt;
&lt;td&gt;55 to 65&lt;/td&gt;
&lt;td&gt;11 to 13&lt;/td&gt;
&lt;td&gt;24 to 32&lt;/td&gt;
&lt;td&gt;FinFET, and double patterning on the lower metals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 nm&lt;/td&gt;
&lt;td&gt;75 to 85&lt;/td&gt;
&lt;td&gt;13 to 15&lt;/td&gt;
&lt;td&gt;32 to 42&lt;/td&gt;
&lt;td&gt;Multi-patterning at its worst, early EUV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 nm&lt;/td&gt;
&lt;td&gt;80 to 90&lt;/td&gt;
&lt;td&gt;14 to 16&lt;/td&gt;
&lt;td&gt;34 to 44&lt;/td&gt;
&lt;td&gt;EUV on many critical layers, extra device-level masks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 nm&lt;/td&gt;
&lt;td&gt;85 to 100&lt;/td&gt;
&lt;td&gt;15 to 18&lt;/td&gt;
&lt;td&gt;36 to 48&lt;/td&gt;
&lt;td&gt;More EUV layers, gate-all-around structures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The metal stack is the reason the totals climb so steadily. Each metal level
needs a mask for the wires and another for the vias beneath them, so a level
costs two plates, and the lowest two or three levels need multiple patterning
at 16 nm and below. Roughly half of a modern mask set is wiring.&lt;/p&gt;
&lt;p&gt;Treat these as ranges, not specifications. Every foundry has several flavours of
each node, and options such as extra metal layers, embedded flash, high-voltage
devices or radio-frequency passives add masks on top.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Mask set cost, in round numbers&lt;/h2&gt;
&lt;p&gt;Mask cost is not published in any consistent way, and real quotes are covered by
non-disclosure. What follows are the orders of magnitude generally cited in the
public literature and in multi-project wafer programme pricing. Use them for
deciding which node to think about, and never for a purchase order.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;Full mask set (A0)&lt;/th&gt;
&lt;th&gt;Metal-only spin (A1)&lt;/th&gt;
&lt;th&gt;Per-plate feel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;250 nm&lt;/td&gt;
&lt;td&gt;£50k to £120k&lt;/td&gt;
&lt;td&gt;£15k to £40k&lt;/td&gt;
&lt;td&gt;A few thousand each, no resolution enhancement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;180 nm&lt;/td&gt;
&lt;td&gt;£100k to £250k&lt;/td&gt;
&lt;td&gt;£30k to £80k&lt;/td&gt;
&lt;td&gt;Still cheap plates, more of them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;130 nm&lt;/td&gt;
&lt;td&gt;£200k to £400k&lt;/td&gt;
&lt;td&gt;£60k to £140k&lt;/td&gt;
&lt;td&gt;Optical proximity correction starts to matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90 nm&lt;/td&gt;
&lt;td&gt;£400k to £800k&lt;/td&gt;
&lt;td&gt;£120k to £250k&lt;/td&gt;
&lt;td&gt;Correction on most layers, phase shift on critical ones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65 nm&lt;/td&gt;
&lt;td&gt;£700k to £1.5M&lt;/td&gt;
&lt;td&gt;£200k to £450k&lt;/td&gt;
&lt;td&gt;Heavy resolution enhancement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40 nm&lt;/td&gt;
&lt;td&gt;£1.5M to £3M&lt;/td&gt;
&lt;td&gt;£400k to £900k&lt;/td&gt;
&lt;td&gt;Critical plates cost tens of thousands each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28 nm&lt;/td&gt;
&lt;td&gt;£2M to £4M&lt;/td&gt;
&lt;td&gt;£600k to £1.2M&lt;/td&gt;
&lt;td&gt;The value node, still popular for exactly this reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16 nm&lt;/td&gt;
&lt;td&gt;£4M to £8M&lt;/td&gt;
&lt;td&gt;£1.2M to £2.5M&lt;/td&gt;
&lt;td&gt;FinFET plus double patterning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 nm&lt;/td&gt;
&lt;td&gt;£8M to £15M&lt;/td&gt;
&lt;td&gt;£2.5M to £5M&lt;/td&gt;
&lt;td&gt;Mask count and plate cost rise together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 nm&lt;/td&gt;
&lt;td&gt;£12M to £25M&lt;/td&gt;
&lt;td&gt;£4M to £8M&lt;/td&gt;
&lt;td&gt;EUV plates alone are six figures each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 nm&lt;/td&gt;
&lt;td&gt;£20M to £40M&lt;/td&gt;
&lt;td&gt;£6M to £12M&lt;/td&gt;
&lt;td&gt;Where "who is this for" becomes the real question&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two things drive the climb. The plates get more numerous, as the previous table
shows. And each critical plate gets more expensive, because the chrome pattern
is no longer a copy of the drawn layout: &lt;strong&gt;optical proximity correction&lt;/strong&gt;
deliberately distorts it, adding serifs, hammerheads and scattering bars so that
the diffracted image on the wafer comes out the shape you wanted. Those
corrections are computed for every polygon on the layer, which is why a single
advanced critical mask can take days of compute and a six-figure sum before it
is written.&lt;/p&gt;
&lt;p&gt;And the mask set is only one line in the total. Design tool licences,
intellectual property blocks, engineering time, packaging, test development and
qualification are usually larger. A 28 nm project is commonly quoted in the tens
of millions all-in; a 5 nm one in the hundreds of millions. The mask set is the
part that is easiest to point at, not the part that dominates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not every project buys a set.&lt;/strong&gt; A &lt;strong&gt;multi-project wafer&lt;/strong&gt; run shares one
reticle between many customers, each taking a few square millimetres, and splits
the mask cost accordingly. University and small-company shuttles at 180 nm or
130 nm come in at low tens of thousands, and open-source shuttles on a 130 nm
process have put a small design on real silicon for a few hundred. You get a few
tens of packaged parts, not a product, but for learning, for research, and for
proving an idea, prototyping never touches a full mask set.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A0, A1, B0: the price of being wrong&lt;/h2&gt;
&lt;p&gt;Silicon revisions are named by a convention borrowed from the processor
vendors, and it encodes exactly the distinction the two columns above draw.&lt;/p&gt;
&lt;p&gt;A stepping name is a letter and a digit. The letter counts base layer
revisions, meaning the transistors themselves. The digit counts metal
revisions on top of those base layers.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stepping&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Letter&lt;/th&gt;
&lt;th&gt;Digit&lt;/th&gt;
&lt;th&gt;Masks needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A0&lt;/td&gt;
&lt;td&gt;First silicon&lt;/td&gt;
&lt;td&gt;A, the first&lt;/td&gt;
&lt;td&gt;0, no metal revisions yet&lt;/td&gt;
&lt;td&gt;Full set, every layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;Metal-only spin&lt;/td&gt;
&lt;td&gt;A, unchanged&lt;/td&gt;
&lt;td&gt;increments to 1&lt;/td&gt;
&lt;td&gt;Metal and via layers only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;All-layer spin&lt;/td&gt;
&lt;td&gt;increments to B&lt;/td&gt;
&lt;td&gt;resets to 0&lt;/td&gt;
&lt;td&gt;Full set again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The digit climbs while the transistors stay as they are: A0, A1, A2. The moment
a fix needs a different transistor rather than a different wire, the letter
increments, the digit resets, and the part becomes B0 at full mask set cost.
Metal spins on the new base layers then start again at B1.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;metal-only spin&lt;/strong&gt; works because the fault is in the connections, not the
devices. The fab holds partly processed wafers at the point where the
transistors are finished and the wiring has not started, a stage usually called
the wafer bank. A new set of metal and via plates goes on top of banked
material, so an A1 pays for a fraction of the plates and skips a fraction of the
processing.&lt;/p&gt;
&lt;p&gt;Two consequences follow, and both are decisions taken long before the bug
exists.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It has to be designed for.&lt;/strong&gt; A metal fix can only rewire gates that are
already on the die. Teams scatter spare cells across the floorplan, or use a
gate-array style fabric, precisely so that an engineering change order has
something to connect. Without that, every fix is a B0.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The time matters more than the money.&lt;/strong&gt; An all-layer spin repeats the full
flow: ten to fourteen weeks at an advanced node, six to eight at a mature one,
before packaged parts come back. A metal-only spin starts from banked wafers and
turns around in three to six weeks. In a market with a launch date, the schedule
usually decides the argument, not the several million pounds.&lt;/p&gt;
&lt;p&gt;This is the real reason verification budgets look the way they do. The
alternative to finding a bug in simulation is a number from one of those two
columns, plus a quarter of a year.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The thing the price tag is actually measuring&lt;/h2&gt;
&lt;p&gt;The natural reading of that table is that advanced silicon is expensive. Turned
around, it says something closer to the truth.&lt;/p&gt;
&lt;p&gt;Cost per transistor has fallen for fifty years. Cost per &lt;strong&gt;design&lt;/strong&gt; has risen
for the same fifty years. The mask set is not the price of a chip. It is the
price of the decision to make a chip that is different from every other chip,
paid before you know whether the design is correct.&lt;/p&gt;
&lt;p&gt;Every structural feature of the industry follows from those two curves crossing.
System on chip integration exists because once you are paying for a mask set,
adding more function to the same die is nearly free, while a second die means a
second set. FPGAs exist because someone else already paid for the mask set and
sells you a share of it. Chiplets exist because splitting a system lets the
parts that need 3 nm go there while the parts that do not stay on 28 nm and keep
their cheap masks. Mature nodes stay busy for decades because a design that fits
in 130 nm has no reason to pay 5 nm mask costs. The forty pence microcontroller
is a mask set from years ago, fully amortised, still printing.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Before you pick a node&lt;/h2&gt;
&lt;p&gt;A short checklist for the moment this stops being theory.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Work out the mask cost per unit at your expected volume first. A £3M mask set
  over 10,000 parts is £300 per part before anything else. Over 10 million it is
  30 pence.&lt;/li&gt;
&lt;li&gt;Ask whether you need the density. If the design fits comfortably in 130 nm or
  65 nm, the cheap masks and the mature yield are worth more than the transistor
  count.&lt;/li&gt;
&lt;li&gt;Check whether a multi-project wafer covers your prototype. Two shuttle runs
  and a full set afterwards is usually cheaper than getting a full set wrong.&lt;/li&gt;
&lt;li&gt;Count your metal layers. They are two masks each, they are around half the
  set, and an over-generous stack is a quiet line item.&lt;/li&gt;
&lt;li&gt;Decide before tape-out whether you want the option of a metal-only spin, and
  put the spare cells in to support it. That choice separates a three week A1
  from a three month B0.&lt;/li&gt;
&lt;li&gt;Treat tape-out as irreversible. There is no patch. Everything the verification
  and sign-off effort costs is being compared against the numbers in that table.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The masks make transistors. The next article wires two kinds of transistor into
the smallest useful circuits: the inverter, the NAND and the NOR, and the
pull-up and pull-down networks that decide why those three are the gates the
libraries are built from.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/digital-design-02-pull-up-and-pull-down-networks.html"&gt;Digital Design 02: Pull-up and pull-down networks&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="Digital Design"/><category term="Electronics"/><category term="Semiconductors"/><category term="CMOS"/><category term="MOSFET"/><category term="Photolithography"/><category term="Photomask"/><category term="Fabrication"/><category term="ASIC"/></entry><entry><title>SoC Intermediate 03: Pipeline design and hazards</title><link href="http://lizard-spock.co.uk/soc-intermediate-03-pipeline-design-and-hazards.html" rel="alternate"/><published>2026-08-16T00:00:00+01:00</published><updated>2026-08-16T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-16:/soc-intermediate-03-pipeline-design-and-hazards.html</id><summary type="html">&lt;p&gt;Why adding a pipeline stage can make a processor slower, and the hazard machinery that decides the outcome: forwarding paths, load-use stalls, branch prediction and flush, structural conflicts, multi-cycle units, and the valid bits that keep exceptions precise.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI03/00-pipeline-hero-HQ.png"&gt;&lt;img alt="A processor pipeline drawn as a transparent assembly line, with several instructions in flight at once moving through fetch, decode, execute, memory and writeback stages" src="http://lizard-spock.co.uk/images/SoC/ArticleI03/00-pipeline-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Intermediate SoC Design | Article 3 of 10&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The stage that made it slower&lt;/h2&gt;
&lt;p&gt;The timing report said the execute stage was the critical path. You split it in
two, added a pipeline register, and closed timing at 1.3 GHz instead of 1.2. An
extra eight per cent of clock, for one register stage and a week of work.&lt;/p&gt;
&lt;p&gt;Three of the four benchmarks gained around eight per cent, exactly as the
frequency did. The fourth, a pointer-heavy workload full of branches that go
whichever way the data feels like, lost four per cent. Same RTL, same compiler,
same test, faster clock, less work done.&lt;/p&gt;
&lt;p&gt;This article is about pipeline hazards: the conflicts that appear the moment
instructions overlap, the hardware that resolves them, and the reason stage
count is such a fraught number. It targets engineers designing or verifying a
processor pipeline, and anyone reading a microarchitecture manual and wondering
why nobody simply builds a forty-stage machine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What overlapping actually buys&lt;/h2&gt;
&lt;p&gt;Classic reduced instruction set computer (RISC) pipelines are usually explained
with five stages, and the explanation is worth keeping because every later
complication attaches to one of them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IF&lt;/td&gt;
&lt;td&gt;Instruction fetch&lt;/td&gt;
&lt;td&gt;Read instruction memory or the instruction cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;Decode and register read&lt;/td&gt;
&lt;td&gt;Decode the opcode, read the register file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EX&lt;/td&gt;
&lt;td&gt;Execute&lt;/td&gt;
&lt;td&gt;Arithmetic logic unit operation or address calculation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEM&lt;/td&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;Data memory or data cache access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WB&lt;/td&gt;
&lt;td&gt;Write back&lt;/td&gt;
&lt;td&gt;Write the result to the register file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A single instruction still takes five cycles from fetch to writeback. Nothing
about pipelining makes one instruction faster. What it changes is how many
instructions can be in progress at the same time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_626cdeae0d4b7eb82e91a09ed0d04b0d.svg"&gt;&lt;/p&gt;
&lt;p&gt;Latency is five cycles. Throughput approaches one instruction per cycle. Every
stage boundary you add shortens the critical path between registers, which is
why the timing report kept suggesting more of them.&lt;/p&gt;
&lt;p&gt;The diagram above is also a lie of omission. It shows four instructions that
have nothing to do with each other. Real code is not like that.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Three ways instructions collide&lt;/h2&gt;
&lt;p&gt;An instruction in a pipeline is no longer isolated. It may need a value, a
decision, or a piece of hardware that an older instruction has not finished
with. Those conflicts are pipeline hazards, and they come in three kinds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data hazards.&lt;/strong&gt; An instruction needs a value that is not available yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control hazards.&lt;/strong&gt; The next program counter depends on a branch or an
  exception that has not resolved yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structural hazards.&lt;/strong&gt; Two stages want the same piece of hardware in the
  same cycle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything else in this article is the disciplined handling of those three,
without producing wrong results and without stalling more than necessary.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Data hazards and the bypass network&lt;/h2&gt;
&lt;p&gt;Take the shortest dependent pair in any instruction set:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x2&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// x3 = x1 + x2&lt;/span&gt;
&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x4&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// needs x3 immediately&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;add&lt;/code&gt; writes &lt;code&gt;x3&lt;/code&gt; in WB, in cycle 4. The &lt;code&gt;sub&lt;/code&gt; reads its operands in ID, in
cycle 2, and needs them in EX in cycle 3. Left alone, the &lt;code&gt;sub&lt;/code&gt; reads the old
value of &lt;code&gt;x3&lt;/code&gt; and quietly computes the wrong answer.&lt;/p&gt;
&lt;p&gt;Waiting is one answer, and it costs three cycles on a pattern that appears in
almost every basic block. The better answer is that the value already exists.
It sits in the EX/MEM pipeline register at the end of cycle 3, fully computed.
It is simply not in the register file yet.&lt;/p&gt;
&lt;p&gt;Forwarding, also called bypassing, routes that result straight back to the
input of the execute stage instead of waiting for writeback.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_9d743a3ee62322c0ccdb0c65f4d1014f.svg"&gt;&lt;/p&gt;
&lt;p&gt;Each execute-stage operand becomes a multiplexer choosing between three
sources: the register file value, the result of the immediately preceding
instruction sitting in EX/MEM, and the result of the instruction before that
sitting in MEM/WB. Priority matters. If both bypass sources match, the younger
one wins, because it holds the more recent value.&lt;/p&gt;
&lt;p&gt;With those two paths in place, back-to-back arithmetic dependencies cost
nothing at all. This is the single highest-value structure in an in-order
pipeline, and it is why a naive stall-on-dependency design performs so badly by
comparison.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The one it cannot fix&lt;/h2&gt;
&lt;p&gt;Forwarding works because the value exists somewhere earlier than the register
file. Loads break that assumption.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;lw&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="no"&gt;x3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;x4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The load data arrives from the data cache at the end of MEM, in cycle 3. The
dependent &lt;code&gt;add&lt;/code&gt; wants it in EX, also in cycle 3, and no wire runs backwards in
time. One cycle of stall is unavoidable.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_5c53e275ae3630fadbd4e968b000bc2e.svg"&gt;&lt;/p&gt;
&lt;p&gt;The pipeline holds IF and ID in place for a cycle and injects a bubble into EX.
After the stall, the ordinary MEM/WB bypass path delivers the value and the
&lt;code&gt;add&lt;/code&gt; proceeds.&lt;/p&gt;
&lt;p&gt;Detecting it is a comparison between the instruction in ID and the load already
in EX:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;load_use_hazard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ex_is_load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex_rd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;5&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ex_rd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id_rs1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex_rd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id_rs2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stall_if&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;load_use_hazard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stall_id&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;load_use_hazard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insert_bubble_ex&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;load_use_hazard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;ex_rd != 5'd0&lt;/code&gt; term earns its place. On RISC-V, register &lt;code&gt;x0&lt;/code&gt; reads as
zero and discards writes, so a load into &lt;code&gt;x0&lt;/code&gt; produces no value anybody can
depend on. Without the check, every discarded load stalls a following
instruction that reads &lt;code&gt;x0&lt;/code&gt;, which is a common idiom. Whatever your instruction
set, find its equivalent and prove it does not create phantom hazards.&lt;/p&gt;
&lt;p&gt;Compilers know about this stall and schedule an independent instruction into
the gap when they can find one. When they cannot, the cost is real, and it
scales with how many cycles late the load data is.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Control hazards and the cost of guessing&lt;/h2&gt;
&lt;p&gt;A branch changes the program counter, and the pipeline has already fetched past
it. In a five-stage design the condition resolves in EX, by which point two
younger instructions are in flight.&lt;/p&gt;
&lt;p&gt;Waiting for the branch is the honest option and the slow one. It costs two
cycles on roughly one instruction in five. Resolving the branch earlier in ID
helps, at the price of a comparator on the critical path and a new forwarding
problem for the values being compared.&lt;/p&gt;
&lt;p&gt;Every high-performance pipeline takes the third option: guess, and clean up
when the guess is wrong.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_fc084bf1517c1aff459cfa6709eb16c9.svg"&gt;&lt;/p&gt;
&lt;p&gt;Two fetched instructions become bubbles. Note what determines that number: it
is the distance between the fetch stage and the stage where the branch
resolves. Not the total pipeline depth, and not the branch itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Predictors, and why two bits&lt;/h2&gt;
&lt;p&gt;A static predictor guesses from the instruction alone. Backward branches are
assumed taken and forward branches not taken, which is a decent guess because
backward branches are usually loops. It costs almost nothing and is right about
two thirds of the time.&lt;/p&gt;
&lt;p&gt;Dynamic prediction learns from what the branch did before. The smallest useful
structure is a two-bit saturating counter per branch.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_2010cf533c92c3da59f14a344ae185cb.svg"&gt;&lt;/p&gt;
&lt;p&gt;The second bit is the whole point. A loop that runs a hundred times and exits
once would, with a one-bit predictor, mispredict twice per execution of the
loop: once on the exit, and once again on the next entry, because the single
bit was flipped by that exit. The two-bit counter absorbs the anomaly, drops
from strong to weak, and keeps predicting taken.&lt;/p&gt;
&lt;p&gt;Real branch prediction units go much further, with global history, tagged
geometric-length tables, branch target buffers, and return address stacks. They
all rest on the same trade: storage and complexity in exchange for a lower
misprediction rate, because misprediction is the thing that costs cycles.&lt;/p&gt;
&lt;p&gt;When the guess is wrong, the younger instructions have to disappear:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flush_if_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch_resolved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch_mispredict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flush_id_ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch_resolved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch_mispredict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next_pc&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch_mispredict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;branch_target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pc_plus_4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A flush turns wrong-path instructions into bubbles. It must also suppress every
side effect they could have had: register writes, memory writes, control and
status register updates, and any outstanding request already issued to a
multi-cycle unit. A flush that clears the valid bit but leaves a store enable
asserted is a memory corruption bug, and it will only appear when the branch
and the store line up in one specific way.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What the extra stage actually bought&lt;/h2&gt;
&lt;p&gt;Now the opening result. Return to that pointer-heavy benchmark, and put numbers
against the two designs.&lt;/p&gt;
&lt;p&gt;Cycles per instruction (CPI) starts at 1.0 for a full pipeline and gets worse
from there. On this workload, a quarter of instructions are branches, a fifth
of those branches are mispredicted because the data decides them, and loads
feed a dependent instruction immediately about half the time.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contribution&lt;/th&gt;
&lt;th&gt;Five-stage&lt;/th&gt;
&lt;th&gt;Six-stage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch mispredict penalty&lt;/td&gt;
&lt;td&gt;3 cycles, 0.15&lt;/td&gt;
&lt;td&gt;5 cycles, 0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load-use stall&lt;/td&gt;
&lt;td&gt;1 cycle, 0.075&lt;/td&gt;
&lt;td&gt;2 cycles, 0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache misses and other&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.325&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clock&lt;/td&gt;
&lt;td&gt;1.2 GHz&lt;/td&gt;
&lt;td&gt;1.3 GHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instructions per second&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;906 M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;867 M&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The extra register stage did exactly what it was asked to do. It shortened the
critical path and the clock went up. It also moved branch resolution one stage
further from fetch and put the load data one cycle further from the consumer,
so every mistake got more expensive at the same time.&lt;/p&gt;
&lt;p&gt;Here is the reframe. A pipeline stage costs nothing while the pipeline is
right, and costs the entire pipeline when it is wrong. You are not buying
throughput with depth. You are buying frequency, and paying for it with the
penalty on every event that discards work.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI03/01-depth-penalty-HQ.png"&gt;&lt;img alt="Two pipelines drawn side by side, a short one and a long one, each flushing after a mispredicted branch, with the long pipeline discarding many more instructions than the short one" src="http://lizard-spock.co.uk/images/SoC/ArticleI03/01-depth-penalty-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That explains the split in the results. The three benchmarks that gained were
predictable ones, where the pipeline is right almost all the time and depth is
nearly free. The one that lost was the one that is wrong constantly, where
depth is charged on every mistake.&lt;/p&gt;
&lt;p&gt;It also explains the shape of the industry. The deepest commercial pipelines
appeared alongside the most elaborate branch predictors ever built, and that is
not a coincidence. Depth is only affordable if you are right.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Structural hazards&lt;/h2&gt;
&lt;p&gt;The third class is the most mundane and the easiest to design out, if you find
it before tape-out. A structural hazard is hardware oversubscription: two
stages wanting one resource in one cycle.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI03/02-structural-hazard-HQ.png"&gt;&lt;img alt="A single memory port with two arrows arriving at once, one from instruction fetch and one from a data load, with only one able to proceed" src="http://lizard-spock.co.uk/images/SoC/ArticleI03/02-structural-hazard-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The classic examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One memory port serving both instruction fetch and data access, so every load
  or store stalls a fetch. This is the reason for separate instruction and data
  caches.&lt;/li&gt;
&lt;li&gt;A single register-file write port with two instructions completing at once,
  which happens as soon as any operation has a different latency from the rest.&lt;/li&gt;
&lt;li&gt;A shared multiplier still busy with a multi-cycle operation.&lt;/li&gt;
&lt;li&gt;Cache miss handling resources, such as miss status holding registers, already
  fully allocated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The remedies are duplication, banking, arbitration, or stalling one user.
Choosing to stall is legitimate, and choosing it by accident is not. Every
resource with more than one potential claimant needs a documented arbitration
policy, including what happens when the loser is starved.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Units that take their time&lt;/h2&gt;
&lt;p&gt;Multipliers, dividers, and floating-point units do not fit the one-cycle
execute model. They need their own small state machine, and the pipeline needs
to know when the result is coming.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_515468dcbe2623fe5235bef0fdd08d19.svg"&gt;&lt;/p&gt;
&lt;p&gt;The amber edge is the one that gets forgotten. If a branch flush or an
exception can kill an operation while the unit is mid-calculation, the unit
needs a valid bit or a transaction tag so a stale result cannot commit three
cycles later against a program counter that no longer exists. Divider results
arriving after the flush that should have killed them is a bug class of its
own, and it is invisible until a divide happens to sit just before an
unpredictable branch.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Precise exceptions&lt;/h2&gt;
&lt;p&gt;Software expects an exception to look instantaneous: every older instruction
complete, no younger instruction having changed any architectural state, and a
program counter that says exactly where to resume. The hardware, meanwhile, has
five instructions in flight and has been speculating for the last three cycles.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI03/03-commit-point-HQ.png"&gt;&lt;img alt="A pipeline with a single commit point marked, where instructions before it have changed architectural state and instructions after it are still speculative and can be discarded" src="http://lizard-spock.co.uk/images/SoC/ArticleI03/03-commit-point-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Even an in-order pipeline has to work at this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A faulting load must not be followed by a younger register write.&lt;/li&gt;
&lt;li&gt;Wrong-path instructions must not perform stores.&lt;/li&gt;
&lt;li&gt;Control and status register writes must be ordered with respect to
  exceptions.&lt;/li&gt;
&lt;li&gt;The flush must clear every side-effect enable, not just the valid bits.&lt;/li&gt;
&lt;li&gt;If two instructions fault in the same cycle, the older one wins, always.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Out-of-order processors solve this with a reorder buffer and an explicit commit
stage. In-order pipelines solve it with valid bits and a clearly designated
point past which effects become visible. The mechanism differs, the rule does
not: pick one place where speculation ends, and let nothing escape it early.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Design checklist&lt;/h2&gt;
&lt;p&gt;Settle these before writing the datapath, because retrofitting any of them is
painful:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Put a valid bit beside every pipeline register and treat it as the only
   thing that makes a stage's contents real.&lt;/li&gt;
&lt;li&gt;Name the exact stage that commits register writes, memory writes, and
   control register side effects. Write it in the specification.&lt;/li&gt;
&lt;li&gt;Add forwarding paths only where the value genuinely exists in time, and
   check each one against the timing report before committing to it.&lt;/li&gt;
&lt;li&gt;Prove the zero register, and any other write-discarding destination, cannot
   create a false hazard.&lt;/li&gt;
&lt;li&gt;Treat branch flush, exception flush, and debug halt as three cases of one
   mechanism, and verify all three.&lt;/li&gt;
&lt;li&gt;Give every multi-cycle unit a kill path, and test that a result cannot
   commit after the instruction that produced it was flushed.&lt;/li&gt;
&lt;li&gt;Test the interactions, not the cases: a stall and a flush arriving in the
   same cycle, a load-use pair straddling a mispredicted branch, an exception
   on the instruction directly behind a stalled load.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Count the penalties before adding the stage&lt;/h2&gt;
&lt;p&gt;The design question is never "how deep should the pipeline be". It is "what
does this stage add to the branch resolution distance and the load-use
distance, and what is that worth on the code we actually run".&lt;/p&gt;
&lt;p&gt;Both numbers are countable on a whiteboard before any RTL exists. Multiply each
by how often the event happens in your workload, add them to the base CPI, and
divide the new clock frequency by the result. If that number is not comfortably
larger than the old one, the stage is not worth the week.&lt;/p&gt;
&lt;p&gt;Point 7 of the checklist is where the real bugs live, and it is the point most
often skipped. Stall-and-flush in the same cycle is one line in a verification
plan and, judging by errata sheets, several months of somebody's life.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-intermediate-02-cache-coherency-protocols.html"&gt;Article I-02: Cache coherency protocols&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-intermediate-04-rtl-synthesis-and-timing-closure.html"&gt;Article I-04: RTL synthesis and timing closure&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="RISC-V"/><category term="Pipelines"/><category term="Branch Prediction"/><category term="RTL"/><category term="Verification"/></entry><entry><title>Building a folding bucksaw</title><link href="http://lizard-spock.co.uk/building-a-folding-bucksaw.html" rel="alternate"/><published>2026-08-06T00:00:00+01:00</published><updated>2026-08-06T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-06:/building-a-folding-bucksaw.html</id><summary type="html">&lt;p&gt;A dimensioned build for a takedown bow saw around a 610 mm blade, and why folding it to blade length quietly caps how thick a log it will cut.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/00-hero-HQ.png"&gt;&lt;img alt="A wooden bucksaw shown assembled under cord tension, and folded into a single flat bar with the blade hidden inside" src="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/00-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A bow saw cuts firewood faster than a folding pruning saw, with less effort, and anyone who has used both in the same afternoon knows it within about three strokes. The frame holds the blade in tension, so the blade can be thin. The thinner the blade, the thinner the kerf, and the less material removed per stroke, which makes for an easier cut. Yet the saw most people actually carry is the folding one, because it is more compact and more convenient to pack.&lt;/p&gt;
&lt;p&gt;This is a dimensioned build for a bucksaw that collapses into a bundle the length of its own blade, sized around a standard 610 mm (24 inch) blade. It is aimed at anyone with a saw, a chisel and a drill.&lt;/p&gt;
&lt;h2&gt;Tension is the whole point&lt;/h2&gt;
&lt;p&gt;A panel saw resists buckling by being thick. Push a thin steel strip through a cut and it folds sideways on the return stroke, so a saw meant to be used unsupported needs a plate stiff enough to hold itself straight. That stiffness has to be paid for with metal, and every extra thousandth of plate thickness is more wood turned into dust on every stroke.&lt;/p&gt;
&lt;p&gt;A bow saw cheats. Stretch a thin blade between two points hard enough and it cannot buckle, because it is never in compression. All the straightness comes from the frame. That is why a bow saw blade can be 0.7 mm thick when a comparable hand saw plate is nearer 0.9 mm, and why the same log takes noticeably fewer strokes.&lt;/p&gt;
&lt;p&gt;So the frame has exactly one job: pull the two ends of the blade apart and keep pulling while you work. Everything else in the design is in service of that.&lt;/p&gt;
&lt;p&gt;The mechanism is a windlass. A cord runs between the tops of the two arms. Push a stick through the cord, twist it, and the cord shortens as it winds on itself. The arms pivot about the stretcher between them, so pulling their tops together drives their bottoms apart, and the blade pinned across those bottoms comes taut. The stretcher is the fulcrum. The whole frame is a pair of levers sharing a beam.&lt;/p&gt;
&lt;h2&gt;Making it fold without losing anything&lt;/h2&gt;
&lt;p&gt;The failure mode of a takedown saw is not breakage. It is a small metal part in leaf litter at dusk. Wing nuts, cotter pins and separate blade pins all work beautifully on a bench and all eventually get dropped in a wood.&lt;/p&gt;
&lt;p&gt;So the rule for this build is that nothing on the saw comes off in the field. The blade attaches with a &lt;strong&gt;captive pin&lt;/strong&gt;: a 4 mm pin driven permanently through each arm near its toe, spanning a slot cut down the middle of the arm. The blade eyelet drops over the pin from inside that slot and stays there for the life of the blade. Slacken the cord and the blade goes loose but does not detach. There is no fastener to undo, nothing to unthread, and it works in gloves.&lt;/p&gt;
&lt;p&gt;Which means the blade and the two arms are permanently one assembly. To pack it, the arms have to fold with the blade still attached, and the only thing they can rotate about is the pin the blade already sits on.&lt;/p&gt;
&lt;p&gt;Swing each arm ninety degrees about its own pin and it lies flat along the blade. One arm covers the left half, the other covers the right. The blade ends up sheathed inside the slots in its own arms, which is exactly where you want a set of exposed teeth to be.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/02-folded-HQ.png"&gt;&lt;img alt="Folded arrangement: each arm swings ninety degrees about its blade pin and lies along the blade, sheathing it, with the total bundle 637 mm long" src="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/02-folded-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The dimension you do not get to choose&lt;/h2&gt;
&lt;p&gt;Here is the part that reorganises the rest of the build.&lt;/p&gt;
&lt;p&gt;If both arms fold down onto the blade and lie end to end, then together they can be no longer than the blade span. Each arm gets half. A 610 mm blade has roughly 597 mm between eyelet centres, so each arm can be about 300 mm above its pin and no more. Go longer and the two arms collide in the middle before either has finished folding.&lt;/p&gt;
&lt;p&gt;The arm length is therefore fixed by the blade, not chosen by you. And the arm length is what the stretcher position is measured against, and the stretcher position is what sets the throat, the clear gap between blade and stretcher that decides the fattest log the saw will pass through.&lt;/p&gt;
&lt;p&gt;You do not pick the throat depth of a folding bucksaw. The blade picked it, at the moment you decided the saw should fold.&lt;/p&gt;
&lt;p&gt;For comparison, the classic non-folding H-frame uses 400 mm arms with the stretcher at mid height, and gets around 200 mm of throat from the same blade. Folding costs roughly a third of that. Worth knowing before you cut anything, because if you mostly saw thick rounds, the right answer is a saw that does not fold.&lt;/p&gt;
&lt;h2&gt;Throat against leverage&lt;/h2&gt;
&lt;p&gt;Given a 315 mm arm, the only remaining freedom is where the stretcher sits on it, and that is a straight trade.&lt;/p&gt;
&lt;p&gt;Put the stretcher at mid height and the lever arms above and below the fulcrum are about equal, so the cord carries roughly the same tension as the blade. Comfortable to tension, but the throat comes out around 115 mm.&lt;/p&gt;
&lt;p&gt;Slide the stretcher up to about sixty per cent of the arm height and the throat grows to 146 mm, which is the difference between passing a 115 mm round and a 145 mm round. The cost is leverage: the cord now sits 111 mm above the fulcrum while the blade hangs 169 mm below it, so the cord has to carry about one and a half times the blade tension for the same result. In practice that means a couple more turns on the toggle and a slightly beefier cord.&lt;/p&gt;
&lt;p&gt;This build takes the throat.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/01-elevation-HQ.png"&gt;&lt;img alt="Dimensioned side elevation of the assembled frame showing 315 mm arms, 597 mm blade span, 146 mm throat and the 111 mm and 169 mm lever arms" src="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/01-elevation-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Where the slot and the stretcher fight&lt;/h2&gt;
&lt;p&gt;One detail nearly sinks the design, and it is worth understanding rather than discovering.&lt;/p&gt;
&lt;p&gt;The blade slot has to run the full length of the arm, because the folded blade needs somewhere to go along its whole half. It also has to be deep, because the blade is 25 mm wide, so the slot goes 35 mm into a 45 mm arm and opens on the inner face. That is the same face the stretcher wants to seat against. A conventional mortise cut there would break straight into the slot.&lt;/p&gt;
&lt;p&gt;The fix is to stop treating it as a mortise. Cut a &lt;strong&gt;pocket&lt;/strong&gt; instead: 20 mm along the arm, 10 mm deep, 19 mm across, opening on the inner face. Because the slot is only 1.5 mm wide and the pocket is far wider, the pocket does not cross the slot, it simply widens its mouth for 20 mm. The stretcher end drops in, tension holds it there, and the folded blade passes through the same space with room to spare. Leaving 3 mm of wood on each side of the pocket stops the stretcher wandering sideways.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/03-sections-HQ.png"&gt;&lt;img alt="Three arm cross-sections: at the blade pin, mid arm showing the 35 mm deep 1.5 mm kerf, and at the stretcher socket showing the 10 by 19 mm pocket" src="http://lizard-spock.co.uk/images/Outdoor/FoldingBucksaw/03-sections-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Two things about that slot are worth stating because they look alarming and are not.&lt;/p&gt;
&lt;p&gt;It costs almost nothing in strength. The arm bends in the plane of the frame, and a 1.5 mm cut straddling the neutral axis removes about three and a half per cent of the second moment of area. The arm is essentially as stiff as it was.&lt;/p&gt;
&lt;p&gt;But it must stay full section at the pocket. The pocket sits at the fulcrum, which is where the bending moment peaks. Taper the arms above and below it if you want to lose weight, and leave the wood alone within about 40 mm either side.&lt;/p&gt;
&lt;h2&gt;Cut list&lt;/h2&gt;
&lt;p&gt;Ash is the right wood: tough, springy, and it takes the repeated tension cycles without setting. Beech and hickory both work. Avoid softwood, which crushes at the pocket.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Qty&lt;/th&gt;
&lt;th&gt;Length&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;th&gt;Thickness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arm&lt;/td&gt;
&lt;td&gt;2 (mirror pair)&lt;/td&gt;
&lt;td&gt;315 mm&lt;/td&gt;
&lt;td&gt;45 mm&lt;/td&gt;
&lt;td&gt;25 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stretcher&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;572 mm&lt;/td&gt;
&lt;td&gt;38 mm&lt;/td&gt;
&lt;td&gt;19 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;200 mm&lt;/td&gt;
&lt;td&gt;19 mm&lt;/td&gt;
&lt;td&gt;6 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pin&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;25 mm&lt;/td&gt;
&lt;td&gt;4 mm diameter&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Plus a 610 mm bow saw blade and about 1.4 m of 550 paracord.&lt;/p&gt;
&lt;p&gt;The blade used here is the Bahco 23-24, a 24 inch raker tooth hard point bowsaw blade, sold as
&lt;a href="https://www.axminstertools.com/bahco-23-24-raker-tooth-hard-point-bowsaw-blade-600mm-24-112224"&gt;600 mm at Axminster&lt;/a&gt;
and also stocked
&lt;a href="https://www.amazon.co.uk/Bahco-23-24-Raker-Tooth-Bowsaw/dp/B0001IX71K/"&gt;on Amazon&lt;/a&gt;.
Raker tooth is the pattern to want for green wood: the rakers clear the sawdust
out of the kerf instead of packing it in. Note that suppliers describe the same
24 inch blade as either 600 mm or 610 mm, which is another reason to measure the
eyelet spacing on the blade in your hand rather than trusting the listing.&lt;/p&gt;
&lt;p&gt;The arms are a mirror pair, not two identical parts. The slot opens on the inner face of each, so they are handed. Mark them before you cut.&lt;/p&gt;
&lt;p&gt;Measure your own blade before committing to the stretcher length. Eyelet spacing varies between makers and 597 mm is typical, not universal. Better still, cut the stretcher last and fit it to the frame you actually built.&lt;/p&gt;
&lt;p&gt;The stretcher looks slender for the load. It is not: at 572 mm between shoulders, 38 by 19 mm in ash, Euler buckling comes out around 7.9 kN assuming pinned ends and a modulus near 12 GPa. That is an order of magnitude check rather than a certification, but it is a comfortable order of magnitude.&lt;/p&gt;
&lt;h2&gt;Building it&lt;/h2&gt;
&lt;p&gt;Shape everything before the pins go in. Once a pin is driven the blade is captive, and every subsequent operation has a sharp 610 mm blade swinging off the workpiece.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Dress both arm blanks to 315 by 45 by 25 mm. Mark which face is inner on each, remembering they are handed.&lt;/li&gt;
&lt;li&gt;Rip the blade slot down the inner face of each arm, 1.5 mm wide and 35 mm deep, running the full length. A bandsaw or a thin-plate rip saw both work. Test that the blade slides in freely before going further.&lt;/li&gt;
&lt;li&gt;Chisel the stretcher pocket in the inner face, 20 mm along the arm and 10 mm deep, centred 189 mm up from the toe, leaving 3 mm of wood on each cheek.&lt;/li&gt;
&lt;li&gt;Round the arm tops and cut a shallow notch on each outer edge, about 15 mm down from the top, to stop the cord riding off.&lt;/li&gt;
&lt;li&gt;Shape the lower ends into handles. Chamfer everything. Sand now, because you will not get clean access later.&lt;/li&gt;
&lt;li&gt;Drill the 4 mm pin holes 20 mm up from each toe, square through both cheeks.&lt;/li&gt;
&lt;li&gt;Drop the blade eyelets into the slots, line them up with the holes, and drive the pins. Peen or epoxy the ends flush.&lt;/li&gt;
&lt;li&gt;Assemble the frame and cut the stretcher to the length that gives you a light interference fit in both pockets.&lt;/li&gt;
&lt;li&gt;Fit the cord as a loop of four strands between the arm tops, above the notches, and slip the toggle through it.&lt;/li&gt;
&lt;li&gt;Oil the wood. Boiled linseed or a hardwax oil, several thin coats.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Tensioning and packing&lt;/h2&gt;
&lt;p&gt;Wind the toggle four to six turns and stop when a flick of the blade gives a clear note rather than a dull slap. Rest the toggle end against the stretcher so it cannot unwind.&lt;/p&gt;
&lt;p&gt;Slacken it when you finish. Cord left wound overnight creeps, and wood left under load takes a set, so a saw stored tensioned is a saw that will not tension properly next time.&lt;/p&gt;
&lt;p&gt;To pack: unwind fully, lift the stretcher out of its pockets, swing both arms down onto the blade, and lash the stretcher and toggle alongside with the cord. The bundle comes out 637 mm long and about 45 by 50 mm in section, which lashes to the outside of a pack and will not go inside one. That is the honest cost of a 610 mm blade, and it is the same for every folding design.&lt;/p&gt;
&lt;h2&gt;What it will not do&lt;/h2&gt;
&lt;p&gt;The throat is 146 mm. Roll a bigger log and cut from two sides and you will manage 290 mm, but slowly and with a wandering kerf.&lt;/p&gt;
&lt;p&gt;Seven millimetres of teeth stay exposed at the middle of the folded bundle where the two arms meet. It is not enough to cut a pack, but it is enough to find a thumb. A scrap of leather over the join solves it.&lt;/p&gt;
&lt;p&gt;And replacing the blade means punching out a pin, which is a workshop job rather than a field one. That is the deliberate trade behind never losing a fastener in the dark. Carry a spare cord, because that is the part that actually fails, and leave the spare blade at home.&lt;/p&gt;</content><category term="Outdoor"/><category term="bushcraft"/><category term="woodwork"/><category term="tools"/><category term="scouting"/></entry><entry><title>SoC Intermediate 02: Cache coherency protocols</title><link href="http://lizard-spock.co.uk/soc-intermediate-02-cache-coherency-protocols.html" rel="alternate"/><published>2026-08-06T00:00:00+01:00</published><updated>2026-08-06T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-06:/soc-intermediate-02-cache-coherency-protocols.html</id><summary type="html">&lt;p&gt;Why two threads with separate counters can run slower than one thread, and the coherency machinery that explains it: the single-writer rule, MESI states, read-for-ownership, snooping, directories, snoop filters, and the cache line granularity that no amount of reading the source code reveals.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI02/00-coherency-hero-HQ.png"&gt;&lt;img alt="A multicore cache coherency fabric on a system on chip, with four processor caches around a shared interconnect that carries snoop and ownership traffic to a directory and a memory controller" src="http://lizard-spock.co.uk/images/SoC/ArticleI02/00-coherency-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Intermediate SoC Design | Article 2 of 10&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Two counters that will not scale&lt;/h2&gt;
&lt;p&gt;Here is a loop that counts events. It runs in about 2 seconds.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// thread 0 runs: for (...) stats[0].hits++;&lt;/span&gt;
&lt;span class="c1"&gt;// thread 1 runs: for (...) stats[1].hits++;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You split it across two threads, one counter each. The threads share no
variables. There is no lock, no atomic, no memory barrier, and no bug that any
code review would catch. Each thread touches its own element and nothing else.&lt;/p&gt;
&lt;p&gt;It now takes 6 seconds. Adding a core made the program three times slower.&lt;/p&gt;
&lt;p&gt;Nothing in that code explains the result. The explanation lives one layer down,
in the hardware that keeps caches agreeing with each other. This article covers
that machinery: what it guarantees, how it works, and why it occasionally
punishes correct-looking code. It targets engineers building or integrating
multicore hardware, and the systems programmers who live with the result.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Coherence is not consistency&lt;/h2&gt;
&lt;p&gt;Two terms sound interchangeable and are not. Getting them confused makes every
later discussion harder.&lt;/p&gt;
&lt;p&gt;Coherence concerns one address at a time. If one core writes address &lt;code&gt;X&lt;/code&gt;, every
other core eventually sees that write to &lt;code&gt;X&lt;/code&gt;, and nobody keeps using a stale
copy. Cache controllers and the interconnect implement it.&lt;/p&gt;
&lt;p&gt;Memory consistency concerns ordering between different addresses. It defines
what software can assume about the order of loads and stores to &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt;.
Barriers and acquire and release semantics live here. The instruction set
architecture defines the model, and cores, store buffers, and the coherency
fabric enforce it together.&lt;/p&gt;
&lt;p&gt;A useful split: coherence is hardware keeping a promise about one location.
Consistency is the contract the hardware offers software about many locations.
This article is about coherence.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The single-writer rule&lt;/h2&gt;
&lt;p&gt;Every coherent system rests on one invariant. Learn this and most of the
protocol becomes predictable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;caches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;hold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;clean&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;readable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copies&lt;/span&gt;.
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;hold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dirty&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;writable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI02/01-single-writer-HQ.png"&gt;&lt;img alt="The single writer rule illustrated: three processor caches holding clean readable copies of the same cache line on one side, and a single cache holding the one dirty writable copy on the other" src="http://lizard-spock.co.uk/images/SoC/ArticleI02/01-single-writer-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Readers are cheap and can be plentiful. Writers are exclusive. When a core
wants to write a line that other caches hold, it must first take ownership and
remove every other copy.&lt;/p&gt;
&lt;p&gt;That removal step is the source of most coherency traffic, most coherency
latency, and the performance mystery in the opening section.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The four states of a cache line&lt;/h2&gt;
&lt;p&gt;MESI is the classic state machine for a cache line, named for its four stable
states. Each cache tracks a state per line, not per variable.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Modified&lt;/td&gt;
&lt;td&gt;This cache holds the only valid copy, and it differs from memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;td&gt;This cache holds the only valid copy, and it matches memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;This cache holds a clean copy, and other caches might hold it too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid&lt;/td&gt;
&lt;td&gt;This cache line holds nothing usable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_c5175f1c17b13dfa4039c458b587ac2f.svg"&gt;&lt;/p&gt;
&lt;p&gt;Exclusive is the state that repays study. A line arrives in Exclusive when a
core reads it and no other cache holds it. The copy is clean, so no writeback
is needed, but the core already has sole ownership. A later write moves it to
Modified with no bus traffic at all.&lt;/p&gt;
&lt;p&gt;Without Exclusive, every first write to a freshly read line would need an
invalidate transaction that has nobody to invalidate. That is why the state
exists, and why protocols that omit it perform worse on private data.&lt;/p&gt;
&lt;p&gt;Real designs add transient states on top of these four. A line waiting for
invalidate acknowledgements is neither Shared nor Modified, and the
implementation needs somewhere to put it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What happens on a read miss&lt;/h2&gt;
&lt;p&gt;A read miss is the simplest coherent transaction, and it introduces every
component involved.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_1ab6b5ab17d6c81f6aedcb4a449c1a11.svg"&gt;&lt;/p&gt;
&lt;p&gt;The snoop filter decides who hears the request. It knows which caches might
hold the line, so it sends the snoop only to those, rather than to everyone.&lt;/p&gt;
&lt;p&gt;If another cache holds the line in Modified, that cache owns the newest data
and memory is stale. It either writes the line back and lets memory answer, or
forwards the data directly to the requester. Direct forwarding, called cache to
cache transfer, is faster and most modern fabrics do it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Taking ownership before a write&lt;/h2&gt;
&lt;p&gt;A core cannot write a line it shares. It first issues a read-for-ownership,
which fetches the data and invalidates every other copy in one transaction.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_653f0e907370ec65201379003ec8f711.svg"&gt;&lt;/p&gt;
&lt;p&gt;Read the last two rows together. The line state changes to Modified only after
both acknowledgements arrive. Until then the requesting core holds a promise,
not ownership.&lt;/p&gt;
&lt;p&gt;That ordering is the single-writer rule expressed in silicon. A design that
transitions early creates a window where two caches both believe they can
write, and the resulting corruption appears only under precise timing.&lt;/p&gt;
&lt;p&gt;The cost is visible in the diagram. Every write to a shared line pays for a
round trip to every sharer before it can proceed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Snooping: every cache hears everything&lt;/h2&gt;
&lt;p&gt;The direct way to keep caches agreeing is to let them all listen to the same
coherence traffic.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI02/02-snooping-HQ.png"&gt;&lt;img alt="Snooping coherency: four processor caches all connected to one shared coherent bus, with a single request broadcast reaching every cache simultaneously" src="http://lizard-spock.co.uk/images/SoC/ArticleI02/02-snooping-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Every cache observes every read, invalidate, and ownership request, and each
one checks whether it holds the line. The model holds few surprises during
bring-up, and latency is low because there is no lookup step before the
broadcast.&lt;/p&gt;
&lt;p&gt;It stops scaling for three reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Snoop traffic grows with the product of core count and request rate&lt;/li&gt;
&lt;li&gt;Every broadcast wakes caches that do not hold the line, which wastes power&lt;/li&gt;
&lt;li&gt;One structure that every cache must observe becomes hard to close timing on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Snooping suits four cores. It does not suit thirty-two.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Directories: telling only the caches that care&lt;/h2&gt;
&lt;p&gt;A directory records which caches might hold each line. The fabric consults it
and sends targeted snoops instead of broadcasting.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI02/03-directory-HQ.png"&gt;&lt;img alt="Directory based coherency: a central directory holding a sharer list for a cache line, sending targeted snoops to only the two caches that hold copies while two other caches are left undisturbed" src="http://lizard-spock.co.uk/images/SoC/ArticleI02/03-directory-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Core3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Modified&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;Sharers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Core0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Core1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Core2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Core3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;State&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A read from Core 1 now disturbs Core 0 and Core 2 only. Core 3 never sees the
request, so its cache stays quiet and its power stays down.&lt;/p&gt;
&lt;p&gt;The costs are real. The directory needs storage proportional to the memory it
covers, which is why designs track only cached lines rather than all of memory.
Race handling also becomes harder, because the directory itself is a shared
structure that can be stale while a transaction is in flight.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Snoop filters and the asymmetry of being wrong&lt;/h2&gt;
&lt;p&gt;A snoop filter is a compact directory whose job is avoiding pointless snoops.
It does not have to be precise, but it does have to be conservative in one
specific direction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Conservative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snoop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Not present&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;definitely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Maybe present&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fabric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;snoop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The two ways of being wrong are not equivalent, and the difference matters more
than any other property of the structure.&lt;/p&gt;
&lt;p&gt;A false positive says a cache might hold a line it does not hold. The fabric
sends a snoop, the cache answers that it has nothing, and the system loses a
little time and power. The result stays correct.&lt;/p&gt;
&lt;p&gt;A false negative says a cache does not hold a line it actually holds. The
fabric skips the snoop, a stale copy survives an invalidate, and two caches
disagree about memory. The result is silent data corruption.&lt;/p&gt;
&lt;p&gt;Treat a false negative as a correctness bug of the highest severity. It is not
a performance tuning question.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The hardware does not share variables&lt;/h2&gt;
&lt;p&gt;Return to the two counters. The threads shared no variables, and that was true.
It was also irrelevant.&lt;/p&gt;
&lt;p&gt;Coherency does not track variables. It has no idea that &lt;code&gt;stats[0].hits&lt;/code&gt; and
&lt;code&gt;stats[1].hits&lt;/code&gt; are different objects, because it never sees objects. It tracks
cache lines, typically 64 bytes, and a line is the smallest thing it can own,
share, or invalidate.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI02/04-false-sharing-HQ.png"&gt;&lt;img alt="False sharing: two processor cores writing to two different variables that occupy the same 64 byte cache line, with the line ownership bouncing back and forth between the two caches on every write" src="http://lizard-spock.co.uk/images/SoC/ArticleI02/04-false-sharing-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;One&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;
&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;takes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Modified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invalidates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;takes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Modified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invalidates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;takes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Modified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invalidates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both counters sit in one line. Every write by either thread must take exclusive
ownership of that line, which means invalidating the other core's copy. The
line ping-pongs between the two caches, and each write pays a full coherency
round trip instead of hitting in L1.&lt;/p&gt;
&lt;p&gt;This is false sharing. The name is precise: the threads share nothing at the
level of the source code, and everything at the level the hardware operates on.
No amount of reading the C source reveals it, because the C source does not
mention the unit that matters.&lt;/p&gt;
&lt;p&gt;It is a software performance bug with a hardware cause, and neither layer is
wrong. The hardware is honouring the single-writer rule exactly as specified.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;When accelerators join the domain&lt;/h2&gt;
&lt;p&gt;Direct memory access engines and accelerators read and write memory without
executing any of the cache maintenance instructions a core would. Systems
handle this in one of three ways.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI02/05-dma-coherency-HQ.png"&gt;&lt;img alt="Three coherency approaches for a direct memory access engine: non-coherent with software cache maintenance, input output coherent participating in snoops for selected transactions, and fully coherent sharing the domain with the processors" src="http://lizard-spock.co.uk/images/SoC/ArticleI02/05-dma-coherency-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Non-coherent:&lt;/strong&gt; Software cleans caches before a transfer out and invalidates them after a transfer in. The hardware is cheap, and a single missed maintenance operation corrupts the transfer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input output coherent:&lt;/strong&gt; The engine participates in the coherency fabric for selected transactions, so its reads snoop the caches. Software stops needing maintenance for those buffers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fully coherent:&lt;/strong&gt; The accelerator holds coherent copies itself, through a protocol such as ACE or CHI, and behaves like another core in the domain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each step up the list gains programmability and costs fabric complexity and
verification effort. Most bugs in this area come from the first option, where a
missing cache maintenance operation produces stale data that appears only when
timing shifts.&lt;/p&gt;
&lt;p&gt;State the coherency domain boundaries explicitly in the specification. Every
engineer integrating a block needs to know which side of the boundary it sits
on.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Atomics need the same ownership&lt;/h2&gt;
&lt;p&gt;Locks, reference counters, and queue indices all need read-modify-write
sequences that no other core can interleave with. Coherency supplies the
mechanism.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;Atomic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;acquires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exclusive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fabric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prevents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;taking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ownership&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unobserved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;another&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;did&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intervene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;software&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Exclusive monitors, load-reserved and store-conditional pairs, and far atomics
executed near memory are all built on this foundation. The coherence protocol
provides ownership, and the instruction set defines what software sees.&lt;/p&gt;
&lt;p&gt;Contended atomics are expensive for the same reason false sharing is expensive.
Every attempt moves a line between caches.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why coherency bugs hide&lt;/h2&gt;
&lt;p&gt;Coherency bugs need specific interleavings, so they survive a great deal of
testing before appearing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two cores racing for ownership of one line&lt;/li&gt;
&lt;li&gt;An eviction colliding with an inbound snoop&lt;/li&gt;
&lt;li&gt;A dirty line forwarded while its writeback is still pending&lt;/li&gt;
&lt;li&gt;A snoop filter entry replaced at the wrong moment&lt;/li&gt;
&lt;li&gt;Barrier ordering combined with outstanding writes&lt;/li&gt;
&lt;li&gt;A transfer reading memory that a cache still owns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these appear in a single-core test, and few appear in a directed
multicore test that runs the same order every time. Verification therefore
combines four approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Protocol checkers that watch the invariants continuously&lt;/li&gt;
&lt;li&gt;Randomised traffic to reach interleavings nobody predicted&lt;/li&gt;
&lt;li&gt;Formal properties for local invariants such as single-writer&lt;/li&gt;
&lt;li&gt;Directed tests for the race classes already known&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Design checklist&lt;/h2&gt;
&lt;p&gt;Decide these before the first line of coherent RTL:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define the coherency domain boundaries in the specification.&lt;/li&gt;
&lt;li&gt;Classify every requester as coherent, input output coherent, or non-coherent.&lt;/li&gt;
&lt;li&gt;Keep stable line state and transient transaction state separate in the RTL.&lt;/li&gt;
&lt;li&gt;Assert the single-writer invariant continuously, not only in directed tests.&lt;/li&gt;
&lt;li&gt;Treat any snoop filter false negative as a correctness failure.&lt;/li&gt;
&lt;li&gt;Measure false sharing and lock contention as part of performance sign-off.&lt;/li&gt;
&lt;li&gt;Verify cache maintenance against real transfer buffers, not synthetic ones.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Padding, and how to check&lt;/h2&gt;
&lt;p&gt;The fix for the opening example is one line. Give each counter its own cache
line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__attribute__&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;aligned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The program returns to 2 seconds, and the memory cost is 56 wasted bytes per
counter. That trade is almost always worth taking for data written by different
threads.&lt;/p&gt;
&lt;p&gt;To find the problem rather than guess at it, use &lt;code&gt;perf c2c&lt;/code&gt;. It reports cache
line contention directly, and names both the offending line and the offsets
within it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;perf&lt;span class="w"&gt; &lt;/span&gt;c2c&lt;span class="w"&gt; &lt;/span&gt;record&lt;span class="w"&gt; &lt;/span&gt;./your_program
perf&lt;span class="w"&gt; &lt;/span&gt;c2c&lt;span class="w"&gt; &lt;/span&gt;report
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Worth running once on anything threaded that scales worse than it should. The
counters that fight are rarely the ones anybody suspects.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-intermediate-01-axi4-protocol-deep-dive.html"&gt;Article I-01: AXI4 protocol deep dive&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-intermediate-03-pipeline-design-and-hazards.html"&gt;Article I-03: Pipeline design and hazards&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Multicore"/><category term="Caches"/><category term="MESI"/><category term="Coherency"/><category term="ARM"/><category term="Verification"/></entry><entry><title>SoC Intermediate 01: AXI4 protocol deep dive</title><link href="http://lizard-spock.co.uk/soc-intermediate-01-axi4-protocol-deep-dive.html" rel="alternate"/><published>2026-08-02T00:00:00+01:00</published><updated>2026-08-02T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-02:/soc-intermediate-01-axi4-protocol-deep-dive.html</id><summary type="html">&lt;p&gt;The mechanisms behind AXI4 throughput and the bugs each one creates: burst encoding, transaction identifiers, out-of-order completion, outstanding transaction tracking, quality of service signalling, and the handshake rules that hand-written bridges break most often.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI01/00-axi4-hero-HQ.png"&gt;&lt;img alt="An AXI4 interconnect at the centre of a system on chip, with a processor, a direct memory access engine, a graphics processor and a memory controller exchanging tagged read and write transactions along coloured channel paths" src="http://lizard-spock.co.uk/images/SoC/ArticleI01/00-axi4-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Series: Intermediate SoC Design | Article 1 of 10&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This article explains how the AXI4 protocol achieves high throughput, and why
that same design makes AXI4 hard to debug. It is written for engineers who
already know the five channels and now have to build, integrate, or debug a
real interface.&lt;/p&gt;
&lt;p&gt;AXI is the Advanced eXtensible Interface, part of the AMBA family from ARM.
AXI4 connects the high-bandwidth blocks of a system on chip: processors,
direct memory access engines, graphics processors, display controllers,
memory controllers, and coherent interconnects. The
&lt;a href="http://lizard-spock.co.uk/soc-article-06-interconnects-and-bus-protocols.html"&gt;introductory article on interconnects&lt;/a&gt;
described AXI as five independent channels with a valid and ready handshake.
This article goes further, into bursts, transaction identifiers, ordering,
backpressure, quality of service, and protocol compliance.&lt;/p&gt;
&lt;p&gt;AXI is not a bus. It is a distributed flow-control contract between managers,
interconnects, subordinates, buffers, bridges, and clock converters. Every one
of those components can break the contract independently.&lt;/p&gt;
&lt;p&gt;Note: this article uses the current AMBA terms manager and subordinate. Older
documents and much existing code use master and slave for the same roles. The
signal names are identical in both.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The five channels revisited&lt;/h2&gt;
&lt;p&gt;AXI4 separates read traffic and write traffic into five independent channels.
Each channel carries its own handshake and makes progress on its own schedule.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI01/01-five-channels-HQ.png"&gt;&lt;img alt="The five AXI4 channels drawn between a manager and a subordinate: write address, write data and write response on the write path, read address and read data on the read path, each channel labelled with its direction and valid and ready signal pair" src="http://lizard-spock.co.uk/images/SoC/ArticleI01/01-five-channels-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write address&lt;/td&gt;
&lt;td&gt;Manager to subordinate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write data&lt;/td&gt;
&lt;td&gt;Manager to subordinate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write response&lt;/td&gt;
&lt;td&gt;Subordinate to manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read address&lt;/td&gt;
&lt;td&gt;Manager to subordinate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read data&lt;/td&gt;
&lt;td&gt;Subordinate to manager&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A transfer occurs only on a rising clock edge where &lt;code&gt;VALID&lt;/code&gt; and &lt;code&gt;READY&lt;/code&gt; are
both high. That single rule governs every channel.&lt;/p&gt;
&lt;p&gt;AXI decouples the address channels from the data channels deliberately. A
manager can send several write addresses before the subordinate accepts all
the write data. A subordinate can delay responses while it continues to accept
new addresses. This decoupling is the source of AXI throughput.&lt;/p&gt;
&lt;p&gt;The cost of decoupling is bookkeeping. A correct design tracks every
outstanding transaction, because the protocol never promises that two channels
stay aligned.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The valid and ready contract&lt;/h2&gt;
&lt;p&gt;The valid and ready handshake carries two discipline rules that hand-written
interfaces break more often than any other part of the protocol.&lt;/p&gt;
&lt;p&gt;The first rule constrains the source. A source must not wait for &lt;code&gt;READY&lt;/code&gt;
before it asserts &lt;code&gt;VALID&lt;/code&gt;. If the source has a transfer available, it asserts
&lt;code&gt;VALID&lt;/code&gt; immediately. A design that waits for &lt;code&gt;READY&lt;/code&gt; first can deadlock
against a destination that waits for &lt;code&gt;VALID&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The second rule constrains the payload. Once &lt;code&gt;VALID&lt;/code&gt; is high, the payload must
stay stable until the transfer completes. The destination is free to assert
and deassert &lt;code&gt;READY&lt;/code&gt; at any time, and that freedom is how backpressure travels
back through the system.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_390a8768414bb7eebe758b1079c75b6b.svg"&gt;&lt;/p&gt;
&lt;p&gt;If the payload changes while &lt;code&gt;VALID&lt;/code&gt; is high and &lt;code&gt;READY&lt;/code&gt; is low, the
destination can capture corrupted information. The failure is intermittent,
because it appears only when the destination applies backpressure at the
moment the source changes its data.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Burst types and addressing&lt;/h2&gt;
&lt;p&gt;A burst moves several data beats from one address phase. AXI4 supports three
burst types, selected by the &lt;code&gt;AxBURST&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI01/02-burst-types-HQ.png"&gt;&lt;img alt="Three AXI4 burst types compared on a memory address grid: FIXED repeatedly accessing one address, INCR stepping forward through consecutive addresses, and WRAP stepping forward then wrapping back to the start of an aligned region" src="http://lizard-spock.co.uk/images/SoC/ArticleI01/02-burst-types-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Burst&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FIXED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The address stays constant for every beat&lt;/td&gt;
&lt;td&gt;A first in, first out data port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INCR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The address increments by the beat size&lt;/td&gt;
&lt;td&gt;A normal memory copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WRAP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The address increments, then wraps at an aligned boundary&lt;/td&gt;
&lt;td&gt;A cache line refill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two fields encode the shape of the burst. &lt;code&gt;AxLEN&lt;/code&gt; holds the beat count minus
one, so the transfer carries &lt;code&gt;AxLEN + 1&lt;/code&gt; beats. &lt;code&gt;AxSIZE&lt;/code&gt; holds the base 2
logarithm of the beat width, so each beat carries &lt;code&gt;2^AxSIZE&lt;/code&gt; bytes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Example&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;beat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INCR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;burst&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ARADDR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x8000&lt;/span&gt;&lt;span class="n"&gt;_1000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ARLEN&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;beats&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ARSIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;per&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;beat&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ARBURST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INCR&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Beat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mh"&gt;0x8000&lt;/span&gt;&lt;span class="n"&gt;_1000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mh"&gt;0x8000&lt;/span&gt;&lt;span class="n"&gt;_1008&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mh"&gt;0x8000&lt;/span&gt;&lt;span class="n"&gt;_1010&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mh"&gt;0x8000&lt;/span&gt;&lt;span class="n"&gt;_1018&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Wrapped bursts and cache line refills&lt;/h3&gt;
&lt;p&gt;A wrapped burst keeps every beat inside one naturally aligned region. Caches
use this to fetch the word the processor stalled on first, then collect the
rest of the line.&lt;/p&gt;
&lt;p&gt;The wrap boundary is the total burst size, which is the beat count multiplied
by the beat width. When the address reaches that boundary, it returns to the
start of the aligned region rather than crossing into the next one.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_067d6deeca2ba432c3082284fb5b1faa.svg"&gt;&lt;/p&gt;
&lt;p&gt;The address &lt;code&gt;RADDR&lt;/code&gt; is shown for illustration. AXI does not carry an address
on the read data channel, because the manager already knows the address from
the read address phase.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Anatomy of a write transaction&lt;/h2&gt;
&lt;p&gt;A write completes only when all three write-side channels finish their work.
The subordinate accepts the address, accepts every data beat, and returns a
response.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_53bdaeb211943d2f6dba1b08c33747ea.svg"&gt;&lt;/p&gt;
&lt;p&gt;The address phase and the data phase are independent. The manager can present
write data before, during, or after the address handshake completes.&lt;/p&gt;
&lt;p&gt;Many broken subordinates assume that &lt;code&gt;AW&lt;/code&gt; and &lt;code&gt;W&lt;/code&gt; arrive together. The
assumption survives a simple testbench, then fails against a real
interconnect that reorders the two channels through separate buffers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WLAST&lt;/code&gt; marks the final data beat. A subordinate that counts beats without
checking &lt;code&gt;WLAST&lt;/code&gt; cannot detect a manager that sends the wrong number.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Anatomy of a read transaction&lt;/h2&gt;
&lt;p&gt;A read uses fewer channels than a write. One address phase produces one or
more read data beats, and the final beat carries &lt;code&gt;RLAST&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_0caf6c6b0bfe0f12cf359012e0c979ac.svg"&gt;&lt;/p&gt;
&lt;p&gt;The read response travels in the &lt;code&gt;RRESP&lt;/code&gt; field of each beat rather than on a
separate channel. A burst can therefore report an error on one beat and
success on another.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Transaction identifiers and ordering&lt;/h2&gt;
&lt;p&gt;Transaction identifiers let a manager keep several requests in flight at once.
The &lt;code&gt;AWID&lt;/code&gt; and &lt;code&gt;ARID&lt;/code&gt; fields tag requests, and the &lt;code&gt;BID&lt;/code&gt; and &lt;code&gt;RID&lt;/code&gt; fields tag
the matching responses.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI01/03-out-of-order-HQ.png"&gt;&lt;img alt="Two tagged read requests entering an interconnect scoreboard and leaving in a different order: a request tagged ID 4 to slow memory issued first but returning second, and a request tagged ID 7 to fast memory issued second but returning first" src="http://lizard-spock.co.uk/images/SoC/ArticleI01/03-out-of-order-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Two ordering rules follow from the identifiers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Transactions that share an identifier must complete in the order the manager issued them.&lt;/li&gt;
&lt;li&gt;Transactions with different identifiers can complete in any order, if the interconnect and the subordinate support it.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;Out&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;completion&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Manager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ARID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;miss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slow&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ARID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fast&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Interconnect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;later&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;two&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;both&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ARID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This rule is what lets a memory controller reorder requests around DRAM bank
conflicts. DRAM is dynamic random access memory, and a row miss costs tens of
nanoseconds that a reordered request can hide.&lt;/p&gt;
&lt;p&gt;The identifier is not an address and not a priority. It is the manager's
statement about which of its own requests can complete out of order. A manager
that reuses one identifier for everything forbids all reordering, and loses
the performance that reordering provides.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Tracking outstanding transactions&lt;/h2&gt;
&lt;p&gt;Every AXI4 component keeps state for the transactions it has started and not
yet finished. The decoupled channels make this bookkeeping mandatory rather
than optional.&lt;/p&gt;
&lt;p&gt;A manager tracks its own outstanding requests. An interconnect tracks routing
state so responses reach the right manager. A subordinate that accepts several
requests tracks the responses it still owes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Read outstanding table

  Slot  ID  Source manager  Target subordinate  Beats left  Status
  ----  --  --------------  ------------------  ----------  ---------
  0     3   CPU0            DDR                 8           waiting
  1     8   DMA             SRAM                1           returning
  2     4   GPU             DDR                 16          waiting
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Five questions decide whether the bookkeeping is correct:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Depth:&lt;/strong&gt; How many outstanding reads and writes each interface supports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scope:&lt;/strong&gt; Whether the limits apply per manager, per subordinate, or globally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interleaving:&lt;/strong&gt; Whether any component still expects write data interleaving.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Errors:&lt;/strong&gt; How error responses reach the originating manager, and whether the counters still balance afterwards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reset:&lt;/strong&gt; Whether a reset during an outstanding transaction leaves a counter or a route table entry stale.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The third answer is fixed by the standard, and it simplifies subordinate
design. AXI3 permitted write data interleaving, so a subordinate could receive
beats from two write bursts mixed together. AXI4 removed it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Response codes&lt;/h2&gt;
&lt;p&gt;Every AXI4 transaction reports a result. The result travels in &lt;code&gt;BRESP&lt;/code&gt; for
writes and &lt;code&gt;RRESP&lt;/code&gt; for reads.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OKAY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The access succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXOKAY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An exclusive access succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SLVERR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The target received the request and failed it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DECERR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Address decode failed, usually an unmapped address&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The interconnect generates &lt;code&gt;DECERR&lt;/code&gt;, not the target peripheral. An unmapped
address has no target peripheral to generate anything, so the decode logic
answers on its behalf.&lt;/p&gt;
&lt;p&gt;A system that returns nothing for an unmapped address hangs instead of
reporting a fault. This is one of the most common integration defects, and a
single read from an unmapped address detects it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Quality of service signalling&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AxQOS&lt;/code&gt; field lets a manager mark the relative urgency of a transaction.
Quality of service, abbreviated QoS, is a 4-bit value carried on both AXI4
address channels.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI01/04-qos-arbitration-HQ.png"&gt;&lt;img alt="Three traffic classes arriving at an AXI arbiter with different quality of service values: a display scanout read at the highest value, a processor cache refill in the middle, and a background copy at the lowest, with the arbiter also weighing a starvation counter and outstanding transaction limits" src="http://lizard-spock.co.uk/images/SoC/ArticleI01/04-qos-arbitration-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A display controller fetching pixels for the next scanline needs low latency,
because late pixels tear the image. A background copy engine can wait. The
&lt;code&gt;AxQOS&lt;/code&gt; field is how the manager states the difference.&lt;/p&gt;
&lt;p&gt;The field is advisory. It changes nothing unless the interconnect or the
memory controller implements an arbitration policy that reads it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;QoS-aware arbitration

  High urgency:  display scanout read   AxQOS=0xF
  Medium:        CPU cache refill       AxQOS=0x8
  Low:           background DMA copy    AxQOS=0x2

  Arbiter decision considers:
    QoS class
    starvation counter
    target availability
    outstanding limit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A policy where the highest value always wins starves the lower classes.
Starved traffic holds buffers, full buffers apply backpressure, and
backpressure can deadlock the system that the priority scheme was meant to
protect. Production arbiters combine priority with an aging counter or a
guaranteed bandwidth share.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Choosing between AXI4 and AXI4-Lite&lt;/h2&gt;
&lt;p&gt;AXI4-Lite is a subset of AXI4 intended for control and status registers. It
removes the features that a register bank never uses.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every transfer is a single beat.&lt;/li&gt;
&lt;li&gt;The burst fields are absent.&lt;/li&gt;
&lt;li&gt;The transaction identifier fields are absent.&lt;/li&gt;
&lt;li&gt;The resulting subordinate is smaller and far quicker to verify.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use AXI4-Lite for register banks. Use full AXI4 for high-throughput data
movement. Attaching a large register block to full AXI4 gains nothing and
costs verification effort, unless system integration forces the choice.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;How AXI fails in practice&lt;/h2&gt;
&lt;p&gt;AXI failures usually appear as a hang rather than an error. A stalled
handshake produces no exception, no trap, and no log entry.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/ArticleI01/05-debug-flow-HQ.png"&gt;&lt;img alt="A debug decision path for a stalled AXI interface: capture all five channels, identify which channel shows valid high with ready low, then determine whether the source or the destination owes progress" src="http://lizard-spock.co.uk/images/SoC/ArticleI01/05-debug-flow-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The common root causes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;VALID&lt;/code&gt; deasserts before the destination asserts &lt;code&gt;READY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The payload changes while the transfer is stalled&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WLAST&lt;/code&gt; is missing, early, or late&lt;/li&gt;
&lt;li&gt;A subordinate assumes &lt;code&gt;AW&lt;/code&gt; and &lt;code&gt;W&lt;/code&gt; arrive in the same cycle&lt;/li&gt;
&lt;li&gt;An outstanding counter underflows or overflows&lt;/li&gt;
&lt;li&gt;A response identifier does not match any outstanding request&lt;/li&gt;
&lt;li&gt;An interconnect frees a route table entry before the final beat&lt;/li&gt;
&lt;li&gt;An unmapped access returns nothing instead of &lt;code&gt;DECERR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A clock domain bridge drops a beat under backpressure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fastest way to diagnose any of these is to capture all five channels
around the stall. Then ask one question: which channel shows &lt;code&gt;VALID&lt;/code&gt; high with
&lt;code&gt;READY&lt;/code&gt; low, and which component owes the next move.&lt;/p&gt;
&lt;p&gt;That question converts a system hang into a single accountable interface. The
component holding &lt;code&gt;READY&lt;/code&gt; low is either waiting for something legitimate or
has lost track of its own state.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A protocol monitor for the write channel&lt;/h2&gt;
&lt;p&gt;A small monitor catches most write channel violations before they reach
silicon. It watches the handshakes and flags any sequence the protocol
forbids.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_ceadc163cb9e12fd2f04182de6a4d334.svg"&gt;&lt;/p&gt;
&lt;p&gt;The three transitions into the error state cover the failures that cost the
most debug time. A response without a request means the identifier tracking is
broken somewhere upstream. Too many beats means a beat counter disagrees with
&lt;code&gt;AWLEN&lt;/code&gt;. A second response means two components both think they own the
transaction.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Design checklist&lt;/h2&gt;
&lt;p&gt;Work through these decisions before the first line of interface code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define the maximum outstanding reads and writes for every manager.&lt;/li&gt;
&lt;li&gt;Decide which managers use multiple transaction identifiers, and how many.&lt;/li&gt;
&lt;li&gt;Size every buffer for worst-case backpressure, not for average throughput.&lt;/li&gt;
&lt;li&gt;Add protocol assertions at every custom AXI boundary in the design.&lt;/li&gt;
&lt;li&gt;Map the full address space so that unmapped accesses return &lt;code&gt;DECERR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep AXI4-Lite register blocks single-beat and strongly ordered.&lt;/li&gt;
&lt;li&gt;Capture all five channels in the waveform setup used for hang debug.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;AXI4 performance comes from decoupling: independent channels, burst transfers,
backpressure, transaction identifiers, and out-of-order completion. Each of
those mechanisms raises throughput, and each one demands discipline in return.&lt;/p&gt;
&lt;p&gt;That discipline covers handshake stability, outstanding transaction tracking,
response ordering, and error handling. Most AXI failures are not a
misunderstanding of one signal. They come from assuming that two independent
channels stay aligned when the protocol explicitly permits them to drift
apart.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-intermediate-02-cache-coherency-protocols.html"&gt;Intermediate Article 02: Cache coherency protocols&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="ARM"/><category term="AMBA"/><category term="AXI"/><category term="AXI4"/><category term="Bus Protocols"/><category term="Verification"/></entry><entry><title>An always-on tmux setup</title><link href="http://lizard-spock.co.uk/tmux-always-on-setup.html" rel="alternate"/><published>2026-08-02T00:00:00+01:00</published><updated>2026-08-02T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-08-02:/tmux-always-on-setup.html</id><summary type="html">&lt;p&gt;Five changes that make tmux disappear into the background: shell-native autostart from bashrc and zprofile that you can still escape from, session persistence with resurrect and continuum, extended key reporting so Shift+Enter survives the tmux layer, a two-row status line with a keybinding hint row, and mouse selection that copies to the system clipboard on X11.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Unix/tmux/tmux-hero-HQ.png"&gt;&lt;img alt="An always-on tmux setup" src="http://lizard-spock.co.uk/images/Unix/tmux/tmux-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you have not met it yet, tmux is a terminal multiplexer. It lets one
terminal window hold many sessions, split into panes, and it keeps those
sessions alive after you disconnect. I wrote up the day-to-day mechanics
previously in the
&lt;a href="http://lizard-spock.co.uk/tmux-training-manual-ghostty-xterm-and-pve-lxc-terminals.html"&gt;tmux Training Manual&lt;/a&gt;, which is
the better starting point if you are learning the keybindings.&lt;/p&gt;
&lt;p&gt;This post is about what came after that. Once tmux is part of your daily
routine, a handful of small annoyances start to grate. You forget to start it
and lose a terminal full of work. A reboot wipes out a carefully arranged set
of windows and panes. Shift+Enter mysteriously stops working in Claude Code
and Codex the moment you are inside tmux. Selecting text with the mouse stops
putting anything on the clipboard. And you spend the first month looking up the
same four keybindings over and over.&lt;/p&gt;
&lt;p&gt;Each of those has a fix, and each fix is only a few lines of config. This post
walks through all of them as they now sit in my
&lt;a href="https://github.com/morganp/dotfiles"&gt;dotfiles repository&lt;/a&gt;. Individually they
are small. Together they turn tmux from something you remember to start into
something that is simply always there.&lt;/p&gt;
&lt;p&gt;The five changes are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Problem it solves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shell-native autostart&lt;/td&gt;
&lt;td&gt;Forgetting to run &lt;code&gt;tmux&lt;/code&gt;, then losing work when the terminal closes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tmux-resurrect and tmux-continuum&lt;/td&gt;
&lt;td&gt;Losing the window and pane layout across a restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extended key reporting&lt;/td&gt;
&lt;td&gt;Shift+Enter being byte-identical to Enter inside tmux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-row status line&lt;/td&gt;
&lt;td&gt;Not remembering keybindings, and losing the host label under a TUI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mouse selection through a copy command&lt;/td&gt;
&lt;td&gt;Selections not reaching the system clipboard, or arriving as escape-sequence garbage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything below lives in the tmux config and the shell profile. On a standard
Linux or macOS setup those are &lt;code&gt;~/.tmux.conf&lt;/code&gt; (or &lt;code&gt;~/.config/tmux/tmux.conf&lt;/code&gt;)
and &lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt;. If you are following along without my dotfiles,
use the paths in the left column below and ignore the rest of this section.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Standard location&lt;/th&gt;
&lt;th&gt;Tracked file in my dotfiles&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.tmux.conf&lt;/code&gt; or &lt;code&gt;~/.config/tmux/tmux.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/tmux/tmux.conf"&gt;&lt;code&gt;config/tmux/tmux.conf&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.zprofile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zprofile"&gt;&lt;code&gt;config/shell/dot-zprofile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.bashrc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-bashrc"&gt;&lt;code&gt;config/shell/dot-bashrc&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two different mechanisms put those in place, and neither is a symlink. tmux
respects &lt;code&gt;XDG_CONFIG_HOME&lt;/code&gt;, and &lt;code&gt;config/shell/dot-profile&lt;/code&gt; sets it to point
straight at the repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;XDG_CONFIG_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/dotfiles/config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So tmux reads &lt;code&gt;~/dotfiles/config/tmux/tmux.conf&lt;/code&gt; directly, with no linking step
at all. The shell files are sourced rather than linked, which keeps
machine-local settings out of the repository. &lt;code&gt;~/.zprofile&lt;/code&gt; on each machine
starts with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/dotfiles/config/shell/dot-zprofile&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;dot-&lt;/code&gt; prefix is a GNU Stow convention, used elsewhere in the repository
for configs that genuinely are linked. The wider layout is covered in
&lt;a href="http://lizard-spock.co.uk/dotfiles-setup-stow-git.html"&gt;Managing Dotfiles with Stow and Git&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Autostart from the shell profile&lt;/h2&gt;
&lt;p&gt;The usual advice is to configure autostart in the terminal emulator. That ties
the behaviour to one application, so it does not follow you over SSH, and it
does not survive changing terminals. Doing it in the shell profile is portable
across every terminal on every machine that gets the dotfiles.&lt;/p&gt;
&lt;p&gt;This goes in &lt;code&gt;~/.zprofile&lt;/code&gt;, which in my case is
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zprofile"&gt;&lt;code&gt;config/shell/dot-zprofile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Start or attach to the shared tmux session for interactive terminals. TMUX is&lt;/span&gt;
&lt;span class="c1"&gt;# set inside tmux, which prevents new panes from creating nested sessions.&lt;/span&gt;
&lt;span class="c1"&gt;# Set NO_TMUX=1 to get a plain shell, e.g. ghostty -e &amp;quot;env NO_TMUX=1 zsh -l&amp;quot;.&lt;/span&gt;
&lt;span class="c1"&gt;# Deliberately not exec: on detach the login shell survives instead of the&lt;/span&gt;
&lt;span class="c1"&gt;# terminal window closing.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;interactive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMUX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NO_TMUX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$+commands&lt;span class="o"&gt;[&lt;/span&gt;tmux&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new-session&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;main
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Bash equivalent goes in &lt;code&gt;~/.bashrc&lt;/code&gt;, mine at
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-bashrc"&gt;&lt;code&gt;config/shell/dot-bashrc&lt;/code&gt;&lt;/a&gt;.
Same logic, portable syntax:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*i*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMUX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NO_TMUX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new-session&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;main
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Four guards matter here, and every one of them prevents a real failure mode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-z "${TMUX:-}"&lt;/code&gt;&lt;/strong&gt; is the important one. tmux sets &lt;code&gt;TMUX&lt;/code&gt; inside every pane
it owns. Without this test, each new pane runs the profile, which starts
another tmux, which runs the profile again. That is an infinite nesting loop
that locks up the terminal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-t 1&lt;/code&gt;&lt;/strong&gt; checks that standard output is a terminal. Non-interactive uses that
still source the profile, such as &lt;code&gt;ssh host command&lt;/code&gt; or an editor spawning a
login shell to read the environment, must not be hijacked into tmux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;command -v tmux&lt;/code&gt;&lt;/strong&gt; keeps the profile portable to machines where tmux is not
installed. A missing binary should not break shell startup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;-z "${NO_TMUX:-}"&lt;/code&gt;&lt;/strong&gt; is the escape hatch. More on it below.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;new-session -A -s main&lt;/code&gt; is the whole trick: attach to the session named &lt;code&gt;main&lt;/code&gt;
if it exists, create it if it does not. One flag, no &lt;code&gt;has-session&lt;/code&gt; test and
conditional branch.&lt;/p&gt;
&lt;p&gt;The Zsh version goes in &lt;code&gt;~/.zprofile&lt;/code&gt;, not &lt;code&gt;~/.zshrc&lt;/code&gt;. &lt;code&gt;zprofile&lt;/code&gt; is sourced
once for a login shell. &lt;code&gt;zshrc&lt;/code&gt; is sourced for every interactive shell, which
would be more places than necessary to run this.&lt;/p&gt;
&lt;h3&gt;Leaving a way out&lt;/h3&gt;
&lt;p&gt;Two details in that block are deliberate, and both exist so you can still get
to a shell that is not inside tmux.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No &lt;code&gt;exec&lt;/code&gt;.&lt;/strong&gt; The common advice is &lt;code&gt;exec tmux new-session -A -s main&lt;/code&gt;, which
replaces the login shell with the tmux client rather than leaving a parent
shell doing nothing. It also removes the thing you detach &lt;em&gt;to&lt;/em&gt;: with &lt;code&gt;exec&lt;/code&gt;
there is no process behind tmux, so &lt;code&gt;prefix + d&lt;/code&gt; has nothing to return to and
the terminal window closes. Detach and quit become the same action. Running
tmux without &lt;code&gt;exec&lt;/code&gt; keeps the login shell alive underneath, so detaching lands
you back at a prompt. The cost is one idle shell per terminal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;NO_TMUX&lt;/code&gt;.&lt;/strong&gt; Set it and the profile skips the autostart entirely:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ghostty&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;env NO_TMUX=1 zsh -l&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both matter for the same reason: when tmux is the thing that is broken, every
terminal you open runs straight back into it. Without a shell to detach to and
a way to start without tmux at all, there is nowhere to stand while you inspect
or kill the server.&lt;/p&gt;
&lt;h3&gt;Login shells inside tmux&lt;/h3&gt;
&lt;p&gt;New tmux panes start as &lt;em&gt;login&lt;/em&gt; Bash shells, showing up as &lt;code&gt;-bash&lt;/code&gt; in the
process list. Login Bash reads &lt;code&gt;~/.bash_profile&lt;/code&gt; and does not read &lt;code&gt;~/.bashrc&lt;/code&gt;
at all, so if the autostart block and everything else lives in &lt;code&gt;~/.bashrc&lt;/code&gt;,
panes come up with none of it. The shim in &lt;code&gt;~/.bash_profile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.bashrc&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.bashrc&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On machines where tmux arrives through
&lt;a href="https://modules.readthedocs.io/"&gt;environment modules&lt;/a&gt; rather than being on
&lt;code&gt;PATH&lt;/code&gt; at login, the module has to be loaded before the &lt;code&gt;command -v&lt;/code&gt; test can
succeed. My Bash version does that inside the guard:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*i*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMUX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NO_TMUX&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;_dotfiles_module_load&lt;span class="w"&gt; &lt;/span&gt;util&lt;span class="w"&gt; &lt;/span&gt;gnu/tmux&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new-session&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;main
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;_dotfiles_module_load&lt;/code&gt; is a small wrapper of mine that is a no-op when no
module system is present, so the same file still works on machines without one.
The output redirection matters: module tools are chatty, and anything they
print during shell startup ends up in scp and rsync sessions, which breaks
them.&lt;/p&gt;
&lt;h2&gt;Session persistence with resurrect and continuum&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/tmux-plugins/tmux-resurrect"&gt;tmux-resurrect&lt;/a&gt; saves and
restores the tmux environment. &lt;a href="https://github.com/tmux-plugins/tmux-continuum"&gt;tmux-continuum&lt;/a&gt;
drives resurrect automatically on a timer and restores on server start. They
are designed to be used together.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;persistence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;across&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;restarts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;resurrect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;saves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;restores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Saves&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s working directory.&lt;/span&gt;
&lt;span class="s1"&gt;# continuum: autosaves every 15 min and auto-restores when the server starts.&lt;/span&gt;
&lt;span class="s1"&gt;set -g @resurrect-capture-pane-contents &amp;#39;&lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;set -g @resurrect-dir &amp;quot;$HOME/.tmux/resurrect&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;set -g @continuum-restore &amp;#39;&lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;set -g @continuum-save-interval &amp;#39;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What actually gets restored: the window and pane layout, window names, the
active window and pane, and each pane's working directory.
&lt;code&gt;@resurrect-capture-pane-contents 'on'&lt;/code&gt; adds the visible scrollback of each
pane, so a restored session looks like the one you left rather than a grid of
empty prompts.&lt;/p&gt;
&lt;p&gt;Running programs are not restored by default, and that is the right default.
Resurrect has an opt-in process whitelist, but blindly re-launching whatever
was running is a good way to have a reboot re-run something you did not want
re-run.&lt;/p&gt;
&lt;p&gt;Saved state lands in &lt;code&gt;~/.tmux/resurrect&lt;/code&gt; as timestamped text files, with a
&lt;code&gt;last&lt;/code&gt; symlink to the most recent:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;tmux_resurrect_20260802T094413&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;span class="n"&gt;tmux_resurrect_20260802T103006&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;span class="n"&gt;pane_contents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="kr"&gt;last&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmux_resurrect_20260802T103006&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;They are plain text. If a restore goes wrong you can read the file and see
exactly what it thought your layout was.&lt;/p&gt;
&lt;p&gt;The manual keys still work alongside the autosave: &lt;code&gt;prefix + Ctrl-s&lt;/code&gt; saves,
&lt;code&gt;prefix + Ctrl-r&lt;/code&gt; restores. Worth using the manual save before a deliberate
reboot rather than waiting up to 15 minutes for the timer.&lt;/p&gt;
&lt;h3&gt;The ordering gotcha&lt;/h3&gt;
&lt;p&gt;This one cost real debugging time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# Keep these at the very bottom. continuum appends itself to status-right, so
# it must run after the status-right set above or autosave is silently lost.
run-shell ~/.tmux/plugins/tmux-resurrect/resurrect.tmux
run-shell ~/.tmux/plugins/tmux-continuum/continuum.tmux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Continuum implements its timer by appending a hook to &lt;code&gt;status-right&lt;/code&gt;. tmux
evaluates the status line on an interval, so continuum piggybacks on that as
its clock. If your own &lt;code&gt;set -g status-right&lt;/code&gt; runs &lt;em&gt;after&lt;/em&gt; continuum loads, it
overwrites continuum's addition and the autosave stops.&lt;/p&gt;
&lt;p&gt;Nothing warns you. Manual saves keep working, so the setup looks healthy right
up until a crash and the newest saved state is hours old. Keep the &lt;code&gt;run-shell&lt;/code&gt;
lines at the very bottom of the file, after every &lt;code&gt;status-right&lt;/code&gt; assignment.&lt;/p&gt;
&lt;p&gt;These are loaded with direct &lt;code&gt;run-shell&lt;/code&gt; calls rather than through the tmux
plugin manager, so the plugins need cloning by hand:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/tmux-plugins/tmux-resurrect&lt;span class="w"&gt; &lt;/span&gt;~/.tmux/plugins/tmux-resurrect
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/tmux-plugins/tmux-continuum&lt;span class="w"&gt; &lt;/span&gt;~/.tmux/plugins/tmux-continuum
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Letting Shift+Enter through&lt;/h2&gt;
&lt;p&gt;In a plain terminal, Enter and Shift+Enter send the same byte: carriage return,
&lt;code&gt;0x0d&lt;/code&gt;. The Shift modifier has nowhere to go in the traditional encoding. This
is not a tmux problem to begin with, but tmux makes it one, because tmux must
choose whether to pass the modern encoding through.&lt;/p&gt;
&lt;p&gt;The modern encoding is CSI u, sometimes called the Kitty keyboard protocol or
extended key reporting. It gives modifiers a place to live in the escape
sequence, so &lt;code&gt;Shift+Enter&lt;/code&gt; becomes distinguishable from &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This matters because terminal AI tools bind Shift+Enter to "insert a newline
without submitting". Claude Code and Codex both do. Inside tmux without the
setting below, that binding is dead: every Shift+Enter submits the prompt
mid-thought.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; Pass extended key reporting (CSI u) through to applications. Without this,
&lt;span class="gh"&gt;#&lt;/span&gt; Shift+Enter is byte-identical to Enter inside tmux, so TUIs like Codex and
&lt;span class="gh"&gt;#&lt;/span&gt; Claude Code cannot bind it to &amp;quot;insert newline&amp;quot;.
set -s extended-keys on
set -as terminal-features &amp;#39;xterm*:extkeys&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Two lines, two different jobs:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set -s extended-keys on&lt;/code&gt; is a server option that tells tmux to forward
extended keys to the application when the application asks for them. The
alternative value &lt;code&gt;always&lt;/code&gt; sends them unconditionally, which is more aggressive
and can confuse applications that never opted in. &lt;code&gt;on&lt;/code&gt; is the safer setting.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;set -as terminal-features 'xterm*:extkeys'&lt;/code&gt; tells tmux that the &lt;em&gt;outer&lt;/em&gt;
terminal is capable of the protocol. Without this, tmux has no reason to
believe the sequences will survive on the way out, so it will not emit them.
Note the &lt;code&gt;-a&lt;/code&gt; flag: it appends to the existing feature list rather than
replacing it.&lt;/p&gt;
&lt;p&gt;Both halves are required. The chain is terminal, then tmux, then application,
and any link that does not speak the protocol breaks it. Your outer terminal
also needs to support extended keys. Ghostty, Kitty, WezTerm and recent iTerm2
do. Extended keys need tmux 3.2 or newer.&lt;/p&gt;
&lt;h2&gt;The status line and a hint row&lt;/h2&gt;
&lt;p&gt;Next is the status line. Two things were wrong with the default. The
host label from the Starship prompt disappears the moment a full-screen TUI
takes over the pane, so on a multi-machine SSH setup you lose track of where
you are. And tmux keybindings are hard to remember while you are still learning
them.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bg=#1e1e2e,fg=#a6adc8&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;left&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#[fg=#89b4fa,bold] #S #[default]&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;right&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#[fg=#89b4fa,bold]󰂋 #{?#{E:DOTFILES_HOST},#{E:DOTFILES_HOST},#h}#[fg=#6c7086]  •  #[fg=#f9e2af]%H:%M &amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#[fg=#6c7086] #I:#W &amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;window&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#[fg=#cba6f7,bold] #I:#W &amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status-format[1]&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#[align=centre,fg=#7f849c]Prefix: Ctrl-b • c: create • ,: rename • n/p: next/previous • d: detach&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;set -g status 2&lt;/code&gt; is the interesting one. Most people know &lt;code&gt;status on&lt;/code&gt; and
&lt;code&gt;status off&lt;/code&gt;, but the option also takes a row count up to 5. With two rows,
&lt;code&gt;status-format[0]&lt;/code&gt; is the normal status line and &lt;code&gt;status-format[1]&lt;/code&gt; is a second
row you can fill with anything. Here it holds a centred cheat sheet of the
keybindings you need while learning tmux. It is a comment in the config that
you actually read, and deleting it later is a one-line change.&lt;/p&gt;
&lt;p&gt;Keep the hint row short. Long labels and wide separators wrap on a narrow pane,
and a row that wraps stops being readable at a glance. Window numbers are
already on the row above, so the row does not need to explain &lt;code&gt;0-9&lt;/code&gt; either.&lt;/p&gt;
&lt;p&gt;The host label uses a conditional format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt;{?#{E:DOTFILES_HOST},#{E:DOTFILES_HOST},#h}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;#{?condition,true,false}&lt;/code&gt; is the tmux ternary. &lt;code&gt;#{E:VAR}&lt;/code&gt; expands an
environment variable from the session environment. So this reads: if
&lt;code&gt;DOTFILES_HOST&lt;/code&gt; is set, show it, otherwise fall back to &lt;code&gt;#h&lt;/code&gt;, tmux's own short
hostname. The fallback means the config still works on a machine that has tmux
but not the rest of the dotfiles.&lt;/p&gt;
&lt;h3&gt;Where DOTFILES_HOST comes from&lt;/h3&gt;
&lt;p&gt;The variable is set in the shell, not in tmux, which is the point: one
definition feeds both the Starship prompt and the tmux status line, so they can
never disagree about which machine you are on.&lt;/p&gt;
&lt;p&gt;In
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zprompt"&gt;&lt;code&gt;config/shell/dot-zprompt&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;_dotfiles_set_host_label&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;fqdn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;fqdn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;hostname&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;hostname&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%s&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;fqdn&lt;/span&gt;&lt;span class="p"&gt;%%.*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;login*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$fqdn&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*.*.*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;fqdn&lt;/span&gt;&lt;span class="p"&gt;#*.&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;%%.*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;typeset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DOTFILES_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
_dotfiles_set_host_label
&lt;span class="nb"&gt;unset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;_dotfiles_set_host_label
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;DOTFILES_HOST
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Bash version in &lt;code&gt;dot-bashrc&lt;/code&gt; is the same logic with &lt;code&gt;export&lt;/code&gt; in place of
&lt;code&gt;typeset -g&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Normally this is just the short hostname: take the fully qualified name and cut
everything from the first dot. The interesting part is the &lt;code&gt;login*&lt;/code&gt; branch.
Compute clusters tend to name their entry points &lt;code&gt;login1&lt;/code&gt;, &lt;code&gt;login2&lt;/code&gt;, &lt;code&gt;login3&lt;/code&gt;,
which tells you nothing about which cluster you have landed on. When the short
name starts with &lt;code&gt;login&lt;/code&gt; and the FQDN has at least two dots, the label becomes
the next domain component instead. So &lt;code&gt;login2.helios.example.com&lt;/code&gt; displays as
&lt;code&gt;helios&lt;/code&gt;, which is the piece of information you actually wanted.&lt;/p&gt;
&lt;p&gt;The function runs once per shell and is then unset, so it costs nothing on
redraw. &lt;code&gt;export&lt;/code&gt; is what matters for tmux: only exported variables reach the
tmux session environment where &lt;code&gt;#{E:DOTFILES_HOST}&lt;/code&gt; can read them.&lt;/p&gt;
&lt;p&gt;Starship picks up the same variable through its &lt;code&gt;env_var&lt;/code&gt; module in
&lt;code&gt;config/starship/starship.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[env_var.DOTFILES_HOST]&lt;/span&gt;
&lt;span class="c1"&gt;# Shared with tmux; cluster login hosts are normalized by dot-zprompt.&lt;/span&gt;
&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DOTFILES_HOST&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[󰂋 ]($style)[$env_value]($style) &amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#89b4fa bold&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The same Nerd Font glyph and the same blue appear in the tmux &lt;code&gt;status-right&lt;/code&gt;
above, so the label looks identical whether it is being drawn by the prompt or
by tmux. When a TUI takes over the pane and the prompt scrolls away, the status
line keeps showing it.&lt;/p&gt;
&lt;h3&gt;Keeping the label fresh&lt;/h3&gt;
&lt;p&gt;Session environment variables are captured when the session is created, so a
long-lived session can hold a stale value. This line fixes that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Refresh the label when attaching from a new client. The explicit array index&lt;/span&gt;
&lt;span class="c1"&gt;# makes repeated config reloads idempotent.&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;update-environment[99]&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DOTFILES_HOST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;update-environment&lt;/code&gt; is a list of variables tmux refreshes from the attaching
client. Setting an explicit index rather than using &lt;code&gt;-a&lt;/code&gt; to append means
reloading the config repeatedly overwrites slot 99 each time instead of piling
up duplicate entries.&lt;/p&gt;
&lt;p&gt;The rest is cosmetic and matches the Catppuccin Mocha palette used elsewhere in
the setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;set -g pane-border-style &amp;#39;fg=#313244&amp;#39;
set -g pane-active-border-style &amp;#39;fg=#89b4fa&amp;#39;
set -g message-style &amp;#39;bg=#313244,fg=#cdd6f4&amp;#39;
set -g mode-style &amp;#39;bg=#cba6f7,fg=#1e1e2e,bold&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Mouse scroll and the clipboard&lt;/h2&gt;
&lt;p&gt;The last piece sits at the top of the config: the block that decides what the
mouse does.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wheel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;scroll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tmux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pane&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;history&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Shift&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nv"&gt;drag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;native&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;terminal&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;selection&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;On&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;RHEL&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;X11&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pipe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tmux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;selections&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;xclip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;instead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;emitting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;OSC52&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;clipboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;escapes&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;up&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;garbage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;some&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;terminal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;paths&lt;/span&gt;.
&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mouse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;
&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;clipboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;off&lt;/span&gt;
&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xclip -selection clipboard -in&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mouse&lt;/span&gt;
&lt;span class="nv"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;vi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MouseDragEnd1Pane&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;pipe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;clear&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;

#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt;
&lt;span class="nv"&gt;setw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;mouse on&lt;/code&gt; is not only about clicking to select panes. Without it, wheel and
trackpad scroll is translated into arrow keys, and a TUI reading arrow keys as
prompt history navigation will scroll your prompt history instead of the pane
scrollback. That is a confusing bug until you know the cause.&lt;/p&gt;
&lt;p&gt;Turning it on, though, means tmux now owns the mouse, and that is where the
copy and paste trouble starts. Three separate problems, three lines.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The selection vanishes the moment you release the button.&lt;/strong&gt; tmux's default
&lt;code&gt;MouseDragEnd1Pane&lt;/code&gt; binding is &lt;code&gt;copy-selection-and-cancel&lt;/code&gt;, which copies and
then immediately exits copy mode and clears the highlight. You get no visual
confirmation that anything was selected at all. Rebinding to a &lt;code&gt;-no-clear&lt;/code&gt;
variant keeps the highlight up after the drag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;vi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MouseDragEnd1Pane&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;pipe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;clear&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The copy does not reach the system clipboard.&lt;/strong&gt; By default tmux hands the
selection to the terminal using OSC 52, an escape sequence asking the terminal
emulator to set the clipboard on tmux's behalf. When it works it is excellent,
because it survives SSH. When something in the chain does not interpret it, the
bytes land in the terminal as visible garbage and the clipboard stays empty.
That is what happens on RHEL over X11.&lt;/p&gt;
&lt;p&gt;Stop tmux emitting the escape sequence and give it a real command to pipe
through instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;set -g set-clipboard off
set -s copy-command &amp;#39;xclip -selection clipboard -in&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;copy-command&lt;/code&gt; is what the &lt;code&gt;-C&lt;/code&gt; flag on &lt;code&gt;copy-pipe-no-clear&lt;/code&gt; invokes, so the
binding, the pipe target and the disabled OSC 52 all work as one unit. On X11
that means &lt;a href="https://github.com/astrand/xclip"&gt;xclip&lt;/a&gt;. Wayland wants
&lt;code&gt;wl-copy --type text/plain&lt;/code&gt;, and macOS wants &lt;code&gt;pbcopy&lt;/code&gt;, so this line is the one
to change per machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes you want the terminal's own selection, not tmux's.&lt;/strong&gt; Rectangular
selection across panes, or a drag that a remote tmux should not intercept. Most
terminals give that to you with Shift held down while dragging, which bypasses
mouse reporting entirely. For everything else there is a toggle:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bind M set -g mouse
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;prefix + M&lt;/code&gt; flips mouse mode off and on. Off, the terminal gets the mouse back
completely and normal drag-to-select works as if tmux were not there.&lt;/p&gt;
&lt;h2&gt;Gotchas worth repeating&lt;/h2&gt;
&lt;p&gt;The five things most likely to bite:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Nested sessions.&lt;/strong&gt; The &lt;code&gt;TMUX&lt;/code&gt; guard in the shell profile is not optional.
   Without it, opening a pane starts a new tmux inside the pane, forever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No &lt;code&gt;exec&lt;/code&gt;, and an escape hatch.&lt;/strong&gt; With &lt;code&gt;exec&lt;/code&gt;, detaching closes the
   terminal instead of returning to a shell, so there is no way to reach a
   prompt outside tmux when tmux itself is what needs debugging. The bare call
   plus the &lt;code&gt;NO_TMUX&lt;/code&gt; guard are what keep that possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuum's ordering.&lt;/strong&gt; &lt;code&gt;run-shell&lt;/code&gt; for continuum must come after every
   &lt;code&gt;status-right&lt;/code&gt; assignment, or the autosave silently stops while manual saves
   keep working.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extended keys need both lines.&lt;/strong&gt; &lt;code&gt;extended-keys on&lt;/code&gt; alone does nothing if
   tmux does not also believe the outer terminal supports the protocol.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Login Bash in panes.&lt;/strong&gt; tmux panes are login shells, so on Bash the
   autostart block needs &lt;code&gt;~/.bash_profile&lt;/code&gt; to source &lt;code&gt;~/.bashrc&lt;/code&gt; or none of it
   runs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Reload after editing with &lt;code&gt;prefix + r&lt;/code&gt;, which is bound in the config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bind r source-file $XDG_CONFIG_HOME/tmux/tmux.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that reloading does not undo settings, it only applies new ones. When
debugging status line problems, kill the tmux server with &lt;code&gt;tmux kill-server&lt;/code&gt;
and start fresh rather than trusting a reload.&lt;/p&gt;
&lt;p&gt;Tested on tmux 3.7b. The full configuration is
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/tmux/tmux.conf"&gt;&lt;code&gt;config/tmux/tmux.conf&lt;/code&gt;&lt;/a&gt;
in the &lt;a href="https://github.com/morganp/dotfiles"&gt;dotfiles repository&lt;/a&gt;.&lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="tmux"/><category term="shell"/><category term="dotfiles"/><category term="zsh"/><category term="bash"/><category term="terminal"/></entry><entry><title>Zsh startup analysis and modern setup</title><link href="http://lizard-spock.co.uk/zsh-startup-analysis-and-modern-setup.html" rel="alternate"/><published>2026-07-31T00:00:00+01:00</published><updated>2026-07-31T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-31:/zsh-startup-analysis-and-modern-setup.html</id><summary type="html">&lt;p&gt;Profiling Zsh startup, then rebuilding the setup around Oh My Zsh, the Git plugin, autosuggestions, syntax highlighting, and Spaceship Prompt. Covers where the milliseconds go, how completion caching changes under a framework, and every tracked file needed to reproduce it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Unix/zsh/zsh-hero-HQ.png"&gt;&lt;img alt="Zsh startup analysis and modern setup" src="http://lizard-spock.co.uk/images/Unix/zsh/zsh-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This post documents the current Zsh setup and the measurements that shaped it.
It covers startup performance, completion behavior, plugins, and prompt design.
The intended audience is anyone maintaining or reproducing this dotfiles setup.&lt;/p&gt;
&lt;p&gt;The complete configuration is available in the
&lt;a href="https://github.com/morganp/dotfiles"&gt;dotfiles repository&lt;/a&gt;. The current setup
uses Oh My Zsh, the Git plugin, command autosuggestions, syntax highlighting,
and Spaceship Prompt.&lt;/p&gt;
&lt;h2&gt;Original startup performance&lt;/h2&gt;
&lt;p&gt;The baseline identifies which startup costs existed before the framework and
plugins. The investigation started with a small Zsh configuration.
A configured interactive login shell started in 103.60 ms at the median.
The macOS global configuration required 27.88 ms, leaving 75.72 ms for user
configuration.&lt;/p&gt;
&lt;p&gt;The final benchmark used 5 warm-up launches and 60 measured launches. A Python
harness used &lt;code&gt;time.perf_counter()&lt;/code&gt; around &lt;code&gt;/bin/zsh&lt;/code&gt; subprocesses. The harness
redirected standard output and standard error to &lt;code&gt;/dev/null&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Median&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Mean&lt;/th&gt;
&lt;th style="text-align: right;"&gt;95th percentile&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Minimum&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Maximum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Configured interactive login&lt;/td&gt;
&lt;td style="text-align: right;"&gt;103.60 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;101.75 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;113.95 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;83.62 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;116.52 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS global files only&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27.88 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;26.29 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29.97 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;19.10 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31.34 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configured login with &lt;code&gt;zprof&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;111.38 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;107.03 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;116.10 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;82.70 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;120.02 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The original &lt;code&gt;zprof&lt;/code&gt; result identified completion initialization as the main
shell-function cost. A warm completion cache required 20.78 ms. Rebuilding the
cache required 246.60 ms.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Warm total&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rebuild total&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rebuild self time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compinit&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;20.78 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;246.60 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;95.20 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compdef&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;Not present&lt;/td&gt;
&lt;td style="text-align: right;"&gt;84.18 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;84.18 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compdump&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;Not present&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52.39 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52.39 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compaudit&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10.97 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14.83 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14.83 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Top-level external commands do not appear in &lt;code&gt;zprof&lt;/code&gt;. Separate measurements
attributed about 27.35 ms to &lt;code&gt;brew shellenv&lt;/code&gt;. Two &lt;code&gt;uname&lt;/code&gt; processes added about
12.34 ms.&lt;/p&gt;
&lt;h2&gt;Changes added to the setup&lt;/h2&gt;
&lt;p&gt;This chapter summarizes each feature added after the baseline measurement. The
setup combines a framework with focused interactive features. The tracked
profile guards optional dependencies, so missing Homebrew packages do not
prevent shell startup.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Installation source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Oh My Zsh&lt;/td&gt;
&lt;td&gt;Framework, completion integration, and plugin loading&lt;/td&gt;
&lt;td&gt;Manual Git clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git plugin&lt;/td&gt;
&lt;td&gt;Git aliases and helper functions&lt;/td&gt;
&lt;td&gt;Bundled with Oh My Zsh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spaceship Prompt&lt;/td&gt;
&lt;td&gt;Structured asynchronous prompt&lt;/td&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zsh Autosuggestions&lt;/td&gt;
&lt;td&gt;History-based command suggestions&lt;/td&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zsh Syntax Highlighting&lt;/td&gt;
&lt;td&gt;Command-line token and validity highlighting&lt;/td&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Victor Mono Nerd Font&lt;/td&gt;
&lt;td&gt;Prompt and status glyphs&lt;/td&gt;
&lt;td&gt;Homebrew cask&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracked &lt;code&gt;dot-zprofile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Portable shared configuration&lt;/td&gt;
&lt;td&gt;Dotfiles repository&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The portable setup lives in the
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zprofile"&gt;tracked Zsh profile&lt;/a&gt;.
Machine-local configuration remains in &lt;code&gt;~/.zprofile&lt;/code&gt;. That local file
sources the tracked profile before applying machine-specific settings.&lt;/p&gt;
&lt;p&gt;The shared profile loads Oh My Zsh first. It then loads
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zshrc"&gt;the existing interactive configuration&lt;/a&gt;.
This order preserves existing aliases because later alias definitions override
Oh My Zsh aliases with the same name.&lt;/p&gt;
&lt;h2&gt;How completion changed&lt;/h2&gt;
&lt;p&gt;This chapter compares the original completion path with the framework-managed
path. Zsh provides the completion engine through &lt;code&gt;compinit&lt;/code&gt;. Oh My Zsh does
not replace that engine. It configures, validates, and caches the same system.&lt;/p&gt;
&lt;h3&gt;Previous completion path&lt;/h3&gt;
&lt;p&gt;The previous configuration called &lt;code&gt;compinit&lt;/code&gt; directly. An optimization ran a
full scan once each day, then used &lt;code&gt;compinit -C&lt;/code&gt; for later shells.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;autoload&lt;span class="w"&gt; &lt;/span&gt;-Uz&lt;span class="w"&gt; &lt;/span&gt;compinit
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="s1"&gt;&amp;#39;%j&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;stat&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Sm&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%j&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.zcompdump&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;compinit
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;compinit&lt;span class="w"&gt; &lt;/span&gt;-C
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This approach used &lt;code&gt;~/.zcompdump&lt;/code&gt; and reduced repeated security audits. It
saved about 12 ms inside &lt;code&gt;compinit&lt;/code&gt;, but &lt;code&gt;date&lt;/code&gt; and &lt;code&gt;stat&lt;/code&gt; reduced the measured
shell saving to about 4 ms.&lt;/p&gt;
&lt;h3&gt;Current Oh My Zsh completion path&lt;/h3&gt;
&lt;p&gt;Oh My Zsh prepares &lt;code&gt;fpath&lt;/code&gt; before running &lt;code&gt;compinit&lt;/code&gt;. It adds framework,
custom, and enabled plugin completion directories. This ordering lets plugin
completion definitions participate in the initial cache.&lt;/p&gt;
&lt;p&gt;Oh My Zsh uses a host-specific and Zsh-version-specific dump file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~/.zcompdump-${SHORT_HOST}-${ZSH_VERSION}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The framework records its Git revision and the complete &lt;code&gt;fpath&lt;/code&gt; inside that
dump. It removes the dump when either value changes. It also compiles the dump
to Zsh Word Code with &lt;code&gt;zrecompile&lt;/code&gt;, which reduces parsing work on later starts.&lt;/p&gt;
&lt;p&gt;Completion security behavior also changes. Oh My Zsh uses &lt;code&gt;compinit -i&lt;/code&gt; and
reports insecure completion directories through its &lt;code&gt;compfix&lt;/code&gt; support. The
current profile leaves this security behavior enabled.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;th&gt;Previous configuration&lt;/th&gt;
&lt;th&gt;Current Oh My Zsh configuration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Completion engine&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;compinit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;compinit&lt;/code&gt; managed by Oh My Zsh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dump name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.zcompdump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Host and Zsh-version-specific dump&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh policy&lt;/td&gt;
&lt;td&gt;Full scan once daily&lt;/td&gt;
&lt;td&gt;Refresh when framework revision or &lt;code&gt;fpath&lt;/code&gt; changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin completions&lt;/td&gt;
&lt;td&gt;Existing &lt;code&gt;fpath&lt;/code&gt; only&lt;/td&gt;
&lt;td&gt;Plugin directories added before &lt;code&gt;compinit&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security check&lt;/td&gt;
&lt;td&gt;Full daily, skipped with &lt;code&gt;-C&lt;/code&gt; afterward&lt;/td&gt;
&lt;td&gt;Checked through &lt;code&gt;compinit&lt;/code&gt; and &lt;code&gt;compfix&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled dump&lt;/td&gt;
&lt;td&gt;Not configured&lt;/td&gt;
&lt;td&gt;Recompiled to Zsh Word Code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The daily optimization remains as a fallback in &lt;code&gt;dot-zshrc&lt;/code&gt;. It runs only when
Oh My Zsh is unavailable. This guard prevents 2 &lt;code&gt;compinit&lt;/code&gt; calls in one shell.&lt;/p&gt;
&lt;p&gt;Pressing Tab twice lists the matches with their descriptions:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Zsh completion menu listing git subcommands with descriptions" src="http://lizard-spock.co.uk/images/Unix/zsh/zsh-completion-menu.png"&gt;&lt;/p&gt;
&lt;p&gt;Oh My Zsh also configures completion menus, caching, process completion, and
Bash completion compatibility. The local matcher style still provides
case-insensitive completion after the framework loads.&lt;/p&gt;
&lt;h2&gt;What the Git plugin provides&lt;/h2&gt;
&lt;p&gt;This chapter separates Git command shortcuts from the Git information shown in
the prompt. The
&lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git"&gt;Oh My Zsh Git plugin&lt;/a&gt;
provides a large alias library and several Git-aware functions. It does not
install Git or change repository data during shell startup.&lt;/p&gt;
&lt;p&gt;The aliases cover common branch, commit, fetch, log, merge, pull, push, rebase,
stash, and worktree operations. The following examples remain available when
they do not conflict with local aliases.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alias or function&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gaa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;git add --all&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gcb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and check out a branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show a one-line decorated graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change to the repository root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gbda&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete merged branches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git_current_branch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Return the current branch name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git_main_branch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Detect the repository's primary branch name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gwip&lt;/code&gt; and &lt;code&gt;gunwip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and remove temporary work-in-progress commits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Local aliases load after the plugin and retain their established behavior. For
example, Oh My Zsh defines &lt;code&gt;gp&lt;/code&gt; as &lt;code&gt;git push&lt;/code&gt;. This setup keeps the existing
&lt;code&gt;gp='git pull'&lt;/code&gt; alias. It also preserves the custom &lt;code&gt;gl&lt;/code&gt; graph format and other
long-standing shortcuts.&lt;/p&gt;
&lt;p&gt;The plugin and Spaceship Git section serve different purposes. The plugin
provides commands and functions. Spaceship displays repository state in the
prompt.&lt;/p&gt;
&lt;h2&gt;Autosuggestions and syntax highlighting&lt;/h2&gt;
&lt;p&gt;This chapter covers the 2 plugins that provide feedback while a command is
being entered.
&lt;a href="https://github.com/zsh-users/zsh-autosuggestions"&gt;Zsh Autosuggestions&lt;/a&gt; displays
faint command suggestions while text is entered. It uses command history by
default, which makes repeated commands available without a history search. The
suggested remainder appears in a dimmed colour and is accepted with the right
arrow key:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Zsh autosuggestion showing dimmed ghost text after a partial command" src="http://lizard-spock.co.uk/images/Unix/zsh/zsh-autosuggestion.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting"&gt;Zsh Syntax Highlighting&lt;/a&gt;
colors command-line tokens before execution. Its main highlighter distinguishes
valid commands, invalid commands, paths, options, strings, and shell syntax.
This feedback can reveal a typing error before the command runs.&lt;/p&gt;
&lt;p&gt;The recording below shows all 3 features in one session. An autosuggestion is
accepted, Tab lists the completion matches, an unknown command turns red, and
the corrected command turns green:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Animated demonstration of autosuggestions, tab completion, and syntax highlighting" src="http://lizard-spock.co.uk/images/Unix/zsh/zsh-completion.gif"&gt;&lt;/p&gt;
&lt;p&gt;Syntax highlighting loads last because it wraps Zsh Line Editor widgets. A
later plugin could replace those wrappers and prevent highlighting from
tracking edits correctly.&lt;/p&gt;
&lt;p&gt;Both plugins come from the
&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/brew/Brewfile"&gt;tracked Brewfile&lt;/a&gt;.
The profile checks each installed path before sourcing it. A machine without
Homebrew therefore starts Zsh without these optional features.&lt;/p&gt;
&lt;h2&gt;How Spaceship changes the prompt&lt;/h2&gt;
&lt;p&gt;This chapter compares the original one-line theme with the configured
Spaceship layout.
&lt;a href="https://spaceship-prompt.sh/"&gt;Spaceship Prompt&lt;/a&gt; replaces the Oh My Zsh
&lt;code&gt;robbyrussell&lt;/code&gt; theme. The configuration disables &lt;code&gt;ZSH_THEME&lt;/code&gt; and sources the
Homebrew Spaceship package after Oh My Zsh loads.&lt;/p&gt;
&lt;p&gt;The prompt uses this order:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;SPACESHIP_PROMPT_ASYNC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="nv"&gt;SPACESHIP_PROMPT_ADD_NEWLINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="nv"&gt;SPACESHIP_CHAR_SYMBOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;⚡&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;SPACESHIP_PROMPT_ORDER&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;user
&lt;span class="w"&gt;  &lt;/span&gt;dir
&lt;span class="w"&gt;  &lt;/span&gt;git
&lt;span class="w"&gt;  &lt;/span&gt;line_sep
&lt;span class="w"&gt;  &lt;/span&gt;char
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each entry controls one prompt section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user&lt;/code&gt; shows the username for Secure Shell sessions, root, or user changes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dir&lt;/code&gt; shows the current directory and truncates long paths.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt; shows the branch and dirty, ahead, behind, or diverged state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;line_sep&lt;/code&gt; places the command entry point on a separate line.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;char&lt;/code&gt; shows a lightning symbol that changes color with command status.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Spaceship also provides a &lt;code&gt;time&lt;/code&gt; section that prints a timestamp. This setup
omits it deliberately. The section is absent from &lt;code&gt;SPACESHIP_PROMPT_ORDER&lt;/code&gt;, so
Spaceship never loads it. Its &lt;code&gt;SPACESHIP_TIME_SHOW&lt;/code&gt; variable also defaults to
&lt;code&gt;false&lt;/code&gt;, which means listing &lt;code&gt;time&lt;/code&gt; in the order alone would render nothing.&lt;/p&gt;
&lt;p&gt;The Git section tracks the working tree. Stashing every change clears the dirty
indicators, and restoring them brings the indicators back:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Spaceship prompt showing the Git section change as the working tree changes" src="http://lizard-spock.co.uk/images/Unix/zsh/zsh-prompt-git-state.png"&gt;&lt;/p&gt;
&lt;p&gt;Asynchronous rendering prevents Git inspection from blocking command entry.
Spaceship renders the prompt immediately, then updates asynchronous sections
when their data becomes available. The Git section uses this behavior by
default.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;robbyrussell&lt;/code&gt; theme uses one line. It shows a status arrow, the final
directory component, the Git branch, and a dirty marker. Spaceship uses 2
lines, a path of up to 3 levels, and detailed Git synchronization state.&lt;/p&gt;
&lt;p&gt;The lightning symbol changes from green to red after a failed command. The
Brewfile includes Victor Mono Nerd Font for branch and status glyphs.&lt;/p&gt;
&lt;h2&gt;Startup cost after adding features&lt;/h2&gt;
&lt;p&gt;The added features increase startup time, but the result remains below 200 ms
at the median. Each row comes from a separate 60-launch benchmark batch, so the
differences are directional rather than a controlled component breakdown.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Median startup&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Change from previous row&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original configuration&lt;/td&gt;
&lt;td style="text-align: right;"&gt;103.60 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily completion optimization&lt;/td&gt;
&lt;td style="text-align: right;"&gt;99.25 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;-4.35 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oh My Zsh and 3 plugins with &lt;code&gt;robbyrussell&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;187.07 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;+87.82 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oh My Zsh, 3 plugins, and Spaceship&lt;/td&gt;
&lt;td style="text-align: right;"&gt;178.53 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;-8.54 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That Spaceship run measured 178.69 ms mean and 186.35 ms at the 95th
percentile. The minimum was 167.73 ms, and the maximum was 189.13 ms.&lt;/p&gt;
&lt;p&gt;The Spaceship row was measured while the &lt;code&gt;time&lt;/code&gt; section was still enabled. The
section was removed afterwards, and the benchmark has not been repeated. The
current prompt should therefore cost slightly less than this row reports.&lt;/p&gt;
&lt;p&gt;Oh My Zsh accounts for most of the increase. Its measured functions include
framework sourcing, completion validation, plugin loading, and cache
compilation. Spaceship renders expensive Git information asynchronously, which
protects interactive responsiveness after the shell becomes available.&lt;/p&gt;
&lt;h2&gt;Reproduce the setup&lt;/h2&gt;
&lt;p&gt;This chapter identifies every tracked file required to reproduce the shared
setup. The repository contains the portable configuration and installation
manifest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/morganp/dotfiles"&gt;Dotfiles repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/README.md"&gt;Installation README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zprofile"&gt;Shared Zsh profile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/shell/dot-zshrc"&gt;Interactive Zsh configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/morganp/dotfiles/blob/main/config/brew/Brewfile"&gt;Homebrew Brewfile&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the Homebrew packages from the repository root.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;sh
   brew bundle install --file="$HOME/dotfiles/config/brew/Brewfile"&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone Oh My Zsh because it has no Homebrew formula.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;sh
   git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "$HOME/.oh-my-zsh"&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a local &lt;code&gt;~/.zprofile&lt;/code&gt; that sources the shared profile.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;zsh
   source "$HOME/dotfiles/config/shell/dot-zprofile"&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Machine-local secrets and paths remain outside the repository.
Application Programming Interface (API) keys live in &lt;code&gt;~/.config/codex/env&lt;/code&gt;,
which is readable only by the owning user. The shared profile sources that
file only when it exists.&lt;/p&gt;
&lt;h2&gt;Verification and remaining improvements&lt;/h2&gt;
&lt;p&gt;This chapter provides repeatable checks and identifies remaining optimization
work. Measure wall-clock startup with a login shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/bin/zsh&lt;span class="w"&gt; &lt;/span&gt;-lic&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;exit 0&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Enable function-level profiling with the existing debug switch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ZSH_DEBUGRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/bin/zsh&lt;span class="w"&gt; &lt;/span&gt;-lic&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;exit 0&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Future work can avoid repeated &lt;code&gt;brew shellenv&lt;/code&gt; calls in inherited environments.
It can also replace external &lt;code&gt;uname&lt;/code&gt; calls with Zsh parameters. De-duplicating
&lt;code&gt;fpath&lt;/code&gt; remains useful if nested shells create repeated Homebrew completion
paths.&lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="zsh"/><category term="shell"/><category term="dotfiles"/><category term="performance"/><category term="oh-my-zsh"/><category term="spaceship"/><category term="homebrew"/></entry><entry><title>Knife Grinds and Their Main Uses</title><link href="http://lizard-spock.co.uk/knife-grinds-and-their-main-uses.html" rel="alternate"/><published>2026-07-23T00:00:00+01:00</published><updated>2026-07-23T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-23:/knife-grinds-and-their-main-uses.html</id><summary type="html">&lt;p&gt;Overview of common knife grind types (scandi, flat, hollow, convex, chisel) and what each is best suited for.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/hero-HQ.png"&gt;&lt;img alt="Fixed-blade bushcraft knife in profile" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Knife blades differ in more than shape and steel. Grind, the cross-section profile ground into edge, drives how blade cuts, how easy sharpen, and how well edge holds up to different tasks.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/overview-HQ.png"&gt;&lt;img alt="Five grind cross-sections compared: flat, scandi, hollow, convex, chisel" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/overview-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Flat Grind&lt;/h2&gt;
&lt;p&gt;Blade tapers straight from spine to edge, no secondary bevel curve. Simple, strong, easy sharpen.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-flat-HQ.png"&gt;&lt;img alt="Flat grind cross-section" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-flat-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main uses&lt;/strong&gt;: general-purpose knives, kitchen knives, everyday carry.&lt;/p&gt;
&lt;h2&gt;Scandi (Scandinavian) Grind&lt;/h2&gt;
&lt;p&gt;Single bevel runs from partway down blade straight to edge, no secondary edge bevel. Very easy sharpen on flat stone, good control for wood work.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-scandi-HQ.png"&gt;&lt;img alt="Scandi grind cross-section" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-scandi-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main uses&lt;/strong&gt;: bushcraft, whittling, carving, favoured for scouting/camp knives.&lt;/p&gt;
&lt;h2&gt;Hollow Grind&lt;/h2&gt;
&lt;p&gt;Concave curve ground into blade sides, edge thin and sharp. Sharpens to very fine edge, but weaker and edge rolls easier under hard use.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-hollow-HQ.png"&gt;&lt;img alt="Hollow grind cross-section" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-hollow-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main uses&lt;/strong&gt;: straight razors, fillet knives, precision cutting tasks.&lt;/p&gt;
&lt;h2&gt;Convex Grind&lt;/h2&gt;
&lt;p&gt;Edge curves outward, opposite of hollow. Strong edge, good for chopping, holds up well to impact.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-convex-HQ.png"&gt;&lt;img alt="Convex grind cross-section" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-convex-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main uses&lt;/strong&gt;: axes, machetes, heavy-use survival knives.&lt;/p&gt;
&lt;h2&gt;Chisel Grind&lt;/h2&gt;
&lt;p&gt;Only one side ground, other stays flat. Very sharp, but asymmetric cut pulls to one side.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-chisel-HQ.png"&gt;&lt;img alt="Chisel grind cross-section" src="http://lizard-spock.co.uk/images/Outdoor/KnifeGrinds/grind-chisel-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main uses&lt;/strong&gt;: some Japanese kitchen knives, specialist woodworking tools.&lt;/p&gt;
&lt;h2&gt;Choosing a Grind&lt;/h2&gt;
&lt;p&gt;For scouting and cub activities, scandi grind usually best starting point. Easy sharpen in field, good control for carving, safe predictable cut.&lt;/p&gt;</content><category term="Outdoor"/><category term="knives"/><category term="bushcraft"/><category term="scouting"/><category term="tools"/></entry><entry><title>SoC Article 11: HW/SW co-design, bridging software and silicon</title><link href="http://lizard-spock.co.uk/soc-article-11-hw-sw-co-design.html" rel="alternate"/><published>2026-07-06T00:00:00+01:00</published><updated>2026-07-06T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-06:/soc-article-11-hw-sw-co-design.html</id><summary type="html">&lt;p&gt;How hardware and software are designed together on a SoC: the boot process from reset to running OS, device drivers, the HW/SW partitioning decision, virtual platforms, and the RTOS vs Linux choice.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 11 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/hw-sw-co-design-HQ.png"&gt;&lt;img alt="HW/SW Co-Design header illustration" src="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/hw-sw-co-design-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Throughout this series, we have focused primarily on the hardware side of SoC design: the processors, memory, buses, and physical implementation. But a SoC without software is inert. The firmware that boots it, the operating system that manages its resources, the drivers that talk to its peripherals, and the applications that use it are just as important as the hardware itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HW/SW Co-Design&lt;/strong&gt; is the discipline of designing hardware and software together, recognising that they are inseparable aspects of the same system. This article brings together the software side of the SoC story, covering the boot process, device drivers, the OS interface, and the co-design trade-offs that shape every product decision.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The HW/SW interface: where they meet&lt;/h2&gt;
&lt;p&gt;Hardware and software communicate through a small number of well-defined mechanisms:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/hwsw-interface-HQ.png"&gt;&lt;img alt="HW/SW Interface Mechanisms diagram" src="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/hwsw-interface-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Memory-Mapped Registers&lt;/h3&gt;
&lt;p&gt;The primary interface between software and hardware on a SoC is &lt;strong&gt;memory-mapped I/O&lt;/strong&gt;: every hardware block exposes a set of control and status registers (CSRs) at fixed addresses in the physical address space.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;Typical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UART&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x4000&lt;/span&gt;&lt;span class="nx"&gt;_0000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Offset&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Access&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;--------------------------------------------------------&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;THR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;WO&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Transmit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Holding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RBR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RO&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Receive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x04&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;IER&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RW&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Interrupt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;IIR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RO&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Interrupt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Identification&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x0C&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;LCR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RW&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Control&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;MCR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RW&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Modem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Control&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x14&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;LSR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RO&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x18&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;MSR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RO&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Modem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mh"&gt;0x1C&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;SCR&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;RW&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Scratch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Register&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To send a byte over UART, firmware:
1. Read LSR to check that the TX FIFO is not full.
2. Write the byte to THR.
3. Hardware takes over and drives the UART TX pin.&lt;/p&gt;
&lt;p&gt;This read-modify-write register interface is the universal pattern for SoC firmware.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Software Stack&lt;/h2&gt;
&lt;p&gt;The software on a SoC is organised in layers, each depending on the layer below it:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/software-stack-HQ.png"&gt;&lt;img alt="Software stack layers diagram" src="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/software-stack-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Boot Process&lt;/h2&gt;
&lt;p&gt;Bringing a SoC from cold power-up to a running operating system is a multi-stage process, each stage loading and executing the next:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_819c53f62250209c99e94d71fd94ad95.svg"&gt;&lt;/p&gt;
&lt;h3&gt;DDR initialisation: a hardware-SW co-design example&lt;/h3&gt;
&lt;p&gt;One of the most hardware-specific firmware tasks is &lt;strong&gt;DDR initialisation&lt;/strong&gt;. Modern LPDDR5 DRAM requires a training sequence: a complex set of calibration steps that establishes the optimal timing relationships between the SoC and the DRAM:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_1fad7251bc2bc79150338cd35f537451.svg"&gt;&lt;/p&gt;
&lt;p&gt;The FSBL writes a sequence of magic values to the DDR controller and DRAM mode registers, then reads test patterns back to calibrate write and read timing. Only after this sequence completes successfully can the rest of the software stack use DRAM.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Device Drivers&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;device driver&lt;/strong&gt; is the software module responsible for managing a specific hardware peripheral. Drivers abstract the hardware details, presenting a clean, standardised interface to higher software layers.&lt;/p&gt;
&lt;p&gt;In Linux, a character device driver for a simple UART might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Simplified UART platform driver skeleton */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/platform_device.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/serial_core.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/io.h&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;#define UART_THR  0x00&lt;/span&gt;
&lt;span class="cp"&gt;#define UART_LSR  0x14&lt;/span&gt;
&lt;span class="cp"&gt;#define LSR_THRE  BIT(5)   &lt;/span&gt;&lt;span class="cm"&gt;/* TX holding register empty */&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;uart_write_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;uart_port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Wait until TX FIFO has space */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;membase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_LSR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LSR_THRE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;cpu_relax&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Write character to hardware register */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;writel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;membase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_THR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;uart_probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;platform_device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pdev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__iomem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Get MMIO base address from device tree */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;platform_get_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IORESOURCE_MEM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;devm_ioremap_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pdev&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Register with Linux serial framework */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... uart_add_one_port() ... */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;Device Tree&lt;/strong&gt; (DTS) is the mechanism by which Linux discovers what hardware is present without hard-coding it in the kernel:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Device Tree fragment for a UART */&lt;/span&gt;
&lt;span class="nl"&gt;uart0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;serial&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;40000000&lt;/span&gt;&lt;span class="cm"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;compatible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;myvendor,uart-v1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;reg&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0x40000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x100&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="cm"&gt;/* MMIO base, size */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;interrupts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;GIC_SPI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;IRQ_TYPE_LEVEL_HIGH&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;clocks&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="na"&gt;clk_periph&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;clock-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;uartclk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;status&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;okay&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When Linux boots, it reads the Device Tree, matches &lt;code&gt;compatible&lt;/code&gt; strings to registered drivers, and calls the driver's &lt;code&gt;probe()&lt;/code&gt; function for each matching node.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;HW/SW Partitioning&lt;/h2&gt;
&lt;p&gt;The most important architectural decision in co-design is &lt;strong&gt;which functions to implement in hardware and which in software&lt;/strong&gt;. This partitioning determines performance, power, flexibility, and development cost.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/hwsw-partitioning-HQ.png"&gt;&lt;img alt="HW/SW partitioning decision matrix" src="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/hwsw-partitioning-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Real trade-off examples&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;AES Encryption:&lt;/strong&gt;
- Software (Cortex-A, 1 core): ~300 MB/s, ~200 mW
- Hardware accelerator: ~10 GB/s, ~5 mW
- Decision: hardware, because encryption happens on every packet, latency is critical, and the algorithm is stable&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JSON Parsing:&lt;/strong&gt;
- Software (optimised C): ~500 MB/s
- Hardware: would require custom finite-state machine; format evolves
- Decision: software, because flexibility matters more than raw speed&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Decode (H.265):&lt;/strong&gt;
- Software (4 CPU cores): ~720p@30fps, ~2 W
- Hardware codec: ~8K@120fps, ~50 mW
- Decision: always hardware in a mobile SoC&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Co-Simulation and Co-Verification&lt;/h2&gt;
&lt;p&gt;HW/SW co-design requires testing both halves together before silicon exists. There are several approaches:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Virtual platform and virtual prototype&lt;/strong&gt;: a software model of the SoC, typically written in SystemC/Transaction-Level Modelling (TLM), that runs on a host workstation. The firmware binary is compiled for the target ISA and runs on an instruction-set simulator (ISS). This allows firmware development to begin before RTL is complete.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/virtual-platform-HQ.png"&gt;&lt;img alt="Virtual platform architecture diagram" src="http://lizard-spock.co.uk/posts/2026-07-06_SoC_Article_11_HW_SW_Co_Design/virtual-platform-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FPGA prototyping&lt;/strong&gt;: the RTL is synthesised onto one or more large FPGAs, running at 5-50 MHz. Real firmware and software run on the FPGA, providing a cycle-accurate model that can run full Linux. FPGA prototyping boards (Xilinx VCU118, Intel Stratix 10) are essential tools for pre-silicon software development.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware emulation&lt;/strong&gt;: as discussed in Article 10, emulators compile the RTL into custom FPGAs that run at MHz speeds, supporting real-time testing with actual peripherals.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The RTOS vs Linux choice&lt;/h2&gt;
&lt;p&gt;For the CPU running on a SoC, a fundamental software decision is operating system choice:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OS Type&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bare Metal&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Ultra-simple, few ms startup, max performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTOS&lt;/td&gt;
&lt;td&gt;FreeRTOS, Zephyr, ThreadX&lt;/td&gt;
&lt;td&gt;Hard real-time, &amp;lt; 1ms interrupt latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Linux, Android&lt;/td&gt;
&lt;td&gt;Feature-rich, large driver ecosystem, networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hypervisor&lt;/td&gt;
&lt;td&gt;Xen, KVM&lt;/td&gt;
&lt;td&gt;Running multiple OSes simultaneously (automotive, industrial)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Many SoCs run &lt;strong&gt;both&lt;/strong&gt;: a Cortex-M managing power and safety-critical functions runs an RTOS (real-time operating system) or bare metal, while a Cortex-A cluster running Linux or Android handles the application layer. This heterogeneous multi-OS configuration is increasingly common.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;IP Management and Reuse&lt;/h2&gt;
&lt;p&gt;As noted in Article 02, IP reuse is central to managing SoC complexity. Co-design extends this to include &lt;strong&gt;software deliverables&lt;/strong&gt; alongside hardware IP:&lt;/p&gt;
&lt;p&gt;A well-packaged IP block delivers:
- RTL source (encrypted or open)
- Testbench and UVM verification environment
- &lt;strong&gt;Device driver&lt;/strong&gt; (Linux kernel module or RTOS driver)
- &lt;strong&gt;Register description file&lt;/strong&gt; (IP-XACT, SystemRDL) for automatic header/driver generation
- &lt;strong&gt;Reference firmware&lt;/strong&gt; (bare-metal startup, peripheral init code)
- &lt;strong&gt;Documentation&lt;/strong&gt; (Technical Reference Manual, integration guide)&lt;/p&gt;
&lt;p&gt;Increasingly, EDA tools automatically generate C header files and Linux device tree bindings from the hardware register descriptions, reducing the human effort and error risk in the software-hardware interface.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Looking back: the complete picture&lt;/h2&gt;
&lt;p&gt;We have now traversed the entire SoC landscape:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-01-from-room-to-silicon.html"&gt;Article 01: From room to silicon&lt;/a&gt;: integration, motivation, anatomy&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-02-anatomy-and-motivation.html"&gt;Article 02: What is a System on Chip&lt;/a&gt;: anatomy, motivation, IP cores&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-03-design-stack.html"&gt;Article 03: The SoC design stack&lt;/a&gt;: abstraction layers, IP cores, Y-chart&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-04-processor-cores.html"&gt;Article 04: Processor cores&lt;/a&gt;: CPU, DSP, GPU, NPU, big.LITTLE&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-05-memory-architecture.html"&gt;Article 05: Memory architecture&lt;/a&gt;: cache hierarchy, DRAM, MMU&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-06-interconnects-and-bus-protocols.html"&gt;Article 06: Interconnects and bus protocols&lt;/a&gt;: AXI, AHB, APB, crossbar, NoC&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-07-clocking-reset-and-power-domains.html"&gt;Article 07: Clocking, reset, and power domains&lt;/a&gt;: PLL, CDC, DVFS, power gating&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-08-peripherals-and-io.html"&gt;Article 08: Peripherals and I/O&lt;/a&gt;: UART, SPI, I2C, USB, DMA, IRQ&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-09-hardware-description-languages-rtl-design.html"&gt;Article 09: HDL and RTL design&lt;/a&gt;: SystemVerilog, FSM, simulation&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/soc-article-10-design-flow.html"&gt;Article 10: The SoC design flow&lt;/a&gt;: spec to RTL to synthesis to P&amp;amp;R to tape-out&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Where to Go Next&lt;/h2&gt;
&lt;p&gt;With the introductory series complete, you are ready to explore the intermediate topics. These build directly on the foundations laid here:&lt;/p&gt;
&lt;h3&gt;Intermediate series (10 articles)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AXI4 Protocol Deep Dive&lt;/strong&gt;: burst types, out-of-order, QoS, debug&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache Coherency Protocols&lt;/strong&gt;: MESI in hardware, multi-core coherency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipeline Design and Hazards&lt;/strong&gt;: forwarding, stalling, branch prediction basics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTL Synthesis and Timing Closure&lt;/strong&gt;: SDC, STA, ECO flows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clock Domain Crossing Techniques&lt;/strong&gt;: synchronisers, async FIFOs, CDC analysis tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SoC Verification with UVM&lt;/strong&gt;: agents, sequences, scoreboards, coverage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DMA Controller Architecture&lt;/strong&gt;: descriptor chains, scatter-gather, QoS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interrupt Controllers in Depth&lt;/strong&gt;: GIC, NVIC, priority, virtualization extensions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory-Mapped I/O and Linux Device Drivers&lt;/strong&gt;: writing real kernel drivers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SoC Power Management Techniques&lt;/strong&gt;: DVFS governors, CPUIdle, power domains in Linux&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Advanced series (10 articles)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Out-of-Order Execution Architecture&lt;/strong&gt;: Tomasulo, ROB, reservation stations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network-on-Chip Design&lt;/strong&gt;: topology, routing, flow control, virtual channels&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DRAM Subsystem Timing&lt;/strong&gt;: LPDDR5 protocol, refresh, power states, thermal throttle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Physical Design: Floorplanning and P&amp;amp;R&lt;/strong&gt;: CTS, ECO, IR drop, antenna effects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware Security in SoC&lt;/strong&gt;: TrustZone, secure boot, side-channel attacks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI/ML Accelerator Architecture&lt;/strong&gt;: systolic arrays, dataflow, memory bandwidth analysis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formal Verification Methods&lt;/strong&gt;: model checking, SVA, property specification languages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Post-Silicon Debug and Validation&lt;/strong&gt;: JTAG, CoreSight ETM, scan chains&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FPGA-Based SoC Design&lt;/strong&gt;: Zynq-7000, Cyclone V HPS, PetaLinux&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heterogeneous SoC Partitioning&lt;/strong&gt;: HW/SW co-exploration, automated co-design tools&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;HW/SW co-design recognises that hardware and software are two facets of the same system: neither is meaningful without the other. The boot process takes a SoC from silicon power-on to a running application through several carefully orchestrated stages. Device drivers form the critical interface between the operating system and hardware registers. The partitioning of function between hardware and software is the most impactful architectural decision. Virtual platforms, FPGA prototypes, and emulators allow software development to proceed before silicon is available. Mastering co-design is what separates SoC systems engineers from specialists who know only one half of the system.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-10-design-flow.html"&gt;Article 10: The SoC design flow&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="Linux"/><category term="Firmware"/><category term="Boot"/><category term="Device Drivers"/><category term="RTOS"/></entry><entry><title>12-Bar Blues on Guitar with 6th and 5th String Barre Chords</title><link href="http://lizard-spock.co.uk/12-bar-blues-guitar-barre-chords.html" rel="alternate"/><published>2026-07-02T00:00:00+01:00</published><updated>2026-07-02T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-02:/12-bar-blues-guitar-barre-chords.html</id><summary type="html">&lt;p&gt;The 12-bar blues on guitar with just two movable barre shapes. The 1-4-5 geometry of the neck puts all three chords within two frets of each other, in any key.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;previous post&lt;/a&gt; played the 12-bar blues with open guitar chords, which works beautifully in E or A and not at all in Bb. Barre chords remove that limit: two movable shapes and the whole progression follows your fretting hand up and down the guitar neck, one shape per string, any key.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;Recap: where 1, 4, and 5 sit on the neck&lt;/h2&gt;
&lt;p&gt;The &lt;a href="http://lizard-spock.co.uk/diatonic-chords.html"&gt;Diatonic Chords post&lt;/a&gt; built the full root recipe on the 6th and 5th strings:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Music/DiatonicChords/diatonic-root-recipe-HQ.png"&gt;&lt;img alt="The I-ii-iii-IV-V-vi root recipe on the 6th and 5th strings, worked from G at fret 3" src="http://lizard-spock.co.uk/images/Music/DiatonicChords/diatonic-root-recipe-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The blues only needs the 1, 4, and 5 corners of that map, and they form a tight little triangle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1&lt;/strong&gt; -- root on the 6th string.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4&lt;/strong&gt; -- same fret, 5th string. No counting; the tuning interval between the strings &lt;em&gt;is&lt;/em&gt; a perfect 4th.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5&lt;/strong&gt; -- two frets up from the 4, still on the 5th string.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is just that blues subset on its own, in G:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_e9f109afd753652848ada88d8c8c720b.svg"&gt;&lt;/p&gt;
&lt;p&gt;Find one note and the other two chords are already under your hand. That triangle is the same at fret 3, fret 8, or anywhere else, which is what makes the barre version of the progression movable.&lt;/p&gt;
&lt;h2&gt;The two shapes&lt;/h2&gt;
&lt;p&gt;Each corner of the triangle gets a barre shape named after the open chord it is built from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;E shape&lt;/strong&gt; (6th string root) for the &lt;strong&gt;1&lt;/strong&gt; -- the open E chord fingered with 2-3-4, with the index finger barring where the nut used to be.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A shape&lt;/strong&gt; (5th string root) for the &lt;strong&gt;4&lt;/strong&gt; and the &lt;strong&gt;5&lt;/strong&gt; -- the open A chord with an index barre, moved to each root.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Worked example: blues in G&lt;/h2&gt;
&lt;p&gt;Same G at fret 3 as the recap diagram. The 1 is the E shape at fret 3; the 4 sits directly below it at the same fret in the A shape; the 5 is that A shape slid up two frets:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_ef3d0dd694b291e3b3b56f12ed63edea.svg"&gt;&lt;/p&gt;
&lt;p&gt;The 4 is the 1's pattern dropped one string set at the same frets, and the 5 is the 4 slid two frets up -- the triangle from the recap, now wearing full chords. Play any 12-bar grid from the previous post with these three and notice how little the hand travels: the 1-to-4 change is a string hop at the same fret, and the 4-to-5 change is a two-fret slide of the same shape. The shuffle feel carries over unchanged.&lt;/p&gt;
&lt;h2&gt;Any key, same triangle&lt;/h2&gt;
&lt;p&gt;Slide the whole triangle so the E shape lands on the key's root note on the 6th string:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;1 (E shape, 6th string)&lt;/th&gt;
&lt;th&gt;4 (A shape, 5th string)&lt;/th&gt;
&lt;th&gt;5 (A shape, 5th string)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;fret 1&lt;/td&gt;
&lt;td&gt;fret 1&lt;/td&gt;
&lt;td&gt;fret 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;fret 3&lt;/td&gt;
&lt;td&gt;fret 3&lt;/td&gt;
&lt;td&gt;fret 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;fret 5&lt;/td&gt;
&lt;td&gt;fret 5&lt;/td&gt;
&lt;td&gt;fret 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bb&lt;/td&gt;
&lt;td&gt;fret 6&lt;/td&gt;
&lt;td&gt;fret 6&lt;/td&gt;
&lt;td&gt;fret 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;fret 8&lt;/td&gt;
&lt;td&gt;fret 8&lt;/td&gt;
&lt;td&gt;fret 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;fret 10&lt;/td&gt;
&lt;td&gt;fret 10&lt;/td&gt;
&lt;td&gt;fret 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is the answer to the horn-player problem from the last post: when someone calls "blues in Bb", nothing about the progression changes -- the triangle just parks at fret 6.&lt;/p&gt;
&lt;h2&gt;The 7th variations&lt;/h2&gt;
&lt;p&gt;Want the bluesier dominant 7th sound from the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;previous post&lt;/a&gt;? Each barre shape turns into a movable 7th chord by &lt;em&gt;lifting&lt;/em&gt; one finger: the freed string drops back onto the index-finger barre, and that barre note is the b7. In the E shape, lift the finger on the D string; in the A shape, lift the finger on the G string:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_5509893805ff5027978f2085f45ddb6e.svg"&gt;&lt;/p&gt;
&lt;p&gt;Same triangle, same frets, one finger fewer per chord. Swap them in wherever the grid wants more grit -- the 5 chord first, then all three -- and the triangle still plays the form anywhere on the neck.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Music Theory"/><category term="Blues"/><category term="Barre Chords"/><category term="Nashville Number System"/></entry><entry><title>12-Bar Blues on Irish Bouzouki in G, A, and E</title><link href="http://lizard-spock.co.uk/12-bar-blues-irish-bouzouki-g-a-e.html" rel="alternate"/><published>2026-07-02T00:00:00+01:00</published><updated>2026-07-02T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-02:/12-bar-blues-irish-bouzouki-g-a-e.html</id><summary type="html">&lt;p&gt;The 12-bar blues on Irish bouzouki in GDAD tuning: the same 1-4-5 number chart with chord shapes for the keys of G, A, and E, plus the modal drone options the instrument does best.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Same series, different instrument again: the 12-bar blues (&lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;the form&lt;/a&gt;, &lt;a href="http://lizard-spock.co.uk/12-bar-blues-guitar-barre-chords.html"&gt;guitar barre chords&lt;/a&gt;) converted to Irish bouzouki. Shapes below are for &lt;strong&gt;GDAD&lt;/strong&gt; tuning, the most common Irish setup; each pair of strings is a unison course, so the diagrams show four strings for four courses. Keys covered: G, A, and E.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;The form, unchanged&lt;/h2&gt;
&lt;p&gt;Twelve bars, three chords, turnaround in bar 12:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1  1  1  1
4  4  1  1
5  4  1  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Quick change, long 5, and the shuffle feel all carry over from the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;first post&lt;/a&gt; untouched. Only the shapes change -- and on bouzouki, how much of each chord you actually play is a stylistic choice, covered at the end.&lt;/p&gt;
&lt;h2&gt;Key of G&lt;/h2&gt;
&lt;p&gt;1-4-5 in G is G, C, D. GDAD is practically built for the key of G -- the 1 chord is two open courses and one finger:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_8979ae472ed7c193e7ba64560b62f091.svg"&gt;&lt;/p&gt;
&lt;p&gt;The C voicing keeps the open G course ringing underneath (technically C/G); on a droning instrument that is a feature, not a compromise.&lt;/p&gt;
&lt;h2&gt;Key of A&lt;/h2&gt;
&lt;p&gt;1-4-5 in A is A, D, E:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_f5b4b7690814da652c37358538b9d20c.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Key of E&lt;/h2&gt;
&lt;p&gt;1-4-5 in E is E, A, B. The B is the A shape from the key of A slid two frets up -- the same "same shape, two frets" move the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-guitar-barre-chords.html"&gt;guitar barre post&lt;/a&gt; uses for every 4-to-5 change:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_2828298a33f6f23a8ccf0b318bc9933a.svg"&gt;&lt;/p&gt;
&lt;p&gt;Drop the 3rd (the fret-6 note) and the barre alone gives 4-4-x-4 style bare fifths -- a B5 that drives just as well. Which leads to the real bouzouki move:&lt;/p&gt;
&lt;h2&gt;Drones, dyads, and the 7th&lt;/h2&gt;
&lt;p&gt;Irish bouzouki accompaniment leans modal: two- and three-note shapes, no 3rd, open courses droning. That vocabulary works on a blues too -- the 3rd of each chord is optional when the melody or the singer is supplying the blue notes, and 1-4-5 played as bare fifths with a shuffle drives just as hard as the full chords. Drop the labelled 3 from any diagram above and what remains is the modal version of the same chord.&lt;/p&gt;
&lt;p&gt;For the bluesier colour the series keeps returning to, the 7th is one lifted or added finger away here too. The best of them falls out of the tuning for free in the key of E: lower the top course of the E shape to open and the open D course &lt;em&gt;is&lt;/em&gt; the b7 --&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_c29019076dcfa0e1a62e3e68645c933a.svg"&gt;&lt;/p&gt;
&lt;p&gt;Play the 12-bar grid in E with that shape as the 1 and the form starts sounding like it grew up on this instrument after all.&lt;/p&gt;</content><category term="Music"/><category term="Irish Bouzouki"/><category term="Music Theory"/><category term="Blues"/><category term="Nashville Number System"/><category term="Chords"/></entry><entry><title>The 12-Bar Blues: One Progression, Every Key</title><link href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html" rel="alternate"/><published>2026-07-02T00:00:00+01:00</published><updated>2026-07-02T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-02:/12-bar-blues-one-progression-every-key.html</id><summary type="html">&lt;p&gt;Learn the 12-bar blues once as 1-4-5 and you can play it in any key. The form and its common variations, a chord lookup table, and an animated look at the shuffle feel.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ask a blues band what they are about to play and the answer is usually three numbers: "blues in E, quick change". That is the whole chart. The &lt;a href="http://lizard-spock.co.uk/diatonic-chords.html"&gt;Nashville Number System&lt;/a&gt; makes this possible because the 12-bar blues is not really a set of chords, it is a set of &lt;em&gt;relationships&lt;/em&gt;: the 1, the 4, and the 5 of whatever key you are in, arranged in a pattern that has not changed in a hundred years.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;The form&lt;/h2&gt;
&lt;p&gt;Twelve bars, three chords, one line per four bars:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1  1  1  1
4  4  1  1
5  4  1  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Read it as a story in three acts. The first line sits at home on the 1. The second line steps away to the 4 and comes back. The third line builds tension on the 5, softens through the 4, and lands home again, ready to repeat.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bar 12 is the turnaround.&lt;/strong&gt; The last bar's job is to point back to bar 1. The simplest version replaces the final 1 with a 5; fancier versions walk up chromatically into the 5 chord. Either way, the listener hears "here we go again" rather than "the end".&lt;/p&gt;
&lt;h2&gt;Common variations&lt;/h2&gt;
&lt;p&gt;The grid above is the base form, but bands rarely play it completely straight. All the common variants keep the same twelve-bar skeleton and change one or two cells.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick change.&lt;/strong&gt; Bar 2 becomes a 4, which stops the four opening bars of 1 from feeling static. This is the most-called variant; note the turnaround 5 written into bar 12:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1  4  1  1
4  4  1  1
5  4  1  5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Long 5.&lt;/strong&gt; Bar 10 stays on the 5 instead of dropping to the 4, holding the tension a bar longer. Common in early rock and roll:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1  1  1  1
4  4  1  1
5  5  1  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Minor blues.&lt;/strong&gt; Same skeleton with the 1 and 4 as minor chords, and a b6 chord adding drama in bar 9 before the 5. In A minor that third line is F, E7, Am, E7:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1- 1- 1- 1-
4- 4- 1- 1-
b6 5  1- 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Jazz blues.&lt;/strong&gt; Jazz players keep the twelve bars but recolour the third line, most noticeably replacing the 5-4 of bars 9-10 with a 2-5 cadence. Worth recognising when someone counts in "Straight No Chaser", but it is its own rabbit hole.&lt;/p&gt;
&lt;p&gt;Whatever the variant, someone can call it in a few words ("quick change", "hold the five") because everyone shares the same numbered map.&lt;/p&gt;
&lt;h2&gt;Chords by key&lt;/h2&gt;
&lt;p&gt;Because the form is written in numbers, playing it in a new key is a lookup, not a transposition exercise:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;E and A are the guitar keys: the chords sit in open position and the boogie patterns below fall under the fingers. Horn players will call for Bb or F; that is what barre chords and a capo are for.&lt;/p&gt;
&lt;p&gt;Plain major chords work fine. For more of a blues feel, swap any or all of them for dominant 7ths (E7, A7, B7): the added b7 gives the chords that unresolved, gritty blues colour. The 5 chord is the most common place to start.&lt;/p&gt;
&lt;h2&gt;The three chords in E&lt;/h2&gt;
&lt;p&gt;Open E and A, plus B7, the friendliest way to play the 5 in this key (the full B barre chord is no fun in the middle of a shuffle):&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_4b4676bef65c5b1e6153f1aa7ffebb55.svg"&gt;&lt;/p&gt;
&lt;h2&gt;The shuffle feel&lt;/h2&gt;
&lt;p&gt;The chord grid says nothing about rhythm, and rhythm is half the style. Blues is rarely played with even eighth notes: it is played with a &lt;strong&gt;shuffle&lt;/strong&gt; (or swing) feel, where each beat is split long-short instead of half-and-half. Count it as triplets, "1-and-a, 2-and-a", and play only the first and last note of each triplet: the offbeat lands two-thirds of the way through the beat, not halfway.&lt;/p&gt;
&lt;p&gt;The animation below shows one bar of eighth notes, each dot lighting up when it is played. The straight row pulses evenly; the shuffle row plays the same notes, but the offbeats arrive late:&lt;/p&gt;
&lt;div class="shuffle-demo"&gt;
&lt;style&gt;
.shuffle-demo { max-width: 560px; margin: 1.5em auto; font-family: inherit; }
.shuffle-demo .sd-row { display: flex; align-items: center; margin: 0.9em 0; }
.shuffle-demo .sd-label { flex: none; width: 80px; font-size: 0.85em; color: #555; }
.shuffle-demo .sd-beat { display: flex; gap: 6px; margin-right: 16px; }
.shuffle-demo .sd-note {
  width: 30px; height: 30px; border-radius: 50%;
  background: #d8d8d8; color: #555;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.8em;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}
@keyframes sd-hit-even {
  0%, 12.4% { background: #c9552e; color: #fff; transform: scale(1.18); }
  12.5%, 100% { background: #d8d8d8; color: #555; transform: scale(1); }
}
@keyframes sd-hit-long {
  0%, 16.6% { background: #c9552e; color: #fff; transform: scale(1.18); }
  16.7%, 100% { background: #d8d8d8; color: #555; transform: scale(1); }
}
@keyframes sd-hit-short {
  0%, 8.2% { background: #c9552e; color: #fff; transform: scale(1.18); }
  8.3%, 100% { background: #d8d8d8; color: #555; transform: scale(1); }
}
.shuffle-demo .sd-even { animation-name: sd-hit-even; }
.shuffle-demo .sd-long { animation-name: sd-hit-long; }
.shuffle-demo .sd-short { animation-name: sd-hit-short; }
@media (prefers-reduced-motion: reduce) {
  .shuffle-demo .sd-note { transform: none !important; }
}
&lt;/style&gt;
&lt;div class="sd-row"&gt;
  &lt;div class="sd-label"&gt;Straight&lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -3s;"&gt;1&lt;/div&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -2.625s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -2.25s;"&gt;2&lt;/div&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -1.875s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -1.5s;"&gt;3&lt;/div&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -1.125s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -0.75s;"&gt;4&lt;/div&gt;
    &lt;div class="sd-note sd-even" style="animation-delay: -0.375s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class="sd-row"&gt;
  &lt;div class="sd-label"&gt;Shuffle&lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-long" style="animation-delay: -3s;"&gt;1&lt;/div&gt;
    &lt;div class="sd-note sd-short" style="animation-delay: -2.5s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-long" style="animation-delay: -2.25s;"&gt;2&lt;/div&gt;
    &lt;div class="sd-note sd-short" style="animation-delay: -1.75s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-long" style="animation-delay: -1.5s;"&gt;3&lt;/div&gt;
    &lt;div class="sd-note sd-short" style="animation-delay: -1s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="sd-beat"&gt;
    &lt;div class="sd-note sd-long" style="animation-delay: -0.75s;"&gt;4&lt;/div&gt;
    &lt;div class="sd-note sd-short" style="animation-delay: -0.25s;"&gt;&amp;amp;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Both rows play eight notes per bar, but the shuffle "&amp;amp;" notes wait until two-thirds of the beat has gone by, and each downbeat note rings twice as long as the offbeat that follows it. That lopsided lope is the blues feel; once you can hear it, you will notice straight eighths sound stiff in this style. Strum the twelve bars above with this feel and it already sounds like blues.&lt;/p&gt;
&lt;h2&gt;Why bother with the numbers?&lt;/h2&gt;
&lt;p&gt;Because the number chart is the only version you have to memorise. Learn the grid at the top of this post once and you now know the 12-bar blues in E, A, G, D, C, and every other key: the table converts numbers to chord names. When a jam leader calls "blues in A, quick change, watch for the stops", every word of that maps onto something in this post except the stops, and those are just bars where the band hits beat one and lets the singer have the rest.&lt;/p&gt;
&lt;p&gt;Next in the series: &lt;a href="http://lizard-spock.co.uk/a-shape-open-chord-variations.html"&gt;variations on the open A shape&lt;/a&gt;, plus the D and E shapes, to grow the chord vocabulary used in this progression.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Music Theory"/><category term="Blues"/><category term="Nashville Number System"/><category term="Chords"/></entry><entry><title>12-Bar Blues on Ukulele in G, A, and E</title><link href="http://lizard-spock.co.uk/12-bar-blues-ukulele-g-a-e.html" rel="alternate"/><published>2026-07-02T00:00:00+01:00</published><updated>2026-07-02T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-02:/12-bar-blues-ukulele-g-a-e.html</id><summary type="html">&lt;p&gt;The 12-bar blues moved to ukulele: the same 1-4-5 number chart, with chord shapes for the keys of G, A, and E in standard GCEA tuning.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The 12-bar blues charts in this series (&lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;the form&lt;/a&gt;, &lt;a href="http://lizard-spock.co.uk/12-bar-blues-guitar-barre-chords.html"&gt;guitar barre chords&lt;/a&gt;) are written in numbers, and numbers do not care what instrument you are holding. This post converts the same 1-4-5 chart to ukulele shapes in standard GCEA tuning, for the three keys a uke player is most likely to get called into: G, A, and E.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;The form, unchanged&lt;/h2&gt;
&lt;p&gt;Twelve bars, three chords, turnaround in bar 12 -- exactly as on guitar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1  1  1  1
4  4  1  1
5  4  1  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All the variations from the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html"&gt;first post&lt;/a&gt; (quick change, long 5, the shuffle feel) apply as written. Only the shapes below change.&lt;/p&gt;
&lt;h2&gt;Key of G&lt;/h2&gt;
&lt;p&gt;1-4-5 in G is G, C, D -- the friendliest blues key on a ukulele, all three chords in the first three frets:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_2610aacf7be2738a9a426890d30e67e5.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Key of A&lt;/h2&gt;
&lt;p&gt;1-4-5 in A is A, D, E:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_c03048496ff9f03b3019c66e4225c194.svg"&gt;&lt;/p&gt;
&lt;p&gt;E major is famously the ukulele's least favourite chord. The 4442 voicing above is the most reliable of the standard options; if it fights you, use E7 instead -- easier, and the blues actively prefers the 7th sound anyway:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_989e85a60c38c897ca66c2bb0ac1f3f4.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Key of E&lt;/h2&gt;
&lt;p&gt;1-4-5 in E is E, A, B. Guitarists love E blues for the open strings; on ukulele it is the workout key, with both the E and B shapes up the neck:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_ce944ef1071fee60c9255cfc2d9ab336.svg"&gt;&lt;/p&gt;
&lt;p&gt;Here too the 7ths are the escape hatch: E7 (1202, above) for the 1 and B7 for the 5 turn the workout key into an easy one. B7 comes in two common voicings -- take your pick:&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_98566de275779d3ead708e5e6ad6083c.svg"&gt;&lt;/p&gt;
&lt;p&gt;The open form is the easier grab -- fingers walk down 4-3-2 and the open A course rings as the b7. The barre form takes a moment more to set, but it damps cleanly for a choppy shuffle and it moves: slide it up a fret and it is C7, up three and it is D7. Let the hands decide.&lt;/p&gt;
&lt;h2&gt;Playing it&lt;/h2&gt;
&lt;p&gt;Strum with the same shuffle feel as on guitar -- the lopsided long-short eighths from the &lt;a href="http://lizard-spock.co.uk/12-bar-blues-one-progression-every-key.html#the-shuffle-feel"&gt;first post&lt;/a&gt;. The ukulele's short sustain suits a damped, percussive shuffle: a light palm or finger mute after each strum stands in for the guitar's palm muting. And since every shape above is fretted with a finger or two, swapping any chord for its dominant 7th costs nothing -- lean on them freely.&lt;/p&gt;</content><category term="Music"/><category term="Ukulele"/><category term="Music Theory"/><category term="Blues"/><category term="Nashville Number System"/><category term="Chords"/></entry><entry><title>Diatonic Chords</title><link href="http://lizard-spock.co.uk/diatonic-chords.html" rel="alternate"/><published>2026-07-01T00:00:00+01:00</published><updated>2026-07-01T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-07-01:/diatonic-chords.html</id><summary type="html">&lt;p&gt;Where chords come from -- every triad is built from the 1st, 3rd, and 5th of a scale. A chord table for every key, a fretboard map showing how I, IV, and V sit relative to each other, and an introduction to the Nashville Number System.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Major chords are made up of the 1st, 3rd, and 5th note of its diatonic major scale. For example, an A chord is the 1-3-5 of the A major scale (A, C#, E), and an E chord is the 1-3-5 of the E major scale (E, G#, B). Different notes, same recipe. From here we build the full set of diatonic chords in a key.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;h2&gt;Every chord comes from a scale&lt;/h2&gt;
&lt;p&gt;The scale behind these chords is the &lt;strong&gt;diatonic major scale&lt;/strong&gt; -- the same scale also called the natural major, the major scale, or the Ionian mode. All four names point at the same seven notes; "diatonic major" is the name used going forward in this post.&lt;/p&gt;
&lt;p&gt;Every diatonic major scale is built from the same fixed pattern of steps: &lt;strong&gt;W-W-H-W-W-W-H&lt;/strong&gt;. &lt;code&gt;W&lt;/code&gt; is a whole step (two frets, a tone), &lt;code&gt;H&lt;/code&gt; is a half step (one fret, a semitone). Starting on any root note and following that pattern produces a major scale in that key -- it's the pattern that makes a scale "major", not the starting note.&lt;/p&gt;
&lt;p&gt;Applying W-W-H-W-W-W-H starting on A:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Music/DiatonicChords/a-major-scale-steps-HQ.png"&gt;&lt;img alt="A major scale built from the W-W-H-W-W-W-H step formula" src="http://lizard-spock.co.uk/images/Music/DiatonicChords/a-major-scale-steps-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That gives the A diatonic major scale: A B C# D E F# G#. Pull out the 1st, 3rd, and 5th notes -- A, C#, E -- and that triad is the A major chord.&lt;/p&gt;
&lt;div style="display: flex; gap: 1.5rem; flex-wrap: wrap;"&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_1c85fa841df5ad1d2418cb43440ced59.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_11a8c7b3e8c5719912907c5e4ee40c3b.svg"&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Do the same with the E diatonic major scale, applying W-W-H-W-W-W-H starting on E:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Music/DiatonicChords/e-major-scale-steps-HQ.png"&gt;&lt;img alt="E major scale built from the W-W-H-W-W-W-H step formula" src="http://lizard-spock.co.uk/images/Music/DiatonicChords/e-major-scale-steps-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That gives E F# G# A B C# D#. The 1st, 3rd, and 5th are E, G#, B -- the E major chord.&lt;/p&gt;
&lt;div style="display: flex; gap: 1.5rem; flex-wrap: wrap;"&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_afb8744e939e391d9779617c3ac6b8a5.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_df3f4814a34b54112abde73cb2df7fa2.svg"&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Same shape of recipe, different scale, different notes. Every major chord in every key is built this way: 1-3-5 of its own major scale. That's the whole idea behind a "major chord" -- it isn't a fixed set of notes, it's a fixed &lt;em&gt;relationship&lt;/em&gt; to whichever scale it's rooted on.&lt;/p&gt;
&lt;h2&gt;Diatonic chords: stacking the major scale&lt;/h2&gt;
&lt;p&gt;The A and E examples above both stacked a 1-3-5 triad starting on the scale's own root. Stack that same triad shape starting on &lt;em&gt;every&lt;/em&gt; degree of the scale, not just the first, and you get the full set of chords that naturally belong to a key -- the &lt;strong&gt;diatonic&lt;/strong&gt; chords.&lt;/p&gt;
&lt;p&gt;"1-3-5" is really shorthand for a rule: take a note, skip one, take the next, skip one, take the next -- staying entirely inside the notes of the scale. Do that starting on the 1st degree of C major (C D E F G A B) and the third note is E, giving C-E-G. Do the &lt;em&gt;same&lt;/em&gt; skip-one pattern starting on the 2nd degree instead and the third note is F, giving D-F-A. Same recipe, different starting point, but the scale only supplies the notes it has -- there's no F# to reach for, so the gap between D and F comes out smaller than the gap between C and E.&lt;/p&gt;
&lt;p&gt;That gap is what decides major versus minor. The distance from root to third is measured in semitones (frets): a &lt;strong&gt;major third&lt;/strong&gt; is 4 semitones, a &lt;strong&gt;minor third&lt;/strong&gt; is 3 semitones. Breaking each triad down into its two internal gaps (root-to-3rd, and 3rd-to-5th) shows exactly where that difference comes from, and shows something that &lt;em&gt;doesn't&lt;/em&gt; change alongside it -- the outer gap, root straight to fifth:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Degree&lt;/th&gt;
&lt;th&gt;Triad notes&lt;/th&gt;
&lt;th&gt;Root-to-3rd&lt;/th&gt;
&lt;th&gt;3rd-to-5th&lt;/th&gt;
&lt;th&gt;Root-to-5th&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;C-E-G&lt;/td&gt;
&lt;td&gt;4 semitones&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;7 semitones&lt;/td&gt;
&lt;td&gt;Major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;D-F-A&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;4 semitones&lt;/td&gt;
&lt;td&gt;7 semitones&lt;/td&gt;
&lt;td&gt;Minor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;E-G-B&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;4 semitones&lt;/td&gt;
&lt;td&gt;7 semitones&lt;/td&gt;
&lt;td&gt;Minor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;F-A-C&lt;/td&gt;
&lt;td&gt;4 semitones&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;7 semitones&lt;/td&gt;
&lt;td&gt;Major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;G-B-D&lt;/td&gt;
&lt;td&gt;4 semitones&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;7 semitones&lt;/td&gt;
&lt;td&gt;Major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;A-C-E&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;4 semitones&lt;/td&gt;
&lt;td&gt;7 semitones&lt;/td&gt;
&lt;td&gt;Minor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;B-D-F&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;3 semitones&lt;/td&gt;
&lt;td&gt;6 semitones&lt;/td&gt;
&lt;td&gt;Diminished&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Every row's root-to-3rd and 3rd-to-5th gaps add up to the same root-to-5th distance: 7 semitones, a perfect fifth, on every degree except the 7th. Whether the chord reads as major or minor comes entirely from which gap -- 4 then 3, or 3 then 4 -- comes first; the outer boundary of the triad never moves. The 7th degree is the one exception: both inner gaps are 3 semitones, so they add up short, to 6 rather than 7 -- a flattened, or diminished, fifth. Stacking two minor thirds back to back is what makes that triad diminished rather than simply minor.&lt;/p&gt;
&lt;p&gt;This major/minor/diminished pattern -- Maj min min Maj Maj min dim, reading up the scale -- is fixed for every major key; only the notes change, the same way the A and E chords earlier shared a recipe but not notes.&lt;/p&gt;
&lt;p&gt;Each of these seven chords also gets a roman numeral, counting up from the root chord: I ii iii IV V vi vii&amp;deg;. Uppercase marks a major chord, lowercase marks minor, and the degree symbol marks diminished -- the numerals encode the same major/minor/diminished pattern from the table above, just as a label rather than a semitone count. Session musicians use a plain-digit version of the same idea -- 1 2m 3m 4 5 6m 7dim -- called the &lt;strong&gt;Nashville Number System&lt;/strong&gt;, covered in full further down; it's the same major/minor/diminished labelling here, just written as numbers instead of numerals so the same chart works in any key.&lt;/p&gt;
&lt;p&gt;I, IV, and V are the three major chords in the key, which is why they combine so freely in progressions: any two of them share strong voice-leading and neither introduces a note outside the key.&lt;/p&gt;
&lt;h2&gt;Chord table for every key&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;I&lt;/th&gt;
&lt;th&gt;ii&lt;/th&gt;
&lt;th&gt;iii&lt;/th&gt;
&lt;th&gt;IV&lt;/th&gt;
&lt;th&gt;V&lt;/th&gt;
&lt;th&gt;vi&lt;/th&gt;
&lt;th&gt;vii&amp;deg;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Dm&lt;/td&gt;
&lt;td&gt;Em&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;Am&lt;/td&gt;
&lt;td&gt;Bdim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C#/Db&lt;/td&gt;
&lt;td&gt;Db&lt;/td&gt;
&lt;td&gt;Ebm&lt;/td&gt;
&lt;td&gt;Fm&lt;/td&gt;
&lt;td&gt;Gb&lt;/td&gt;
&lt;td&gt;Ab&lt;/td&gt;
&lt;td&gt;Bbm&lt;/td&gt;
&lt;td&gt;Cdim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Em&lt;/td&gt;
&lt;td&gt;F#m&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Bm&lt;/td&gt;
&lt;td&gt;C#dim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D#/Eb&lt;/td&gt;
&lt;td&gt;Eb&lt;/td&gt;
&lt;td&gt;Fm&lt;/td&gt;
&lt;td&gt;Gm&lt;/td&gt;
&lt;td&gt;Ab&lt;/td&gt;
&lt;td&gt;Bb&lt;/td&gt;
&lt;td&gt;Cm&lt;/td&gt;
&lt;td&gt;Ddim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;F#m&lt;/td&gt;
&lt;td&gt;G#m&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;C#m&lt;/td&gt;
&lt;td&gt;D#dim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;Gm&lt;/td&gt;
&lt;td&gt;Am&lt;/td&gt;
&lt;td&gt;Bb&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Dm&lt;/td&gt;
&lt;td&gt;Edim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F#/Gb&lt;/td&gt;
&lt;td&gt;F#&lt;/td&gt;
&lt;td&gt;G#m&lt;/td&gt;
&lt;td&gt;A#m&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;D#m&lt;/td&gt;
&lt;td&gt;E#dim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;Am&lt;/td&gt;
&lt;td&gt;Bm&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Em&lt;/td&gt;
&lt;td&gt;F#dim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G#/Ab&lt;/td&gt;
&lt;td&gt;Ab&lt;/td&gt;
&lt;td&gt;Bbm&lt;/td&gt;
&lt;td&gt;Cm&lt;/td&gt;
&lt;td&gt;Db&lt;/td&gt;
&lt;td&gt;Eb&lt;/td&gt;
&lt;td&gt;Fm&lt;/td&gt;
&lt;td&gt;Gdim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Bm&lt;/td&gt;
&lt;td&gt;C#m&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;F#m&lt;/td&gt;
&lt;td&gt;G#dim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A#/Bb&lt;/td&gt;
&lt;td&gt;Bb&lt;/td&gt;
&lt;td&gt;Cm&lt;/td&gt;
&lt;td&gt;Dm&lt;/td&gt;
&lt;td&gt;Eb&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;Gm&lt;/td&gt;
&lt;td&gt;Adim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;C#m&lt;/td&gt;
&lt;td&gt;D#m&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;F#&lt;/td&gt;
&lt;td&gt;G#m&lt;/td&gt;
&lt;td&gt;A#dim&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Find your key, read across. The I, IV, V columns are the three chords used most often in simple progressions.&lt;/p&gt;
&lt;h2&gt;Where I, IV, and V sit on the neck&lt;/h2&gt;
&lt;p&gt;Every diatonic root note in a key can be found from a single starting point using the 6th and 5th strings, without needing a separate shape or position memorised for each chord:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start on the root -- that's &lt;strong&gt;I&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Move up 2 frets on the same string -- that's &lt;strong&gt;ii&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Move up 2 more frets on the same string -- that's &lt;strong&gt;iii&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Drop down one string, back to the &lt;em&gt;starting&lt;/em&gt; fret -- that's &lt;strong&gt;IV&lt;/strong&gt;. Adjacent strings (except G to B) are tuned 5 semitones apart -- a perfect 4th, the same distance as root to IV -- so no fret-counting is needed here at all.&lt;/li&gt;
&lt;li&gt;Move up 2 frets on this new string -- that's &lt;strong&gt;V&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Move up 2 more frets -- that's &lt;strong&gt;vi&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worked example, root G on the 6th string at fret 3:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Music/DiatonicChords/diatonic-root-recipe-HQ.png"&gt;&lt;img alt="The I-ii-iii-IV-V-vi root recipe on the 6th and 5th strings, worked from G at fret 3" src="http://lizard-spock.co.uk/images/Music/DiatonicChords/diatonic-root-recipe-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;G-A-B on the 6th string are I-ii-iii; dropping to the 5th string at the same fret 3 lands on C, the IV; C-D-E on the 5th string are IV-V-vi. Every "+2 frets" step is a whole step, matching the W in the W-W-H-W-W-W-H formula -- the only reason the recipe can skip past the H (iii to IV) without adjusting the fret count is the one-string drop, which supplies that gap for free via the tuning itself. Once a root is located by name on either string, this same shape of movement finds every other diatonic root in that key.&lt;/p&gt;
&lt;h2&gt;The Nashville Number System&lt;/h2&gt;
&lt;p&gt;Session musicians don't read "G, C, D" off a chart -- they read "1, 4, 5". Numbers replace chord names so the same chart works in any key: the guitarist reads it in E, the piano player reads it in Bb, nobody transposes anything, they just play the same numbers off their own instrument's shapes.&lt;/p&gt;
&lt;p&gt;Conventions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Uppercase or plain digit = major&lt;/strong&gt; (&lt;code&gt;I&lt;/code&gt;, &lt;code&gt;IV&lt;/code&gt;, &lt;code&gt;V&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;4&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lowercase or digit + &lt;code&gt;m&lt;/code&gt; = minor&lt;/strong&gt; (&lt;code&gt;ii&lt;/code&gt;, &lt;code&gt;iii&lt;/code&gt;, &lt;code&gt;vi&lt;/code&gt;, or &lt;code&gt;2m&lt;/code&gt;, &lt;code&gt;3m&lt;/code&gt;, &lt;code&gt;6m&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;°&lt;/code&gt; or &lt;code&gt;dim&lt;/code&gt; = diminished&lt;/strong&gt; (&lt;code&gt;vii°&lt;/code&gt; becomes &lt;code&gt;7dim&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Slash chords show the bass note after a slash: &lt;code&gt;5/7&lt;/code&gt; means a V chord with the 7th in the bass&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the numbers are relative to the key, not absolute notes, a chart written once works for a band where every player is in a different key on their own instrument. This is the foundation for the next post in this series, which applies it directly to a full 12-bar blues progression.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Music Theory"/><category term="Chords"/><category term="Nashville Number System"/></entry><entry><title>Using ctags with Verilog and SystemVerilog</title><link href="http://lizard-spock.co.uk/ctags-verilog-systemverilog.html" rel="alternate"/><published>2026-06-19T00:00:00+01:00</published><updated>2026-06-19T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-06-19:/ctags-verilog-systemverilog.html</id><summary type="html">&lt;p&gt;How to generate and use ctags for Verilog/SystemVerilog projects, covering design.vc file lists, editor setup for Vim, Neovim, Emacs, and VS Code, and keeping tags up to date automatically.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Unix/ctags/ctags-verilog-hero-HQ.png"&gt;&lt;img alt="Ctags for Verilog navigation" src="http://lizard-spock.co.uk/images/Unix/ctags/ctags-verilog-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Navigating a large Verilog or SystemVerilog codebase without tag-based Go to Definition is painful. This tutorial shows how to generate and use &lt;code&gt;ctags&lt;/code&gt; for projects that use a &lt;code&gt;design.vc&lt;/code&gt; file list — common in simulators like Verilator, VCS, and xrun.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Universal Ctags&lt;/strong&gt; (preferred over Exuberant Ctags — it has better SystemVerilog support)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install on macOS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;universal-ctags
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On Linux:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;universal-ctags&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# Debian/Ubuntu&lt;/span&gt;
brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;universal-ctags&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# Homebrew on Linux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check your version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;1. Understand the &lt;code&gt;design.vc&lt;/code&gt; format&lt;/h2&gt;
&lt;p&gt;A typical &lt;code&gt;design.vc&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;+incdir+./rtl
+incdir+./ip
./rtl/top.sv
./rtl/subsystem.sv
./ip/uart/uart.sv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Lines beginning with &lt;code&gt;+incdir+&lt;/code&gt; define include paths.&lt;/li&gt;
&lt;li&gt;Remaining lines are source files (usually &lt;code&gt;.v&lt;/code&gt; / &lt;code&gt;.sv&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Relative paths are normally relative to the &lt;code&gt;design.vc&lt;/code&gt; location.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. Create a file list for ctags&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ctags&lt;/code&gt; expects plain file paths. Strip the &lt;code&gt;+incdir+&lt;/code&gt; lines and any comments with &lt;code&gt;awk&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;  /^\+incdir\+/ {next}&lt;/span&gt;
&lt;span class="s1"&gt;  /^\s*#/ {next}&lt;/span&gt;
&lt;span class="s1"&gt;  /^\s*$/ {next}&lt;/span&gt;
&lt;span class="s1"&gt;  {print}&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;design.vc&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want the include paths separately for other tools:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^\+incdir\+&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;design.vc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/^\+incdir\+//&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;design.incdirs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note: &lt;code&gt;ctags&lt;/code&gt; does not use include paths for preprocessing; it parses files directly. Make sure all files that define symbols are listed in &lt;code&gt;design.files&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;3. Generate the tags file&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;--languages&lt;span class="o"&gt;=&lt;/span&gt;SystemVerilog&lt;span class="w"&gt; &lt;/span&gt;--fields&lt;span class="o"&gt;=&lt;/span&gt;+nKz&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;tags&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Flags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-L design.files&lt;/code&gt; — read source paths from the file list&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--languages=SystemVerilog&lt;/code&gt; — enables SV/Verilog parsing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--fields=+nKz&lt;/code&gt; — includes line number, kind, and scope in each tag&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f tags&lt;/code&gt; — write to &lt;code&gt;tags&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. Configure your editor&lt;/h2&gt;
&lt;h3&gt;Vim / Neovim&lt;/h3&gt;
&lt;p&gt;Tell Vim to search upward from the current file for a &lt;code&gt;tags&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="k"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;./&lt;span class="k"&gt;tags&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Navigate with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Ctrl-]&lt;/code&gt; — jump to definition&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl-t&lt;/code&gt; — jump back&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Emacs&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tags-file-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/path/to/project/tags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Navigate with &lt;code&gt;M-.&lt;/code&gt; (jump) and &lt;code&gt;M-*&lt;/code&gt; (back).&lt;/p&gt;
&lt;h3&gt;VS Code — ctags-companion extension&lt;/h3&gt;
&lt;p&gt;Install the &lt;strong&gt;Ctags Companion&lt;/strong&gt; extension (&lt;code&gt;gediminaszlatkus.ctags-companion&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Default settings that work well for SystemVerilog:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ctags-companion.command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ctags -R --fields=+nKz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ctags-companion.readtagsGoToDefinitionCommand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;readtags -en&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once a &lt;code&gt;tags&lt;/code&gt; file exists at the project root, &lt;code&gt;Go to Definition&lt;/code&gt; (&lt;code&gt;F12&lt;/code&gt; / Cmd-click / right-click) will use it.&lt;/p&gt;
&lt;p&gt;To make Go to Definition open in the current editor group rather than a split pane, add to your user settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;workbench.editor.enablePreviewFromCodeNavigation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;workbench.editor.revealIfOpen&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If split-pane behaviour persists, check that your keybinding is mapped to &lt;code&gt;editor.action.revealDefinition&lt;/code&gt; and not &lt;code&gt;editor.action.revealDefinitionAside&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;5. Keep tags up to date&lt;/h2&gt;
&lt;p&gt;Regenerate after RTL changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;--languages&lt;span class="o"&gt;=&lt;/span&gt;SystemVerilog&lt;span class="w"&gt; &lt;/span&gt;--fields&lt;span class="o"&gt;=&lt;/span&gt;+nKz&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;tags&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add a &lt;code&gt;make&lt;/code&gt; target for convenience:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;
&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/^\+incdir\+/ {next} /^\s*#/ {next} /^\s*$$/ {next} {print}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;design.vc&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;span class="w"&gt;    &lt;/span&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;--languages&lt;span class="o"&gt;=&lt;/span&gt;SystemVerilog&lt;span class="w"&gt; &lt;/span&gt;--fields&lt;span class="o"&gt;=&lt;/span&gt;+nKz&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;tags&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;6. Auto-build on save&lt;/h2&gt;
&lt;h3&gt;Vim: rebuild on BufWritePost&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; s:FindRoot&lt;span class="p"&gt;()&lt;/span&gt; abort
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;:vc &lt;span class="p"&gt;=&lt;/span&gt; findfile&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;design.vc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; expand&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%:p:h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.&lt;span class="s1"&gt;&amp;#39;;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;empty&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:vc&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; fnamemodify&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:vc&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;endif&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;:root &lt;span class="p"&gt;=&lt;/span&gt; finddir&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.git&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; expand&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%:p:h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.&lt;span class="s1"&gt;&amp;#39;;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; empty&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:root&lt;span class="p"&gt;)&lt;/span&gt; ? &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; : fnamemodify&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:root&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; s:BuildTags&lt;span class="p"&gt;()&lt;/span&gt; abort
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;:root &lt;span class="p"&gt;=&lt;/span&gt; s:FindRoot&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; empty&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:root&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; filereadable&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:root . &lt;span class="s1"&gt;&amp;#39;/design.vc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    execute &lt;span class="s1"&gt;&amp;#39;silent !cd &amp;#39;&lt;/span&gt; . shellescape&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:root&lt;span class="p"&gt;)&lt;/span&gt; .
          \ &lt;span class="s1"&gt;&amp;#39; &amp;amp;&amp;amp; awk &amp;#39;&amp;#39;/^\+incdir\+/ {next} /^\s*#/ {next} /^\s*$/ {next} {print}&amp;#39;&amp;#39; design.vc &amp;gt; design.files&amp;#39;&lt;/span&gt; .
          \ &lt;span class="s1"&gt;&amp;#39; &amp;amp;&amp;amp; ctags -R --languages=SystemVerilog --fields=+nKz -f tags -L design.files&amp;#39;&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    execute &lt;span class="s1"&gt;&amp;#39;silent !cd &amp;#39;&lt;/span&gt; . shellescape&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;:root&lt;span class="p"&gt;)&lt;/span&gt; .
          \ &lt;span class="s1"&gt;&amp;#39; &amp;amp;&amp;amp; ctags -R --languages=SystemVerilog --fields=+nKz -f tags .&amp;#39;&lt;/span&gt;
  &lt;span class="k"&gt;endif&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

augroup verilog_tags
  autocmd&lt;span class="p"&gt;!&lt;/span&gt;
  autocmd &lt;span class="nb"&gt;BufWritePost&lt;/span&gt; *.&lt;span class="k"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;*.&lt;span class="k"&gt;sv&lt;/span&gt; &lt;span class="k"&gt;call&lt;/span&gt; s:BuildTags&lt;span class="p"&gt;()&lt;/span&gt;
augroup END
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Neovim: async job (non-blocking)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./tags;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;find_root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;design.vc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%:p:h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fnamemodify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;:h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finddir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%:p:h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fnamemodify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;git&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;:h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;build_tags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;find_root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filereadable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/design.vc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cd &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shellescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;amp;&amp;amp; awk &amp;#39;/^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;+incdir&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;+/ {next} /^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s*#/ {next} /^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s*$/ {next} {print}&amp;#39; design.vc &amp;gt; design.files&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;amp;&amp;amp; ctags -R --languages=SystemVerilog --fields=+nKz -f tags -L design.files&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cd &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shellescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;amp;&amp;amp; ctags -R --languages=SystemVerilog --fields=+nKz -f tags .&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jobstart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;detach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="nv"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nvim_create_autocmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;BufWritePost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;pattern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*.v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*.sv&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;build_tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;gutentags plugin&lt;/h3&gt;
&lt;p&gt;If you prefer a plugin, &lt;code&gt;gutentags&lt;/code&gt; can manage tag generation automatically. Use a small wrapper script so it handles &lt;code&gt;design.vc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c1"&gt;# ctags_from_design_vc.sh — place in PATH&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-euo&lt;span class="w"&gt; &lt;/span&gt;pipefail

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;design.vc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/^\+incdir\+/ {next} /^\s*#/ {next} /^\s*$/ {next} {print}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;design.vc&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;--languages&lt;span class="o"&gt;=&lt;/span&gt;SystemVerilog&lt;span class="w"&gt; &lt;/span&gt;--fields&lt;span class="o"&gt;=&lt;/span&gt;+nKz&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;tags&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;design.files
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;--languages&lt;span class="o"&gt;=&lt;/span&gt;SystemVerilog&lt;span class="w"&gt; &lt;/span&gt;--fields&lt;span class="o"&gt;=&lt;/span&gt;+nKz&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;tags&lt;span class="w"&gt; &lt;/span&gt;.
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then configure &lt;code&gt;gutentags&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;:gutentags_project_root &lt;span class="p"&gt;=&lt;/span&gt; [&lt;span class="s1"&gt;&amp;#39;design.vc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.git&amp;#39;&lt;/span&gt;]
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;:gutentags_ctags_executable &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ctags_from_design_vc.sh&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;7. Troubleshooting&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;No tags generated&lt;/strong&gt; — check that &lt;code&gt;design.files&lt;/code&gt; has correct, accessible paths. Switch to absolute paths if relative ones fail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Missing modules or interfaces&lt;/strong&gt; — confirm your &lt;code&gt;ctags&lt;/code&gt; supports SystemVerilog. Universal Ctags handles it; Exuberant Ctags does not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Include paths not respected&lt;/strong&gt; — &lt;code&gt;ctags&lt;/code&gt; does not preprocess; it parses files directly. Ensure every file that defines a symbol is in &lt;code&gt;design.files&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Strip &lt;code&gt;+incdir+&lt;/code&gt; from &lt;code&gt;design.vc&lt;/code&gt; to create &lt;code&gt;design.files&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;ctags -R --languages=SystemVerilog --fields=+nKz -f tags -L design.files&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure your editor (Vim / Neovim / Emacs / VS Code) to use the &lt;code&gt;tags&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Automate regeneration on save so tags stay current.&lt;/li&gt;
&lt;/ol&gt;</content><category term="Unix &amp; Tools"/><category term="verilog"/><category term="systemverilog"/><category term="ctags"/><category term="vim"/><category term="neovim"/><category term="vscode"/><category term="tools"/><category term="rtl"/></entry><entry><title>A Second MCP Server: Managing a Pelican Blog from Claude</title><link href="http://lizard-spock.co.uk/a-second-mcp-server-managing-a-pelican-blog-from-claude.html" rel="alternate"/><published>2026-06-03T00:00:00+01:00</published><updated>2026-06-03T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-06-03:/a-second-mcp-server-managing-a-pelican-blog-from-claude.html</id><summary type="html">&lt;p&gt;How to add a second self-hosted MCP server alongside an existing one — a blog-management MCP that lets Claude create posts, add images, and publish a Pelican site without leaving the conversation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Once you've set up one self-hosted MCP server, adding a second is mostly a case of reusing the infrastructure you've already built. This post covers adding a blog-management MCP to the same host as an existing MCP server — so the OAuth service, nginx, and firewall rules carry over, and the new server is a matter of writing the tools and adding a new location block to nginx.&lt;/p&gt;
&lt;p&gt;The target: a &lt;code&gt;blog-mcp&lt;/code&gt; server that lets Claude pull the repo, create and edit posts, add images, run &lt;code&gt;make github&lt;/code&gt; to build and publish, and push the source — all without leaving the conversation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What's Already in Place&lt;/h2&gt;
&lt;p&gt;This guide assumes the first MCP server setup from the previous post is done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 22 and supergateway installed on the host&lt;/li&gt;
&lt;li&gt;nginx with OAuth bearer token validation (the &lt;code&gt;map {}&lt;/code&gt; block and auth variable defined at the &lt;code&gt;http {}&lt;/code&gt; level)&lt;/li&gt;
&lt;li&gt;The OAuth server handling the OAuth authorization code + PKCE flow&lt;/li&gt;
&lt;li&gt;Firewall rules blocking direct access to internal supergateway ports&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're starting fresh, read that post first. The OAuth setup is the hard part; it isn't repeated here.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Blog Repo Structure&lt;/h2&gt;
&lt;p&gt;The blog is a &lt;a href="https://getpelican.com"&gt;Pelican&lt;/a&gt; static site. Two branches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;main&lt;/code&gt;&lt;/strong&gt; — Pelican source: &lt;code&gt;content/&lt;/code&gt;, &lt;code&gt;Makefile&lt;/code&gt;, &lt;code&gt;pelicanconf.py&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;gh-pages&lt;/code&gt;&lt;/strong&gt; — built HTML output; GitHub Pages serves this branch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;make github&lt;/code&gt; does three things: runs Pelican to build, runs &lt;code&gt;ghp-import&lt;/code&gt; to stage the output onto &lt;code&gt;gh-pages&lt;/code&gt;, then pushes that branch to GitHub.&lt;/p&gt;
&lt;p&gt;When you clone a GitHub Pages repo, &lt;code&gt;git&lt;/code&gt; defaults to the &lt;code&gt;gh-pages&lt;/code&gt; branch (it's the GitHub default). The source is on &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;git@github.com:yourname/yourname.github.io.git&lt;span class="w"&gt; &lt;/span&gt;/opt/blog
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/opt/blog
git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Adding the Second Server&lt;/h2&gt;
&lt;p&gt;The blog MCP runs on the same nginx port as the first MCP — no new public port needed. The difference is the URL path: the existing MCP server is reachable at &lt;code&gt;/mcp&lt;/code&gt;, and the blog MCP is exposed at &lt;code&gt;/blog-mcp&lt;/code&gt;. Internally, each path reverse-proxies to its own supergateway instance on a fixed localhost port.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;https://your-mcp-host/mcp       → supergateway (first MCP)   [localhost:XXXX]
https://your-mcp-host/blog-mcp  → supergateway (blog MCP)    [localhost:YYYY]  ← new
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The OAuth server, bearer token validation, and &lt;code&gt;/.well-known/&lt;/code&gt; discovery endpoints are shared — they're defined once and apply to all location blocks on the same server.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Setting Up the Pelican Environment&lt;/h2&gt;
&lt;p&gt;Debian Trixie blocks &lt;code&gt;pip install&lt;/code&gt; at the system level (&lt;code&gt;externally-managed-environment&lt;/code&gt;). Use a virtual environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;python3-pip&lt;span class="w"&gt; &lt;/span&gt;python3-venv&lt;span class="w"&gt; &lt;/span&gt;make
python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;/opt/blog-venv
/opt/blog-venv/bin/pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;/opt/blog/requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Makefile references &lt;code&gt;pelican&lt;/code&gt; and &lt;code&gt;ghp-import&lt;/code&gt; by bare name. Point it at the venv by adding two variables at the top:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;PELICAN&lt;/span&gt;&lt;span class="o"&gt;?=&lt;/span&gt;/opt/blog-venv/bin/pelican
&lt;span class="nv"&gt;GHPIMPORT&lt;/span&gt;&lt;span class="o"&gt;?=&lt;/span&gt;/opt/blog-venv/bin/ghp-import
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And update the &lt;code&gt;github&lt;/code&gt; target to use &lt;code&gt;$(GHPIMPORT)&lt;/code&gt; instead of &lt;code&gt;ghp-import&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Test manually before wiring anything into MCP:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/opt/blog&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;github
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;GitHub will reject commits from a private email address. Use the GitHub noreply address (find yours at github.com/settings/emails):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;user.email&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;12345+username@users.noreply.github.com&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;The MCP Server&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/opt/blog-mcp/server.js&lt;/code&gt; — a Node.js ES module MCP server. The tools:&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;blog_pull&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BLOG_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pull&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--rebase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;30000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Always call at the start of a session. Returns the git output.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;blog_list_posts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Walks &lt;code&gt;content/posts/&lt;/code&gt; recursively (posts with images live in subdirectories), parses Pelican metadata from each &lt;code&gt;.md&lt;/code&gt;/&lt;code&gt;.markdown&lt;/code&gt; file, returns one summary line per post sorted by date descending.&lt;/p&gt;
&lt;p&gt;Supports filtering by &lt;code&gt;status&lt;/code&gt; (&lt;code&gt;published&lt;/code&gt;, &lt;code&gt;draft&lt;/code&gt;, &lt;code&gt;all&lt;/code&gt;) and &lt;code&gt;category&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JS footgun to avoid:&lt;/strong&gt; In an async &lt;code&gt;for...of&lt;/code&gt; loop, &lt;code&gt;return&lt;/code&gt; exits the entire function — not just the current iteration. Use &lt;code&gt;continue&lt;/code&gt; to skip. This caused &lt;code&gt;blog_list_posts&lt;/code&gt; to return nothing on the first run because a post without a &lt;code&gt;Title&lt;/code&gt; triggered an early &lt;code&gt;return&lt;/code&gt; from the walk function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// exits walk(), finds nothing&lt;/span&gt;

&lt;span class="c1"&gt;// Right:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// skips this file, keeps walking&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;&lt;code&gt;blog_new_post&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.md`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;POSTS_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Writes a correctly-formatted Pelican metadata block. Hard-codes the author name and &lt;code&gt;Status: draft&lt;/code&gt;. The LLM supplies title, category, tags, and body.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;blog_edit_post&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Find/replace in a post file, identified by slug, filename, or relative path. Used to flip status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;blog_edit_post(slug=&amp;quot;my-post&amp;quot;, old_string=&amp;quot;Status: draft&amp;quot;, new_string=&amp;quot;Status: published&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;&lt;code&gt;blog_make&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Runs an allowlisted make target. The allowlist is &lt;code&gt;{ "html", "publish", "github" }&lt;/code&gt;. &lt;code&gt;clean&lt;/code&gt; is deliberately excluded — losing your output directory mid-session is annoying.&lt;/p&gt;
&lt;p&gt;The key detail: pass the venv &lt;code&gt;bin/&lt;/code&gt; to the make subprocess via &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MAKE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BLOG_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;120000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;VENV_BIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Without this, make can't find &lt;code&gt;pelican&lt;/code&gt; or &lt;code&gt;ghp-import&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make github&lt;/code&gt; takes 10–30 seconds depending on site size. Set &lt;code&gt;proxy_read_timeout 120s&lt;/code&gt; in nginx — the default 60s is marginal.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;blog_commit&lt;/code&gt; and &lt;code&gt;blog_push&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BLOG_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BLOG_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BLOG_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Separated intentionally — commit first, review &lt;code&gt;blog_status&lt;/code&gt;, then push.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;blog_add_image&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Node 22 has &lt;code&gt;fetch&lt;/code&gt; built in. Saves to &lt;code&gt;content/images/{subdir}/{filename}&lt;/code&gt; and returns the &lt;code&gt;{attach}&lt;/code&gt; path for use in post markdown:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;The Systemd Service&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/etc/systemd/system/blog-mcp.service&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Blog MCP Server&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="na"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/bin/bash -c &amp;#39;cd /opt/blog-mcp &amp;amp;&amp;amp; npm install --silent&amp;#39;&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/supergateway --stdio &amp;#39;node /opt/blog-mcp/server.js&amp;#39; &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;--port &amp;lt;internal-port&amp;gt; --outputTransport streamableHttp&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;BLOG_ROOT=/opt/blog&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;VENV_BIN=/opt/blog-venv/bin&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;daemon-reload
systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--now&lt;span class="w"&gt; &lt;/span&gt;blog-mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Block the internal supergateway port from external access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;nft&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;rule&lt;span class="w"&gt; &lt;/span&gt;inet&lt;span class="w"&gt; &lt;/span&gt;filter&lt;span class="w"&gt; &lt;/span&gt;input&lt;span class="w"&gt; &lt;/span&gt;iif&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lo&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tcp&lt;span class="w"&gt; &lt;/span&gt;dport&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;internal-port&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;drop
nft&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;ruleset&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/nftables.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;The nginx Block&lt;/h2&gt;
&lt;p&gt;Rather than a new server block, the blog MCP gets a new &lt;code&gt;location&lt;/code&gt; block inside the existing server. The &lt;code&gt;map {}&lt;/code&gt; for bearer token validation was already defined at the &lt;code&gt;http {}&lt;/code&gt; level, so the auth variable is available here automatically.&lt;/p&gt;
&lt;p&gt;Add this location to the existing MCP server block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/blog-mcp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mcp_auth_ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;add_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;WWW-Authenticate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Bearer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;realm=&amp;quot;blog-mcp&amp;quot;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;resource_metadata=&amp;quot;http://&lt;/span&gt;&lt;span class="nv"&gt;$http_host/.well-known/oauth-protected-resource&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;&amp;quot;error&amp;quot;:&amp;quot;unauthorized&amp;quot;}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:&amp;lt;internal-port&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_http_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_buffering&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;120s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;chunked_transfer_encoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;nginx&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;reload&lt;span class="w"&gt; &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;/.well-known/&lt;/code&gt; discovery endpoints, &lt;code&gt;/authorize&lt;/code&gt;, and &lt;code&gt;/token&lt;/code&gt; locations are already present in the existing server block and serve both MCPs without any changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A Session in Practice&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;blog_pull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Already up to date.&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;blog_new_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my-topic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;My Topic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Programming&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Created content/posts/2026-06-03_my-topic.md (Status: draft)&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;blog_edit_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my-topic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;old_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Status: draft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Status: published&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Edited content/posts/2026-06-03_my-topic.md&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;blog_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;add: my-topic post&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[main abc1234] add: my-topic post&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;blog_make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Done: Processed 432 articles... git push origin gh-pages&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;blog_push&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;main -&amp;gt; main&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Seven tool calls, one published post.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why Not Just Use the Existing MCP?&lt;/h2&gt;
&lt;p&gt;The first MCP server already has &lt;code&gt;write_text&lt;/code&gt; and &lt;code&gt;edit&lt;/code&gt; tools. In theory you could manage a blog repo with those. In practice, it's the wrong tool — you'd be reading files to learn the Pelican metadata format, manually constructing filenames, running shell commands out of band, and hoping nothing breaks in the &lt;code&gt;make&lt;/code&gt; step.&lt;/p&gt;
&lt;p&gt;A purpose-built MCP server encodes the domain knowledge: where posts live, what the metadata block looks like, which &lt;code&gt;make&lt;/code&gt; targets are safe to run, how to handle collateral like images and PDFs. The LLM just supplies the content.&lt;/p&gt;
&lt;p&gt;Having built the first MCP server already, a few things carry over directly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Domain-specific tools beat generic ones.&lt;/strong&gt; &lt;code&gt;blog_new_post&lt;/code&gt; is worth writing because it encodes the Pelican metadata format, the file naming convention, and the draft-by-default behaviour. The alternative — &lt;code&gt;write_text&lt;/code&gt; with a format the LLM has to reconstruct every time — is slower and more fragile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server-side computation is free tokens.&lt;/strong&gt; Alphabetical sorting, date stamping, filename construction — anything the server can do deterministically, it should. Don't ask the LLM to reason about things it doesn't need to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep the allowlist tight.&lt;/strong&gt; The &lt;code&gt;make&lt;/code&gt; target allowlist exists because mistakes are much easier to prevent than to fix — &lt;code&gt;make clean&lt;/code&gt; is not something you want an LLM calling by accident.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;proxy_buffering off&lt;/code&gt; is not optional.&lt;/strong&gt; Pelican builds can produce a lot of output. Without it, the nginx buffer fills, the connection stalls, and the tool times out without explanation.
Slug: second-mcp-pelican-blog&lt;/p&gt;</content><category term="Hardware &amp; Homelab"/><category term="MCP"/><category term="Pelican"/><category term="homelab"/><category term="Node.js"/><category term="nginx"/><category term="Claude"/></entry><entry><title>Building a Personal MCP Server for Your Second Brain</title><link href="http://lizard-spock.co.uk/building-a-personal-mcp-server.html" rel="alternate"/><published>2026-06-03T00:00:00+01:00</published><updated>2026-06-03T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-06-03:/building-a-personal-mcp-server.html</id><summary type="html">&lt;p&gt;How to build a self-hosted MCP server that exposes a markdown vault as Claude tools — list projects, search notes, write entries, and commit changes, all from a conversation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Claude can do more than answer questions — it can read and write files on a server you control. A &lt;a href="https://modelcontextprotocol.io"&gt;Model Context Protocol&lt;/a&gt; (MCP) server is the bridge. This post covers building a personal MCP server that exposes a markdown vault as tools: list projects, search notes, create entries, and commit changes — all from a Claude conversation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What It Is&lt;/h2&gt;
&lt;p&gt;The vault is a git repository of markdown files organised around the &lt;a href="https://fortelabs.com/blog/para/"&gt;PARA method&lt;/a&gt; (Projects, Areas, Resources, Archive). The MCP server is a Node.js process that reads and writes those files in response to tool calls. Claude never sees the raw filesystem — it sees a curated set of typed tools with well-defined behaviour.&lt;/p&gt;
&lt;p&gt;The key design principle: &lt;strong&gt;progressive disclosure&lt;/strong&gt;. Don't give Claude a raw file API. Give it typed tools that encode domain knowledge — what a project file looks like, where the frontmatter ends, which fields are indexable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The File Structure&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GTD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;due&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;areas&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ongoing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;responsibilities&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;goals&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cancelled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;excluded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retired&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;areas&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;longer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;research&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;deploy&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;systemd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;units&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;brain&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;brain&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;drafts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;drafts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blog_draft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mf"&gt;2026.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;see&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;below&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;oauth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OAuth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;covered&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;README&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;always&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;brief&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;every&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;INDEX&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;per&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_touched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next_action&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;material&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;CLAUDE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Claude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;glossary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;acronym&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deleted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;via&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MCP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each &lt;code&gt;projects/&amp;lt;id&amp;gt;.md&lt;/code&gt; has frontmatter: &lt;code&gt;domain&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;started&lt;/code&gt;, &lt;code&gt;last_touched&lt;/code&gt;, &lt;code&gt;next_action&lt;/code&gt;. These are the indexed fields — the tools scan frontmatter without reading bodies.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Tools&lt;/h2&gt;
&lt;h3&gt;Index tools (frontmatter-only scans)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;list_projects(status?, domain?, include_archive?)
list_actions(stage?, domain?, due_before?, include_archive?)
list_areas(domain?, include_archive?)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These walk their directory, parse only the frontmatter block from each file, and return one summary line per entry. The LLM starts here every session — it gets an overview without reading thousands of lines of content.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;include_archive&lt;/code&gt; defaults to &lt;code&gt;false&lt;/code&gt;. Archive-excluded-by-default keeps lists fast and focused on active material. Pass &lt;code&gt;include_archive: true&lt;/code&gt; only when you need historical entries.&lt;/p&gt;
&lt;h3&gt;Read and search&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;
&lt;span class="n"&gt;grep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;include_archive&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;across&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; is the backbone of reference and fact lookup. The glossary file grows to thousands of lines — never bulk-read it; always grep it.&lt;/p&gt;
&lt;h3&gt;Write tools&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;write_text(path, content)
append(path, content)
edit(path, old_string, new_string)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;edit&lt;/code&gt; does an exact find/replace of the first match. The server never rewrites a whole file when you're changing one line — it's faster and produces cleaner diffs in git history.&lt;/p&gt;
&lt;h3&gt;Archive tools&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;archive_project(id)
archive_action(id)
archive_area(id)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Move the file to &lt;code&gt;archive/&amp;lt;type&amp;gt;/&lt;/code&gt;, flip &lt;code&gt;status&lt;/code&gt; to &lt;code&gt;done&lt;/code&gt;. No manual file moves or editor needed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Auto-Commit on Write&lt;/h2&gt;
&lt;p&gt;Every write tool ends with a git commit and push:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BRAIN_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BRAIN_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`mcp: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BRAIN_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives you a full history of every change Claude made, browsable in any git client. It also means the vault stays in sync with the remote backup — no separate sync step needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gotcha: systemd doesn't set HOME.&lt;/strong&gt; When the service runs as root, &lt;code&gt;git push&lt;/code&gt; fails with &lt;code&gt;fatal: unable to set up default path&lt;/code&gt; because &lt;code&gt;/root/.gitconfig&lt;/code&gt; and &lt;code&gt;.git-credentials&lt;/code&gt; aren't found. Fix this with a systemd drop-in:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/systemd/system/vault-mcp.service.d/home.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;HOME=/root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Without this, writes succeed locally but the push silently fails — the vault appears to work but isn't backing up.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Sync Timer&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;brain-sync.timer&lt;/code&gt; runs every 10 minutes as a backstop:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BRAIN_ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;--rebase&lt;span class="w"&gt; &lt;/span&gt;--autostash
git&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BRAIN_ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This handles the case where a push failed mid-session. The remote git repo is the source of truth for backup; the server is the source of truth for content.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;supergateway&lt;/h2&gt;
&lt;p&gt;The MCP server is a Node.js stdio process. &lt;a href="https://github.com/supercorp-ai/supergateway"&gt;supergateway&lt;/a&gt; bridges it to a Streamable HTTP endpoint so it can be reached over the network:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;supergateway&lt;span class="w"&gt; &lt;/span&gt;--stdio&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;node /opt/brain/mcp/server.js&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--outputTransport&lt;span class="w"&gt; &lt;/span&gt;streamableHttp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--streamableHttpPath&lt;span class="w"&gt; &lt;/span&gt;/mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Claude Code connects via &lt;code&gt;~/.mcp.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;brain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://your-server:3001/mcp&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Exposing the Server: Cloudflare Tunnel&lt;/h2&gt;
&lt;p&gt;supergateway listens on a local port. To reach it from outside your network — for example from Claude.ai on mobile — you need to expose it. The cleanest option for a home server is a &lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/"&gt;Cloudflare Tunnel&lt;/a&gt;, which creates an outbound-only connection from your server to Cloudflare's edge. No open inbound ports, no port-forwarding rules.&lt;/p&gt;
&lt;h3&gt;Setup&lt;/h3&gt;
&lt;p&gt;Install &lt;code&gt;cloudflared&lt;/code&gt; on the same host as the MCP server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Debian/Ubuntu&lt;/span&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;https://pkg.cloudflare.com/cloudflare-main.gpg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;/usr/share/keyrings/cloudflare-main.gpg&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/sources.list.d/cloudflared.list
apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;cloudflared
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Authenticate and create the tunnel from the &lt;a href="https://one.dash.cloudflare.com/"&gt;Zero Trust dashboard&lt;/a&gt; (Networks → Tunnels → Create tunnel), or via CLI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cloudflared&lt;span class="w"&gt; &lt;/span&gt;tunnel&lt;span class="w"&gt; &lt;/span&gt;login
cloudflared&lt;span class="w"&gt; &lt;/span&gt;tunnel&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;mcp-tunnel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Configure the tunnel to route traffic to the local MCP port:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/cloudflared/config.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;tunnel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;&amp;lt;tunnel-id&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;credentials-file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/root/.cloudflared/&amp;lt;tunnel-id&amp;gt;.json&lt;/span&gt;

&lt;span class="nt"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;your-mcp-host.example.com&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http://localhost:3001&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;http_status:404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Add the DNS record and start the tunnel as a system service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cloudflared&lt;span class="w"&gt; &lt;/span&gt;tunnel&lt;span class="w"&gt; &lt;/span&gt;route&lt;span class="w"&gt; &lt;/span&gt;dns&lt;span class="w"&gt; &lt;/span&gt;mcp-tunnel&lt;span class="w"&gt; &lt;/span&gt;your-mcp-host.example.com
cloudflared&lt;span class="w"&gt; &lt;/span&gt;service&lt;span class="w"&gt; &lt;/span&gt;install
systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--now&lt;span class="w"&gt; &lt;/span&gt;cloudflared
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The MCP server is now reachable at &lt;code&gt;https://your-mcp-host.example.com/mcp&lt;/code&gt; without any firewall changes.&lt;/p&gt;
&lt;h3&gt;Connecting Claude Code via the Public URL&lt;/h3&gt;
&lt;p&gt;Once the tunnel is running, update &lt;code&gt;~/.mcp.json&lt;/code&gt; to use the public HTTPS URL instead of the local port:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;brain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://your-mcp-host.example.com/mcp&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is also the URL you'd add as a remote MCP connector in Claude.ai on mobile.&lt;/p&gt;
&lt;h3&gt;Security Warning: the Server is Unauthenticated at This Point&lt;/h3&gt;
&lt;p&gt;The tunnel exposes the MCP server to the public internet with no authentication. Anyone who discovers the URL can read and write every file in the vault. This is an accepted short-term risk for initial setup and testing, but should not be left in production.&lt;/p&gt;
&lt;p&gt;There are two options, but with an important caveat:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Zero Trust Access&lt;/strong&gt; — gates the tunnel at the edge before traffic reaches your server. You can restrict by email, IP, or issue service tokens. This works for Claude Code on the command line (where you can inject custom headers or use a service token), but &lt;strong&gt;does not work with Claude.ai's web MCP connector&lt;/strong&gt;. Claude.ai's connector follows the MCP auth spec and expects OAuth — it has no mechanism to supply a Cloudflare Access service token.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OAuth on the MCP server&lt;/strong&gt; — the approach covered in the next post. A minimal Node.js OAuth server sits behind nginx; nginx validates the bearer token on every &lt;code&gt;/mcp&lt;/code&gt; request. This is what Claude.ai's connector expects when you add a remote server in the UI, and it works across all clients — Claude Code, Claude.ai web, and Claude.ai on mobile.&lt;/p&gt;
&lt;p&gt;If you only need Claude Code access (local or on a trusted network), Cloudflare Access alone is sufficient. If you need Claude.ai web or mobile, OAuth is the only option.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The systemd Service&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/etc/systemd/system/vault-mcp.service&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Vault MCP Server&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="na"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/bin/bash -c &amp;#39;cd /opt/brain/mcp &amp;amp;&amp;amp; npm install --silent&amp;#39;&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/supergateway &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;--stdio &amp;#39;node /opt/brain/mcp/server.js&amp;#39; &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;--port 3001 &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;--outputTransport streamableHttp &lt;/span&gt;\
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;--streamableHttpPath /mcp&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;BRAIN_ROOT=/opt/brain&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;daemon-reload
systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--now&lt;span class="w"&gt; &lt;/span&gt;vault-mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ExecStartPre&lt;/code&gt; runs &lt;code&gt;npm install&lt;/code&gt; on every start — no manual dependency management after updates. Intentionally &lt;strong&gt;no&lt;/strong&gt; &lt;code&gt;git pull&lt;/code&gt; here: the service might start during a write, and a pull could clobber in-flight changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What You Get&lt;/h2&gt;
&lt;p&gt;Once running, a session looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;list_projects(status=&amp;quot;active&amp;quot;, domain=&amp;quot;code&amp;quot;)
→ 12 projects, one line each

read_text(&amp;quot;projects/my-project.md&amp;quot;)
→ full file contents

edit(&amp;quot;projects/my-project.md&amp;quot;,
     old_string=&amp;quot;next_action: review&amp;quot;,
     new_string=&amp;quot;next_action: ship v1&amp;quot;)
→ committed and pushed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;No file paths to remember, no git commands to run, no editor to open. The vault stays in sync and the history tells you exactly what changed and when.&lt;/p&gt;
&lt;p&gt;The next post covers adding OAuth authentication to this server so it can be safely exposed over the internet and connected to Claude.ai on mobile.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Full Source: server.js&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt;&lt;code&gt;mcp/server.js&lt;/code&gt; — v0.4.0&lt;/summary&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env node&lt;/span&gt;
&lt;span class="c1"&gt;// brain-mcp: custom MCP server for the personal-brain vault.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Design goal: progressive disclosure. Tools return the minimum tokens needed&lt;/span&gt;
&lt;span class="c1"&gt;// at each tier:&lt;/span&gt;
&lt;span class="c1"&gt;//   tier 1  list_projects / list_actions / list_areas  -&amp;gt; frontmatter summary only&lt;/span&gt;
&lt;span class="c1"&gt;//   tier 2  read_text      -&amp;gt; one full file, on demand&lt;/span&gt;
&lt;span class="c1"&gt;//   tier 3  grep           -&amp;gt; matching lines only, from glossary/reference/etc&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// All paths are vault-relative and sandboxed to BRAIN_ROOT.&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;McpServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@modelcontextprotocol/sdk/server/mcp.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StdioServerTransport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@modelcontextprotocol/sdk/server/stdio.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;zod&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:fs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:child_process&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;promisify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:util&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;execFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BRAIN_ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Files grep/list walks skip. Binary + noise.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SKIP_DIRS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node_modules&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.obsidian&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BINARY_EXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.epub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.jpeg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.gif&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.zip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.db&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GREP_LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// max match lines returned, guards against accidental whole-vault dumps&lt;/span&gt;

&lt;span class="c1"&gt;// PARA buckets: live dir -&amp;gt; archive dir. One file per item, frontmatter = index.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ARCHIVE_MAP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive/projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive/actions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;areas&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive/area&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Resolve a vault-relative path and refuse anything that escapes ROOT.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ne"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`path escapes vault root: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Parse a leading YAML frontmatter block into a flat key-&amp;gt;string map.&lt;/span&gt;
&lt;span class="c1"&gt;// Intentionally simple: our frontmatter is flat scalars, no nested YAML.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;---&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n---&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^([A-Za-z0-9_-]+):\s*(.*)$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^[&amp;quot;&amp;#39;]|[&amp;quot;&amp;#39;]$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Flip status/stage to archived and stamp an archived date into frontmatter.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stampArchived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^status:[ \t].*$/m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;status: archived&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^stage:[ \t].*$/m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;stage: archived&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;---&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n---&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^archived:/m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`\narchived: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;withFileTypes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SKIP_DIRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BINARY_EXT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Commit and push a single file after a write operation.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitAndPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`mcp: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pull&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--rebase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--autostash&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; — pushed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nothing to commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;` — push failed: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Commit a move (new file added, old path deleted) in one commit.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldRel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newRel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newRel&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldRel&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`mcp: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;oldRel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newRel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pull&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--rebase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--autostash&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; — pushed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;nothing to commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;` — push failed: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Shared: move &amp;lt;kind&amp;gt;/&amp;lt;id&amp;gt;.md into its archive dir, flipping status/stage.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;srcRel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.md`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;srcRel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;srcRel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; not found`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stampArchived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;destRel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ARCHIVE_MAP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.md`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destRel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;srcRel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;destRel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`archived &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;srcRel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;destRel&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;brain-mcp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.4.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ---- tier 1: derived indexes -----------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list_projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;List projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Derived project index. Scans frontmatter of projects/*.md (skips _template) and returns one summary line per project. Reads frontmatter only, never bodies. Start here every session.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;active&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;idle&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;done&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;filter by status&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;filter by domain, e.g. woodwork, code, 3d, trip, work&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;also scan archive/projects/ (default false)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive/projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_template.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;?&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;?&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | touched &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_touched&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;?&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | next: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next_action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(no projects match)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list_actions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;List actions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Derived action index (GTD). Scans frontmatter of actions/*.md (skips _template), returns one line per action sorted by due date (undated last). Reads frontmatter only.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;someday&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;next&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;waiting&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;filter by stage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;filter by tag, e.g. 3d, workshop, code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;due_before&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;only actions with due &amp;lt;= this YYYY-MM-DD&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;also scan archive/actions/ (default false)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;due_before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;actions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive/actions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_template.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;due_before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;due_before&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;?&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | due &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | tags &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(no actions match)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list_areas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;List areas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Derived area index. Scans frontmatter of areas/*.md (skips _template), one line per area of responsibility / long-term goal. Reads frontmatter only.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;filter by domain&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;also scan archive/area/ (default false)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;areas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive/area&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_template.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;?&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | updated &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;last-updated&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_touched&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;?&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(no areas match)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- tier 2: read one file -------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;read_text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Read file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Read a full vault file as text. Use after list_projects/grep narrows to one file.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-relative path, e.g. projects/woodwork-table.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- tier 3: content search ------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;grep&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Grep content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Search file contents by regex, return matching lines as &amp;#39;relpath:lineno: text&amp;#39;. Use for glossary/reference/fact lookups instead of reading whole files. Skips archive/ by default.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;JavaScript regex, case-insensitive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;limit to this file or subdir (default whole vault)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;also search archive/ (default false)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;i&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`bad regex: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFile&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveAbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;startInArchive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveAbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;archiveAbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;include_archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;startInArchive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveAbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;archiveAbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GREP_LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;relp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GREP_LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;relp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GREP_LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n(truncated)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(no matches)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- writes ----------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;append&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Append to file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Append text to a file (created if missing). For append-only data: journal lines, project Log entries.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-relative path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text to append; a trailing newline is added if absent&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitAndPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;append&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`appended to &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Edit file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Replace the first exact occurrence of old_string with new_string in a file. Targeted edit, no full rewrite.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;old_string&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;exact text to find (must be unique enough to match once)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;new_string&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;old_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new_string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old_string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;old_string not found&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new_string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;old_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitAndPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;edit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`edited &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;write_text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Write file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Create or overwrite a file. Use for new project/action/area files from a template; prefer append/edit for existing files.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitAndPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;write&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`wrote &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;List directory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;List files and directories at a vault path.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-relative dir, default root&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;withFileTypes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;SKIP_DIRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(empty)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- archive ---------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive_project&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Archive project&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Move projects/&amp;lt;id&amp;gt;.md to archive/projects/&amp;lt;id&amp;gt;.md and set status: archived.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;project id (filename without .md)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;projects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive_action&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Archive action&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Move actions/&amp;lt;id&amp;gt;.md to archive/actions/&amp;lt;id&amp;gt;.md and set stage: archived.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;action id (filename without .md)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;actions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;archive_area&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Archive area&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Move areas/&amp;lt;id&amp;gt;.md to archive/area/&amp;lt;id&amp;gt;.md and set status: archived.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;area id (filename without .md)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archiveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;areas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- glossary --------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;glossary_add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Add glossary entry&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Insert a new row into glossary.md in the correct alphabetical position by acronym. &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Server-side sort — do not read glossary.md first.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;acronym&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The acronym or short form, e.g. &amp;#39;ACE&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Full expansion, e.g. &amp;#39;AMBA Coherency Extensions&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Optional context or disambiguation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;acronym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;glossary.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDataRow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="sr"&gt;/.test(l) &amp;amp;&amp;amp; !/&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;/.test(l);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;acronymOf&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;|&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;acronym&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;insertAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDataRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;acronymOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;insertAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;insertAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;insertAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;/.test(l)) + 1;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newRow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`| &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;acronym&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; |`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;insertAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newRow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitAndPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;glossary_add&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`added &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;acronym&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39; at line &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;insertAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- blog drafts -----------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blog_draft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Create blog draft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Create a new blog draft in drafts/blog-{slug}.md with correct frontmatter. &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The &amp;#39;blog&amp;#39; tag is always included; add domain-specific tags alongside it.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kebab-case identifier, e.g. &amp;#39;mcp-oauth-setup&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Post title, used as the H1 heading&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Extra tags beyond &amp;#39;blog&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Initial markdown body content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allTags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blog&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blog&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`drafts/blog-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.md`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;---&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="sb"&gt;`id: blog-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="sb"&gt;`stage: draft`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="sb"&gt;`tags: [&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allTags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="sb"&gt;`created: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;---&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="sb"&gt;`# &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;trimEnd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitCommitAndPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;blog_draft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`created &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ---- delete ----------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;delete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Delete file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Permanently delete a vault file. A small set of core files (glossary.md, CLAUDE.md, README.md, todo.md) are protected.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vault-relative path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PROTECTED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;glossary.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CLAUDE.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;README.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;todo.md&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROTECTED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39; is a protected file and cannot be deleted`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolveInRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39; not found`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--cached&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;commit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`mcp: delete &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pull&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--rebase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--autostash&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;execFileAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-C&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;push&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`deleted &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; — pushed`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`deleted &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; (git: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;)`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;/details&gt;</content><category term="Hardware &amp; Homelab"/><category term="MCP"/><category term="Node.js"/><category term="homelab"/><category term="Claude"/><category term="supergateway"/><category term="git"/></entry><entry><title>Adding OAuth Authentication to a Self-Hosted MCP Server</title><link href="http://lizard-spock.co.uk/mcp-server-oauth-authentication.html" rel="alternate"/><published>2026-06-03T00:00:00+01:00</published><updated>2026-06-03T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-06-03:/mcp-server-oauth-authentication.html</id><summary type="html">&lt;p&gt;How to add OAuth 2.1 authentication to a self-hosted MCP server: nginx as an authenticating reverse proxy, a minimal Node.js OAuth server, and the key gotchas that will trip you up.&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you're running a self-hosted MCP server and exposing it over HTTP, you need authentication. Without it, anyone who can reach your port can invoke your tools.&lt;/p&gt;
&lt;p&gt;This post covers adding OAuth 2.1 auth to the vault MCP server from the previous post. Claude.ai's MCP connector implements the full &lt;a href="https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/authorization/"&gt;OAuth 2.1 authorization code + PKCE flow&lt;/a&gt; — it expects an OAuth Client ID and Secret, not a pre-shared bearer token. So that's what we build.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Starting Point&lt;/h2&gt;
&lt;p&gt;The server runs on a Debian container on Proxmox. The stack before this work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server.js&lt;/code&gt; — Node.js MCP server (stdio transport)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;supergateway&lt;/code&gt; — bridges stdio → Streamable HTTP on port 3001&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vault-mcp.service&lt;/code&gt; — systemd unit managing supergateway&lt;/li&gt;
&lt;li&gt;No authentication — port 3001 open, any client could read and write vault files&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Step 1: nginx as an Authenticating Reverse Proxy&lt;/h2&gt;
&lt;p&gt;supergateway has no built-in support for validating incoming authentication — &lt;code&gt;--oauth2Bearer&lt;/code&gt; is outbound-only, adding headers to requests supergateway makes upstream, not checking headers on requests coming in.&lt;/p&gt;
&lt;p&gt;The solution is to place nginx in front: move supergateway to an internal port, and let nginx handle bearer token validation before any request reaches the MCP server.&lt;/p&gt;
&lt;p&gt;Claude.ai's MCP connector requires three things from an OAuth server:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt; discovery endpoint (RFC 8414)&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;/authorize&lt;/code&gt; endpoint it can redirect the user's browser to&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;/token&lt;/code&gt; endpoint where it exchanges the authorization code for an access token&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;nginx serves the discovery endpoints statically and proxies &lt;code&gt;/authorize&lt;/code&gt; and &lt;code&gt;/token&lt;/code&gt; to a small Node.js OAuth server running on localhost.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Step 2: The Architecture&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;MCP client (Claude.ai)
      │  HTTP  Authorization: Bearer &amp;lt;token&amp;gt;
      ▼
 nginx :3001  (public)
      │  validates bearer token
      │  serves /.well-known/ OAuth discovery endpoints
      │  proxies /authorize and /token to OAuth server
      ▼
 OAuth server :3003  (localhost only)
      │  minimal OAuth 2.0 AS — auto-approves, issues static token
      ▼
 supergateway :3002  (localhost only, blocked by firewall)
      │
      ▼
 node server.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Step 3: Move supergateway, Install nginx, Block the Internal Port&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Move supergateway to an internal port&lt;/span&gt;
sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/--port 3001/--port 3002/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/systemd/system/vault-mcp.service
systemctl&lt;span class="w"&gt; &lt;/span&gt;daemon-reload&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;vault-mcp

&lt;span class="c1"&gt;# Install nginx&lt;/span&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;nginx

&lt;span class="c1"&gt;# Block external access to the internal supergateway port&lt;/span&gt;
nft&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;rule&lt;span class="w"&gt; &lt;/span&gt;inet&lt;span class="w"&gt; &lt;/span&gt;filter&lt;span class="w"&gt; &lt;/span&gt;input&lt;span class="w"&gt; &lt;/span&gt;iif&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lo&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tcp&lt;span class="w"&gt; &lt;/span&gt;dport&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3002&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;drop
nft&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;ruleset&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/nftables.conf
systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nftables
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Step 4: A Minimal OAuth Server in ~150 Lines of Node.js&lt;/h2&gt;
&lt;p&gt;For a single-user personal server, a "real" OAuth AS doesn't need to be complex. The key insight: &lt;strong&gt;auto-approve all authorization requests&lt;/strong&gt; — there's only one trusted client — and &lt;strong&gt;always issue the same static access token&lt;/strong&gt; so nginx validation stays simple and restarts don't force re-authentication.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// oauth-server.js (abridged)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:http&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:crypto&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAUTH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAUTH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAUTH_ACCESS_TOKEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pendingCodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// GET /authorize — auto-approve, redirect with code&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code_challenge_method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAUTH_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;invalid_client&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;base64url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pendingCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;codeChallenge&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code_challenge_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;60&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;state&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// POST /token — verify PKCE + client secret, return static token&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ... validate client_id, client_secret, code, PKCE verifier ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAUTH_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;token_type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Bearer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;31536000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// 1 year — effectively non-expiring&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3003&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;127.0.0.1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Failure 3: crypto.timingSafeEqual Crashes on Mismatched Lengths&lt;/h3&gt;
&lt;p&gt;When testing with a wrong &lt;code&gt;client_secret&lt;/code&gt;, the server crashed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;RangeError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;crypto.timingSafeEqual&lt;/code&gt; requires both buffers to be exactly the same byte length — it throws rather than returning &lt;code&gt;false&lt;/code&gt;. Always guard it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secretBuf&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client_secret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expectedBuf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OAUTH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secretOk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secretBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expectedBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretBuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expectedBuf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Without the guard, a wrong-length secret becomes a denial-of-service vector — the crash causes a 502 from nginx.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Step 5: Wire nginx to the OAuth Server&lt;/h2&gt;
&lt;p&gt;Updated nginx config — the key additions are proxying &lt;code&gt;/authorize&lt;/code&gt; and &lt;code&gt;/token&lt;/code&gt;, and adding the OAuth discovery metadata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/.well-known/oauth-protected-resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;default_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;add_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;&amp;quot;resource&amp;quot;:&amp;quot;http://$http_host&amp;quot;,&amp;quot;authorization_servers&amp;quot;:[&amp;quot;http://$http_host&amp;quot;]}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/.well-known/oauth-authorization-server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;default_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;add_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;&amp;quot;issuer&amp;quot;:&amp;quot;http://$http_host&amp;quot;,&amp;quot;authorization_endpoint&amp;quot;:&amp;quot;http://$http_host/authorize&amp;quot;,&amp;quot;token_endpoint&amp;quot;:&amp;quot;http://$http_host/token&amp;quot;,&amp;quot;response_types_supported&amp;quot;:[&amp;quot;code&amp;quot;],&amp;quot;grant_types_supported&amp;quot;:[&amp;quot;authorization_code&amp;quot;],&amp;quot;code_challenge_methods_supported&amp;quot;:[&amp;quot;S256&amp;quot;,&amp;quot;plain&amp;quot;],&amp;quot;token_endpoint_auth_methods_supported&amp;quot;:[&amp;quot;client_secret_post&amp;quot;]}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/authorize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:3003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_http_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:3003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_http_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/mcp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mcp_auth_ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kn"&gt;add_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;WWW-Authenticate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Bearer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;realm=&amp;quot;mcp&amp;quot;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;resource_metadata=&amp;quot;http://&lt;/span&gt;&lt;span class="nv"&gt;$http_host/.well-known/oauth-protected-resource&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kn"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;&amp;quot;error&amp;quot;:&amp;quot;unauthorized&amp;quot;}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:3002&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_buffering&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;300s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;chunked_transfer_encoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Step 6: The $http_host Gotcha&lt;/h2&gt;
&lt;p&gt;Per RFC 8414, the &lt;code&gt;issuer&lt;/code&gt; in OAuth metadata must match the URL used to discover it. A mismatch is a valid reason for a client to reject the metadata entirely. Even if the connector didn't strictly validate the issuer, a private LAN IP is unreachable from Claude.ai's infrastructure if the token exchange happens server-side.&lt;/p&gt;
&lt;p&gt;This surfaces as the OAuth flow appearing to start but never completing — the nginx access log shows the browser hitting &lt;code&gt;/authorize&lt;/code&gt; (302 ✓), then Claude.ai's connector hitting &lt;code&gt;/mcp&lt;/code&gt; again with 401, with no &lt;code&gt;/token&lt;/code&gt; call in between.&lt;/p&gt;
&lt;p&gt;If you're seeing this, check your &lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt; response. A hardcoded private IP or a hostname that doesn't match the public domain the client is connecting through is almost certainly the cause:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://192.168.x.x:3001&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;token_endpoint&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://192.168.x.x:3001/token&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use nginx's &lt;code&gt;$http_host&lt;/code&gt; variable in the metadata JSON so the URLs always reflect whatever hostname the request arrived on:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;&amp;quot;issuer&amp;quot;:&amp;quot;http://$http_host&amp;quot;,&amp;quot;token_endpoint&amp;quot;:&amp;quot;http://$http_host/token&amp;quot;,...}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After this change: ✅ Claude.ai completed the full OAuth flow and connected successfully.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The OAuth Server systemd Service&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/etc/systemd/system/oauth-server.service&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;MCP OAuth Server&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;node /opt/mcp-oauth/oauth-server.js&lt;/span&gt;
&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;OAUTH_CLIENT_ID=your-client-id&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;OAUTH_CLIENT_SECRET=your-client-secret&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;OAUTH_ACCESS_TOKEN=your-static-bearer-token&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The static &lt;code&gt;OAUTH_ACCESS_TOKEN&lt;/code&gt; is the value in nginx's &lt;code&gt;map {}&lt;/code&gt; block. Generate all three secrets with &lt;code&gt;openssl rand -base64 48&lt;/code&gt; and treat them like passwords.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Lessons&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Lesson&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;proxy_buffering off&lt;/code&gt; is essential for MCP streaming — without it clients hang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;crypto.timingSafeEqual&lt;/code&gt; throws on mismatched buffer lengths — always check lengths first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Never hardcode IPs in OAuth metadata — use &lt;code&gt;$http_host&lt;/code&gt; so the issuer always matches the discovery URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nftables iif != "lo" tcp dport XXXX drop&lt;/code&gt; is the cleanest way to lock an internal service to loopback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The minimal OAuth server approach (auto-approve + static token) is a good fit for a personal server. It's not a security shortcut — the actual secret is the client credentials, PKCE is enforced, and the issued token is what nginx validates. The "simplification" is just removing the login page that makes no sense for a single trusted client.&lt;/p&gt;
&lt;p&gt;The next post covers adding a second MCP server to the same host — reusing all of this OAuth infrastructure for a blog-management server on a different URL path.&lt;/p&gt;</content><category term="Hardware &amp; Homelab"/><category term="MCP"/><category term="OAuth"/><category term="nginx"/><category term="Node.js"/><category term="homelab"/><category term="security"/><category term="Claude"/></entry><entry><title>SoC Article 10: The SoC design flow - From specification to silicon</title><link href="http://lizard-spock.co.uk/soc-article-10-design-flow.html" rel="alternate"/><published>2026-06-03T00:00:00+01:00</published><updated>2026-06-03T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-06-03:/soc-article-10-design-flow.html</id><summary type="html">&lt;p&gt;A complete walkthrough of the SoC design flow, from specification and architecture through RTL coding, functional verification, synthesis, physical design, and sign-off to tape-out and fabrication.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 10 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Designing a System-on-Chip (SoC) is not a single act of creation. It is a disciplined, iterative engineering process. A team of hundreds, sometimes thousands, of engineers spends one to three years completing it. The &lt;strong&gt;design flow&lt;/strong&gt; is the structured sequence of phases, tools, and verification checkpoints that transforms an initial concept into a physical chip ready for manufacture.&lt;/p&gt;
&lt;p&gt;This article walks through the entire flow, from the first line of specification to the GDSII file sent to the foundry, explaining what happens at each stage and why each step is necessary.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why the flow matters&lt;/h2&gt;
&lt;p&gt;Semiconductor manufacturing is extraordinarily expensive and irreversible. Taping out a 7 nm chip can cost $20--50 million in NRE (Non-Recurring Engineering) costs. A single bug that reaches silicon can require a complete re-spin, delaying the product by 6--12 months and costing tens of millions of dollars. Examples include a wrong bit in a register address or a timing path that fails at high temperature.&lt;/p&gt;
&lt;p&gt;The design flow exists to make bugs cheap to find early and impossible to reach silicon. Every stage adds more confidence that the design will work correctly when manufactured.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Overview of the flow&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article10/10-design-flow-HQ.png"&gt;&lt;img alt="SoC design flow overview" src="http://lizard-spock.co.uk/images/SoC/Article10/10-design-flow-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Stage 1: Specification&lt;/h2&gt;
&lt;p&gt;The design begins with a &lt;strong&gt;specification document&lt;/strong&gt;: a detailed description of what the chip must do, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Functional requirements:&lt;/strong&gt; what operations it must support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance targets:&lt;/strong&gt; minimum operating frequency, latency, throughput&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power budget:&lt;/strong&gt; maximum active power, standby power&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Area budget:&lt;/strong&gt; die size constraint (directly affects cost)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interface requirements:&lt;/strong&gt; which external protocols must be supported&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operating conditions:&lt;/strong&gt; voltage range, temperature range, process corners&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;System architects and product managers write specifications. Specifications are often imprecise at first. The design process refines them as constraints become clearer.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Stage 2: Architecture&lt;/h2&gt;
&lt;p&gt;The architecture phase translates requirements into a block-level design. Key decisions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IP selection:&lt;/strong&gt; Which CPU core? Which bus protocol? Which memory interface? Purchased intellectual property (IP), such as an ARM Cortex-A core or Synopsys DesignWare USB, or custom-designed?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware/software (HW/SW) partitioning:&lt;/strong&gt; Which functions are implemented in fixed hardware and which run in software on the CPU? Hardware is faster and more energy-efficient but inflexible; software is flexible but slower.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory map:&lt;/strong&gt; Where in the 32-bit or 64-bit address space does each block live? The memory map is fundamental: once set, changing it breaks all software.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Block diagram and interface specification:&lt;/strong&gt; Defining the ports of each block and how they connect to the bus.&lt;/p&gt;
&lt;p&gt;The output of the architecture phase is an &lt;strong&gt;architecture specification&lt;/strong&gt;: a document that all downstream engineers work from.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Stage 3: RTL Design&lt;/h2&gt;
&lt;p&gt;Each block in the architecture is coded in SystemVerilog or VHDL at register transfer level (RTL). This phase is typically the longest and involves the most engineers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IP integration:&lt;/strong&gt; Licensed IP is delivered as RTL source files or encrypted IP. They are instantiated in the top-level integration and connected to the bus fabric.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Custom RTL:&lt;/strong&gt; Blocks that do not exist as purchasable IP are designed from scratch by the team's RTL engineers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lint checking:&lt;/strong&gt; Automated tools scan the RTL for common coding mistakes (undriven signals, unintended latches, undeclared ports) before simulation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;RTL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Lint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Checks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Combinational&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;combinational&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Multi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;driven&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;drivers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;wire&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Unintended&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;latch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;always_comb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mismatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;8&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kt"&gt;bit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;16&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kt"&gt;bit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flip&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;flop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;✗&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;propagation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uninitialized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Stage 4: Functional Verification&lt;/h2&gt;
&lt;p&gt;Verification is the process of proving (to sufficient confidence) that the RTL correctly implements the specification. It is the biggest investment in a modern SoC project, typically consuming 60--70% of total design effort and schedule.&lt;/p&gt;
&lt;h3&gt;Simulation&lt;/h3&gt;
&lt;p&gt;Engineers simulate the RTL against a &lt;strong&gt;testbench&lt;/strong&gt; that models the SoC's environment. Most modern SoC testbenches use the Universal Verification Methodology (UVM), a standardised framework for building reusable verification components:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article10/10-verification-uvm-HQ.png"&gt;&lt;img alt="UVM verification environment" src="http://lizard-spock.co.uk/images/SoC/Article10/10-verification-uvm-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Coverage-Driven Verification&lt;/h3&gt;
&lt;p&gt;Engineers define &lt;strong&gt;coverage metrics&lt;/strong&gt;: measurable goals that indicate whether enough of the design space has been tested:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code coverage:&lt;/strong&gt; Has every line and branch of the RTL been exercised?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Functional coverage:&lt;/strong&gt; Have all interesting combinations of inputs occurred? For example: FIFO full during reset, all AXI burst lengths, or all interrupt priority combinations.&lt;/p&gt;
&lt;p&gt;Verification is complete when coverage goals are met, all bugs have been fixed and re-verified, and a formal sign-off review has been passed.&lt;/p&gt;
&lt;h3&gt;Formal Verification&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Formal verification&lt;/strong&gt; uses mathematical techniques to prove properties about the design, rather than testing with specific inputs. It can exhaustively check all possible input sequences (up to a bounded depth).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;Formal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Simulation&lt;/span&gt;:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Simulation&lt;/span&gt;:&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;Tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sequences&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chosen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;engineers&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;→&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;miss&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;corner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cases&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;→&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Fast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;common&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cases&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Formal&lt;/span&gt;:&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;Mathematically&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;proves&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;properties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;hold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;inputs&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;→&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bounded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;depth&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;→&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bugs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;simulation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;never&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;would&lt;/span&gt;
&lt;span class="w"&gt;                      &lt;/span&gt;→&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Slower&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;limited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;smaller&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;blocks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Formal verification tools (Cadence JasperGold, Synopsys VC Formal) are used for:
- Block-level property checking
- Clock domain crossing analysis
- Security property verification
- Register model consistency&lt;/p&gt;
&lt;h3&gt;Hardware Emulation&lt;/h3&gt;
&lt;p&gt;For large SoCs, simulation is too slow to run real software. &lt;strong&gt;Hardware emulators&lt;/strong&gt; (Cadence Palladium, Synopsys ZeBu) compile the RTL into custom field-programmable gate arrays (FPGAs) or application-specific integrated circuits (ASICs) that run 100--1000x faster than software simulation. This allows booting Linux, running real drivers, and executing application-level tests before silicon is available.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Stage 5: Synthesis&lt;/h2&gt;
&lt;p&gt;Once RTL verification is complete, &lt;strong&gt;synthesis&lt;/strong&gt; converts the RTL into a gate-level netlist using a specific technology's standard cell library.&lt;/p&gt;
&lt;h3&gt;Synthesis Constraints&lt;/h3&gt;
&lt;p&gt;The synthesis tool needs to know the timing requirements, expressed in &lt;strong&gt;Synopsys Design Constraints (SDC)&lt;/strong&gt; format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# SDC constraint example&lt;/span&gt;
&lt;span class="nv"&gt;create_clock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cpu_clk&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;period&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;get_ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;clk_in&lt;span class="k"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 2 GHz clock&lt;/span&gt;
&lt;span class="nv"&gt;set_input_delay&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;clock&lt;span class="w"&gt; &lt;/span&gt;cpu_clk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;all_inputs&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;set_output_delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;clock&lt;span class="w"&gt; &lt;/span&gt;cpu_clk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;all_outputs&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;set_max_fanout&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;all_registers&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The tool then maps each RTL construct to available cells from the &lt;strong&gt;standard cell library&lt;/strong&gt;: a characterised set of gates (AND2, OR3, DFF, MUX2, ...) provided by the foundry or cell library vendor.&lt;/p&gt;
&lt;h3&gt;Gate-Level Netlist&lt;/h3&gt;
&lt;p&gt;The output is a &lt;strong&gt;gate-level netlist&lt;/strong&gt;: a flat or hierarchical description of the design in terms of specific cell instances and their interconnections:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example gate-level netlist fragment (post-synthesis)&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter_synth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Cell instances from the standard cell library:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;DFF_X1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FF0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;DFF_X1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FF1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n14&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;AND2_X1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ZN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n12&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;XOR2_X1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n14&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// ... hundreds or thousands of cells&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Stage 6: Physical design (back-end)&lt;/h2&gt;
&lt;p&gt;Physical design transforms the logical netlist into a physical layout: geometric shapes on the silicon used to manufacture the chip. This is the "back-end" of the design flow.&lt;/p&gt;
&lt;h3&gt;Floorplanning&lt;/h3&gt;
&lt;p&gt;The die is partitioned into regions, and major blocks are placed at approximate locations. Phase-locked loops (PLLs), physical layer interfaces (PHYs), static RAM (SRAM), and IP hard macros are placed at fixed locations first.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article10/10-physical-design-HQ.png"&gt;&lt;img alt="Physical design floorplan" src="http://lizard-spock.co.uk/images/SoC/Article10/10-physical-design-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Placement&lt;/h3&gt;
&lt;p&gt;The automatic &lt;strong&gt;placement&lt;/strong&gt; tool places all standard cells within each block's area, minimising wire length and ensuring timing constraints can be met.&lt;/p&gt;
&lt;h3&gt;Clock tree synthesis&lt;/h3&gt;
&lt;p&gt;Clock tree synthesis (CTS) builds the clock distribution tree that delivers the clock signal with minimal skew and jitter to every flip-flop. As discussed in Article 06, CTS is one of the most critical and time-consuming back-end steps.&lt;/p&gt;
&lt;h3&gt;Routing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Routing&lt;/strong&gt; connects all cell inputs and outputs with metal wires according to the netlist. Modern designs have 10--15 metal layers; the router must find paths for millions of wires without violations.&lt;/p&gt;
&lt;h3&gt;Verification After Routing: Sign-off Checks&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Physical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Verification&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;Checks:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;DRC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Design&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Check&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Minimum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;wire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;via&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Antenna&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accumulation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;during&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manufacture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foundry&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;LVS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Layout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Schematic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Does&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;physical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;netlist&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ERC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Electrical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Check&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nets&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Floating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Stage 7: Sign-off&lt;/h2&gt;
&lt;p&gt;Before tape-out, the design must pass a series of &lt;strong&gt;sign-off checks&lt;/strong&gt; that confirm it works correctly after fabrication:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static Timing Analysis (STA):&lt;/strong&gt; Verifies that every logic path in the design meets the required timing, across all process corners (fast/slow transistors), voltages (nominal/low), and temperatures (cold/hot):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;STA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Timing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Example&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Launch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FF&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="n"&gt;Capture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FF&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FF1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Comb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Logic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;──►&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FF2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Setup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Clock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Setup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.47&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arrival&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Launch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Slack&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.47&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;✓&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PASS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;slack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;were&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;negative&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;would&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resizing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;restructuring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;logic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relaxing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Power Analysis:&lt;/strong&gt; Estimates active and leakage power consumption across typical and worst-case workloads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IR Drop Analysis:&lt;/strong&gt; Verifies that the power distribution network delivers sufficient voltage to every cell, accounting for resistive drops through the metal power rails.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Stage 8: Tape-out and fabrication&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tape-out&lt;/strong&gt; is the formal submission of the Graphic Design System II (GDSII) file to the foundry: the geometric database of every polygon on every mask layer used to manufacture the chip.&lt;/p&gt;
&lt;p&gt;After tape-out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Mask making:&lt;/strong&gt; The foundry converts GDSII into photomasks (~$1M--$5M per mask set for advanced nodes).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wafer fabrication:&lt;/strong&gt; An 8--24 week process, typically 400--900 process steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wafer probing:&lt;/strong&gt; Each die is tested electrically before dicing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dicing:&lt;/strong&gt; The wafer is cut into individual dies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packaging:&lt;/strong&gt; Each die is bonded into a package such as a ball grid array (BGA) or quad flat package (QFP).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Final test:&lt;/strong&gt; Packaged devices are tested at speed and temperature.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Characterisation:&lt;/strong&gt; A sample of devices is measured to build a statistical model of the process.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;State machine of the design flow&lt;/h2&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_ab730b9bce731cd4bdc47370270712af.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Tools of the trade&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Industry Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RTL Lint&lt;/td&gt;
&lt;td&gt;Mentor Questa Lint, Synopsys Spyglass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simulation&lt;/td&gt;
&lt;td&gt;Synopsys VCS, Cadence Xcelium, Mentor Questa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formal Verification&lt;/td&gt;
&lt;td&gt;Cadence JasperGold, Synopsys VC Formal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emulation&lt;/td&gt;
&lt;td&gt;Cadence Palladium, Synopsys ZeBu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synthesis&lt;/td&gt;
&lt;td&gt;Synopsys Design Compiler / Fusion Compiler, Cadence Genus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical Design&lt;/td&gt;
&lt;td&gt;Cadence Innovus, Synopsys IC Compiler II&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STA Sign-off&lt;/td&gt;
&lt;td&gt;Synopsys PrimeTime, Cadence Tempus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical Verification&lt;/td&gt;
&lt;td&gt;Siemens Calibre, Cadence PVS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open-Source Alternatives&lt;/td&gt;
&lt;td&gt;Yosys (synthesis), OpenROAD (PD), KLayout (viewing GDSII)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;The SoC design flow is a rigorous, multi-stage process designed to catch errors at the cheapest possible point: before they reach silicon. It begins with specification and architecture, progresses through RTL coding and verification, then synthesis, physical design, and sign-off, culminating in tape-out. Verification is the largest time investment, spanning simulation, assertion-based checking, and formal methods. Physical design transforms the logical netlist into real geometry on the wafer. Every phase generates new insights that may require revisiting earlier phases. The flow is iterative, not linear.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Advanced articles this topic connects to&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;RTL Synthesis and Timing Closure:&lt;/em&gt; Constraint writing, ECO, multi-corner STA&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Physical Design: Floorplanning and Place-and-Route:&lt;/em&gt; In-depth CTS, routing, signoff&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Post-Silicon Debug and Validation:&lt;/em&gt; What happens when the chip comes back from the foundry&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-09-hardware-description-languages-rtl-design.html"&gt;Article 09 -- Hardware Description Languages&lt;/a&gt;&lt;/em&gt; | &lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-11-hw-sw-co-design.html"&gt;Article 11 -- HW/SW Co-Design&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="RTL"/><category term="Verification"/><category term="Synthesis"/><category term="Physical Design"/><category term="Tape-out"/><category term="VLSI"/></entry><entry><title>AMBA low power interface: Q-Channel and P-Channel explained</title><link href="http://lizard-spock.co.uk/amba-lpi-q-channel-p-channel.html" rel="alternate"/><published>2026-05-19T00:00:00+01:00</published><updated>2026-05-19T00:00:00+01:00</updated><author><name>Morgan Prior</name></author><id>tag:lizard-spock.co.uk,2026-05-19:/amba-lpi-q-channel-p-channel.html</id><summary type="html">&lt;p&gt;A practical guide to AMBA Low Power Interface handshake protocols: Q-Channel and P-Channel signal tables, state machines, timing diagrams, and design rules from ARM IHI0068.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Engineering/AMBA_LPI/lpi-hero-HQ.png"&gt;&lt;img alt="SoC power domain management with PMU controlling gated and active domains" src="http://lizard-spock.co.uk/images/Engineering/AMBA_LPI/lpi-hero-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modern System on Chip (SoC) designs contain dozens of independent IP blocks, each drawing power even when inactive. The AMBA (Advanced Microcontroller Bus Architecture) Low Power Interface (LPI) provides every block with a standardized handshake protocol to coordinate safe clock and power removal. This post covers both LPI interface types, Q-Channel and P-Channel, with timing diagrams and state machines drawn from the ARM IHI0068 specification.&lt;/p&gt;
&lt;h2&gt;Why low-power handshaking matters&lt;/h2&gt;
&lt;p&gt;Every IP block on a System on Chip (SoC) draws power even when it sits idle. Clock gating and power gating are the primary tools for reducing that static power drain. Clock gating stops the clock to a block without removing its power rail. Power gating removes the power rail entirely for deeper power savings. Both techniques corrupt internal state if the controller applies them while the block has outstanding transactions or unflushed pipelines. A handshake protocol requires the block to signal that it has finished all activity before the controller removes its clock or power. Without this handshake, the controller has no reliable way to determine whether gating is safe.&lt;/p&gt;
&lt;h2&gt;Signal naming conventions&lt;/h2&gt;
&lt;p&gt;AMBA (Advanced Microcontroller Bus Architecture) LPI (Low Power Interface) uses a consistent naming scheme across all its interfaces. Understanding this scheme makes signal tables and timing diagrams much easier to read.&lt;/p&gt;
&lt;h3&gt;Active-level suffixes&lt;/h3&gt;
&lt;p&gt;Signals with an &lt;code&gt;n&lt;/code&gt; suffix are active low: the signal is asserted when driven to logic 0. &lt;code&gt;QREQn&lt;/code&gt; (quiescence request) is active when its value is 0, meaning the controller is requesting quiescence. Driving it to 1 deasserts the request. Signals without an &lt;code&gt;n&lt;/code&gt; suffix are active high: &lt;code&gt;QDENY&lt;/code&gt; is asserted when its value is 1, meaning the device is denying the request.&lt;/p&gt;
&lt;h3&gt;Width notation&lt;/h3&gt;
&lt;p&gt;Multi-bit signals carry a width specifier in brackets. &lt;code&gt;PSTATE[M-1:0]&lt;/code&gt; is M bits wide, where M is implementation defined. &lt;code&gt;PACTIVE[N-1:0]&lt;/code&gt; is N bits wide, with one bit per monitored power domain. Single-bit signals carry no width notation.&lt;/p&gt;
&lt;h3&gt;Parity check suffix&lt;/h3&gt;
&lt;p&gt;Issue D of IHI0068 introduced optional parity protection. Each primary signal gains a companion check signal with the &lt;code&gt;CHK&lt;/code&gt; suffix. &lt;code&gt;QREQn&lt;/code&gt; pairs with &lt;code&gt;QREQCHK&lt;/code&gt;, and &lt;code&gt;QACCEPTn&lt;/code&gt; pairs with &lt;code&gt;QACCEPTCHK&lt;/code&gt;. Each check signal carries the odd parity of its primary signal, so the XOR of a signal and its check signal is always 1 when no fault is present.&lt;/p&gt;
&lt;h2&gt;Q-Channel: the quiescence handshake&lt;/h2&gt;
&lt;p&gt;Q-Channel (Quiescence Channel) handles the most common low-power scenario: safely stopping a device so the controller can gate its clock or power. It evolved from the AXI Low Power Interface signals &lt;code&gt;CSYSREQ&lt;/code&gt;, &lt;code&gt;CSYSACK&lt;/code&gt;, and &lt;code&gt;CACTIVE&lt;/code&gt;, and remains backward compatible with AXI LPI devices when &lt;code&gt;QDENY&lt;/code&gt; is absent.&lt;/p&gt;
&lt;h3&gt;Q-Channel signals&lt;/h3&gt;
&lt;p&gt;Q-Channel uses four signals. The controller drives &lt;code&gt;QREQn&lt;/code&gt;; the device drives the remaining three.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Active level&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Introduced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QREQn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller to device&lt;/td&gt;
&lt;td&gt;LOW&lt;/td&gt;
&lt;td&gt;Requests the device to quiesce&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QACCEPTn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;td&gt;LOW&lt;/td&gt;
&lt;td&gt;Device has quiesced; clock or power gating is safe&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QDENY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;Device cannot quiesce at this time&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;Device has pending activity&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The specification requires &lt;code&gt;QREQn&lt;/code&gt; to be register-driven at the controller. &lt;code&gt;QACCEPTn&lt;/code&gt; and &lt;code&gt;QDENY&lt;/code&gt; must be register-driven at the device. &lt;code&gt;QACTIVE&lt;/code&gt; can be a combinational OR of internal activity signals.&lt;/p&gt;
&lt;h3&gt;Q-Channel state machine&lt;/h3&gt;
&lt;p&gt;The Q-Channel defines 6 legal states. The values of &lt;code&gt;QREQn&lt;/code&gt;, &lt;code&gt;QACCEPTn&lt;/code&gt;, and &lt;code&gt;QDENY&lt;/code&gt; determine the current state entirely. Two paths exist: an accepted path where the device quiesces, and a denied path where the device refuses and the controller withdraws.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_cf05e37bef7f1ae61226ce8dfb3ffc42.svg"&gt;&lt;/p&gt;
&lt;p&gt;The state table lists every legal combination and the corresponding device status:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;&lt;code&gt;QREQn&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;QACCEPTn&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;QDENY&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Device status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Q_RUN&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Operational&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q_REQUEST&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Draining activity; quiescence requested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q_STOPPED&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Quiescent; clock or power gating is safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q_EXIT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Restoring clock or power (implementation-defined delay)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q_DENIED&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Device denied the request; remains operational&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q_CONTINUE&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Controller deasserted &lt;code&gt;QREQn&lt;/code&gt; after denial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The combination &lt;code&gt;QACCEPTn=0, QDENY=1&lt;/code&gt; is illegal and must never occur.&lt;/p&gt;
&lt;h3&gt;Accepted handshake&lt;/h3&gt;
&lt;p&gt;The accepted path runs from Q_RUN through Q_REQUEST, Q_STOPPED, and Q_EXIT and back to Q_RUN. The controller asserts &lt;code&gt;QREQn&lt;/code&gt; low to start the handshake. The device drains its outstanding activity, drops &lt;code&gt;QACTIVE&lt;/code&gt;, then asserts &lt;code&gt;QACCEPTn&lt;/code&gt; low to confirm it is quiescent. The controller can gate the clock or power during Q_STOPPED. When the controller is ready to restore the device, it deasserts &lt;code&gt;QREQn&lt;/code&gt; high, entering Q_EXIT. The device responds by deasserting &lt;code&gt;QACCEPTn&lt;/code&gt; high once it detects the restored clock, returning to Q_RUN.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_5e294f2eb1b446ccb9e0ce87f109aa56.svg"&gt;&lt;/p&gt;
&lt;h3&gt;Denied handshake&lt;/h3&gt;
&lt;p&gt;The denied path branches from Q_REQUEST when the device cannot quiesce. The device asserts &lt;code&gt;QDENY&lt;/code&gt; high while keeping &lt;code&gt;QACCEPTn&lt;/code&gt; high, entering Q_DENIED. The controller must withdraw its request by deasserting &lt;code&gt;QREQn&lt;/code&gt; high, entering Q_CONTINUE. The device then deasserts &lt;code&gt;QDENY&lt;/code&gt; low, and both sides return to Q_RUN. The controller can reattempt the request after it observes Q_RUN.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_592e7ab390b66186a7c730a2e4b4c8ff.svg"&gt;&lt;/p&gt;
&lt;h3&gt;Handshake transition rules&lt;/h3&gt;
&lt;p&gt;The IHI0068 specification defines strict conditions for each signal transition. These rules prevent illegal states from being reached.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QREQn&lt;/code&gt; rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QREQn&lt;/code&gt; can fall only when &lt;code&gt;QACCEPTn&lt;/code&gt; is HIGH and &lt;code&gt;QDENY&lt;/code&gt; is LOW (the interface is in Q_RUN).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QREQn&lt;/code&gt; can rise when both &lt;code&gt;QACCEPTn&lt;/code&gt; and &lt;code&gt;QDENY&lt;/code&gt; are LOW (Q_STOPPED), or both are HIGH (Q_CONTINUE).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;QACCEPTn&lt;/code&gt; rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QACCEPTn&lt;/code&gt; can fall only when &lt;code&gt;QREQn&lt;/code&gt; is LOW and &lt;code&gt;QDENY&lt;/code&gt; is LOW.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QACCEPTn&lt;/code&gt; can rise only when &lt;code&gt;QREQn&lt;/code&gt; is HIGH and &lt;code&gt;QDENY&lt;/code&gt; is LOW.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;QDENY&lt;/code&gt; rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QDENY&lt;/code&gt; can rise only when &lt;code&gt;QREQn&lt;/code&gt; is LOW and &lt;code&gt;QACCEPTn&lt;/code&gt; is HIGH.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QDENY&lt;/code&gt; can fall only when &lt;code&gt;QREQn&lt;/code&gt; is HIGH and &lt;code&gt;QACCEPTn&lt;/code&gt; is HIGH.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;P-Channel: multi-state power transitions&lt;/h2&gt;
&lt;p&gt;P-Channel (Power Channel) handles power management for devices that support multiple distinct power states. Each state can have different voltages, clock frequencies, and retention configurations. Use P-Channel when Q-Channel's binary run-stop model is insufficient for the power architecture. P-Channel communicates the target state directly to the device through an implementation-defined &lt;code&gt;PSTATE&lt;/code&gt; encoding, rather than an implicit quiesce signal.&lt;/p&gt;
&lt;h3&gt;P-Channel signals&lt;/h3&gt;
&lt;p&gt;P-Channel uses 5 signals. The controller drives &lt;code&gt;PSTATE&lt;/code&gt; and &lt;code&gt;PREQ&lt;/code&gt;; the device drives &lt;code&gt;PACTIVE&lt;/code&gt;, &lt;code&gt;PACCEPT&lt;/code&gt;, and &lt;code&gt;PDENY&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Active level&lt;/th&gt;
&lt;th&gt;Width&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Introduced&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PACTIVE[N-1:0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;N bits&lt;/td&gt;
&lt;td&gt;Device activity indication; one bit per domain&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PSTATE[M-1:0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller to device&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;M bits&lt;/td&gt;
&lt;td&gt;Target power state (implementation defined)&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PREQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller to device&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;1 bit&lt;/td&gt;
&lt;td&gt;Power state transition request&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PACCEPT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;1 bit&lt;/td&gt;
&lt;td&gt;Device accepts the requested transition&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PDENY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;1 bit&lt;/td&gt;
&lt;td&gt;Device denies the requested transition&lt;/td&gt;
&lt;td&gt;IHI0068 B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;PREQ&lt;/code&gt; and &lt;code&gt;PSTATE&lt;/code&gt; must be register-driven at the controller. &lt;code&gt;PACCEPT&lt;/code&gt; and &lt;code&gt;PDENY&lt;/code&gt; must be register-driven at the device. The &lt;code&gt;PSTATE&lt;/code&gt; encoding is implementation defined; ARM publishes recommended values in the Power Policy Unit Specification. Only one of &lt;code&gt;PACCEPT&lt;/code&gt; or &lt;code&gt;PDENY&lt;/code&gt; changes per handshake transition.&lt;/p&gt;
&lt;h3&gt;P-Channel state machine&lt;/h3&gt;
&lt;p&gt;The P-Channel defines 6 states, determined by the values of &lt;code&gt;PREQ&lt;/code&gt;, &lt;code&gt;PACCEPT&lt;/code&gt;, and &lt;code&gt;PDENY&lt;/code&gt;. The normal path transitions from P_STABLE through P_REQUEST and P_ACCEPT before returning to P_STABLE. The denied path branches from P_REQUEST and returns via P_CONTINUE.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_a432f510df58d5ee572621c58b5edd4c.svg"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;&lt;code&gt;PREQ&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;PACCEPT&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;PDENY&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P_STABLE&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Idle; device is in its current power state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P_REQUEST&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Controller requests a transition to &lt;code&gt;PSTATE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P_ACCEPT&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Device accepts; transition is in progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P_COMPLETE&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Controller deasserts &lt;code&gt;PREQ&lt;/code&gt;; transitional state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P_DENIED&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Device denies the requested transition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P_CONTINUE&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Controller returns &lt;code&gt;PREQ&lt;/code&gt; low after denial; transitional state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Accepted transition&lt;/h3&gt;
&lt;p&gt;The controller sets &lt;code&gt;PSTATE&lt;/code&gt; to the target state before asserting &lt;code&gt;PREQ&lt;/code&gt; high. The device samples &lt;code&gt;PSTATE&lt;/code&gt; when it detects &lt;code&gt;PREQ&lt;/code&gt; rising. When the device is ready to transition, it asserts &lt;code&gt;PACCEPT&lt;/code&gt; high. The controller responds by deasserting &lt;code&gt;PREQ&lt;/code&gt; low, entering P_COMPLETE. When the transition completes, the device deasserts &lt;code&gt;PACCEPT&lt;/code&gt; low and both sides return to P_STABLE.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_f469047aecce4d3ee1a5f5f874cdf540.svg"&gt;&lt;/p&gt;
&lt;h3&gt;Denied transition&lt;/h3&gt;
&lt;p&gt;If the device cannot accept the requested transition, it asserts &lt;code&gt;PDENY&lt;/code&gt; high instead of &lt;code&gt;PACCEPT&lt;/code&gt;. The controller must respond by deasserting &lt;code&gt;PREQ&lt;/code&gt; low, entering P_CONTINUE. The device then deasserts &lt;code&gt;PDENY&lt;/code&gt; low, and both sides return to P_STABLE. The controller can reattempt the transition with the same or a different &lt;code&gt;PSTATE&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_ecf79490c3b6cd4ba64a97a204f799fa.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Choosing between Q-Channel and P-Channel&lt;/h2&gt;
&lt;p&gt;Q-Channel covers the majority of clock gating and simple power gating use cases. P-Channel is appropriate when a device supports multiple defined power states that require explicit enumeration. The ARM recommendation is to use Q-Channel where it is sufficient and to introduce P-Channel only for IP blocks with complex power policies.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Q-Channel&lt;/th&gt;
&lt;th&gt;P-Channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Number of power states&lt;/td&gt;
&lt;td&gt;2: run or quiescent&lt;/td&gt;
&lt;td&gt;Many; implementation defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target state communicated to device&lt;/td&gt;
&lt;td&gt;None; quiescence is implicit&lt;/td&gt;
&lt;td&gt;Via &lt;code&gt;PSTATE&lt;/code&gt; encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backward compatible with AXI Low Power Interface (LPI)&lt;/td&gt;
&lt;td&gt;Yes (without &lt;code&gt;QDENY&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;Clock gating, simple power gating&lt;/td&gt;
&lt;td&gt;Retention states, multi-voltage domains, complex power policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Parity protection: Issue D additions&lt;/h2&gt;
&lt;p&gt;Issue D of IHI0068 (published September 2021) added optional parity protection to both Q-Channel and P-Channel. Each primary signal gains a companion check signal with a &lt;code&gt;CHK&lt;/code&gt; suffix. For example, &lt;code&gt;QREQn&lt;/code&gt; pairs with &lt;code&gt;QREQCHK&lt;/code&gt;, and &lt;code&gt;QACCEPTn&lt;/code&gt; pairs with &lt;code&gt;QACCEPTCHK&lt;/code&gt;. Each check signal carries the odd parity of its primary signal, so the XOR of any signal and its check signal equals 1 when no fault is present. Parity protection is optional; systems without fault-detection requirements can omit the check signals.&lt;/p&gt;
&lt;h3&gt;Q-Channel parity signals&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primary signal&lt;/th&gt;
&lt;th&gt;Check signal&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QREQn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QREQCHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller to device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QACCEPTn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QACCEPTCHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QDENY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QDENYCHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QACTIVECHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;P-Channel parity signals&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primary signal&lt;/th&gt;
&lt;th&gt;Check signal&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PACTIVE[N-1:0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PACTIVECHK[N-1:0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PSTATE[M-1:0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PSTATECHK[M-1:0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller to device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PREQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PREQCHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller to device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PACCEPT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PACCEPTCHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PDENY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PDENYCHK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device to controller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Backward compatibility with AXI LPI&lt;/h2&gt;
&lt;p&gt;AXI Low Power Interface (LPI) predates IHI0068 and uses three signals: &lt;code&gt;CSYSREQ&lt;/code&gt;, &lt;code&gt;CSYSACK&lt;/code&gt;, and &lt;code&gt;CACTIVE&lt;/code&gt;. Q-Channel is the direct replacement and maps to these signals as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AXI LPI signal&lt;/th&gt;
&lt;th&gt;Q-Channel equivalent&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CSYSREQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QREQn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller drives both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CSYSACK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QACCEPTn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device drives both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QACTIVE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device drives both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QDENY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tie &lt;code&gt;QDENY&lt;/code&gt; LOW at the controller when connecting to an AXI LPI device&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When connecting a Q-Channel controller to an AXI LPI device that has no &lt;code&gt;QDENY&lt;/code&gt; port, tie &lt;code&gt;QDENY&lt;/code&gt; LOW at the controller. This removes the denied path and reduces the interface to the backward-compatible subset.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;AMBA Low Power Interface (LPI) provides two complementary handshake protocols for coordinating safe clock and power gating on System on Chip (SoC) designs. Q-Channel handles run-stop quiescence with 4 signals and 6 states, making it well suited to clock gating and straightforward power gating. P-Channel handles multi-state power transitions where the controller communicates the target state directly using an implementation-defined &lt;code&gt;PSTATE&lt;/code&gt; encoding. Both interfaces follow strict transition rules to prevent illegal states. Issue D of IHI0068 added optional parity check signals to both interfaces for functional safety applications. The full specification is available from the ARM Documentation Service as &lt;a href="https://documentation-service.arm.com/documentation/ihi0068/d?lang=en&amp;amp;rev=0"&gt;IHI0068D&lt;/a&gt;.&lt;/p&gt;</content><category term="Engineering"/><category term="AMBA"/><category term="LPI"/><category term="SoC"/><category term="Low Power"/><category term="Q-Channel"/><category term="P-Channel"/><category term="Hardware"/><category term="Embedded"/></entry><entry><title>SoC Article 09: Hardware Description Languages and RTL Design</title><link href="http://lizard-spock.co.uk/soc-article-09-hardware-description-languages-rtl-design.html" rel="alternate"/><published>2026-05-01T00:00:00+01:00</published><updated>2026-05-01T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-05-01:/soc-article-09-hardware-description-languages-rtl-design.html</id><summary type="html">&lt;p&gt;How SoC hardware is described in code: the fundamental difference between sequential software and parallel hardware, RTL vs behavioural vs structural design, SystemVerilog key constructs, FSMs, testbenches, assertions, and the synthesis step that turns RTL into gates.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 9 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Hardware does not appear from nowhere. Before manufacturing a single transistor, engineers must describe the behaviour of a System-on-Chip (SoC). They use a language that both humans and tools can process: simulating, verifying, and ultimately synthesising the design into real logic gates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware Description Languages (HDLs)&lt;/strong&gt; are that language. They look superficially like programming languages. In reality they describe hardware: circuits that exist and operate in parallel, not sequential lists of instructions. Understanding this distinction is the single most important step in learning HDL design.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The fundamental difference: parallelism&lt;/h2&gt;
&lt;p&gt;In a software program, statements execute one after another:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Software: sequential execution&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_sensor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# c is calculated AFTER b is calculated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In hardware, all blocks of logic are always active simultaneously. Every combinational logic path is constantly computing its output from its inputs, every clock edge triggers every flip-flop at once:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hardware: everything runs concurrently&lt;/span&gt;
&lt;span class="c1"&gt;// All these assignments happen in PARALLEL, every clock cycle&lt;/span&gt;
&lt;span class="k"&gt;always_ff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;reg_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input_a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// This flip-flop samples input_a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;reg_b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reg_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// This flip-flop samples reg_a + 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;reg_c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reg_b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// This flip-flop samples reg_b shifted left&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c1"&gt;// All three updates happen simultaneously on every rising clock edge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This parallel nature is what makes hardware description language (HDL) design difficult for software engineers at first: there is no "sequence of operations". There is a network of logic gates and registers, all computing simultaneously.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The two major HDLs: Verilog and VHDL&lt;/h2&gt;
&lt;p&gt;The industry is divided between two hardware description languages (HDLs), both established in the 1980s and both standardised by the Institute of Electrical and Electronics Engineers (IEEE):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verilog:&lt;/strong&gt; C-like syntax, concise, originally designed for simulation. Extended into &lt;strong&gt;SystemVerilog&lt;/strong&gt; (IEEE 1800), which adds object-oriented programming (OOP) features, assertions, and randomised verification constructs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VHDL&lt;/strong&gt; (VHSIC Hardware Description Language): Ada-like syntax, strongly typed, verbose. Historically dominant in Europe, aerospace, and defence.&lt;/p&gt;
&lt;p&gt;For this series, examples use &lt;strong&gt;SystemVerilog&lt;/strong&gt; for hardware design and VHDL where instructive for comparison. The concepts are identical.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;RTL vs behavioural vs structural&lt;/h2&gt;
&lt;p&gt;Hardware description language (HDL) code can be written at different levels of abstraction:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_be4b1b73ee5d6b84122a2e2376ba9d43.svg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structural:&lt;/strong&gt; instantiate specific gates and connect them with wires:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Structural: instantiate specific primitives&lt;/span&gt;
&lt;span class="n"&gt;and_gate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wire_and&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;or_gate&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;U1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wire_and&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Behavioural:&lt;/strong&gt; describe &lt;em&gt;what&lt;/em&gt; the circuit does:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Behavioural: synthesis tool decides the gates&lt;/span&gt;
&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in_c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Register transfer level (RTL):&lt;/strong&gt; explicit registers and data paths:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// RTL: explicit registers and data paths&lt;/span&gt;
&lt;span class="k"&gt;always_ff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;negedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;data_reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;data_reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_in&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;SystemVerilog key constructs&lt;/h2&gt;
&lt;h3&gt;Modules&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;module&lt;/strong&gt; is the fundamental building block: equivalent to a function or class in software, but representing a hardware block with defined ports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;adder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;parameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;8&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;// configurable width&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;cin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Modules are &lt;strong&gt;instantiated&lt;/strong&gt; to create the structural hierarchy of a SoC:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;alu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;31&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;31&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;31&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;add_carry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;adder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#(.&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;32&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u_adder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cin&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add_result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add_carry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;always_comb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0000&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;// ADD&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0001&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// SUB&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0010&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// AND&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0011&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// OR&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0100&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// XOR&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0101&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SLL&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;endcase&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Sequential Logic: always_ff&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;always_ff&lt;/code&gt; block describes registers. It executes on every rising (or falling) clock edge:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// 8-entry FIFO with synchronous read and write&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fifo8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;wen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ren&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;always_ff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;negedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;wptr&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rptr&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wptr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wdata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;wptr&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ren&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;rptr&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rdata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rptr&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;assign&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Combinational Logic: always_comb&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;always_comb&lt;/code&gt; block describes purely combinational logic: it re-evaluates whenever any of its inputs change:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Priority encoder: find the index of the highest-priority set bit&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;priority_enc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;always_comb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;6&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;grant_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Finite state machines in RTL&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Finite State Machine (FSM)&lt;/strong&gt; is one of the most important patterns in digital design. An FSM transitions between a finite set of states based on inputs and the current state. Each state produces outputs based on that state.&lt;/p&gt;
&lt;h3&gt;Example: simple UART receiver FSM&lt;/h3&gt;
&lt;p&gt;A universal asynchronous receiver/transmitter (UART) receiver reads serial data one bit at a time. The FSM below controls the bit sampling and framing logic:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DATA&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b11&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uart_state_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uart_rx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;uart_state_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;bit_cnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;shift_reg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;always_ff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;negedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;bit_cnt&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;shift_reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nl"&gt;IDLE:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nl"&gt;START:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;bit_cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DATA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nl"&gt;DATA:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;shift_reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shift_reg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;7&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]};&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bit_cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="n"&gt;bit_cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bit_cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nl"&gt;STOP:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shift_reg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;endcase&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The corresponding state diagram:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_4a564833658ab6fd609f49b340607e27.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Simulation and testbenches&lt;/h2&gt;
&lt;p&gt;Hardware description language (HDL) code is verified by &lt;strong&gt;simulation&lt;/strong&gt;: running the design against a set of inputs (a &lt;strong&gt;testbench&lt;/strong&gt;) and checking the outputs.&lt;/p&gt;
&lt;p&gt;A testbench is not synthesisable. Its job is to:
1. Instantiate the &lt;strong&gt;DUT (Device Under Test)&lt;/strong&gt;.
2. Generate stimulus (clock, inputs).
3. Check outputs against expected values.
4. Report pass/fail.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="no"&gt;`timescale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ps&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tb_counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#(.&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;initial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="mh"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;initial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="mi"&gt;&amp;#39;d4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;$error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;FAIL: expected 4, got %0d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;$display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;PASS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;$finish&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;initial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;$dumpfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;counter_tb.vcd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;$dumpvars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tb_counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;SystemVerilog assertions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Assertions&lt;/strong&gt; are formal, self-checking properties embedded directly in the design or testbench using SystemVerilog Assertions (SVA). They check that the hardware obeys its specification throughout simulation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Assert: write enable must not be asserted when full&lt;/span&gt;
&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;no_overflow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;disable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;wen&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;endproperty&lt;/span&gt;
&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no_overflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;FIFO overflow attempted!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Assert: data must be stable while valid and not yet consumed&lt;/span&gt;
&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data_stable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;disable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$stable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;endproperty&lt;/span&gt;
&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_stable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Data changed while valid/!ready&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Cover: verify the full condition is exercised in tests&lt;/span&gt;
&lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Assertions serve as &lt;strong&gt;executable specifications&lt;/strong&gt;: they document intended behaviour and catch violations automatically during simulation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Common RTL pitfalls&lt;/h2&gt;
&lt;h3&gt;Unintended Latches&lt;/h3&gt;
&lt;p&gt;If a &lt;code&gt;case&lt;/code&gt; or &lt;code&gt;if&lt;/code&gt; statement in &lt;code&gt;always_comb&lt;/code&gt; does not cover all possible input combinations, synthesis infers a &lt;strong&gt;latch&lt;/strong&gt;: level-sensitive storage that was not intended:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// BAD: missing default -&amp;gt; inferred latch on &amp;#39;out&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;always_comb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b00&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b01&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 2&amp;#39;b10 and 2&amp;#39;b11 not covered -&amp;gt; LATCH!&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;endcase&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;// GOOD: add default assignment before the case&lt;/span&gt;
&lt;span class="k"&gt;always_comb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b00&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mh"&gt;2&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b01&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;endcase&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Simulation-Synthesis Mismatch&lt;/h3&gt;
&lt;p&gt;Some SystemVerilog constructs simulate differently from how they synthesise. &lt;code&gt;initial&lt;/code&gt; blocks, delays (&lt;code&gt;#100&lt;/code&gt;), and complex data types exist only for simulation and are ignored or unsupported during synthesis. Always verify that RTL synthesises to the intended hardware, not just simulates correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The synthesis step&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Synthesis&lt;/strong&gt; transforms register transfer level (RTL) code into a &lt;strong&gt;gate-level netlist&lt;/strong&gt;: a description in terms of actual standard cells (AND, OR, flip-flop, multiplexer cells) from a technology library.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_b7885858d1b92706e35cc05075ab3aa8.svg"&gt;&lt;/p&gt;
&lt;p&gt;The synthesis tool reads the RTL plus a &lt;strong&gt;constraints file&lt;/strong&gt; (&lt;code&gt;.sdc&lt;/code&gt;) that specifies the target clock frequency and I/O timing budgets, then maps to whatever cell library the target process provides.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Hardware description languages (HDLs) are the foundation of SoC design. SystemVerilog and VHDL describe digital logic at the register transfer level (RTL): specifying registers and the combinational logic that transfers data between them. The key mental model shift from software is parallelism: all RTL executes simultaneously, not sequentially. Modules provide the structural hierarchy for building large designs from smaller, verifiable blocks. Finite state machines (FSMs) are a core design pattern for controlling sequential behaviour. Simulation and assertions verify correctness before committing to silicon. Synthesis transforms RTL into gate-level hardware.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;RTL Synthesis and Timing Closure:&lt;/em&gt; Constraint writing, static timing analysis, ECO flows&lt;/li&gt;
&lt;li&gt;&lt;em&gt;SoC Verification with UVM:&lt;/em&gt; SystemVerilog class-based testbench architecture&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Formal Verification Methods:&lt;/em&gt; SVA property specification, model checking&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-08-peripherals-and-io.html"&gt;Article 08 -- Peripherals and I/O&lt;/a&gt;&lt;/em&gt; | &lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-10-design-flow.html"&gt;Article 10 -- The SoC Design Flow&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="Verilog"/><category term="SystemVerilog"/><category term="VHDL"/><category term="RTL"/><category term="HDL"/><category term="FPGA"/><category term="Synthesis"/></entry><entry><title>Fretboard Diagrams in Pelican</title><link href="http://lizard-spock.co.uk/fretboard-diagrams-in-pelican.html" rel="alternate"/><published>2026-04-25T00:00:00+01:00</published><updated>2026-04-25T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-25:/fretboard-diagrams-in-pelican.html</id><summary type="html">&lt;p&gt;A walkthrough of the pelican-fretboard plugin -- chord charts, scale boxes, and tab rendered as SVGs directly from fenced code blocks, with a comparison of finger-number and interval-label views.&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a long time my guitar diagrams lived on paper -- annotated chord boxes and scale grids scribbled in notebooks. When it came to writing them up digitally I found the process time consuming and the results inconsistent: different posts would use different styles, sizes, or notation conventions with no easy way to keep them in sync. This post introduces &lt;a href="https://github.com/morganp/pelican-fretboard"&gt;pelican-fretboard&lt;/a&gt;, a plugin that renders chord charts, scale boxes, and tab directly from fenced code blocks in Markdown. Diagrams are generated as SVGs, cached on disk, and served as static files -- nothing to install on the reader's side.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;p&gt;The plugin uses &lt;a href="https://github.com/morganp/fretdrom"&gt;fretdrom&lt;/a&gt; as its rendering engine. Fretdrom takes JSON5 input -- the same wavedrom-style philosophy of a single top-level key determining diagram type -- and emits clean SVG. Blocks use the &lt;code&gt;fretdrom&lt;/code&gt; language identifier in Markdown fences.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The problem with ASCII diagrams&lt;/h2&gt;
&lt;p&gt;The classic way to notate a chord in plain text looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;E Major

e ---0---
B ---0---
G ---1---
D ---2---
A ---2---
E ---0---

Fingers: - 2 3 1 - -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It works, but it is fragile to copy-paste, hard to scan at a glance, and carries no colour or visual hierarchy to distinguish the root note from the rest of the chord.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Chord diagrams&lt;/h2&gt;
&lt;p&gt;The plugin renders a &lt;code&gt;fretdrom&lt;/code&gt; block with a &lt;code&gt;chord&lt;/code&gt; key into a standard box diagram. The &lt;code&gt;frets&lt;/code&gt; string runs low string to high (E A D G B e for standard tuning). &lt;code&gt;x&lt;/code&gt; means muted, &lt;code&gt;0&lt;/code&gt; is open, &lt;code&gt;1&lt;/code&gt;-&lt;code&gt;9&lt;/code&gt; are fret numbers.&lt;/p&gt;
&lt;h3&gt;Fingers view&lt;/h3&gt;
&lt;p&gt;Add a &lt;code&gt;fingers&lt;/code&gt; string and each fretted dot shows the finger number. &lt;code&gt;-&lt;/code&gt; means no label:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ chord: { name: &amp;quot;E Major&amp;quot;, frets: &amp;quot;022100&amp;quot;, fingers: &amp;quot;-231--&amp;quot; } }&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_36611dd13a15531f56b07b2aa0e4769d.svg"&gt;&lt;/p&gt;
&lt;p&gt;Open strings show a small circle above the nut; the first fret is drawn as a thicker nut line.&lt;/p&gt;
&lt;h3&gt;Intervals view&lt;/h3&gt;
&lt;p&gt;Supply an &lt;code&gt;intervals&lt;/code&gt; array instead and each dot shows its interval relative to the root. When &lt;code&gt;intervals&lt;/code&gt; is present the subtitle automatically shows &lt;em&gt;Intervals&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ chord: { name: &amp;quot;E Major&amp;quot;, frets: &amp;quot;022100&amp;quot;, intervals: [&amp;quot;R&amp;quot;, &amp;quot;5&amp;quot;, &amp;quot;R&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;5&amp;quot;, &amp;quot;R&amp;quot;] } }&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_6cc42dc6e8d4e0b07349f93df350b5b0.svg"&gt;&lt;/p&gt;
&lt;p&gt;Comparing fingers and intervals views of the same chord makes it easy to see which finger lands on which interval -- useful when discussing voicings or substitutions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A seventh chord&lt;/h2&gt;
&lt;p&gt;G7 is a good test because it includes four distinct intervals. The intervals view makes the b7 on the high e string immediately visible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ chord: { name: &amp;quot;G7&amp;quot;, frets: &amp;quot;320001&amp;quot;, intervals: [&amp;quot;R&amp;quot;, &amp;quot;5&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;R&amp;quot;, &amp;quot;5&amp;quot;, &amp;quot;b7&amp;quot;] } }&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_406db6961d04f4b19af0d29b2b58208d.svg"&gt;&lt;/p&gt;
&lt;p&gt;The same shape in fingers mode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ chord: { name: &amp;quot;G7&amp;quot;, frets: &amp;quot;320001&amp;quot;, fingers: &amp;quot;32---1&amp;quot; } }&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_7df802efced6dfe74e5de4eb549c7b34.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Barre chords&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;barre&lt;/code&gt; key draws the barre bar. &lt;code&gt;from_string&lt;/code&gt; and &lt;code&gt;to_string&lt;/code&gt; are 1-indexed, low string to high:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ chord: {&lt;/span&gt;
&lt;span class="s"&gt;  name: &amp;quot;F Major (barre)&amp;quot;,&lt;/span&gt;
&lt;span class="s"&gt;  frets: &amp;quot;112331&amp;quot;,&lt;/span&gt;
&lt;span class="s"&gt;  fingers: &amp;quot;112341&amp;quot;,&lt;/span&gt;
&lt;span class="s"&gt;  root_strings: [1, 6],&lt;/span&gt;
&lt;span class="s"&gt;  barre: { fret: 1, from_string: 1, to_string: 6 }&lt;/span&gt;
&lt;span class="s"&gt;}}&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_ac772858bc3ad5ff71a128710127ee9a.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Scale diagrams&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;scale&lt;/code&gt; key renders a fretboard grid. &lt;code&gt;grid&lt;/code&gt; is an array of rows, one per string (low E first), each row an array of cell values. &lt;code&gt;"R"&lt;/code&gt; = root, &lt;code&gt;"x"&lt;/code&gt; = scale note, &lt;code&gt;"."&lt;/code&gt; = empty.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ scale: {&lt;/span&gt;
&lt;span class="s"&gt;  name: &amp;quot;A Minor Pentatonic&amp;quot;,&lt;/span&gt;
&lt;span class="s"&gt;  start_fret: 5,&lt;/span&gt;
&lt;span class="s"&gt;  num_frets: 5,&lt;/span&gt;
&lt;span class="s"&gt;  grid: [&lt;/span&gt;
&lt;span class="s"&gt;    [&amp;quot;R&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;],&lt;/span&gt;
&lt;span class="s"&gt;    [&amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;.&amp;quot;],&lt;/span&gt;
&lt;span class="s"&gt;    [&amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;R&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;.&amp;quot;],&lt;/span&gt;
&lt;span class="s"&gt;    [&amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;.&amp;quot;],&lt;/span&gt;
&lt;span class="s"&gt;    [&amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;],&lt;/span&gt;
&lt;span class="s"&gt;    [&amp;quot;R&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;.&amp;quot;, &amp;quot;x&amp;quot;, &amp;quot;.&amp;quot;]&lt;/span&gt;
&lt;span class="s"&gt;  ]&lt;/span&gt;
&lt;span class="s"&gt;}}&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_b3f628020aefddb9249d782ef8973241.svg"&gt;&lt;/p&gt;
&lt;p&gt;Fret numbers are shown on the left so the position on the neck is always clear. Cell values other than &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, and &lt;code&gt;.&lt;/code&gt; are treated as interval labels and rendered inside the dot.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Tab&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;tab&lt;/code&gt; key takes an array of string lanes, highest string first (standard tab order). Each lane has a &lt;code&gt;name&lt;/code&gt; and a &lt;code&gt;wave&lt;/code&gt; string where each character is one beat: &lt;code&gt;.&lt;/code&gt; is an empty beat (dash), &lt;code&gt;0&lt;/code&gt;-&lt;code&gt;9&lt;/code&gt; are fret numbers. &lt;code&gt;config.bar&lt;/code&gt; draws bar lines every N beats:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ name: &amp;quot;E String Blues Riff&amp;quot;,&lt;/span&gt;
&lt;span class="s"&gt;  tab: [&lt;/span&gt;
&lt;span class="s"&gt;    { name: &amp;quot;e&amp;quot;, wave: &amp;quot;................&amp;quot; },&lt;/span&gt;
&lt;span class="s"&gt;    { name: &amp;quot;B&amp;quot;, wave: &amp;quot;................&amp;quot; },&lt;/span&gt;
&lt;span class="s"&gt;    { name: &amp;quot;G&amp;quot;, wave: &amp;quot;................&amp;quot; },&lt;/span&gt;
&lt;span class="s"&gt;    { name: &amp;quot;D&amp;quot;, wave: &amp;quot;................&amp;quot; },&lt;/span&gt;
&lt;span class="s"&gt;    { name: &amp;quot;A&amp;quot;, wave: &amp;quot;................&amp;quot; },&lt;/span&gt;
&lt;span class="s"&gt;    { name: &amp;quot;E&amp;quot;, wave: &amp;quot;0..3.5.3..0.....&amp;quot; }&lt;/span&gt;
&lt;span class="s"&gt;  ],&lt;/span&gt;
&lt;span class="s"&gt;  config: { bar: 8 }&lt;/span&gt;
&lt;span class="s"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_72daa9b6244bcdd76762dac597583d8b.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Any fretted instrument&lt;/h2&gt;
&lt;p&gt;Set &lt;code&gt;tuning&lt;/code&gt; to match your instrument and the string count adjusts automatically:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```fretdrom&lt;/span&gt;
&lt;span class="s"&gt;{ chord: { name: &amp;quot;E (bass)&amp;quot;, tuning: &amp;quot;EADG&amp;quot;, frets: &amp;quot;0221&amp;quot;, fingers: &amp;quot;-231&amp;quot;, root_strings: [1] } }&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="fretboard diagram" src="http://lizard-spock.co.uk/images/fretboard/fretboard_9e0255f4ac453785ef2404bbc9db44f9.svg"&gt;&lt;/p&gt;
&lt;p&gt;The same syntax works for ukulele (&lt;code&gt;GCEA&lt;/code&gt;), five-string bass (&lt;code&gt;BEADG&lt;/code&gt;), mandola (&lt;code&gt;GDAE&lt;/code&gt;), or any open tuning.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;The plugin registers a Markdown preprocessor that intercepts &lt;code&gt;fretdrom&lt;/code&gt; fenced blocks before they reach the syntax highlighter. Each block is parsed as JSON5 and passed to the &lt;a href="https://github.com/morganp/fretdrom"&gt;fretdrom&lt;/a&gt; CLI, which returns an SVG on stdout. The SVG is saved to &lt;code&gt;content/images/fretboard/&lt;/code&gt; using an MD5 hash of the block content as the filename. The fenced block is replaced inline with a Markdown image reference. The SVG cache persists across &lt;code&gt;make clean&lt;/code&gt; -- diagrams are only regenerated when their source content changes.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;fretdrom&lt;/code&gt; binary is not found the block falls back to a &lt;code&gt;json5&lt;/code&gt; code block so the build never fails.&lt;/p&gt;
&lt;p&gt;Source and installation instructions are at &lt;a href="https://github.com/morganp/pelican-fretboard"&gt;github.com/morganp/pelican-fretboard&lt;/a&gt;.&lt;/p&gt;</content><category term="Music"/><category term="Guitar"/><category term="Pelican"/><category term="Plugin"/><category term="SVG"/><category term="Chord"/><category term="Tab"/><category term="Music Theory"/></entry><entry><title>SoC Article 08: Peripherals and I/O - Connecting the SoC to the world</title><link href="http://lizard-spock.co.uk/soc-article-08-peripherals-and-io.html" rel="alternate"/><published>2026-04-24T00:00:00+01:00</published><updated>2026-04-24T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-24:/soc-article-08-peripherals-and-io.html</id><summary type="html">&lt;p&gt;A survey of the most common peripheral types found in SoCs: GPIO, UART, SPI, I2C, USB, Ethernet, DMA, and interrupt controllers, and how they connect the digital logic inside the chip to the physical world outside it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 8 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;A System-on-Chip (SoC) without input/output (I/O) is a black box. No matter how powerful its central processing units (CPUs) or how fast its memory, it has no way to receive information from the world or send results back out. &lt;strong&gt;Peripherals&lt;/strong&gt; are the hardware blocks that bridge the gap between the digital logic inside the chip and the physical world outside it: sensors, displays, storage, networks, and human interfaces.&lt;/p&gt;
&lt;p&gt;This article surveys the most common peripheral types found in SoCs, explains the protocols they use, and shows how they connect to the rest of the chip.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The peripheral landscape&lt;/h2&gt;
&lt;p&gt;Peripherals vary enormously in bandwidth, latency, and complexity:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_ff72fdad9981b4ddc8227de16028d48f.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;GPIO&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;General-purpose input/output (GPIO)&lt;/strong&gt; is the simplest peripheral: a set of pins that can be individually configured as either digital inputs or digital outputs, under software control.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_deb8861993d460158af07f021e6c9308.svg"&gt;&lt;/p&gt;
&lt;p&gt;GPIO pins on modern SoCs are typically multiplexed: the same physical pin can serve as GPIO or as a dedicated function for a peripheral like UART or SPI. The &lt;strong&gt;IOMUX (I/O multiplexer)&lt;/strong&gt; hardware block selects which function a pin serves:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_f24e538a77e6b1036819d732d0fa740f.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;UART&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Universal Asynchronous Receiver/Transmitter (UART)&lt;/strong&gt; is the oldest and simplest serial communication protocol. It is asynchronous (no shared clock between sender and receiver) and full-duplex (simultaneous send and receive on separate wires).&lt;/p&gt;
&lt;h3&gt;UART frame format&lt;/h3&gt;
&lt;p&gt;A UART frame consists of: start bit, 7-9 data bits, optional parity bit, and 1-2 stop bits.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_963cc49433ada38187d8cf76649dd908.svg"&gt;&lt;/p&gt;
&lt;p&gt;The receiver &lt;strong&gt;samples&lt;/strong&gt; the line at 16x the baud rate to detect the start bit edge, then samples each data bit at the midpoint of its period. Both sides must be configured to the same baud rate (for example, 115200 bps).&lt;/p&gt;
&lt;p&gt;UARTs serve primarily as debug consoles: they are the primary serial debug output tool in embedded systems. Almost every SoC has at least one UART that provides a serial terminal during boot.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;SPI&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Serial Peripheral Interface (SPI)&lt;/strong&gt; is a synchronous serial protocol where the master provides the clock. It is designed for short-distance communication between a SoC and peripheral ICs such as flash memory, sensors, digital-to-analogue converters (DACs), and displays.&lt;/p&gt;
&lt;h3&gt;SPI signals&lt;/h3&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_5b86852400968f6f166a4d2f4dfad756.svg"&gt;&lt;/p&gt;
&lt;h3&gt;SPI timing&lt;/h3&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_beba73c131534f9004f1558f2e73bed6.svg"&gt;&lt;/p&gt;
&lt;p&gt;SPI supports four &lt;strong&gt;modes&lt;/strong&gt; based on CPOL (clock polarity) and CPHA (clock phase), enabling compatibility with different devices. Most flash memories and sensors use Mode 0 or Mode 3.&lt;/p&gt;
&lt;p&gt;SPI has no addressing scheme: the chip select line selects the target device. Multiple slaves require one CS line per slave. &lt;strong&gt;Quad-SPI (QSPI)&lt;/strong&gt; uses four data lines instead of one, quadrupling throughput.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;I2C&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Inter-Integrated Circuit (I2C)&lt;/strong&gt; is a two-wire synchronous protocol supporting multiple masters and multiple slaves on the same bus. It uses &lt;strong&gt;7-bit or 10-bit addressing&lt;/strong&gt; to select which device a transaction targets.&lt;/p&gt;
&lt;h3&gt;I2C signals&lt;/h3&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_b763a625eb137aeb5258814c876bac89.svg"&gt;&lt;/p&gt;
&lt;h3&gt;I2C transaction format&lt;/h3&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_cebacb97a2317a98fbdaa0f42e8722c7.svg"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;START&lt;/strong&gt; condition (SDA falls while SCL is high) begins every transaction. The master sends 7 address bits and an R/W bit, then waits for an &lt;strong&gt;ACK&lt;/strong&gt;: the addressed slave pulls SDA low for one clock cycle. Data bytes follow, each acknowledged by the receiver.&lt;/p&gt;
&lt;p&gt;I2C standard speeds are 100 kHz (standard), 400 kHz (fast), 1 MHz (fast-plus), and 3.4 MHz (high-speed). Common uses include temperature sensors, inertial measurement units (IMUs), power management ICs (PMICs), cameras, electrically erasable programmable read-only memory (EEPROM), and displays.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;USB&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Universal Serial Bus (USB)&lt;/strong&gt; is the dominant PC-to-peripheral protocol, and is increasingly common on embedded SoCs. A complete USB implementation includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;USB PHY (Physical Layer):&lt;/strong&gt; analog front-end handling differential signalling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB controller and IP stack:&lt;/strong&gt; protocol stack implementation (USB 2.0 at 480 Mb/s, USB 3.x up to 20 Gb/s)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB hub and root hub logic:&lt;/strong&gt; in host-mode SoCs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;USB 2.0 uses a tiered star topology with a single host, supporting 127 devices. USB communication is always host-initiated: a device can never spontaneously send data. It must wait to be polled or for the host to grant a transfer.&lt;/p&gt;
&lt;p&gt;USB transfers are classified into four types:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transfer Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;Enumeration, configuration&lt;/td&gt;
&lt;td&gt;Device setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk&lt;/td&gt;
&lt;td&gt;Large data, guaranteed delivery&lt;/td&gt;
&lt;td&gt;Storage (USB flash)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interrupt&lt;/td&gt;
&lt;td&gt;Small, time-bounded data&lt;/td&gt;
&lt;td&gt;Human Interface Device (HID): mouse, keyboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isochronous&lt;/td&gt;
&lt;td&gt;Time-sensitive, no retry&lt;/td&gt;
&lt;td&gt;Audio, video streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;USB 3.x adds &lt;strong&gt;SuperSpeed&lt;/strong&gt; lanes with a completely different physical layer, using 128b/132b coding instead of 8b/10b and separate TX and RX differential pairs. It maintains backward compatibility with USB 2.0.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Ethernet MAC&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ethernet&lt;/strong&gt; connectivity requires two hardware blocks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MAC (Media Access Control):&lt;/strong&gt; implements the Ethernet frame format, collision detection (for legacy half-duplex), flow control, and first-in first-out (FIFO) buffer management. The MAC lives on-chip.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PHY (Physical Layer):&lt;/strong&gt; converts between the digital MAC signals and the analog signals on the twisted-pair cable. The PHY is usually a separate chip, connected to the MAC via the Reduced Gigabit Media Independent Interface (RGMII) or Serial Gigabit Media Independent Interface (SGMII).&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_20734e712cf8ae79529841bc7a8a00c2.svg"&gt;&lt;/p&gt;
&lt;p&gt;The MAC operates as a &lt;strong&gt;DMA master&lt;/strong&gt;: when a packet arrives, the MAC's DMA engine writes it to a pre-allocated buffer in dynamic RAM (DRAM) and signals the CPU via interrupt. Outgoing packets are described by descriptors pointing to DRAM buffers, and the MAC fetches and transmits them independently.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;DMA controller&lt;/h2&gt;
&lt;p&gt;Almost all high-bandwidth peripherals include or connect to a &lt;strong&gt;Direct Memory Access (DMA)&lt;/strong&gt; engine. DMA enables peripherals to transfer large blocks of data to and from memory without the central processing unit (CPU) processing every byte.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_0cb8c5e52ccbef47719e3837d4f22281.svg"&gt;&lt;/p&gt;
&lt;p&gt;DMA is essential for audio streaming, video capture, USB bulk transfers, UART high-speed data, and anything involving moving more than a few bytes at a time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Interrupt controller&lt;/h2&gt;
&lt;p&gt;Peripherals signal the CPU when they need attention using &lt;strong&gt;interrupts&lt;/strong&gt;: electrical signals that cause the CPU to pause its current task and run an &lt;strong&gt;Interrupt Service Routine (ISR)&lt;/strong&gt;. The CPU saves its state before the ISR runs and restores it afterwards.&lt;/p&gt;
&lt;p&gt;A SoC may have dozens of interrupt sources (UART received a byte, SPI transfer complete, GPIO edge, timer expired, DMA done). The &lt;strong&gt;interrupt controller&lt;/strong&gt; collects these signals, prioritises them, and presents the highest-priority pending interrupt to the CPU.&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_93b21dfd0398806f2f9aed8bc4b4fa9f.svg"&gt;&lt;/p&gt;
&lt;p&gt;ARM's &lt;strong&gt;GIC (Generic Interrupt Controller)&lt;/strong&gt; is the standard interrupt controller for Cortex-A SoCs. It supports up to 1020 interrupt sources and 8 priority levels. It can also target interrupts to specific CPU cores, which is useful for load balancing in multi-core systems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Timer and watchdog&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Timers&lt;/strong&gt; are counter circuits that count clock cycles and generate an interrupt when they reach a preset value. They are used for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Operating system (OS) tick (for example, 1 ms for task scheduling)&lt;/li&gt;
&lt;li&gt;PWM generation (Pulse Width Modulation for motor control, LEDs)&lt;/li&gt;
&lt;li&gt;Capture (measuring pulse widths on GPIO inputs)&lt;/li&gt;
&lt;li&gt;Timeouts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_cabb4cea1d1a4d7c2922c4ee264d3cc6.svg"&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;watchdog timer&lt;/strong&gt; is a specific timer that firmware must periodically reset. If the firmware crashes or hangs, the watchdog expires and resets the entire system: essential for reliable embedded operation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Peripherals are the interface between a SoC and the physical world. GPIO provides flexible single-bit I/O. Serial protocols like UART, SPI, and I2C provide low-to-medium bandwidth connections to sensors and devices. USB provides standardised high-speed connectivity to external devices. Ethernet connects the SoC to networks. DMA engines free the CPU from data movement tasks. The interrupt controller efficiently manages asynchronous events from all these sources. Together, these blocks are what allow a SoC to run in a real product.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;DMA Controller Architecture:&lt;/em&gt; Descriptor chains, scatter-gather, channel arbitration&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Interrupt Controllers:&lt;/em&gt; Nested vectored interrupts, GIC architecture, priority grouping&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Memory-Mapped I/O and Linux Device Drivers:&lt;/em&gt; Writing kernel drivers for these peripherals&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-07-clocking-reset-and-power-domains.html"&gt;Article 07 -- Clocking, Reset, and Power Domains&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-09-hardware-description-languages-rtl-design.html"&gt;Article 09 -- Hardware Description Languages and RTL Design&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="Peripherals"/><category term="I/O"/><category term="UART"/><category term="SPI"/><category term="I2C"/><category term="USB"/><category term="Ethernet"/><category term="DMA"/><category term="GPIO"/></entry><entry><title>SoC Article 07: Clocking, Reset, and Power Domains</title><link href="http://lizard-spock.co.uk/soc-article-07-clocking-reset-and-power-domains.html" rel="alternate"/><published>2026-04-18T00:00:00+01:00</published><updated>2026-04-18T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-18:/soc-article-07-clocking-reset-and-power-domains.html</id><summary type="html">&lt;p&gt;The infrastructure that makes a SoC run correctly and efficiently: PLLs for frequency synthesis, clock trees for low-skew distribution, clock domain crossings and metastability, reset sources and synchronisation, and power domains with gating and DVFS.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 7 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Every digital circuit needs a clock: a periodic signal that synchronises all the flip-flops in a System-on-Chip (SoC), causing them to sample their inputs and update their outputs in a coordinated fashion. Designing, distributing, and managing the clock is an entire sub-discipline of SoC engineering.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reset&lt;/strong&gt; places a chip in a known initial state. &lt;strong&gt;Power management&lt;/strong&gt; dynamically controls which blocks are active and at what voltage. Both are critical infrastructure topics that every SoC designer must understand.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why clocking is hard&lt;/h2&gt;
&lt;p&gt;Generating a clock signal that oscillates at a precise frequency seems straightforward. The real challenge is delivering that signal to millions of flip-flops distributed across a die that may be 100 mm² or larger, all arriving at exactly the same time (to within picoseconds), despite:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wire resistance and capacitance:&lt;/strong&gt; signals are delayed differently depending on path length.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Process variation:&lt;/strong&gt; transistors at different corners of the die behave slightly differently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temperature gradients:&lt;/strong&gt; hot areas near power-hungry cores have slower transistors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Voltage droop:&lt;/strong&gt; areas with high switching activity momentarily drop in supply voltage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This collection of problems is called &lt;strong&gt;clock skew&lt;/strong&gt; (spatial variation) and &lt;strong&gt;clock jitter&lt;/strong&gt; (temporal variation). Both reduce the timing margin available for logic between flip-flops.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The clock distribution tree&lt;/h2&gt;
&lt;p&gt;The solution is a carefully engineered &lt;strong&gt;clock tree&lt;/strong&gt;: a hierarchical network of buffers that distributes the clock signal from a single source to all destinations. Engineers size each branch so that all paths have approximately equal delay.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article07/07-clock-tree-HQ.png"&gt;&lt;img alt="H-tree clock distribution showing equal-length paths from PLL output through hierarchical buffers to leaf flip-flops" src="http://lizard-spock.co.uk/images/SoC/Article07/07-clock-tree-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All paths from source to leaf flip-flops are equal length, giving equal delay and low skew.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clock tree synthesis (CTS)&lt;/strong&gt; is an automated step in the physical design flow that constructs this tree. It is one of the most critical steps in achieving timing closure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Phase-locked loops&lt;/h2&gt;
&lt;p&gt;SoCs do not use their raw crystal oscillator frequency (typically 24--100 MHz from an external crystal) directly. Instead, a &lt;strong&gt;phase-locked loop (PLL)&lt;/strong&gt; multiplies and divides this reference to generate the precise frequencies needed by each subsystem.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article07/07-pll-block-HQ.png"&gt;&lt;img alt="PLL block diagram showing reference clock into phase/frequency detector, loop filter, VCO producing output, with feedback divider closing the loop" src="http://lizard-spock.co.uk/images/SoC/Article07/07-pll-block-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Output frequency = Ref × (N/M) where N and M are programmable dividers.&lt;/p&gt;
&lt;p&gt;A PLL is an &lt;strong&gt;analog circuit&lt;/strong&gt; embedded in the otherwise digital SoC. Its output frequency is controlled by a digital divider, but the loop filter and VCO (Voltage-Controlled Oscillator) are analog. This makes PLLs sensitive to supply noise: they have their own isolated power supply on-chip.&lt;/p&gt;
&lt;p&gt;A typical large SoC has 4--12 PLLs:
- Central processing unit (CPU) PLL (0.8--3.5 GHz)
- Graphics processing unit (GPU) PLL
- Double data rate (DDR) PLL (matched to dynamic RAM (DRAM) frequency)
- Peripheral PLL (100--400 MHz for buses)
- Universal Serial Bus (USB) and PCI Express (PCIe) PLLs (specific protocol reference frequencies)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Multiple clock domains&lt;/h2&gt;
&lt;p&gt;Different parts of a SoC operate at different frequencies, and often from different clocks. A &lt;strong&gt;clock domain&lt;/strong&gt; is a group of flip-flops all clocked by the same source.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article07/07-clock-domains-HQ.png"&gt;&lt;img alt="Clock domain tree showing crystal oscillator feeding multiple PLLs that fan out to CPU, GPU, DDR, AXI bus, APB peripherals, and always-on RTC domains at different frequencies" src="http://lizard-spock.co.uk/images/SoC/Article07/07-clock-domains-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When data must cross from one clock domain to another, a &lt;strong&gt;Clock Domain Crossing (CDC)&lt;/strong&gt; circuit is required. For example, the CPU domain and the peripheral Advanced Peripheral Bus (APB) domain run at different frequencies. Failure to handle CDCs correctly is one of the most common sources of functional bugs in SoC designs.&lt;/p&gt;
&lt;h3&gt;The metastability problem&lt;/h3&gt;
&lt;p&gt;When a flip-flop's input changes near its sampling edge, the flip-flop may enter a &lt;strong&gt;metastable&lt;/strong&gt; state: neither a clean 0 nor a clean 1. If the metastable state propagates, it can corrupt data or cause unexpected behaviour.&lt;/p&gt;
&lt;p&gt;The probability of metastability resolving correctly increases with time. A &lt;strong&gt;two-stage synchroniser&lt;/strong&gt; provides enough time in most situations for resolution before the signal is used:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_e89c24ae179973d4a3d0d7532f569d57.svg"&gt;&lt;/p&gt;
&lt;p&gt;The two-stage synchroniser adds &lt;strong&gt;two destination-clock cycles of latency&lt;/strong&gt; but reduces the probability of a metastability-induced failure to negligible levels. More complex CDCs (for multi-bit signals or first-in first-out (FIFO) buffers) use more sophisticated structures.&lt;/p&gt;
&lt;p&gt;&lt;img alt="1-bit two-stage synchroniser schematic: D_in feeds FF1 (teal, may be metastable), FF1 Q feeds FF2, both clocked by DST_CLK, output is SYNC_OUT" src="http://lizard-spock.co.uk/images/SoC/Article07/07-sync-2stage-900w.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Reset: Getting to a known state&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Reset&lt;/strong&gt; is the mechanism that places all flip-flops in their initial, known state. Without reset, the behaviour of a digital circuit after power-up is non-deterministic. Each flip-flop powers up in either 0 or 1, but which value depends on manufacturing variation and environmental conditions. Simulation tools represent this uncertain power-up state as &lt;code&gt;x&lt;/code&gt; (unknown). The state is definite at the hardware level but unpredictable across devices and power cycles. Reset resolves the non-determinism by driving every flip-flop to a specified, known value.&lt;/p&gt;
&lt;h3&gt;Reset sources&lt;/h3&gt;
&lt;p&gt;A SoC typically has several reset sources:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_a691125c1c9c3bd03bd690886221aec9.svg"&gt;&lt;/p&gt;
&lt;h3&gt;Synchronous vs asynchronous reset&lt;/h3&gt;
&lt;p&gt;Register transfer level (RTL) designers must choose between two reset styles:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Asynchronous reset:&lt;/strong&gt; the flip-flop resets immediately when RST is asserted, regardless of the clock. Fast response, but the reset de-assertion must be synchronised to avoid metastability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synchronous reset:&lt;/strong&gt; the flip-flop only resets on the next rising clock edge after RST is asserted. Requires a clean clock, but avoids de-assertion timing issues.&lt;/p&gt;
&lt;p&gt;In practice, most SoC designs use &lt;strong&gt;asynchronous assert, synchronous de-assert&lt;/strong&gt; (ASAD): the reset asserts immediately for reliability, and de-asserts through a synchroniser to prevent metastability.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_c7f72d040abcd0cef5a040dacb184fd6.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Power domains and power management&lt;/h2&gt;
&lt;p&gt;A modern SoC is not a single block of silicon that is either fully on or fully off. It is divided into &lt;strong&gt;power domains&lt;/strong&gt; -- regions that can be independently powered up or down. This enables enormous power savings by only energising the blocks that are currently needed.&lt;/p&gt;
&lt;h3&gt;Power states&lt;/h3&gt;
&lt;p&gt;A typical SoC power state machine:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_fe8a56134e225fc3451ba555b719be7f.svg"&gt;&lt;/p&gt;
&lt;h3&gt;Power gating&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Power gating&lt;/strong&gt; uses header or footer switches (large p-type (PMOS) or n-type (NMOS) transistors) to physically disconnect a domain from its supply rail, reducing leakage current to near zero:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article07/07-power-gating-HQ.png"&gt;&lt;img alt="Power gating diagram showing VDD connected through a PMOS header switch to a virtual VDD rail supplying a logic block, with an NMOS footer switch to GND, both controlled by the PMU" src="http://lizard-spock.co.uk/images/SoC/Article07/07-power-gating-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When the block is gated off, its internal state is lost. If the state must be preserved (for example, cache contents or CPU registers), &lt;strong&gt;retention registers&lt;/strong&gt;: special flip-flops with a separate, always-powered supply, save critical state before power-down.&lt;/p&gt;
&lt;h3&gt;DVFS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Voltage and Frequency Scaling (DVFS)&lt;/strong&gt; reduces power consumption by lowering both the supply voltage and clock frequency when full performance is not needed. Power consumption of complementary metal-oxide-semiconductor (CMOS) logic scales approximately as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;P_dynamic ≈ α × C × V² × f

Where:
  α = activity factor (fraction of gates switching per cycle)
  C = total capacitance
  V = supply voltage
  f = clock frequency
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since power scales with V², halving the voltage reduces dynamic power by 4×. Modern SoC CPUs support many operating points, and the operating system (OS) uses a &lt;strong&gt;DVFS governor&lt;/strong&gt; to select the appropriate operating point based on workload.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article07/07-dvfs-HQ.png"&gt;&lt;img alt="DVFS operating points table showing five performance levels from Ultra Low at 600 MHz / 0.65 V up to Ultra High at 3.0 GHz / 1.05 V" src="http://lizard-spock.co.uk/images/SoC/Article07/07-dvfs-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Clock gating&lt;/h3&gt;
&lt;p&gt;A lighter-weight alternative to power gating is &lt;strong&gt;clock gating&lt;/strong&gt;: stopping the clock to a region of logic. With the clock stopped, flip-flops no longer switch, and dynamic power drops to near zero (though leakage continues). Clock gating is implemented with an &lt;strong&gt;integrated clock gating cell (ICG)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article07/07-clock-gating-HQ.png"&gt;&lt;img alt="Integrated clock gating cell schematic showing EN input into a latch clocked by CLK, with the latch output ANDed with CLK to produce GATED_CLK, preventing glitches on enable transitions" src="http://lizard-spock.co.uk/images/SoC/Article07/07-clock-gating-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When EN = 0, GATED_CLK is held low (no switching). When EN = 1, GATED_CLK follows CLK normally. The internal latch captures EN during the CLK=low phase, ensuring GATED_CLK transitions only at clock boundaries and never produces a glitch:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_e4c951f1e16694394c87578162f02f33.svg"&gt;&lt;/p&gt;
&lt;p&gt;Modern synthesis tools automatically insert clock gating cells throughout the design, reducing dynamic power by 20--40% with no architect effort.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The always-on domain&lt;/h2&gt;
&lt;p&gt;Even in the deepest sleep state, some logic must remain powered. This includes the real-time clock (RTC) to wake the system at a scheduled time, the power management unit to manage power-up sequencing, and retention registers holding critical context. This collection of always-powered logic is the &lt;strong&gt;Always-On (AO) domain&lt;/strong&gt;: it draws power from a supply that is never switched off while the battery is present.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Clocking, reset, and power management are the infrastructure that makes a SoC run correctly and efficiently. PLLs generate the precise, stable frequencies required by each subsystem. The clock tree distributes the clock signal with minimal skew. Clock domain crossings require careful synchronisation to prevent metastability. Reset places all logic in a known initial state, with the source and polarity carefully considered. Power domains, gating, and DVFS together reduce energy consumption dramatically, enabling always-on devices to run for days or years on a small battery.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Intermediate articles this topic connects to&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Clock Domain Crossing Techniques:&lt;/em&gt; Metastability in depth, multi-bit CDC, async FIFOs&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Power Management Techniques:&lt;/em&gt; DVFS, retention, power sequencing, PMU firmware&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Physical Design: Floorplanning and Place-and-Route (Advanced):&lt;/em&gt; CTS, IR drop analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-06-interconnects-and-bus-protocols.html"&gt;Article 06 -- Interconnects and Bus Protocols&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-08-peripherals-and-io.html"&gt;Article 08 -- Peripherals and I/O: Connecting the SoC to the World&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="ARM"/><category term="Clocking"/><category term="Power Management"/><category term="DVFS"/><category term="PLL"/></entry><entry><title>Proxmox: Disable root@pam Login and Create a Sudo User</title><link href="http://lizard-spock.co.uk/proxmox-disable-root-login-sudo-user.html" rel="alternate"/><published>2026-04-17T00:00:00+01:00</published><updated>2026-04-17T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-17:/proxmox-disable-root-login-sudo-user.html</id><summary type="html">&lt;p&gt;How to harden a Proxmox VE host by disabling direct root@pam login, creating a named admin user dave@pam, and granting it passwordless sudo access.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Out of the box, Proxmox VE only has the &lt;code&gt;root@pam&lt;/code&gt; account. Logging in directly as root is convenient during initial setup, but it is a bad habit to keep. This post walks through creating a named PAM user, granting it sudo access, and then disabling root login via both SSH and the web UI.&lt;/p&gt;
&lt;h2&gt;Authentication Realms: @pam vs @pve&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; suffix in Proxmox usernames is not cosmetic -- it identifies which authentication realm validates the password.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@pam&lt;/code&gt;&lt;/strong&gt; -- Linux Pluggable Authentication Modules. Proxmox delegates the password check to the host operating system. The user must exist as a real UNIX account in &lt;code&gt;/etc/passwd&lt;/code&gt;. Password changes happen with &lt;code&gt;passwd&lt;/code&gt; on the shell. SSH login uses the same credentials. This is the realm for admin accounts that also need shell access.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@pve&lt;/code&gt;&lt;/strong&gt; -- Proxmox Virtual Environment internal realm. Passwords are stored in Proxmox's own database (&lt;code&gt;/etc/pve/priv/shadow.cfg&lt;/code&gt;), completely separate from the OS. The user has no UNIX account and cannot SSH in. This is the right realm for giving colleagues or tenants web UI access without touching the OS user database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@ldap&lt;/code&gt; / &lt;code&gt;@ad&lt;/code&gt;&lt;/strong&gt; -- Optional realms for binding to an LDAP directory or Active Directory. Not covered here.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Realm&lt;/th&gt;
&lt;th&gt;Password stored&lt;/th&gt;
&lt;th&gt;SSH access&lt;/th&gt;
&lt;th&gt;UNIX account required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@pam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linux PAM (&lt;code&gt;/etc/shadow&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@pve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proxmox internal DB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For the admin account in this guide we use &lt;code&gt;@pam&lt;/code&gt; because we also want sudo and SSH access.&lt;/p&gt;
&lt;h2&gt;Why Bother&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Audit logs become meaningful. &lt;code&gt;root&lt;/code&gt; in auth logs could be anyone; &lt;code&gt;dave&lt;/code&gt; is traceable.&lt;/li&gt;
&lt;li&gt;SSH brute-force tools target &lt;code&gt;root&lt;/code&gt; first. Disabling it removes the most-attacked entry point.&lt;/li&gt;
&lt;li&gt;Sudo with &lt;code&gt;NOPASSWD&lt;/code&gt; for specific commands is still safer than a permanent root shell.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. Install sudo&lt;/h2&gt;
&lt;p&gt;Proxmox minimal installs may not include sudo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;sudo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;2. Create the System User&lt;/h2&gt;
&lt;p&gt;Log in as root on the Proxmox host (via console or SSH) and add the new UNIX account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;useradd&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;dave
passwd&lt;span class="w"&gt; &lt;/span&gt;dave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;-m&lt;/code&gt; creates a home directory. Set a strong password when prompted.&lt;/p&gt;
&lt;h2&gt;3. Grant sudo Access&lt;/h2&gt;
&lt;p&gt;Add &lt;code&gt;dave&lt;/code&gt; to the &lt;code&gt;sudo&lt;/code&gt; group, which is already configured in &lt;code&gt;/etc/sudoers&lt;/code&gt; to allow full sudo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;usermod&lt;span class="w"&gt; &lt;/span&gt;-aG&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To verify the group membership took effect:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id&lt;span class="w"&gt; &lt;/span&gt;dave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should see &lt;code&gt;sudo&lt;/code&gt; in the groups list.&lt;/p&gt;
&lt;h2&gt;4. Register the User in Proxmox&lt;/h2&gt;
&lt;p&gt;Proxmox has its own user database separate from the Linux PAM database. The &lt;code&gt;@pam&lt;/code&gt; realm bridges them, but you still need to add the user in the Proxmox layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Via the web UI:&lt;/strong&gt; Datacenter &amp;gt; Permissions &amp;gt; Users &amp;gt; Add&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User: &lt;code&gt;dave&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Realm: &lt;code&gt;pam&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enable: checked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Via the CLI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pveum&lt;span class="w"&gt; &lt;/span&gt;user&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;dave@pam
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;5. Assign a Proxmox Role&lt;/h2&gt;
&lt;p&gt;The user needs at least one permission to do anything useful. For a full admin, assign the built-in &lt;code&gt;Administrator&lt;/code&gt; role at the root path &lt;code&gt;/&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pveum&lt;span class="w"&gt; &lt;/span&gt;acl&lt;span class="w"&gt; &lt;/span&gt;modify&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;-user&lt;span class="w"&gt; &lt;/span&gt;dave@pam&lt;span class="w"&gt; &lt;/span&gt;-role&lt;span class="w"&gt; &lt;/span&gt;Administrator
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For a more restricted setup, use &lt;code&gt;PVEAdmin&lt;/code&gt; (everything except node-level config) or define a custom role.&lt;/p&gt;
&lt;h2&gt;6. Test the New Account&lt;/h2&gt;
&lt;p&gt;Before locking out root, confirm the new account works end to end:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open a new terminal (do not close the existing root session).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SSH in as &lt;code&gt;dave&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
ssh dave@&amp;lt;proxmox-ip&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm sudo works:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
sudo -i&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log in to the Proxmox web UI at &lt;code&gt;https://&amp;lt;proxmox-ip&amp;gt;:8006&lt;/code&gt; as &lt;code&gt;dave@pam&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do not proceed to the next step until all three work.&lt;/p&gt;
&lt;h2&gt;7. Disable root SSH Login&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;nano&lt;span class="w"&gt; &lt;/span&gt;/etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Find or add the following line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;PermitRootLogin no
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Reload SSH without dropping existing connections:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;reload&lt;span class="w"&gt; &lt;/span&gt;sshd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Verify by attempting &lt;code&gt;ssh root@&amp;lt;proxmox-ip&amp;gt;&lt;/code&gt; -- it should now be refused.&lt;/p&gt;
&lt;h2&gt;8. Disable root@pam in the Proxmox Web UI&lt;/h2&gt;
&lt;p&gt;Even with SSH root login disabled, &lt;code&gt;root@pam&lt;/code&gt; can still authenticate through the Proxmox web UI. To disable it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Via the web UI:&lt;/strong&gt; Datacenter &amp;gt; Permissions &amp;gt; Users &amp;gt; select &lt;code&gt;root&lt;/code&gt; &amp;gt; Edit &amp;gt; uncheck Enable&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Via the CLI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pveum&lt;span class="w"&gt; &lt;/span&gt;user&lt;span class="w"&gt; &lt;/span&gt;modify&lt;span class="w"&gt; &lt;/span&gt;root@pam&lt;span class="w"&gt; &lt;/span&gt;--enable&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This does not delete root or change anything at the Linux level. It only prevents login through the Proxmox authentication stack.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Linux &lt;code&gt;root&lt;/code&gt; account remains intact. You can always recover it from the physical console or by re-enabling &lt;code&gt;root@pam&lt;/code&gt; through the CLI as your admin user with sudo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Verification Checklist&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Expected Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;dave SSH login&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh dave@&amp;lt;ip&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Login prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dave sudo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo -i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Root shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dave web UI&lt;/td&gt;
&lt;td&gt;Browser &lt;code&gt;https://&amp;lt;ip&amp;gt;:8006&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logged in as dave@pam&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;root SSH blocked&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ssh root@&amp;lt;ip&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Permission denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;root web UI blocked&lt;/td&gt;
&lt;td&gt;Browser login as root@pam&lt;/td&gt;
&lt;td&gt;Login rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useradd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates the Linux PAM account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;apt install sudo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ensures sudo is present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usermod -aG sudo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Grants full sudo via the sudo group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pveum user add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registers the account in Proxmox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pveum acl modify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assigns a Proxmox role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PermitRootLogin no&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blocks root SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pveum user modify root@pam --enable 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blocks root Proxmox web UI login&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content><category term="Hardware &amp; Homelab"/><category term="Proxmox"/><category term="Security"/><category term="Linux"/><category term="Homelab"/><category term="Virtualisation"/></entry><entry><title>Proxmox: Setting Up a New User Account with Dotfiles</title><link href="http://lizard-spock.co.uk/proxmox-user-dotfiles-setup.html" rel="alternate"/><published>2026-04-17T00:00:00+01:00</published><updated>2026-04-17T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-17:/proxmox-user-dotfiles-setup.html</id><summary type="html">&lt;p&gt;After creating a non-root Proxmox user, install essential tools (git, stow, gh) and pull in your dotfiles from GitHub to get a familiar shell environment on a fresh host.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post follows on from &lt;a href="http://lizard-spock.co.uk/proxmox-disable-root-login-sudo-user.html"&gt;Proxmox: Disable root@pam Login and Create a Sudo User&lt;/a&gt;. At that point you have a working &lt;code&gt;dave&lt;/code&gt; account with sudo. The next step is making it feel like home: a few essential tools and your dotfiles pulled from GitHub.&lt;/p&gt;
&lt;p&gt;The dotfiles repo used in this guide is &lt;a href="https://github.com/morganp/dotfiles"&gt;morganp/dotfiles&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Two Strategies&lt;/h2&gt;
&lt;p&gt;Before diving in, it helps to understand the two ways config files are deployed. Most tools use &lt;strong&gt;GNU Stow&lt;/strong&gt;, which creates symlinks from &lt;code&gt;$HOME&lt;/code&gt; (or an XDG config dir) into the repo. A change to the repo file is immediately live everywhere that file is symlinked.&lt;/p&gt;
&lt;p&gt;A small number of files -- shell configs and Vim -- use a &lt;strong&gt;source wrapper&lt;/strong&gt; instead. &lt;code&gt;~/.zshrc&lt;/code&gt; is a real file that exists only on the local machine and contains a single &lt;code&gt;source&lt;/code&gt; line pointing into the repo. This allows host-specific additions (work proxy settings, machine-specific aliases) to live below the source line without touching the shared repo file. It also prevents tools that append to &lt;code&gt;.bashrc&lt;/code&gt; from dirtying the repo.&lt;/p&gt;
&lt;p&gt;The split is:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Most config (git, screen, ctags, ...)&lt;/td&gt;
&lt;td&gt;Stow symlink&lt;/td&gt;
&lt;td&gt;Identical across all hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source wrapper&lt;/td&gt;
&lt;td&gt;Host-specific additions needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.vimrc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source wrapper&lt;/td&gt;
&lt;td&gt;Different plugin sets per host&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Install Basic Tools&lt;/h2&gt;
&lt;p&gt;SSH in as your new user and install the essentials in one pass:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;stow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;gh&lt;/code&gt; (the GitHub CLI) is not in the standard Debian/Ubuntu repos, so it needs the official GitHub apt source:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/keyrings
curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://cli.github.com/packages/githubcli-archive-keyring.gpg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/keyrings/githubcli-archive-keyring.gpg&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;deb [arch=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dpkg&lt;span class="w"&gt; &lt;/span&gt;--print-architecture&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] \&lt;/span&gt;
&lt;span class="s2"&gt;    https://cli.github.com/packages stable main&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;/etc/apt/sources.list.d/github-cli.list&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null

sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;gh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Verify all three are present:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;--version
stow&lt;span class="w"&gt; &lt;/span&gt;--version
gh&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Authenticate with GitHub&lt;/h2&gt;
&lt;p&gt;Before cloning private repos, authenticate &lt;code&gt;gh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gh&lt;span class="w"&gt; &lt;/span&gt;auth&lt;span class="w"&gt; &lt;/span&gt;login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Select &lt;strong&gt;GitHub.com&lt;/strong&gt;, then &lt;strong&gt;HTTPS&lt;/strong&gt;, then &lt;strong&gt;Login with a web browser&lt;/strong&gt; (or a personal access token if the host has no browser). The token is stored in &lt;code&gt;~/.config/gh/hosts.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Confirm it worked:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gh&lt;span class="w"&gt; &lt;/span&gt;auth&lt;span class="w"&gt; &lt;/span&gt;status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Clone the Dotfiles Repository&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gh&lt;span class="w"&gt; &lt;/span&gt;repo&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;morganp/dotfiles&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or with plain git over HTTPS if you skipped &lt;code&gt;gh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/morganp/dotfiles.git&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Strategy 1: Apply with run_stow&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;--dotfiles&lt;/code&gt; flag used throughout the script tells Stow to treat a file named &lt;code&gt;dot-foo&lt;/code&gt; in the package as &lt;code&gt;.foo&lt;/code&gt; in the target directory. This lets the repo store dotfiles without leading dots, making them visible in directory listings and avoiding accidental git ignores.&lt;/p&gt;
&lt;p&gt;The repo ships a &lt;a href="https://github.com/morganp/dotfiles/blob/main/config/run_stow"&gt;&lt;code&gt;run_stow&lt;/code&gt;&lt;/a&gt; script that applies each package explicitly and creates any XDG config directories that may not exist yet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config
bash&lt;span class="w"&gt; &lt;/span&gt;run_stow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Under the hood it does things like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;stow&lt;span class="w"&gt; &lt;/span&gt;input&lt;span class="w"&gt; &lt;/span&gt;--dotfiles&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;~/
stow&lt;span class="w"&gt; &lt;/span&gt;screen&lt;span class="w"&gt; &lt;/span&gt;--dotfiles&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;~/
stow&lt;span class="w"&gt; &lt;/span&gt;ctags&lt;span class="w"&gt; &lt;/span&gt;--dotfiles&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;~/

mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/git
stow&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If Stow finds a file already at the target (e.g. the default &lt;code&gt;.inputrc&lt;/code&gt; written by the system), it will refuse to proceed and print a conflict warning. Remove or back up the conflicting file first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mv&lt;span class="w"&gt; &lt;/span&gt;~/.inputrc&lt;span class="w"&gt; &lt;/span&gt;~/.inputrc.bak
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then re-run the script. Reload the shell once done:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Strategy 2: Shell and Vim via Source Wrapper&lt;/h2&gt;
&lt;p&gt;The shared repo file holds config common to all hosts. Each machine's &lt;code&gt;~/.zshrc&lt;/code&gt; is a thin wrapper that sources the shared file first, then adds anything host-specific below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.zshrc -- this file is NOT in the repo, it lives only on this host&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config/shell/dot-zshrc

&lt;span class="c1"&gt;# host-specific additions below&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;http_proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://proxy.work.example.com:3128
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;backup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rsync -av /data /mnt/nas/backup&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For a work vs. home split, source a second context file or use a hostname conditional:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config/shell/dot-zshrc

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;hostname&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*&lt;span class="s2"&gt;&amp;quot;work&amp;quot;&lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config/shell/dot-zshrc_work
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create &lt;code&gt;~/.bashrc&lt;/code&gt; the same way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.bashrc&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config/shell/dot-bashrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For Vim, &lt;code&gt;dot-vimrc_clean&lt;/code&gt; is a minimal, plugin-free config well suited to servers. Create &lt;code&gt;~/.vimrc&lt;/code&gt; sourcing it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;quot; ~/.vimrc&lt;/span&gt;
&lt;span class="k"&gt;so&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;/dotfiles/&lt;/span&gt;config&lt;span class="sr"&gt;/vim/&lt;/span&gt;dot&lt;span class="p"&gt;-&lt;/span&gt;vimrc_clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The repo also contains &lt;code&gt;dot-vimrc&lt;/code&gt; (full config with plugins) and &lt;code&gt;dot-vimrc_spelling&lt;/code&gt; -- point &lt;code&gt;~/.vimrc&lt;/code&gt; at whichever suits the host. Because these wrapper files are not managed by Stow, they are not tracked by the dotfiles repo on this machine.&lt;/p&gt;
&lt;h2&gt;Verify&lt;/h2&gt;
&lt;p&gt;Check that the Stow symlinks landed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-la&lt;span class="w"&gt; &lt;/span&gt;~/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\-&amp;gt;&amp;#39;&lt;/span&gt;
ls&lt;span class="w"&gt; &lt;/span&gt;-la&lt;span class="w"&gt; &lt;/span&gt;~/.config/git/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\-&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check that the source wrappers are in place:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;head&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;~/.zshrc
head&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;~/.vimrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both should show the &lt;code&gt;source&lt;/code&gt; / &lt;code&gt;so&lt;/code&gt; line pointing into &lt;code&gt;~/dotfiles/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Any future change to a file in &lt;code&gt;~/dotfiles/&lt;/code&gt; is immediately live. Commit and push from &lt;code&gt;~/dotfiles/&lt;/code&gt; as normal to keep the repo in sync across hosts.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stow symlink&lt;/td&gt;
&lt;td&gt;Most config (git, screen, ctags, ...)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bash run_stow&lt;/code&gt; from &lt;code&gt;~/dotfiles/config&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source wrapper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;, &lt;code&gt;~/.vimrc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Real local file with a single &lt;code&gt;source&lt;/code&gt; line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Version control, cloning the dotfiles repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Symlink manager for most config packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitHub CLI for authentication and repo operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content><category term="Hardware &amp; Homelab"/><category term="Proxmox"/><category term="Linux"/><category term="Dotfiles"/><category term="Git"/><category term="Stow"/><category term="Homelab"/><category term="Productivity"/></entry><entry><title>Copy and Paste in a Multi-OS System</title><link href="http://lizard-spock.co.uk/copy-paste-multi-os.html" rel="alternate"/><published>2026-04-15T00:00:00+01:00</published><updated>2026-04-15T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-15:/copy-paste-multi-os.html</id><summary type="html">&lt;p&gt;Unifying copy and paste shortcuts when working across macOS locally and RHEL remotely via OpenText Exceed. Covers terminal emulator behaviour, gVim clipboard mapping, and Karabiner-Elements rules to make Ctrl-Shift-C/V and Cmd-C/V work consistently in both directions.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Working across macOS locally and a remote RHEL system through &lt;a href="https://www.opentext.com/products/exceed-turbo-x"&gt;OpenText Exceed TurboX&lt;/a&gt; creates a clipboard shortcut mismatch. Linux applications expect &lt;code&gt;Ctrl-Shift-C&lt;/code&gt; / &lt;code&gt;Ctrl-Shift-V&lt;/code&gt; for copy and paste; macOS expects &lt;code&gt;Cmd-C&lt;/code&gt; / &lt;code&gt;Cmd-V&lt;/code&gt;. Switching mental models every time the active window changes is error-prone and slow. This post covers a setup that makes both shortcut conventions work regardless of which application is in focus.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Copy&lt;/th&gt;
&lt;th&gt;Paste&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS native apps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Cmd-C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Cmd-V&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux terminal via Exceed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-V&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gVim via Exceed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"+y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"+gP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The goal is to be able to use either convention and have it work correctly -- mapping macOS shortcuts into Linux ones when Exceed is active, and mapping Linux shortcuts into macOS ones everywhere else.&lt;/p&gt;
&lt;h2&gt;eXceed Usage&lt;/h2&gt;
&lt;h3&gt;Terminal Emulator&lt;/h3&gt;
&lt;p&gt;A terminal emulator running inside Exceed already supports the Linux convention natively -- no configuration is required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Ctrl-Shift-C&lt;/code&gt; -- copy to system clipboard&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl-Shift-V&lt;/code&gt; -- paste from system clipboard&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;gVim: Adding Ctrl-Shift-C / Ctrl-Shift-V&lt;/h3&gt;
&lt;p&gt;Vim does not use the system clipboard by default. Add the following to &lt;code&gt;.vimrc&lt;/code&gt; to map the Linux terminal shortcuts to yank and paste from the system clipboard (&lt;code&gt;+&lt;/code&gt; register):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;quot; Map Ctrl-Shift-C / Ctrl-Shift-V to system clipboard copy / paste&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;map &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;C&lt;span class="p"&gt;-&lt;/span&gt;S&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;quot;+y&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;map &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;C&lt;span class="p"&gt;-&lt;/span&gt;S&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;v&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;quot;+gP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Karabiner-Elements: Unifying the Shortcuts on macOS&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://karabiner-elements.pqrs.org/"&gt;Karabiner-Elements&lt;/a&gt; is a keyboard remapper for macOS. Two sets of rules are needed -- one for all applications except Exceed, and one for Exceed specifically.&lt;/p&gt;
&lt;h3&gt;Rule Set 1: Ctrl-Shift shortcuts outside Exceed&lt;/h3&gt;
&lt;p&gt;For every application except Exceed, map the Linux-style shortcuts to their macOS equivalents. This means pressing &lt;code&gt;Ctrl-Shift-C&lt;/code&gt; in any native macOS app triggers &lt;code&gt;Cmd-C&lt;/code&gt;, so the Linux muscle memory works everywhere.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Unix/CopyPaste/karabiner-non-exceed-HQ.png"&gt;&lt;img alt="Karabiner-Elements: Ctrl-Shift rules applied to all apps except Exceed" src="http://lizard-spock.co.uk/images/Unix/CopyPaste/karabiner-non-exceed-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The four rules (copy, paste, cut, undo):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Ctrl+Shift+C to Copy (Cmd+C)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_unless&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Ctrl+Shift+V to Paste (Cmd+V)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_unless&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Ctrl+Shift+X to Cut (Cmd+X)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_unless&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Ctrl+Shift+Z to Undo (Cmd+Z)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_unless&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Rule Set 2: Cmd shortcuts inside Exceed&lt;/h3&gt;
&lt;p&gt;When Exceed is the active application, the reverse mapping applies: &lt;code&gt;Cmd-C/V/X/Z&lt;/code&gt; are translated into &lt;code&gt;Ctrl-Shift-C/V/X/Z&lt;/code&gt; so they reach gVim and other Linux applications with the shortcuts they expect.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/Unix/CopyPaste/karabiner-exceed-HQ.png"&gt;&lt;img alt="Karabiner-Elements: Cmd rules applied when Exceed is active" src="http://lizard-spock.co.uk/images/Unix/CopyPaste/karabiner-exceed-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Copy Cmd+C to Ctrl+Shift+C on Exceed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_if&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Paste Cmd+V to Ctrl+Shift+V on Exceed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_if&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Cut Cmd+X to Ctrl+Shift+X on Exceed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_if&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Map Undo Cmd+Z to Ctrl+Shift+Z on Exceed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;manipulators&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;conditions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bundle_identifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^com\\.OpenText\\.Exceed-TurboX-Client$&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;frontmost_application_if&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mandatory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;left_command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;key_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;modifiers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;control&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;shift&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;basic&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;With both rule sets in place:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Outside Exceed&lt;/th&gt;
&lt;th&gt;Inside Exceed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-C&lt;/code&gt; or &lt;code&gt;Cmd-C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-C&lt;/code&gt; or &lt;code&gt;Cmd-C&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-V&lt;/code&gt; or &lt;code&gt;Cmd-V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-V&lt;/code&gt; or &lt;code&gt;Cmd-V&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cut&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-X&lt;/code&gt; or &lt;code&gt;Cmd-X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-X&lt;/code&gt; or &lt;code&gt;Cmd-X&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-Z&lt;/code&gt; or &lt;code&gt;Cmd-Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl-Shift-Z&lt;/code&gt; or &lt;code&gt;Cmd-Z&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both shortcut conventions work in both contexts. Switching between a macOS application and a remote RHEL session in Exceed requires no change in keyboard habit.&lt;/p&gt;
&lt;h2&gt;Managing Karabiner Configuration with Stow&lt;/h2&gt;
&lt;p&gt;Karabiner-Elements stores its configuration as JSON at &lt;code&gt;~/.config/karabiner/karabiner.json&lt;/code&gt;. This makes it straightforward to track in a dotfiles repository and deploy with &lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the dotfiles repo, place the config at &lt;code&gt;config/karabiner/karabiner.json&lt;/code&gt; to mirror the target path. Then create the directory and stow it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/karabiner
stow&lt;span class="w"&gt; &lt;/span&gt;karabiner&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/karabiner
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This creates a symlink from &lt;code&gt;~/.config/karabiner/karabiner.json&lt;/code&gt; into the dotfiles repo, so any edits made via the Karabiner-Elements UI are immediately reflected in the tracked file.&lt;/p&gt;
&lt;p&gt;The Karabiner config for this setup is included in &lt;a href="https://github.com/morganp/dotfiles"&gt;morganp/dotfiles&lt;/a&gt;.&lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="macOS"/><category term="Linux"/><category term="RHEL"/><category term="Karabiner-Elements"/><category term="gVim"/><category term="Productivity"/><category term="Keyboard"/></entry><entry><title>Parametric Knob Maker for OpenSCAD</title><link href="http://lizard-spock.co.uk/parametric-knob-maker-openscad.html" rel="alternate"/><published>2026-04-13T00:00:00+01:00</published><updated>2026-04-13T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-13:/parametric-knob-maker-openscad.html</id><summary type="html">&lt;p&gt;A fork of aminGhafoory's parametric-knob-maker that adds a generalised round knob module with configurable offset base, chamfers, and grip cutouts. Covers the knob() and hex_knob() modules, the key parameters, and how to combine the result with a BOSL2 screw for a complete thumbscrew assembly.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Parametric thumbscrew knobs come up often enough in 3D printing projects that it is worth having a reusable library for them. This post covers a fork of &lt;a href="https://github.com/aminGhafoory/parametric-knob-maker"&gt;aminGhafoory/parametric-knob-maker&lt;/a&gt; that adds a generalised round knob module. The fork is at &lt;a href="https://github.com/morganp/parametric-knob-maker"&gt;morganp/parametric-knob-maker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The library was used to generate the thumbscrew knob in the &lt;a href="http://lizard-spock.co.uk/openscad-pinch-rods.html"&gt;pinch rods&lt;/a&gt; post.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;Clone or download the repository into your OpenSCAD libraries folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~/.local/share/OpenSCAD/libraries/     (Linux)
~/Documents/OpenSCAD/libraries/        (macOS)
My Documents\OpenSCAD\libraries\       (Windows)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;The &lt;code&gt;knob()&lt;/code&gt; Module&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;parametric_knob.scad&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The round knob has a narrower offset base for mounting clearance, chamfered top and bottom edges, and equally-spaced cylindrical grip cutouts around the perimeter. Z=0 is at the base, making it straightforward to position on top of a screw shaft.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// height of the main knob body         (default: 15)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_diam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;// diameter of the main knob body       (default: 30)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;offset_height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// height of the narrower base section  (default: 5)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;offset_diam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// diameter of the base section         (default: knob_diam/2)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_chamfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// top edge chamfer depth at 45 deg     (default: 2)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;base_chamfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// base edge chamfer depth at 45 deg    (default: 1)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;num_grip_cutouts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// number of finger grip cutouts        (default: 15)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;grip_cutout_diam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// diameter of each grip cutout         (default: 4)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cutout_radius_adj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// outward offset of cutout centres     (default: 1)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_color&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;// preview colour                       (default: &amp;quot;red&amp;quot;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Default knob&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Default knob -- 30mm diameter, 15mm tall, 15 grip cutouts" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/knob_default.png"&gt;&lt;/p&gt;
&lt;h3&gt;Small thumbscrew knob&lt;/h3&gt;
&lt;p&gt;Compact knob for M3/M4 screws where finger space is limited.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knob_height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;knob_diam&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset_height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Small knob -- 20mm diameter, 8mm tall" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/knob_small.png"&gt;&lt;/p&gt;
&lt;h3&gt;Large adjustment knob&lt;/h3&gt;
&lt;p&gt;Wider knob with more cutouts for applications needing higher torque or finer control.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_height&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_diam&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;num_grip_cutouts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;grip_cutout_diam&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_color&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SteelBlue&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Large knob -- 50mm diameter, 20mm tall, 20 grip cutouts" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/knob_large.png"&gt;&lt;/p&gt;
&lt;h3&gt;Slim base&lt;/h3&gt;
&lt;p&gt;A narrow offset base minimises the footprint on the mounting surface -- useful when the knob sits close to a panel or bracket.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_height&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_diam&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;offset_height&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;offset_diam&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_color&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DarkOrange&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Slim base knob -- 35mm diameter with narrow 10mm base" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/knob_slim_base.png"&gt;&lt;/p&gt;
&lt;h3&gt;Combining with a BOSL2 screw&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;knob()&lt;/code&gt; module pairs neatly with BOSL2's &lt;code&gt;screw()&lt;/code&gt; to produce a complete thumbscrew assembly. Place the screw with &lt;code&gt;anchor=BOTTOM&lt;/code&gt; so the thread points downward, then translate the knob up to sit on top of the shaft.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;BOSL2/std.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;BOSL2/screws.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;THREAD_LEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;union&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;screw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;M5&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;THREAD_LEN&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="l"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BOTTOM&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;THREAD_LEN&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knob_height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset_height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Thumbscrew assembly -- knob with M5 threaded shaft below" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/knob_thumbscrew.png"&gt;&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;hex_knob()&lt;/code&gt; Module&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;parametric_hex_knob.scad&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The hex knob is intended to capture a hex-head bolt in its base recess and provide a through-hole for the shaft, removing the need for any additional fastener. The hex screw recess is currently under development and commented out -- the module renders the knob body and grip cutouts only.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cpf"&gt;parametric-knob-maker/parametric_hex_knob.scad&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;hex_knob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;// height of the knob body           (default: 15)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;knob_diam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// diameter of the knob              (default: 30)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;screwhead_facetoface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// hex head face-to-face size (mm)   (default: 8)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;screwhead_depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// depth of hex recess               (default: 12)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;thru_hole_diam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// through-hole diameter             (default: 4)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;num_grip_cutouts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// number of finger grip cutouts     (default: 20)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;grip_cutout_diam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// diameter of each grip cutout      (default: 4)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cutout_radius_adj&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// outward offset of cutout centres  (default: 1)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;screwhead_facetoface&lt;/code&gt; parameter corresponds to the face-to-face dimension &lt;code&gt;s&lt;/code&gt; in DIN 933. The knob is designed to capture the bolt head in the base recess with the shaft passing through -- the hex recess feature is still under development.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hex knob underside with M8 hex bolt ready to insert" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/hex_knob_with_bolt.png"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Default hex knob&lt;/th&gt;
&lt;th&gt;Large hex knob&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt="Default hex knob -- 30mm diameter, 15mm tall" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/hex_knob_default.png"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img alt="Large hex knob -- 45mm diameter, 20mm tall" src="http://lizard-spock.co.uk/images/OpenSCAD/ParametricKnob/hex_knob_large.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Implementation Notes&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;knob()&lt;/code&gt; geometry is built from 2D profiles rotated with &lt;code&gt;rotate_extrude()&lt;/code&gt;. Chamfers are cut using &lt;code&gt;difference()&lt;/code&gt; against polygon shapes. Grip cutouts are placed by a helper module &lt;code&gt;rotate_on_circle()&lt;/code&gt; that distributes cylinders at equal angular intervals around the perimeter using &lt;code&gt;sin()&lt;/code&gt;/&lt;code&gt;cos()&lt;/code&gt; positioning.&lt;/p&gt;
&lt;p&gt;The source is on GitHub at &lt;a href="https://github.com/morganp/parametric-knob-maker"&gt;morganp/parametric-knob-maker&lt;/a&gt;.&lt;/p&gt;</content><category term="Engineering"/><category term="OpenSCAD"/><category term="3D Printing"/><category term="BOSL2"/><category term="CAD"/><category term="Parametric Design"/></entry><entry><title>RFC 2119 Requirements Keywords</title><link href="http://lizard-spock.co.uk/rfc-2119-requirements-keywords.html" rel="alternate"/><published>2026-04-13T00:00:00+01:00</published><updated>2026-04-13T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-13:/rfc-2119-requirements-keywords.html</id><summary type="html">&lt;p&gt;RFC 2119 defines a vocabulary of requirement keywords -- MUST, SHOULD, MAY and their negatives -- widely used in IETF specifications and software engineering. This post covers the full RFC 2119 keyword set, the capitalisation convention introduced by RFC 8174, and the key differences from the IEEE normative term conventions covered in the earlier requirements writing post.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A previous post on &lt;a href="http://lizard-spock.co.uk/requirements-writing.html"&gt;Requirements Writing&lt;/a&gt; covered the IEEE normative term conventions used in hardware and semiconductor product development. This post covers a related but distinct standard: &lt;a href="https://datatracker.ietf.org/doc/html/rfc2119"&gt;RFC 2119&lt;/a&gt;, authored by Scott Bradner and published by the IETF in 1997. RFC 2119 defines a small vocabulary of requirement keywords intended for internet protocol specifications, but its usage has spread widely into software engineering, open source project documentation, and API specifications.&lt;/p&gt;
&lt;h2&gt;The RFC 2119 Keywords&lt;/h2&gt;
&lt;p&gt;RFC 2119 defines five levels of requirement, expressed through the following keywords and their synonyms:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keyword&lt;/th&gt;
&lt;th&gt;Synonyms&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MUST&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED, SHALL&lt;/td&gt;
&lt;td&gt;An absolute requirement of the specification.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MUST NOT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SHALL NOT&lt;/td&gt;
&lt;td&gt;An absolute prohibition of the specification.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SHOULD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RECOMMENDED&lt;/td&gt;
&lt;td&gt;There may be valid reasons to ignore this in particular circumstances, but the full implications must be understood and weighed before doing so.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SHOULD NOT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NOT RECOMMENDED&lt;/td&gt;
&lt;td&gt;There may be valid reasons when this behaviour is acceptable, but the full implications should be understood before proceeding.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MAY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;The item is truly optional. Implementations that omit it and implementations that include it must interoperate correctly.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RFC 2119 provides explicit definitions for both the positive and negative forms of each level. The negative forms -- MUST NOT and SHOULD NOT -- are first-class entries in the standard, not merely implied.&lt;/p&gt;
&lt;h2&gt;The Capitalisation Convention (RFC 8174)&lt;/h2&gt;
&lt;p&gt;A critical and commonly overlooked aspect of RFC 2119 is that the special meanings apply &lt;strong&gt;only when the keywords appear in ALL CAPITALS&lt;/strong&gt;. When the same words appear in lowercase, they carry their ordinary English meanings and make no normative claim. &lt;a href="https://datatracker.ietf.org/doc/html/rfc8174"&gt;RFC 8174&lt;/a&gt;, published in 2017, was issued specifically to clarify and reinforce this distinction after widespread confusion in practice.&lt;/p&gt;
&lt;p&gt;Documents that use RFC 2119 vocabulary should include a boilerplate statement at the top, such as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals, as shown here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This convention means that an author can write "the system should log errors" as informal guidance without it carrying normative weight, while "the system SHOULD log errors" is a normative SHOULD-level requirement. The IEEE standard has no equivalent mechanism -- every occurrence of "shall", "should", or "may" carries normative meaning regardless of case.&lt;/p&gt;
&lt;h2&gt;Key Differences from IEEE Conventions&lt;/h2&gt;
&lt;p&gt;The IEEE Standards Style Manual and RFC 2119 share the same intent -- precise, unambiguous requirements -- but they make different choices in several areas. The differences are significant enough that mixing vocabulary from both standards in the same document without clarification will cause confusion.&lt;/p&gt;
&lt;h3&gt;MUST vs SHALL&lt;/h3&gt;
&lt;p&gt;This is the sharpest conflict between the two conventions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RFC 2119:&lt;/strong&gt; MUST and SHALL are exact synonyms. Either may be used to express an absolute requirement, and the choice between them is purely stylistic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IEEE:&lt;/strong&gt; The word "must" is deprecated for requirements use. "Shall" is the sole term for a mandatory requirement. "Must" is reserved for describing unavoidable situations -- physical constraints or facts of nature -- not obligations imposed by the specification.&lt;/p&gt;
&lt;p&gt;In a hardware or semiconductor context following IEEE conventions, writing MUST where the IEEE expects SHALL is non-conforming. In an IETF or software context following RFC 2119, writing "shall" is unusual but not incorrect. When working across both domains, choose one convention and state it explicitly at the outset.&lt;/p&gt;
&lt;h3&gt;Negative Forms&lt;/h3&gt;
&lt;p&gt;The IEEE style manual covers negative requirements through phrasing such as "shall not" but does not give MUST NOT or SHOULD NOT the same explicit definitional treatment that RFC 2119 does. RFC 2119 is more thorough here: MUST NOT is defined as an absolute prohibition on equal footing with MUST as an absolute requirement.&lt;/p&gt;
&lt;h3&gt;OPTIONAL as a Synonym for MAY&lt;/h3&gt;
&lt;p&gt;RFC 2119 defines OPTIONAL as a formal synonym for MAY, emphasising interoperability: if a feature is OPTIONAL, both implementations that include it and those that omit it are conforming, and they must work together. The IEEE treatment of "may" does not carry this interoperability framing -- it signals permission within the specification, not a guarantee of cross-implementation compatibility.&lt;/p&gt;
&lt;h3&gt;Scope of Use&lt;/h3&gt;
&lt;p&gt;RFC 2119 includes explicit guidance that its keywords "must be used with care and sparingly". They should only appear where a requirement is genuinely necessary for interoperability or to prevent harmful behaviour -- not to impose implementation style or preferred approaches. The IEEE standard is similarly precise, but the RFC 2119 framing is explicitly interoperability-first, reflecting its origin in network protocol design.&lt;/p&gt;
&lt;h2&gt;When to Use Which Convention&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Recommended convention&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware, silicon, semiconductor product specs&lt;/td&gt;
&lt;td&gt;IEEE Standards Style Manual (shall / should / may)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IETF internet protocol specifications&lt;/td&gt;
&lt;td&gt;RFC 2119 / RFC 8174 (MUST / SHOULD / MAY in caps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software project specifications, APIs, open source&lt;/td&gt;
&lt;td&gt;RFC 2119 is common; state the convention explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed hardware/software systems&lt;/td&gt;
&lt;td&gt;Choose one; document it in a definitions section&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Whichever convention is in use, the principle from the requirements writing post applies: terms must be used precisely and consistently. Imprecision in normative vocabulary -- using "should" when "shall" is meant, or omitting MUST NOT when a prohibition is intended -- produces requirements that are untraceable and unverifiable.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc2119"&gt;RFC 2119 -- Key Words for Use in RFCs to Indicate Requirement Levels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc8174"&gt;RFC 8174 -- Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/requirements-writing.html"&gt;Requirements Writing&lt;/a&gt; -- IEEE normative terms and requirement categories&lt;/li&gt;
&lt;/ul&gt;</content><category term="Engineering"/><category term="Design"/><category term="Requirements"/><category term="Standards"/><category term="RFC"/></entry><entry><title>3D Printed Pinch Rods in OpenSCAD</title><link href="http://lizard-spock.co.uk/openscad-pinch-rods.html" rel="alternate"/><published>2026-04-12T00:00:00+01:00</published><updated>2026-04-12T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-12:/openscad-pinch-rods.html</id><summary type="html">&lt;p&gt;Designing a parametric set of pinch rods in OpenSCAD using the BOSL2 library. Pinch rods are a traditional woodworking tool for checking carcases for square and transferring interior dimensions without measuring.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/assembled-HQ.png"&gt;&lt;img alt="Assembled pinch rods" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/assembled-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;What are Pinch Rods?&lt;/h2&gt;
&lt;p&gt;Pinch rods are a traditional woodworking measuring tool that predate the tape measure as the reliable way to check whether a box, cabinet, or carcase is square. The concept is simple: two flat rods with pointed ends overlap and slide through a pair of guide blocks. You nestle the tips into opposite diagonal corners of an assembly, lock the rods in place, then check the other diagonal. If both diagonals match, the assembly is square.&lt;/p&gt;
&lt;p&gt;The beauty of pinch rods over a tape measure is that you never read a number. The rods physically span the diagonal, and you use that same span directly on the other diagonal. Any discrepancy is immediately obvious as a gap or an overrun at one tip.&lt;/p&gt;
&lt;p&gt;They have a second use too: transferring interior dimensions. Set the rods to fit snugly inside a space, lock them, carry the tool to your workpiece, and mark directly. No arithmetic, no misread ruler.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/assembled_side-HQ.png"&gt;&lt;img alt="Side profile showing both guides and the two overlapping rods" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/assembled_side-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Lost Art Press have documented pinch rods across several articles spanning 2013 to 2025, from a home-centre build using steel tube sleeves and heart pine, through to the machined brass Crucible Tool hardware and a new all-metal prototype using 1/4" keystock. The design has been remarkably stable across all those years -- two rods, two guides, one thumbscrew.&lt;/p&gt;
&lt;h2&gt;The OpenSCAD Design&lt;/h2&gt;
&lt;p&gt;A parametrised OpenSCAD model. The guides with or without the rods can be 3D printed. The full source is on GitHub at &lt;a href="https://github.com/morganp/OpenSCAD_PinchRods"&gt;morganp/OpenSCAD_PinchRods&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The model parameters:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;STOCK_THICKNESS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// height of ONE rod -- two stack to fill the opening&lt;/span&gt;
&lt;span class="n"&gt;STOCK_WIDTH&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// width of each rod&lt;/span&gt;
&lt;span class="n"&gt;WALL_THICKNESS&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// guide wall thickness&lt;/span&gt;
&lt;span class="n"&gt;GUIDE_LENGTH&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// depth of each guide block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The two rods are 19mm wide and 9.5mm tall each. They sit one on top of the other inside both guide blocks, so the guide opening is 19mm x 19mm total.&lt;/p&gt;
&lt;h3&gt;Display Mode&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;MODE&lt;/code&gt; variable at the top switches between a full assembly preview and single-part export for printing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;MODE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;assembled&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// or &amp;quot;print&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;PRINT_PART&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;guide2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;fastener&amp;quot; | &amp;quot;guide1&amp;quot; | &amp;quot;guide2&amp;quot; | &amp;quot;rod&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;The Two Guides&lt;/h2&gt;
&lt;p&gt;The guides are intentionally different. Each one locks a different rod, so you can set one end, then adjust the other independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Guide 1&lt;/strong&gt; is mounted inverted in the assembly. Flipping it 180 degrees around the X axis keeps the rod opening in exactly the same position (the opening is symmetric around the centreline at Z = 14.5mm), but moves the countersunk screw hole from the bottom to the top. A screw through that top hole locks onto the top rod.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Guide 2&lt;/strong&gt; has a cylindrical standoff boss on top with an M5 threaded hole. A clamping membrane -- a thin slab just inside the opening -- transmits force from the thumbscrew down onto the bottom rod when the knob is tightened. BOSL2's &lt;code&gt;screw_hole()&lt;/code&gt; cuts the threaded recess into the standoff, and &lt;code&gt;screw()&lt;/code&gt; with &lt;code&gt;anchor=BOTTOM&lt;/code&gt; generates the matching fastener.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Guide 1 -- inverted, screw hole on top&lt;/th&gt;
&lt;th&gt;Guide 2 -- M5 thumbscrew knob&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/guide1_detail-HQ.png"&gt;&lt;img alt="Guide 1" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/guide1_detail-900w.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/guide2_detail-HQ.png"&gt;&lt;img alt="Guide 2" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/guide2_detail-900w.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Tapered Tips&lt;/h2&gt;
&lt;p&gt;Each rod has a 45-degree chisel taper at one end. Both tapers point toward the shared centreline between the two stacked rods, so from the side the two tips form a symmetric arrowhead shape pointing at the Z midpoint.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/tips_detail-HQ.png"&gt;&lt;img alt="Tapered rod tips exiting guide 1" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/tips_detail-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The taper is generated using &lt;code&gt;hull()&lt;/code&gt; between a knife-edge line at the tip and a full rectangular cross-section a little further along. At 45 degrees the horizontal run equals the vertical rise, so the taper length is exactly &lt;code&gt;STOCK_THICKNESS&lt;/code&gt; (9.5mm). The two rods have their tapers on opposite ends of the assembly so each tip engages a different corner of the workpiece.&lt;/p&gt;
&lt;h2&gt;Printable Parts&lt;/h2&gt;
&lt;p&gt;Four parts are printed separately. For 3mf export, set &lt;code&gt;MODE = "print"&lt;/code&gt; and select the part with &lt;code&gt;PRINT_PART&lt;/code&gt;. Note that Guide 1 is printed in its natural orientation (flat bottom on the bed) and then flipped over when assembled. Print two rods and flip one on assembly -- both are the same shape.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Guide 1&lt;/th&gt;
&lt;th&gt;Guide 2&lt;/th&gt;
&lt;th&gt;Thumbscrew&lt;/th&gt;
&lt;th&gt;Rod (print x2)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_guide1-HQ.png"&gt;&lt;img alt="Guide 1 part" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_guide1-900w.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_guide2-HQ.png"&gt;&lt;img alt="Guide 2 part" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_guide2-900w.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_fastener-HQ.png"&gt;&lt;img alt="Fastener part" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_fastener-900w.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_rod-HQ.png"&gt;&lt;img alt="Rod part" src="http://lizard-spock.co.uk/images/OpenSCAD/PinchRods/part_rod-900w.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;p&gt;The SCAD file requires two OpenSCAD libraries in your libraries folder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/BelfrySCAD/BOSL2"&gt;BOSL2&lt;/a&gt; -- parametric screws, threads, and screw holes&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/morganp/parametric-knob-maker"&gt;parametric-knob-maker&lt;/a&gt; -- the thumbscrew knob profile (fork with generalised round knob module)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The source is on GitHub at &lt;a href="https://github.com/morganp/OpenSCAD_PinchRods"&gt;morganp/OpenSCAD_PinchRods&lt;/a&gt;.&lt;/p&gt;</content><category term="Woodwork"/><category term="OpenSCAD"/><category term="3D Printing"/><category term="BOSL2"/><category term="Woodworking"/><category term="CAD"/><category term="Parametric Design"/></entry><entry><title>SoC Article 06: Interconnects and Bus Protocols - AXI, AHB, and APB</title><link href="http://lizard-spock.co.uk/soc-article-06-interconnects-and-bus-protocols.html" rel="alternate"/><published>2026-04-09T00:00:00+01:00</published><updated>2026-04-09T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-09:/soc-article-06-interconnects-and-bus-protocols.html</id><summary type="html">&lt;p&gt;How SoC blocks communicate: the AMBA bus family from simple APB peripherals through pipelined AHB to the high-performance AXI4 with its five independent channels, valid/ready handshake, and crossbar interconnects.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 6 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Every SoC block, including CPUs, DMA engines, memory controllers, and peripherals, must communicate with every other block. The internal communication fabric that makes this possible is called the &lt;strong&gt;interconnect&lt;/strong&gt; or &lt;strong&gt;bus&lt;/strong&gt;. Choosing and designing the interconnect is one of the most consequential architectural decisions in SoC design: it determines bandwidth, latency, arbitration, and overall system throughput.&lt;/p&gt;
&lt;p&gt;This article introduces the major bus standards used in modern SoCs, with emphasis on &lt;strong&gt;ARM's AMBA (Advanced Microcontroller Bus Architecture)&lt;/strong&gt; family, which is the dominant standard in the industry.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why a Standard Bus Matters&lt;/h2&gt;
&lt;p&gt;Without a standard interface, every IP block would need a custom interface to every other block: an impractical explosion of design effort. A standard bus protocol defines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Signal names and widths&lt;/strong&gt; -- both sides know how to connect&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handshake mechanism&lt;/strong&gt; -- how a master requests and a slave responds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transaction semantics&lt;/strong&gt; -- what "read", "write", and "burst" mean&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timing requirements&lt;/strong&gt; -- setup times, hold times, latency bounds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IP vendors design their blocks to be &lt;strong&gt;plug-compatible&lt;/strong&gt; with the standard, so a UART controller from one vendor and a DMA engine from another can both connect to the same ARM CoreLink interconnect without any custom glue logic.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The AMBA Family&lt;/h2&gt;
&lt;p&gt;ARM's &lt;strong&gt;AMBA&lt;/strong&gt; specification, first published in 1996 and evolving through multiple generations, defines a family of bus protocols at different performance tiers:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article06/06-amba-family-HQ.png"&gt;&lt;img alt="AMBA protocol family spectrum showing ACE/CHI, AXI4, AHB, and APB from high-performance to simple" src="http://lizard-spock.co.uk/images/SoC/Article06/06-amba-family-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The family spans from simple, low-power APB peripherals on the right through pipelined AHB, high-performance AXI4, all the way to cache-coherent ACE/CHI protocols used in multi-core processor clusters on the left.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;APB: advanced peripheral bus&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;APB&lt;/strong&gt; is the simplest AMBA protocol, designed for low-bandwidth, low-power peripheral registers. It is a &lt;strong&gt;synchronous, non-pipelined&lt;/strong&gt; bus with no burst mode -- every transfer takes a minimum of two clock cycles.&lt;/p&gt;
&lt;h3&gt;APB Signals&lt;/h3&gt;
&lt;p&gt;Key APB signals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PCLK&lt;/code&gt;: clock&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PRESETn&lt;/code&gt;: active-low reset&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PADDR&lt;/code&gt;: address (12-32 bit)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PSEL&lt;/code&gt;: select, asserted to indicate target peripheral&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PENABLE&lt;/code&gt;: enable, on second cycle of every transfer&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PWRITE&lt;/code&gt;: direction, high = write, low = read&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PWDATA&lt;/code&gt;: write data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PRDATA&lt;/code&gt;: read data (from slave)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PREADY&lt;/code&gt;: slave extends transfer if not ready&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PSLVERR&lt;/code&gt;: slave error response&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;APB Write Timing&lt;/h3&gt;
&lt;p&gt;An APB write transaction takes place over two phases: &lt;strong&gt;Setup&lt;/strong&gt; and &lt;strong&gt;Access&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_79b5927f0a87823d50c3de26c99e8452.svg"&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;Setup&lt;/strong&gt; phase, PSEL is asserted and the address and write data are presented. In the &lt;strong&gt;Access&lt;/strong&gt; phase, PENABLE goes high confirming the transfer. PREADY allows a slow peripheral to extend the access phase by holding it low.&lt;/p&gt;
&lt;p&gt;APB is ideal for: UART, SPI, I2C, GPIO, timer, and watchdog registers -- anything that does not require high data throughput.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AHB: advanced high-performance bus&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AHB&lt;/strong&gt; sits in the middle tier. It is a &lt;strong&gt;pipelined&lt;/strong&gt;, higher-bandwidth protocol supporting burst transfers. AHB separates the &lt;strong&gt;address phase&lt;/strong&gt; and &lt;strong&gt;data phase&lt;/strong&gt;, allowing the next address to be issued while the current data transfer is in progress.&lt;/p&gt;
&lt;h3&gt;Key AHB Signals&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HCLK&lt;/code&gt;, &lt;code&gt;HRESETn&lt;/code&gt;: clock and reset&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HADDR&lt;/code&gt;: address (32-bit)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTRANS&lt;/code&gt;: transfer type, IDLE, BUSY, NONSEQ, or SEQ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HWRITE&lt;/code&gt;: direction&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HSIZE&lt;/code&gt;: transfer size (8, 16, 32, 64... bit)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HBURST&lt;/code&gt;: burst type (SINGLE, INCR, WRAP4, INCR4...)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HWDATA&lt;/code&gt; and &lt;code&gt;HRDATA&lt;/code&gt;: write and read data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HREADY&lt;/code&gt;: transfer complete or extend&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HRESP&lt;/code&gt;: response (OKAY or ERROR)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AHB Pipelined Burst Read&lt;/h3&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_b0bc3916a3a9fe56242afe61c3f4ef2c.svg"&gt;&lt;/p&gt;
&lt;p&gt;The key insight: while the data for A0 is being returned, the address A1 is already being presented. This pipeline overlap hides address-to-data latency and improves throughput.&lt;/p&gt;
&lt;h3&gt;AHB Arbitration&lt;/h3&gt;
&lt;p&gt;When multiple masters (CPU, DMA) contend for the AHB, an &lt;strong&gt;arbiter&lt;/strong&gt; decides who gets access using a bus grant / bus request protocol. Masters assert &lt;code&gt;HBUSREQx&lt;/code&gt; when they want the bus; the arbiter asserts &lt;code&gt;HGRANTx&lt;/code&gt; to indicate the winner. This round-robin or priority-based arbitration introduces a form of time-division multiplexing onto the shared bus.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AXI: advanced eXtensible interface&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AXI4&lt;/strong&gt; is the highest-performance AMBA protocol, used to connect CPUs, memory controllers, DMA engines, GPUs, and other high-bandwidth masters and slaves. It is the backbone of most modern SoCs.&lt;/p&gt;
&lt;p&gt;AXI4's key innovation is &lt;strong&gt;independent channels&lt;/strong&gt;: read and write transactions are completely separate, and multiple transactions can be in flight simultaneously.&lt;/p&gt;
&lt;h3&gt;The five AXI channels&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article06/06-axi-channels-HQ.png"&gt;&lt;img alt="AXI4 five-channel architecture diagram showing master and slave blocks connected by AW, W, B, AR, and R channels with signal names" src="http://lizard-spock.co.uk/images/SoC/Article06/06-axi-channels-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The five channels are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AW (Write Address)&lt;/strong&gt;: master sends the address and burst parameters for a write&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;W (Write Data)&lt;/strong&gt;: master sends the data beats with byte strobes and WLAST flag&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B (Write Response)&lt;/strong&gt;: slave confirms the write completed (or reports an error)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AR (Read Address)&lt;/strong&gt;: master sends the address and burst parameters for a read&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R (Read Data)&lt;/strong&gt;: slave returns data beats with RLAST flag&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Valid/Ready Handshake&lt;/h3&gt;
&lt;p&gt;Every AXI channel uses the same &lt;strong&gt;valid/ready handshake&lt;/strong&gt; protocol. The sender asserts &lt;code&gt;VALID&lt;/code&gt; when it has data to transfer. The receiver asserts &lt;code&gt;READY&lt;/code&gt; when it can accept data. The transfer completes on the rising clock edge when &lt;strong&gt;both&lt;/strong&gt; are high:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_6921d6179fd61809d2c1545f92cf2bfe.svg"&gt;&lt;/p&gt;
&lt;p&gt;This decoupled handshake is powerful: the master can issue addresses continuously; the slave can stall when its internal buffers are full, without requiring the master to back off.&lt;/p&gt;
&lt;h3&gt;AXI burst transfers&lt;/h3&gt;
&lt;p&gt;Rather than issuing many single transactions, AXI supports &lt;strong&gt;burst transfers&lt;/strong&gt; -- a single address phase followed by multiple data beats. This dramatically improves efficiency for sequential memory access.&lt;/p&gt;
&lt;p&gt;Key burst parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWLEN / ARLEN&lt;/strong&gt; -- number of data beats (0=1 beat, 255=256 beats for AXI4)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWSIZE / ARSIZE&lt;/strong&gt; -- bytes per beat (1, 2, 4, 8, 16, 32, 64, 128)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWBURST / ARBURST&lt;/strong&gt; -- burst type:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FIXED&lt;/code&gt; -- same address repeated (useful for FIFOs)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INCR&lt;/code&gt; -- address increments each beat (normal memory access)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WRAP&lt;/code&gt; -- like INCR but wraps at a boundary (useful for cache lines)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AXI IDs and out-of-order transactions&lt;/h3&gt;
&lt;p&gt;AXI assigns each transaction an &lt;strong&gt;ID&lt;/strong&gt; (&lt;code&gt;AWID&lt;/code&gt; / &lt;code&gt;ARID&lt;/code&gt;). A slave may respond to transactions &lt;strong&gt;out of order&lt;/strong&gt; as long as responses within the same ID are in order. This allows a memory controller to optimise DRAM access patterns rather than being forced to respond in the exact order of requests.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The interconnect: from bus to crossbar&lt;/h2&gt;
&lt;p&gt;A simple shared bus forces all masters to share a single data path -- only one master can use it at a time. This creates a bottleneck in systems with multiple high-bandwidth masters.&lt;/p&gt;
&lt;p&gt;The solution is an &lt;strong&gt;AXI crossbar&lt;/strong&gt; (also called a &lt;strong&gt;switch matrix&lt;/strong&gt;): it provides dedicated paths between every master-slave pair, allowing multiple simultaneous transfers as long as they target different slaves.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article06/06-bus-vs-crossbar-HQ.png"&gt;&lt;img alt="Shared bus versus AXI crossbar comparison showing bottleneck on left and simultaneous transfers on right" src="http://lizard-spock.co.uk/images/SoC/Article06/06-bus-vs-crossbar-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ARM's CoreLink NIC-400 and NIC-450 are examples of AXI mux-style interconnects, where a fixed matrix routes each master to a set of slaves. The CoreLink NI-700 takes a different approach: it is a Network-on-Chip (NoC) switched network that routes transactions independently through the fabric, allowing better scalability for large numbers of endpoints. RISC-V SoCs commonly use TileLink or AXI crossbars built with open-source IP.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QoS: Quality of Service in Interconnects&lt;/h2&gt;
&lt;p&gt;Not all traffic is equal. A display engine reading pixel data must deliver frames at a guaranteed rate, or the screen will tear. A background DMA transfer moving log data is not time-critical. AXI4 supports &lt;strong&gt;QoS signalling&lt;/strong&gt; (&lt;code&gt;ARQOS&lt;/code&gt; / &lt;code&gt;AWQOS&lt;/code&gt;) -- a 4-bit priority tag attached to every transaction.&lt;/p&gt;
&lt;p&gt;The interconnect uses these tags to arbitrate between competing masters, ensuring that time-sensitive traffic is served first without starvation of low-priority traffic.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AXI4-Lite: the simplified register interface&lt;/h2&gt;
&lt;p&gt;Full AXI4 has substantial complexity -- burst management, ID tracking, out-of-order responses. For accessing peripheral register banks (where individual 32-bit reads and writes are all that is needed), &lt;strong&gt;AXI4-Lite&lt;/strong&gt; offers a simplified subset:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No burst support (single transfers only)&lt;/li&gt;
&lt;li&gt;No transaction IDs&lt;/li&gt;
&lt;li&gt;No out-of-order support&lt;/li&gt;
&lt;li&gt;Simple 32-bit or 64-bit data width&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AXI4-Lite is extremely common for connecting peripheral IP blocks to the system bus, where its simplicity makes it easy to implement correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;APB bridge: connecting the bus tiers&lt;/h2&gt;
&lt;p&gt;Because APB is simpler and lower power, slow peripherals are typically attached to an &lt;strong&gt;APB bus&lt;/strong&gt; rather than directly to the AXI/AHB backbone. An &lt;strong&gt;AXI-to-APB bridge&lt;/strong&gt; converts between the protocols:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article06/06-soc-bus-hierarchy-HQ.png"&gt;&lt;img alt="SoC bus hierarchy showing CPU core, L2 cache, AXI crossbar connecting to DRAM, GPU, DMA, and APB bridge leading to peripheral bus" src="http://lizard-spock.co.uk/images/SoC/Article06/06-soc-bus-hierarchy-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The bridge runs the APB at a lower, slower clock (e.g., 100 MHz) while the AXI backbone runs at 500 MHz or more, reducing dynamic power in the peripherals.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Network-on-Chip (NoC)&lt;/h2&gt;
&lt;p&gt;In the largest, most complex SoCs -- server processors, AI accelerators -- even an AXI crossbar becomes a bottleneck. The solution is a &lt;strong&gt;Network-on-Chip (NoC)&lt;/strong&gt;: a switched packet network embedded on the die, with routers at each node and links between them.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article06/06-noc-mesh-HQ.png"&gt;&lt;img alt="Network-on-Chip 4x4 mesh topology showing router nodes connected by bidirectional links with CPU, GPU, DRAM, and NPU blocks at the edges" src="http://lizard-spock.co.uk/images/SoC/Article06/06-noc-mesh-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;NoCs provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt; -- adding more processing tiles does not require redesigning the interconnect&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bandwidth&lt;/strong&gt; -- multiple simultaneous transfers across different links&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modularity&lt;/strong&gt; -- standard router IP can be assembled in different topologies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is an advanced topic covered in the advanced article series.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;SoC interconnects form the communication backbone that links every functional block. The AMBA protocol family provides a hierarchy of protocols: APB for simple peripherals, AHB for mid-range transfers, and AXI for high-performance connections. AXI's independent channel architecture and valid/ready handshake enable high-throughput, pipelined, out-of-order transfers. Crossbar interconnects replace shared buses to allow simultaneous transfers between different master-slave pairs. Understanding these protocols is essential for designing IP blocks that plug into an SoC and for debugging system-level performance issues.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;AXI4 Protocol Deep Dive&lt;/em&gt;: burst types, transaction IDs, out-of-order response ordering&lt;/li&gt;
&lt;li&gt;&lt;em&gt;DMA Controller Architecture&lt;/em&gt;: how a DMA engine uses AXI to move data autonomously&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Network-on-Chip Design (Advanced)&lt;/em&gt;: topology, routing algorithms, virtual channels&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-05-memory-architecture.html"&gt;Article 05 -- Memory Architecture&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-07-clocking-reset-and-power-domains.html"&gt;Article 07 -- Clocking, Reset, and Power Domains&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="ARM"/><category term="AMBA"/><category term="AXI"/><category term="AHB"/><category term="APB"/><category term="Bus Protocols"/></entry><entry><title>SoC Article 05: Memory Architecture - Caches, DRAM, and On-chip Storage</title><link href="http://lizard-spock.co.uk/soc-article-05-memory-architecture.html" rel="alternate"/><published>2026-04-04T00:00:00+01:00</published><updated>2026-04-04T00:00:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-04-04:/soc-article-05-memory-architecture.html</id><summary type="html">&lt;p&gt;How modern SoCs bridge the speed gap between fast CPU cores and slow external DRAM through cache hierarchies, SRAM, and DRAM controllers. Covers cache organisation, MESI coherency, the MMU, and on-chip storage.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 5 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Of all the factors that determine a SoC's real-world performance, &lt;strong&gt;memory&lt;/strong&gt; is often the most important and the most overlooked by beginners. A modern CPU core can theoretically execute billions of operations per second, but most of that potential is wasted if data cannot be delivered fast enough.&lt;/p&gt;
&lt;p&gt;The art of SoC memory architecture is bridging the massive speed gap between the processor (which wants data in nanoseconds) and the external DRAM (which takes tens of nanoseconds to respond) in the most energy-efficient way possible.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Memory Speed Gap&lt;/h2&gt;
&lt;p&gt;The problem starts with a fundamental mismatch in technology:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-memory-technology-comparison-HQ.png"&gt;&lt;img alt="Memory technology comparison table showing capacity, latency, bandwidth and power from CPU registers through DRAM to flash storage" src="http://lizard-spock.co.uk/images/SoC/Article05/05-memory-technology-comparison-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notice the gap: a CPU register delivers data in under a nanosecond, while external DRAM takes 50-70 ns - roughly 100x slower. Without a caching strategy, the CPU would spend most of its time waiting for memory.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Memory Hierarchy&lt;/h2&gt;
&lt;p&gt;The solution is a &lt;strong&gt;hierarchy&lt;/strong&gt; of storage technologies, organised by speed, cost, and distance from the processor:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-memory-hierarchy-HQ.png"&gt;&lt;img alt="Memory hierarchy pyramid showing registers, L1 cache, L2 cache, L3 cache, DRAM and flash storage with latency and capacity annotations" src="http://lizard-spock.co.uk/images/SoC/Article05/05-memory-hierarchy-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The hierarchy works because of &lt;strong&gt;locality&lt;/strong&gt; - the observation that most programs access the same data repeatedly over a short period (&lt;em&gt;temporal locality&lt;/em&gt;) and access data at adjacent addresses in sequence (&lt;em&gt;spatial locality&lt;/em&gt;). Caches exploit both properties.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;How Caches Work&lt;/h2&gt;
&lt;p&gt;A cache is a small, fast SRAM that stores copies of recently-used data from slower memory. When the CPU reads an address, it first checks the cache:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache hit&lt;/strong&gt; - the data is in the cache; it is returned immediately (low latency)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache miss&lt;/strong&gt; - the data is not in the cache; it must be fetched from a slower level&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_b8d5e322e8edb747d88c0ef3d9f3d343.svg"&gt;&lt;/p&gt;
&lt;h3&gt;Cache Organisation&lt;/h3&gt;
&lt;p&gt;A cache is organised into &lt;strong&gt;lines&lt;/strong&gt; (also called blocks) - the minimum unit of transfer between memory levels. A typical cache line is &lt;strong&gt;64 bytes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Three important cache parameters:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Capacity&lt;/strong&gt; - total amount of data the cache can hold.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Associativity&lt;/strong&gt; - how many possible cache locations a given memory address can map to. A &lt;em&gt;direct-mapped&lt;/em&gt; cache is simplest: each address maps to exactly one line. A &lt;em&gt;fully-associative&lt;/em&gt; cache can hold any address in any line. A &lt;em&gt;4-way set-associative&lt;/em&gt; cache is a compromise: each address maps to a set of 4 lines.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-cache-organisation-HQ.png"&gt;&lt;img alt="Cache associativity comparison showing direct-mapped cache versus 4-way set-associative cache organisation" src="http://lizard-spock.co.uk/images/SoC/Article05/05-cache-organisation-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write policy&lt;/strong&gt; - what happens when the CPU writes to a cached address:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Write-through&lt;/em&gt;: simultaneously update both cache and memory (simple, but uses memory bandwidth)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Write-back&lt;/em&gt;: update only the cache, mark the line "dirty", and write to memory only when the line is evicted (more efficient)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Static RAM (SRAM): the cache technology&lt;/h2&gt;
&lt;p&gt;Caches are built from &lt;strong&gt;SRAM&lt;/strong&gt; (Static RAM). Each bit is stored in a &lt;strong&gt;six-transistor cell&lt;/strong&gt; (6T SRAM) - two cross-coupled inverters and two access transistors.&lt;/p&gt;
&lt;p&gt;SRAM retains data as long as power is applied (it is &lt;em&gt;volatile&lt;/em&gt;). It is fast (sub-nanosecond access) but uses significant area - roughly 100-150 F per bit (where F is the minimum feature size). This is why caches are small relative to DRAM.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;DRAM: the main memory technology&lt;/h2&gt;
&lt;p&gt;External DRAM uses a &lt;strong&gt;one-transistor, one-capacitor&lt;/strong&gt; (1T1C) cell. The charge on a capacitor represents the bit value.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-sram-dram-cells-HQ.png"&gt;&lt;img alt="SRAM 6T cell versus DRAM 1T1C cell comparison showing transistor and capacitor layout with wordline and bitline connections" src="http://lizard-spock.co.uk/images/SoC/Article05/05-sram-dram-cells-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The 1T1C cell is much smaller than a 6T SRAM cell, enabling much higher density - today's DRAM stores tens of gigabits per die. However, the capacitor leaks charge over time, so each cell must be &lt;strong&gt;refreshed&lt;/strong&gt; (read and rewritten) thousands of times per second. This refresh activity consumes power and introduces brief periods where the memory cannot be accessed.&lt;/p&gt;
&lt;h3&gt;DRAM Organisation&lt;/h3&gt;
&lt;p&gt;DRAM is organised into &lt;strong&gt;banks&lt;/strong&gt;, &lt;strong&gt;rows&lt;/strong&gt;, and &lt;strong&gt;columns&lt;/strong&gt;. Accessing data requires:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Activate&lt;/strong&gt; (open) a row - copies the row into a row buffer (sense amplifiers)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read/Write&lt;/strong&gt; column - access the desired column from the row buffer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Precharge&lt;/strong&gt; - close the row (prepare for the next row activation)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_0301a316868482bdbb1947198a5e921c.svg"&gt;&lt;/p&gt;
&lt;p&gt;Key timing parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;tRCD&lt;/strong&gt; - Row-to-Column Delay (time after ACT before a column can be accessed)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CL&lt;/strong&gt; - CAS Latency (time after READ command before data appears)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tRP&lt;/strong&gt; - Row Precharge time (time to precharge before next ACT)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tRAS&lt;/strong&gt; - Row Active time (minimum time row must be open)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;On-chip SRAM (Tightly Coupled Memory)&lt;/h2&gt;
&lt;p&gt;In addition to caches, many SoCs include blocks of &lt;strong&gt;Tightly Coupled Memory (TCM)&lt;/strong&gt; - SRAM directly connected to the processor core, accessed through a dedicated bus rather than through the cache hierarchy. TCM provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Guaranteed latency&lt;/strong&gt; - always one cycle, no possibility of a cache miss&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deterministic behaviour&lt;/strong&gt; - essential for real-time systems&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DMA-accessible storage&lt;/strong&gt; - used for buffers shared with peripherals&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is particularly common in ARM Cortex-M microcontrollers, where code critical for interrupt service routines may be placed in ITCM (Instruction TCM) to ensure it always executes at maximum speed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The DRAM Controller&lt;/h2&gt;
&lt;p&gt;Between the SoC's system bus and the DRAM package sits the &lt;strong&gt;DRAM controller&lt;/strong&gt; - a complex piece of hardware that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Translates read/write requests from the bus into DRAM-specific command sequences (ACT, RD, WR, PRE, REF)&lt;/li&gt;
&lt;li&gt;Schedules requests to maximise row-buffer hit rate and memory bandwidth&lt;/li&gt;
&lt;li&gt;Issues periodic &lt;strong&gt;refresh&lt;/strong&gt; commands to prevent data loss&lt;/li&gt;
&lt;li&gt;Manages multiple banks and ranks for parallel access&lt;/li&gt;
&lt;li&gt;Implements &lt;strong&gt;QoS (Quality of Service)&lt;/strong&gt; to ensure latency-sensitive initiators (for example, display engines) get priority&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-dram-controller-HQ.png"&gt;&lt;img alt="DRAM controller architecture block diagram showing AXI slave interface, command scheduler, refresh engine, and PHY interface connecting to LPDDR5 DRAM" src="http://lizard-spock.co.uk/images/SoC/Article05/05-dram-controller-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Non-Volatile Storage&lt;/h2&gt;
&lt;p&gt;Beyond volatile DRAM, SoCs interface with non-volatile storage where data must persist after power-off:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;eMMC (embedded MultiMediaCard)&lt;/strong&gt; - flash memory in a BGA package, soldered to the board. Used in mid-range phones and single-board computers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UFS (Universal Flash Storage)&lt;/strong&gt; - faster, lower-latency serial protocol with command queuing. Used in high-end smartphones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOR Flash&lt;/strong&gt; - byte-addressable, execute-in-place (XIP) capable. Often used for bootloaders on embedded systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NAND Flash&lt;/strong&gt; - high density, page/block-based access. Requires a flash translation layer (FTL) to manage wear levelling.&lt;/p&gt;
&lt;p&gt;The SoC connects to these through dedicated controller IP blocks: eMMC controller on AHB/APB, UFS controller on AXI, QSPI controller on APB for NOR flash, and NAND controller on AHB.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Memory mapping: the software view&lt;/h2&gt;
&lt;p&gt;From software's perspective, all memory - SRAM, DRAM, memory-mapped registers, flash - appears as a flat &lt;strong&gt;address space&lt;/strong&gt;. The processor simply reads and writes to 32-bit or 64-bit addresses; the hardware decides which memory or peripheral handles each range.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-address-map-HQ.png"&gt;&lt;img alt="32-bit SoC address space layout showing peripheral registers, private CPU peripherals, external DRAM, on-chip SRAM, and flash ROM regions" src="http://lizard-spock.co.uk/images/SoC/Article05/05-address-map-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;memory map&lt;/strong&gt; is one of the first things a firmware developer consults when writing code for a new SoC. It is typically documented in the SoC's &lt;strong&gt;Technical Reference Manual (TRM)&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Cache coherency: the multi-core problem&lt;/h2&gt;
&lt;p&gt;When multiple CPU cores share a memory system, a challenge arises: &lt;strong&gt;what happens if two cores have different cached copies of the same address?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the &lt;strong&gt;cache coherency problem&lt;/strong&gt;. If Core 0 writes to address A and Core 1 has a stale cached copy of A, Core 1 will read incorrect data.&lt;/p&gt;
&lt;p&gt;Hardware cache coherency protocols solve this. The most common is &lt;strong&gt;MESI&lt;/strong&gt; (Modified, Exclusive, Shared, Invalid):&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_a25d2b06f62d26e489ba5d1cdc5ad725.svg"&gt;&lt;/p&gt;
&lt;p&gt;Each cache line has a state tag (M, E, S, or I) that determines how it may be used. A bus snooping mechanism or directory protocol ensures all cores agree on the current state.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Memory Management Unit (MMU)&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;MMU&lt;/strong&gt; translates &lt;strong&gt;virtual addresses&lt;/strong&gt; (used by software) into &lt;strong&gt;physical addresses&lt;/strong&gt; (used by hardware). This translation enables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Process isolation&lt;/strong&gt; - each process has its own virtual address space&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory protection&lt;/strong&gt; - preventing processes from accessing each other's memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual memory&lt;/strong&gt; - presenting more memory than is physically available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Translation is performed via a &lt;strong&gt;page table&lt;/strong&gt; hierarchy stored in DRAM. To avoid the latency of a full table walk for every access, the CPU caches recent translations in the &lt;strong&gt;TLB (Translation Lookaside Buffer)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article05/05-mmu-translation-HQ.png"&gt;&lt;img alt="MMU virtual to physical address translation diagram showing VPN fields, TLB lookup path, page table walk on miss, and physical frame number output" src="http://lizard-spock.co.uk/images/SoC/Article05/05-mmu-translation-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The ARM SMMU (System Memory Management Unit) extends this concept to non-CPU masters (DMA engines, GPU), ensuring that peripheral DMA transfers are also constrained to authorised memory regions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;The SoC memory architecture is a carefully designed hierarchy that bridges the speed gap between fast-but-small on-chip SRAM and slow-but-large external DRAM. Caches exploit locality to keep frequently-used data close to the processor. DRAM controllers manage the complex command sequencing needed to drive modern LPDDR memory. The MMU virtualises the address space to enable protection and isolation. Together, these components are often the primary determinant of real-world SoC performance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Intermediate Articles This Topic Connects To&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Cache Coherency Protocols&lt;/em&gt;: MESI, MOESI, directory-based coherency in detail&lt;/li&gt;
&lt;li&gt;&lt;em&gt;DRAM Subsystem Timing (Advanced)&lt;/em&gt;: tRCD, tCL, CWL, refresh, power states, LPDDR5&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Memory-Mapped I/O and Linux Device Drivers&lt;/em&gt;: how software talks to hardware registers&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-04-processor-cores.html"&gt;Article 04 -- Processor Cores&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-06-interconnects-and-bus-protocols.html"&gt;Article 06 -- Interconnects and Bus Protocols&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="Memory"/><category term="DRAM"/><category term="SRAM"/><category term="Cache"/><category term="ARM"/></entry><entry><title>SoC Article 04: Processor Cores - CPU, DSP, GPU and Hardware Accelerators</title><link href="http://lizard-spock.co.uk/soc-article-04-processor-cores.html" rel="alternate"/><published>2026-03-27T00:00:00+00:00</published><updated>2026-03-27T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-27:/soc-article-04-processor-cores.html</id><summary type="html">&lt;p&gt;A survey of the main processor types in modern SoCs: CPUs for general-purpose code, DSPs for signal processing, GPUs for parallel workloads, and hardware accelerators for AI, video, and cryptography.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 4 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The processor is the heart of any SoC. But "processor" is a broad term that covers a remarkably diverse family of designs, each optimised for a different class of computation. A modern smartphone SoC may contain a dozen or more distinct processing engines - general-purpose CPUs, a graphics processor, a signal processing cluster, a neural network accelerator, and several smaller microcontrollers managing power and connectivity.&lt;/p&gt;
&lt;p&gt;This article surveys the main classes of processor used in SoCs, explains what makes each suited to its domain, and introduces the key architectural concepts every SoC designer needs to understand.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Processing Landscape&lt;/h2&gt;
&lt;p&gt;Different computational tasks have different shapes. Some are sequential and unpredictable (parsing a web page). Others are massively parallel and regular (multiplying a matrix). Still others require precisely timed, low-latency responses (handling a radio frame). These different "shapes" of computation call for different processor architectures.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-task-taxonomy-HQ.png"&gt;&lt;img alt="Computational Task Taxonomy showing the spectrum from sequential irregular control-heavy tasks handled by CPUs through DSPs and GPUs to parallel regular data-heavy tasks handled by neural engines" src="http://lizard-spock.co.uk/images/SoC/Article04/04-task-taxonomy-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;General-Purpose CPU Cores&lt;/h2&gt;
&lt;h3&gt;Architecture Fundamentals&lt;/h3&gt;
&lt;p&gt;A CPU core executes a sequential stream of instructions: fetch, decode, execute, access memory, write back. The efficiency of this loop determines performance.&lt;/p&gt;
&lt;p&gt;Modern CPU cores in SoCs are overwhelmingly &lt;strong&gt;RISC&lt;/strong&gt; (Reduced Instruction Set Computer) designs. The dominant ISA is &lt;strong&gt;ARM&lt;/strong&gt;, with &lt;strong&gt;RISC-V&lt;/strong&gt; growing rapidly in open-source and embedded applications.&lt;/p&gt;
&lt;p&gt;The key subsystems inside a CPU core are:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-cpu-core-internals-HQ.png"&gt;&lt;img alt="CPU core internal architecture showing the instruction fetch, decode and dispatch, execute units (ALU, FPU, multiplier), register file, load/store unit, and branch predictor" src="http://lizard-spock.co.uk/images/SoC/Article04/04-cpu-core-internals-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Pipeline Stages&lt;/h3&gt;
&lt;p&gt;A five-stage pipeline is the canonical teaching model:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_9dbcb49039a3e254bc98df89b49e18d6.svg"&gt;&lt;/p&gt;
&lt;h3&gt;The big.LITTLE concept&lt;/h3&gt;
&lt;p&gt;For power-sensitive SoCs (smartphones, wearables), ARM developed the &lt;strong&gt;big.LITTLE&lt;/strong&gt; architecture, which pairs high-performance "big" cores with energy-efficient "little" cores on the same die:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-big-little-cluster-HQ.png"&gt;&lt;img alt="ARM big.LITTLE DynamIQ cluster configuration showing the performance cluster with Cortex-A78 cores and the efficiency cluster with Cortex-A55 cores, sharing an L3 cache and system interconnect" src="http://lizard-spock.co.uk/images/SoC/Article04/04-big-little-cluster-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The OS scheduler assigns heavy tasks (video decoding, gaming) to the big cores, and lightweight tasks (receiving notifications, idle polling) to the little cores. This can reduce energy consumption by 10x or more compared to running everything on the big cores.&lt;/p&gt;
&lt;h3&gt;Key Performance Metrics&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IPC (Instructions Per Cycle)&lt;/strong&gt; - how much useful work a core does per clock cycle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clock frequency&lt;/strong&gt; - cycles per second (GHz)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance = IPC x Frequency&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPI (Cycles Per Instruction)&lt;/strong&gt; = 1/IPC; lower is better&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modern high-performance cores achieve IPC values of 4-8 by executing multiple instructions simultaneously (superscalar) and speculatively executing future instructions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Soft vs Hard CPU Cores&lt;/h2&gt;
&lt;p&gt;An important distinction for SoC designers is between &lt;strong&gt;soft&lt;/strong&gt; and &lt;strong&gt;hard&lt;/strong&gt; processor cores:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hard cores&lt;/strong&gt; are transistor-level implementations physically embedded in the silicon - you cannot change them. They are highly optimised for performance and area. The ARM Cortex-A cores in a smartphone SoC are hard cores licensed from ARM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Soft cores&lt;/strong&gt; are RTL descriptions that you synthesise yourself onto the target process or onto FPGA fabric. They are portable and configurable but less efficient. Examples include:
- &lt;strong&gt;ARM Cortex-M0&lt;/strong&gt; (available as soft core for ASIC/FPGA)
- &lt;strong&gt;RISC-V&lt;/strong&gt; cores (PicoRV32, VexRiscv, BOOM, Rocket)
- &lt;strong&gt;MicroBlaze&lt;/strong&gt; (Xilinx/AMD FPGA soft core)
- &lt;strong&gt;Nios II&lt;/strong&gt; (Altera/Intel FPGA soft core)&lt;/p&gt;
&lt;p&gt;SoC designers on FPGAs (see Article 09) almost always use soft cores since they are placing logic into reconfigurable fabric.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Digital Signal Processors (DSPs)&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;DSP&lt;/strong&gt; is a processor specialised for signal processing algorithms. Its architecture is tuned for two operations that appear constantly in such algorithms: &lt;strong&gt;multiply-accumulate (MAC)&lt;/strong&gt; and &lt;strong&gt;data movement&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;The MAC operation&lt;/h3&gt;
&lt;p&gt;The core of almost all digital signal processing is the &lt;strong&gt;dot product&lt;/strong&gt;: multiply pairs of numbers and sum the results. This appears in:
- &lt;strong&gt;FIR filters&lt;/strong&gt;: y[n] = h[0]x[n] + h[1]x[n-1] + ... + h[N]x[n-N]
- &lt;strong&gt;FFT&lt;/strong&gt; (Fast Fourier Transform)
- &lt;strong&gt;Convolution&lt;/strong&gt; in image processing and neural networks&lt;/p&gt;
&lt;p&gt;A MAC operation computes: &lt;strong&gt;Accumulator += A x B&lt;/strong&gt; in a single cycle.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-dsp-mac-unit-HQ.png"&gt;&lt;img alt="DSP MAC Unit showing the single-cycle multiply-accumulate pipeline: A and B input registers feeding a multiplier, then an adder, then a 40-bit accumulator with feedback" src="http://lizard-spock.co.uk/images/SoC/Article04/04-dsp-mac-unit-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;DSP architecture features&lt;/h3&gt;
&lt;p&gt;DSPs include hardware features that a general-purpose CPU lacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardware loopers&lt;/strong&gt; - zero-overhead loops without branch penalty&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dual-MAC units&lt;/strong&gt; - two multiply-accumulate operations per cycle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Circular addressing&lt;/strong&gt; - automatic modulo addressing for filter delay lines&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bit-reverse addressing&lt;/strong&gt; - essential for FFT butterfly operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SIMD instructions&lt;/strong&gt; - Single Instruction Multiple Data, processing several samples simultaneously&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DSPs are commonly found in SoCs handling audio codecs, cellular modem baseband processing, image signal processing (ISPs), and radar systems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Graphics Processing Units (GPUs)&lt;/h2&gt;
&lt;p&gt;A GPU is architected around &lt;strong&gt;massive parallelism&lt;/strong&gt;. Instead of a few powerful, complex cores, a GPU contains hundreds or thousands of simple &lt;strong&gt;shader cores&lt;/strong&gt; that execute in lockstep on large datasets.&lt;/p&gt;
&lt;p&gt;The programming model is based on the observation that rendering a 3D scene, applying a filter to an image, or training a neural network involves applying the &lt;strong&gt;same operation&lt;/strong&gt; to &lt;strong&gt;many different data points&lt;/strong&gt; independently.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-gpu-vs-cpu-HQ.png"&gt;&lt;img alt="GPU vs CPU core organisation: the CPU has 4 large complex cores with large caches, while the GPU has 2048 simple shader cores - showing how CPUs are optimised for serial branchy tasks while GPUs excel at regular parallel tasks" src="http://lizard-spock.co.uk/images/SoC/Article04/04-gpu-vs-cpu-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In mobile SoCs, GPUs are used for:
- 3D gaming and UI rendering
- Video encode/decode
- Computer vision and augmented reality
- Machine learning inference (alongside or instead of dedicated NPUs)&lt;/p&gt;
&lt;p&gt;Common mobile GPU families include &lt;strong&gt;ARM Mali&lt;/strong&gt;, &lt;strong&gt;Qualcomm Adreno&lt;/strong&gt;, and &lt;strong&gt;Apple's own GPU&lt;/strong&gt; (unnamed, integrated in A-series chips).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Hardware Accelerators&lt;/h2&gt;
&lt;p&gt;As compute-intensive AI workloads have become dominant, SoC vendors have added &lt;strong&gt;dedicated hardware accelerators&lt;/strong&gt; - fixed-function silicon blocks that execute one type of computation extremely efficiently.&lt;/p&gt;
&lt;h3&gt;Neural processing unit (NPU and neural engine)&lt;/h3&gt;
&lt;p&gt;An NPU accelerates neural network inference - the process of running a trained model on new input data. The core operation is matrix-vector multiplication (essentially a large MAC array).&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-systolic-array-HQ.png"&gt;&lt;img alt="Systolic array architecture used in NPU designs: a grid of Processing Elements (PEs) where weights flow downward and activations flow rightward, each PE computing one MAC per clock cycle, with partial sums accumulating downward" src="http://lizard-spock.co.uk/images/SoC/Article04/04-systolic-array-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In a systolic array, data flows through the processing elements rhythmically. Weights are pre-loaded; activations and partial sums flow through, with each PE performing one MAC per cycle. This achieves very high utilisation of the multiplication hardware.&lt;/p&gt;
&lt;h3&gt;Video codec engine&lt;/h3&gt;
&lt;p&gt;Encoding and decoding H.264/H.265/AV1 video in software is extremely CPU-intensive. A dedicated hardware codec can process 4K video at 30+ frames per second while drawing milliwatts, compared to watts for a software implementation. Modern SoCs include fixed-function codec blocks that implement the specific algorithms of each video standard.&lt;/p&gt;
&lt;h3&gt;Cryptographic accelerator&lt;/h3&gt;
&lt;p&gt;AES encryption, SHA hashing, RSA/ECC public-key operations, and True Random Number Generation (TRNG) are all candidates for hardware acceleration. Performing AES-128 in hardware can be 50-100x more energy-efficient than software.&lt;/p&gt;
&lt;h3&gt;Image signal processor (ISP)&lt;/h3&gt;
&lt;p&gt;The camera pipeline - demosaicing, noise reduction, white balance, tone mapping, HDR merging - runs in a dedicated ISP. This is a DSP-like block hardwired to the specific algorithms of computational photography.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The processor cluster: putting it together&lt;/h2&gt;
&lt;p&gt;A high-end mobile SoC might contain the following processing elements:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article04/04-soc-processor-subsystem-HQ.png"&gt;&lt;img alt="Modern mobile SoC processor subsystem showing the CPU cluster (prime, big and little cores), GPU, DSP, NPU, ISP and modem as distinct blocks on the die" src="http://lizard-spock.co.uk/images/SoC/Article04/04-soc-processor-subsystem-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Processor state machine: a simplified view&lt;/h2&gt;
&lt;p&gt;Every processor can be modelled as a state machine. Here is a simplified view of a CPU's states in a typical embedded SoC context:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_d3b3a56743e4c3f1c5d4b7c3e7a5609b.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Choosing the right processor for your SoC&lt;/h2&gt;
&lt;p&gt;The processor choice depends critically on the application:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Primary Processor&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IoT sensor node&lt;/td&gt;
&lt;td&gt;Cortex-M0+ (soft/hard)&lt;/td&gt;
&lt;td&gt;Ultra-low power, simple code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTOS control system&lt;/td&gt;
&lt;td&gt;Cortex-M4 / M7&lt;/td&gt;
&lt;td&gt;Real-time response, DSP extensions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automotive ADAS&lt;/td&gt;
&lt;td&gt;Cortex-A + DSP cluster&lt;/td&gt;
&lt;td&gt;Linux needed + CV workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smartphone&lt;/td&gt;
&lt;td&gt;big.LITTLE Cortex-A&lt;/td&gt;
&lt;td&gt;Variable load, battery priority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge AI inference&lt;/td&gt;
&lt;td&gt;ARM + NPU&lt;/td&gt;
&lt;td&gt;Dominates workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5G baseband&lt;/td&gt;
&lt;td&gt;Multi-DSP cluster&lt;/td&gt;
&lt;td&gt;Fixed-point signal processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FPGA prototyping&lt;/td&gt;
&lt;td&gt;RISC-V soft core&lt;/td&gt;
&lt;td&gt;Open, configurable, no licence fee&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;SoC processing subsystems are not monolithic: they combine CPUs for general-purpose code, DSPs for signal processing, GPUs for parallel/graphics workloads, and hardware accelerators for specific high-demand functions like AI inference, video coding, and cryptography. Each architecture is optimised for its domain. The RISC philosophy underpins most modern SoC CPUs, while the systolic array and SIMD principles dominate accelerators. Understanding the trade-offs between these options is one of the core competencies of SoC architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Intermediate Articles This Topic Connects To&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Pipeline Design and Hazards&lt;/em&gt;: data, structural, and control hazards; forwarding networks&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Cache Coherency Protocols&lt;/em&gt;: how multiple CPU cores agree on the state of shared memory&lt;/li&gt;
&lt;li&gt;&lt;em&gt;AI Accelerator Architecture (Advanced)&lt;/em&gt;: dataflow, weight stationarity, memory bandwidth&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-03-design-stack.html"&gt;Article 03 -- The SoC Design Stack&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-05-memory-architecture.html"&gt;Article 05 -- Memory Architecture: Caches, DRAM, and On-chip Storage&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="CPU"/><category term="DSP"/><category term="GPU"/><category term="NPU"/><category term="RISC-V"/><category term="ARM"/></entry><entry><title>SoC Article 03: The SoC design stack, from transistors to software</title><link href="http://lizard-spock.co.uk/soc-article-03-design-stack.html" rel="alternate"/><published>2026-03-21T00:00:00+00:00</published><updated>2026-03-21T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-21:/soc-article-03-design-stack.html</id><summary type="html">&lt;p&gt;How SoC design is organised as a stack of abstraction layers, from transistors at the bottom to application software at the top, and the languages and tools used at each level.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 3 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In the previous article we looked at what a SoC &lt;em&gt;is&lt;/em&gt;. Now we need to understand how it is described, designed, and built. The answer is not a single language or a single tool - it is a &lt;strong&gt;stack of abstraction layers&lt;/strong&gt;, each hiding complexity from the layer above it.&lt;/p&gt;
&lt;p&gt;This concept of layered abstraction is one of the most powerful ideas in all of engineering, and it is particularly rich in SoC design, where the stack spans from quantum mechanics at the silicon level all the way up to the C programs and operating systems that run on the finished chip.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Abstraction Hierarchy&lt;/h2&gt;
&lt;p&gt;Think of SoC design as a series of nested boxes. Each layer can be understood and reasoned about independently, so long as it respects the contracts defined by the layers around it.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/01-abstraction-stack-HQ.png"&gt;&lt;img alt="The SoC abstraction stack from physical layout at the bottom to application software at the top, with ISA highlighted as the hardware/software boundary" src="http://lizard-spock.co.uk/images/SoC/Article03/01-abstraction-stack-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Engineers who work in SoC design typically specialise in one or two adjacent layers. A physical design engineer thinks in terms of polygons and resistance; a firmware engineer thinks in terms of memory-mapped registers and interrupt vectors. The stack is the shared vocabulary that lets them collaborate.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Gajski-Kuhn Y-Chart&lt;/h2&gt;
&lt;p&gt;A classic way to visualise the design space is the &lt;strong&gt;Y-chart&lt;/strong&gt;, introduced by Daniel Gajski and Robert Kuhn in 1983. It organises design descriptions along three axes (or "domains"), each of which can be examined at multiple levels of abstraction:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/03-y-chart-HQ.png"&gt;&lt;img alt="The Gajski-Kuhn Y-Chart showing three design domains - behavioural, structural, and physical - with abstraction levels along each axis" src="http://lizard-spock.co.uk/images/SoC/Article03/03-y-chart-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The key insight of the Y-chart is that every design activity maps a description from one domain into another at the same level of abstraction. &lt;strong&gt;Synthesis&lt;/strong&gt; maps a behavioural RTL description into a structural gate-level netlist. &lt;strong&gt;Place-and-route&lt;/strong&gt; maps a structural netlist into a physical layout.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Layer 1: Devices and Transistors&lt;/h2&gt;
&lt;p&gt;At the very bottom of the stack sits the &lt;strong&gt;transistor&lt;/strong&gt; - the fundamental switch of digital electronics. Modern SoCs are built using &lt;strong&gt;CMOS&lt;/strong&gt; (Complementary Metal-Oxide-Semiconductor) technology, which uses two complementary transistor types: &lt;strong&gt;nMOS&lt;/strong&gt; (conducts when gate is high) and &lt;strong&gt;pMOS&lt;/strong&gt; (conducts when gate is low).&lt;/p&gt;
&lt;p&gt;A single CMOS inverter (NOT gate) uses one nMOS and one pMOS transistor. This pairing is elegant: it ensures that when the output is stable, no DC path exists from power to ground, so the circuit draws near-zero static power.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/02-cmos-inverter-HQ.png"&gt;&lt;img alt="CMOS inverter schematic showing pMOS and nMOS transistors in complementary configuration, with IN and OUT signals and Vdd/GND rails" src="http://lizard-spock.co.uk/images/SoC/Article03/02-cmos-inverter-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The process of manufacturing transistors is described by the &lt;strong&gt;technology node&lt;/strong&gt; - a number like 5 nm, 7 nm, or 28 nm. This roughly corresponds to the minimum feature size achievable. Smaller nodes pack more transistors into the same area but require more expensive processes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Layer 2: Logic Gates&lt;/h2&gt;
&lt;p&gt;Transistors are combined to form &lt;strong&gt;logic gates&lt;/strong&gt; - circuits that implement boolean operations. Gates are the building blocks of all digital logic.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/06-logic-gates-HQ.png"&gt;&lt;img alt="Six common logic gates: AND, OR, NOT, NAND, NOR, and XOR, drawn as hand-sketched IEEE symbols with Boolean equations. NAND and NOR highlighted in teal as universal gates." src="http://lizard-spock.co.uk/images/SoC/Article03/06-logic-gates-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In practice, &lt;strong&gt;NAND and NOR&lt;/strong&gt; gates are the most fundamental - any other gate can be built from them (they are "universal"). Standard cell libraries contain dozens to hundreds of gate variants with different drive strengths, optimised for speed or area.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Layer 3: Register Transfer Level (RTL)&lt;/h2&gt;
&lt;p&gt;Above the gate level sits the &lt;strong&gt;Register Transfer Level (RTL)&lt;/strong&gt;, the primary working abstraction for SoC designers. RTL describes a circuit in terms of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Registers&lt;/strong&gt; - collections of flip-flops that hold state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Combinational logic&lt;/strong&gt; - the boolean functions that compute new values from current state and inputs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data transfers&lt;/strong&gt; - moving values between registers, through functional units (ALUs, multiplexers)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RTL is described using a &lt;strong&gt;Hardware Description Language (HDL)&lt;/strong&gt; - either &lt;strong&gt;Verilog/SystemVerilog&lt;/strong&gt; or &lt;strong&gt;VHDL&lt;/strong&gt;. Here is a simple 4-bit counter in both:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// SystemVerilog -- 4-bit synchronous counter with enable&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#(&lt;/span&gt;&lt;span class="k"&gt;parameter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// active-low reset&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;logic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;always_ff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="k"&gt;posedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;negedge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;&amp;#39;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="mb"&gt;&amp;#39;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;endmodule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- VHDL -- equivalent 4-bit synchronous counter&lt;/span&gt;
&lt;span class="k"&gt;library&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ieee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ieee.std_logic_1164.&lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ieee.numeric_std.&lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;generic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;integer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;std_logic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;std_logic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;std_logic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;std_logic_vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;downto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;architecture&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;rtl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;signal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;downto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rst_n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;others&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;elsif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rising_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;std_logic_vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;architecture&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The behaviour of this counter over time is shown in the timing diagram below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_eb26cdc141712ed315fcbdc995cf70b7.svg"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Layer 4: The Instruction Set Architecture (ISA)&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;ISA&lt;/strong&gt; is the contract between hardware and software. It defines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;programmer-visible registers&lt;/strong&gt; (for example, x0–x31 in RISC-V)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;instruction set&lt;/strong&gt; - the opcodes and their semantics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory addressing modes&lt;/strong&gt; - how addresses are formed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exception and interrupt behaviour&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Privilege levels&lt;/strong&gt; - user, supervisor, machine mode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ISA is intentionally a stable interface. A program compiled for RISC-V will run correctly on any RISC-V implementation, regardless of how many pipeline stages it has, how large its caches are, or whether it executes instructions out of order. The microarchitecture can change entirely as long as it correctly implements the ISA.&lt;/p&gt;
&lt;p&gt;There are two broad architectural philosophies:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;CISC&lt;/th&gt;
&lt;th&gt;RISC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Philosophy&lt;/td&gt;
&lt;td&gt;Complex instructions doing more work&lt;/td&gt;
&lt;td&gt;Simple, fast instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instruction size&lt;/td&gt;
&lt;td&gt;Variable (1-15 bytes on x86)&lt;/td&gt;
&lt;td&gt;Fixed (4 bytes in RISC-V, ARM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Register count&lt;/td&gt;
&lt;td&gt;Historically few&lt;/td&gt;
&lt;td&gt;Many (16-32+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Examples&lt;/td&gt;
&lt;td&gt;x86 / x86-64&lt;/td&gt;
&lt;td&gt;ARM, RISC-V, MIPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common in SoCs&lt;/td&gt;
&lt;td&gt;Desktop/server&lt;/td&gt;
&lt;td&gt;Mobile, embedded, IoT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most modern SoCs use RISC-based ISAs (particularly ARM) because their simpler, more regular instruction encodings are easier to implement efficiently in hardware.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Layer 5: Microarchitecture&lt;/h2&gt;
&lt;p&gt;The microarchitecture is the &lt;em&gt;implementation&lt;/em&gt; of the ISA - the actual pipeline, caches, branch predictors, and functional units that execute instructions. It is typically described at RTL level.&lt;/p&gt;
&lt;p&gt;A simple five-stage pipeline illustrates the key idea:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/04-pipeline-HQ.png"&gt;&lt;img alt="5-stage pipeline grid showing three instructions executing in parallel across clock cycles, with Execute stages in teal and Write Back in orange" src="http://lizard-spock.co.uk/images/SoC/Article03/04-pipeline-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Multiple instructions are in-flight simultaneously, improving throughput. The art of microarchitecture is managing the interactions between them - particularly &lt;strong&gt;hazards&lt;/strong&gt; where one instruction depends on the result of a previous one that hasn't finished yet.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;IP cores: pre-built design blocks&lt;/h2&gt;
&lt;p&gt;One of the most important concepts in SoC design is the &lt;strong&gt;Intellectual Property (IP) core&lt;/strong&gt; - a pre-designed, pre-verified block that can be reused in a new design. IP reuse is what makes SoC development tractable: instead of designing every block from scratch, engineers assemble proven components.&lt;/p&gt;
&lt;p&gt;IP cores come in three forms:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/05-ip-core-types-HQ.png"&gt;&lt;img alt="IP core classification spectrum from Soft IP (RTL source, portable) through Firm IP (netlist) to Hard IP (GDSII layout, fully optimised), with opposing arrows showing the portability vs optimisation trade-off" src="http://lizard-spock.co.uk/images/SoC/Article03/05-ip-core-types-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ARM's Cortex-M series are delivered as &lt;strong&gt;soft IP&lt;/strong&gt; - you receive the RTL description and synthesise it yourself. ARM's Cortex-A series in advanced processes often comes as &lt;strong&gt;hard IP&lt;/strong&gt; - the physical layout is fixed for a particular foundry process.&lt;/p&gt;
&lt;p&gt;Common IP blocks found in SoCs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU cores&lt;/strong&gt; - ARM Cortex-A/M/R, RISC-V cores&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU IP&lt;/strong&gt; - ARM Mali, Imagination PowerVR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB PHY and controller&lt;/strong&gt; - Synopsys DesignWare&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PCIe controller&lt;/strong&gt; - Synopsys, Cadence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory controllers&lt;/strong&gt; - DDR PHY from specialised vendors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ethernet MAC/PHY&lt;/strong&gt; - various vendors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cryptography engines&lt;/strong&gt; - AES, SHA, PKA implementations&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;The Design Flow Overview&lt;/h2&gt;
&lt;p&gt;Taking an SoC from concept to fabricated silicon follows a structured sequence of steps, each with its own tools and verification checkpoints:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article03/07-design-flow-HQ.png"&gt;&lt;img alt="SoC design flow from Specification through Architecture, RTL Design, Synthesis, Place and Route, Sign-off to Tape-out, with RTL Simulation feeding into Synthesis and a feedback arrow showing iteration back to RTL Design." src="http://lizard-spock.co.uk/images/SoC/Article03/07-design-flow-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Article 10 in this series covers the design flow in detail. For now, the important point is that design is not a linear process - it is iterative. Problems discovered during synthesis or place-and-route often require revisiting the RTL, and sometimes the architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Modelling languages: choosing the right abstraction&lt;/h2&gt;
&lt;p&gt;Different languages are suited to different levels of the design stack:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Primary Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SystemC&lt;/td&gt;
&lt;td&gt;Architecture / TLM&lt;/td&gt;
&lt;td&gt;System-level modelling, HW/SW co-design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SystemVerilog&lt;/td&gt;
&lt;td&gt;RTL / Gate&lt;/td&gt;
&lt;td&gt;Hardware design, simulation, formal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VHDL&lt;/td&gt;
&lt;td&gt;RTL / Gate&lt;/td&gt;
&lt;td&gt;Hardware design (historically in Europe/defence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verilog&lt;/td&gt;
&lt;td&gt;RTL / Gate&lt;/td&gt;
&lt;td&gt;Older HDL, still widely used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C/C++&lt;/td&gt;
&lt;td&gt;Algorithm / Firmware&lt;/td&gt;
&lt;td&gt;SW design, hardware test benches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Verification / Tools&lt;/td&gt;
&lt;td&gt;Test automation, tooling, cocotb test benches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;SoC design is organised as a stack of abstraction layers, from quantum-mechanical effects in transistors at the bottom to application software at the top. Each layer hides complexity from the layer above it, enabling teams of specialists to collaborate without needing to understand every detail. The key levels are: device/transistor, logic gate, RTL, ISA, microarchitecture, and software. IP reuse is the mechanism that makes modern SoC development tractable. The design flow moves from specification through RTL, synthesis, physical design, and sign-off before tape-out.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Intermediate Articles This Topic Connects To&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;RTL Synthesis and Timing Closure&lt;/em&gt;: how EDA tools map RTL to gates and meet timing&lt;/li&gt;
&lt;li&gt;&lt;em&gt;SoC Verification with UVM&lt;/em&gt;: proving the RTL is correct before committing to silicon&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Formal Verification Methods&lt;/em&gt;: using mathematics to prove hardware correctness&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-02-anatomy-and-motivation.html"&gt;Article 02 -- What is a System on Chip?&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-04-processor-cores.html"&gt;Article 04 -- Processor Cores: CPU, DSP, GPU and Hardware Accelerators&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/><category term="RTL"/><category term="Verilog"/><category term="VHDL"/></entry><entry><title>SoC Article 02: What is a System on Chip, anatomy and motivation</title><link href="http://lizard-spock.co.uk/soc-article-02-anatomy-and-motivation.html" rel="alternate"/><published>2026-03-15T00:00:00+00:00</published><updated>2026-03-15T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-15:/soc-article-02-anatomy-and-motivation.html</id><summary type="html">&lt;p&gt;What blocks make up a System on Chip, how do they relate, and why does integration deliver such dramatic benefits over traditional board-level designs?&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 2 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In the previous article we traced the seventy-year journey from room-sized mainframes to silicon-level integration, arriving at the &lt;strong&gt;System on Chip&lt;/strong&gt;: a complete computer system fabricated on a single piece of silicon. Now it is time to look inside one: what blocks does it contain, how do they relate, and why does integration deliver such dramatic benefits?&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Board-to-Silicon Transition&lt;/h2&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-15_SoC_Article_02_Anatomy_and_Motivation/Gemini_Generated_Image_yhyy7hyhyy7hyhyy-901w.png"&gt;&lt;/p&gt;
&lt;p&gt;In traditional electronics, a "system" was a &lt;strong&gt;circuit board&lt;/strong&gt; populated with many separate chips, each performing one role: a CPU chip, a separate memory controller, a graphics processor, communication peripheral chips, and a power management IC, all connected by copper traces on the PCB.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;System on Chip&lt;/strong&gt; collapses most or all of those components onto a &lt;strong&gt;single piece of silicon&lt;/strong&gt;. The processor, memory interfaces, graphics engine, USB controller, cryptographic accelerator, radio interface, and many other functional blocks are fabricated together in one integrated circuit.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article02/01-board-vs-soc-HQ.png"&gt;&lt;img alt="Side-by-side comparison: a traditional board-level system with many discrete chips connected by slow PCB traces versus a System on Chip with the same blocks integrated onto a single die connected by fast on-chip wires" src="http://lizard-spock.co.uk/images/SoC/Article02/01-board-vs-soc-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The term "SoC" emerged formally in the mid-1990s when transistor densities crossed roughly 100 million per chip, the threshold at which integrating a complete system became both technically practical and economically compelling. Today, modern SoCs routinely contain &lt;strong&gt;tens of billions of transistors&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why Does Integration Matter?&lt;/h2&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-15_SoC_Article_02_Anatomy_and_Motivation/Gemini_Generated_Image_yhyy7hyhyy7hyhyy-2-900w.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article02/02-integration-benefits-HQ.png"&gt;&lt;img alt="Four benefits of integration shown as a 2x2 grid: Speed (on-chip nanoseconds vs PCB 60-100ns), Power Efficiency (low-voltage on-chip signalling vs high-drive off-chip), Area and Cost (fewer chips, smaller PCB), and Reliability (fewer solder joints)" src="http://lizard-spock.co.uk/images/SoC/Article02/02-integration-benefits-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Putting everything on one die is not just about convenience. It delivers fundamental advantages across every dimension that matters.&lt;/p&gt;
&lt;h3&gt;Speed&lt;/h3&gt;
&lt;p&gt;On-chip wires are nanometres wide and micrometres long. Signals cross them in fractions of a nanosecond. Signals crossing a PCB encounter far greater inductance and capacitance, longer paths, and the need for high-drive output buffers, all of which introduce significant delay. An on-chip memory access takes 1-5 ns; an off-chip access might take 60-100 ns for the same data.&lt;/p&gt;
&lt;h3&gt;Power Efficiency&lt;/h3&gt;
&lt;p&gt;Driving a signal off-chip requires pumping it up to a voltage level and current that the PCB can handle reliably - this is fundamentally wasteful. On-chip signalling operates at much lower voltages (as little as 0.4 V for the fastest paths) and does not need the drive strength of an off-chip interface. Every chip-to-chip boundary eliminated saves power directly.&lt;/p&gt;
&lt;h3&gt;Area and Cost&lt;/h3&gt;
&lt;p&gt;Fewer chips means a smaller PCB, which means a smaller, lighter product. At the volumes of the smartphone market (billions of units per year), even a 5 mm² reduction in PCB area translates into substantial savings. One complex SoC may cost less to manufacture and assemble than the collection of chips it replaces.&lt;/p&gt;
&lt;h3&gt;Reliability&lt;/h3&gt;
&lt;p&gt;Solder joints and connectors are the most common failure points in electronic assemblies. Fewer chips mean fewer joints, longer mean time between failures, and better suitability for vibration-prone environments (automotive, industrial, aerospace).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;The Anatomy of a Generic SoC&lt;/h2&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-15_SoC_Article_02_Anatomy_and_Motivation/Gemini_Generated_Image_yhyy7hyhyy7hyhyy-3-900w.png"&gt;&lt;/p&gt;
&lt;p&gt;While no two SoCs are identical, most share a recognisable set of functional blocks:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article02/03-soc-anatomy-HQ.png"&gt;&lt;img alt="SoC anatomy block diagram showing the processing subsystem (CPU cores, GPU/DSP, hardware accelerators) connected via the AXI high-speed interconnect to on-chip SRAM, DDR memory controller, DMA engine, and peripheral bridge, with clock/reset, power management, and security engine as spanning blocks" src="http://lizard-spock.co.uk/images/SoC/Article02/03-soc-anatomy-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Each block has its own dedicated article later in the series. Here is a brief map of what each does:&lt;/p&gt;
&lt;h3&gt;Processing Subsystem&lt;/h3&gt;
&lt;p&gt;The "brains" of the SoC. One or more &lt;strong&gt;CPU cores&lt;/strong&gt; execute general-purpose software. Alongside them are specialised processors - &lt;strong&gt;DSPs&lt;/strong&gt; for signal processing, &lt;strong&gt;GPUs&lt;/strong&gt; for graphics and parallel computation, and dedicated &lt;strong&gt;hardware accelerators&lt;/strong&gt; for AI inference, video coding, and cryptography. (Article 04)&lt;/p&gt;
&lt;h3&gt;Memory Subsystem&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;On-chip SRAM&lt;/strong&gt; provides fast, low-latency storage close to the processors. &lt;strong&gt;DRAM controllers&lt;/strong&gt; connect to external memory for the large working set. A cache hierarchy sits between the two. (Article 05)&lt;/p&gt;
&lt;h3&gt;System Interconnect&lt;/h3&gt;
&lt;p&gt;The internal "highway" connecting all blocks. High-bandwidth connections use &lt;strong&gt;AXI&lt;/strong&gt;; lower-bandwidth peripheral connections use &lt;strong&gt;AHB&lt;/strong&gt; or &lt;strong&gt;APB&lt;/strong&gt;. (Article 06)&lt;/p&gt;
&lt;h3&gt;DMA Engine&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Direct Memory Access&lt;/strong&gt; allows peripherals to move blocks of data to/from memory without involving the CPU - essential for high-throughput peripherals like USB, Ethernet, and cameras. (Article 08)&lt;/p&gt;
&lt;h3&gt;Peripherals&lt;/h3&gt;
&lt;p&gt;The interfaces that connect the chip to the external world: UART, SPI, I2C, GPIO, USB, Ethernet, and many others. (Article 08)&lt;/p&gt;
&lt;h3&gt;Clock and Reset&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;PLL&lt;/strong&gt; generates the precise, stable frequencies each subsystem requires. The reset subsystem places all flip-flops in a known initial state after power-up. (Article 07)&lt;/p&gt;
&lt;h3&gt;Power Management&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;PMU&lt;/strong&gt; selectively powers down idle blocks, runs the CPU at lower voltage when full speed is not needed, and manages battery charging in portable devices. (Article 07)&lt;/p&gt;
&lt;h3&gt;Security Engine&lt;/h3&gt;
&lt;p&gt;Dedicated hardware for encryption, secure boot, key storage, and access control. ARM &lt;strong&gt;TrustZone&lt;/strong&gt; partitions the chip into secure and non-secure domains. (Article 11)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;SoC Examples in the Real World&lt;/h2&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-15_SoC_Article_02_Anatomy_and_Motivation/Gemini_Generated_Image_yhyy7hyhyy7hyhyy-4-900w.png"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SoC&lt;/th&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Notable Features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple A18 (iPhone 16)&lt;/td&gt;
&lt;td&gt;Smartphone&lt;/td&gt;
&lt;td&gt;6-core CPU, 6-core GPU, Neural Engine, 16 B transistors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qualcomm Snapdragon 8 Gen 3&lt;/td&gt;
&lt;td&gt;Android phone&lt;/td&gt;
&lt;td&gt;big.LITTLE cluster, Hexagon DSP, integrated 5G modem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA Orin&lt;/td&gt;
&lt;td&gt;Autonomous vehicles&lt;/td&gt;
&lt;td&gt;12-core ARM, Ampere GPU, deep learning accelerator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raspberry Pi RP2040&lt;/td&gt;
&lt;td&gt;Hobbyist MCU&lt;/td&gt;
&lt;td&gt;Dual Cortex-M0+, PIO state machines, 264 KB SRAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nordic nRF5340&lt;/td&gt;
&lt;td&gt;IoT / BLE&lt;/td&gt;
&lt;td&gt;Dual ARM cores, Bluetooth 5.3 radio integrated on-chip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Xilinx Zynq UltraScale+&lt;/td&gt;
&lt;td&gt;FPGA SoC&lt;/td&gt;
&lt;td&gt;ARM quad-core + FPGA fabric + GPU + DSP slices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The range here is important. An SoC is not just a smartphone chip. The same integration principle applies from a $0.50 microcontroller running a light switch to a $200 chip managing an autonomous vehicle.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article02/04-soc-spectrum-HQ.png"&gt;&lt;img alt="SoC application spectrum arranged left to right by complexity and cost: tiny IoT/MCU chip at far left through embedded controller, smartphone SoC, automotive SoC, to HPC/data-centre SoC at far right, with transistor count and power budget indicated for each" src="http://lizard-spock.co.uk/images/SoC/Article02/04-soc-spectrum-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-15_SoC_Article_02_Anatomy_and_Motivation/Gemini_Generated_Image_yhyy7hyhyy7hyhyy-5-900w.png"&gt;&lt;/p&gt;
&lt;h2&gt;The Complexity Challenge&lt;/h2&gt;
&lt;p&gt;Integration delivers benefits, but it raises design complexity to a different level entirely. A modern SoC is designed by teams of hundreds of engineers over one to three years. The design must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Meet timing requirements across all operating conditions (voltage, temperature, process variation)&lt;/li&gt;
&lt;li&gt;Be verified correct before manufacture - a bug in silicon costs millions to fix&lt;/li&gt;
&lt;li&gt;Balance the competing bandwidth demands of many simultaneous data flows&lt;/li&gt;
&lt;li&gt;Boot reliably from cold power-off and recover gracefully from faults&lt;/li&gt;
&lt;li&gt;Meet strict power and thermal budgets across wildly different workloads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Managing this complexity is the central discipline of SoC design, and it is the reason for the structured methodologies this series explores.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;HW/SW Duality&lt;/h2&gt;
&lt;p&gt;A crucial insight for SoC design is that &lt;strong&gt;hardware and software are both valid ways to implement any function&lt;/strong&gt;. The choice between them is a trade-off, and it shapes the entire architecture:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Hardware&lt;/th&gt;
&lt;th&gt;Software&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Very high (parallel, GHz)&lt;/td&gt;
&lt;td&gt;Lower (sequential execution)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;Fixed after tape-out&lt;/td&gt;
&lt;td&gt;Updateable/patchable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Energy efficiency&lt;/td&gt;
&lt;td&gt;Excellent for specific tasks&lt;/td&gt;
&lt;td&gt;Lower for the same task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Development time&lt;/td&gt;
&lt;td&gt;Long, expensive&lt;/td&gt;
&lt;td&gt;Shorter, cheaper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging difficulty&lt;/td&gt;
&lt;td&gt;Very high post-silicon&lt;/td&gt;
&lt;td&gt;Much easier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most SoCs exploit this duality deliberately - placing performance-critical, stable functions in hardware, and flexible or complex control logic in software. Understanding where to draw that line is one of the core skills in SoC architecture and is the subject of Article 11.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article02/05-hw-sw-tradeoff-HQ.png"&gt;&lt;img alt="Two-column comparison of hardware vs software implementation: left column shows hardware block icons with labels - parallel execution, fixed function, high efficiency; right column shows software/CPU icons - sequential execution, flexible, patchable; a central dividing line labelled &amp;quot;The HW/SW Boundary&amp;quot; with a spectrum arrow beneath" src="http://lizard-spock.co.uk/images/SoC/Article02/05-hw-sw-tradeoff-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;A System on Chip integrates what was once a board full of discrete chips - processors, memory controllers, communication interfaces, and more - onto a single piece of silicon. Integration delivers speed (proximity), power efficiency (no off-chip driving), smaller area (fewer chips), and higher reliability (fewer solder joints). Every SoC shares a recognisable anatomy: processing subsystem, memory subsystem, system interconnect, DMA engine, peripherals, clock/reset, power management, and security. The following articles examine each of these in turn.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Intermediate Articles This Topic Connects To&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;AXI4 Protocol Deep Dive&lt;/em&gt;: the interconnect in detail&lt;/li&gt;
&lt;li&gt;&lt;em&gt;SoC Power Management Techniques&lt;/em&gt;: DVFS, power gating, retention&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Heterogeneous SoC Partitioning (Advanced)&lt;/em&gt;: formal HW/SW co-exploration&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Previous: &lt;a href="http://lizard-spock.co.uk/soc-article-01-from-room-to-silicon.html"&gt;Article 01 -- From Room to Silicon&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-03-design-stack.html"&gt;Article 03 -- The SoC Design Stack: From Transistors to Software&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="Electronics"/><category term="Embedded Systems"/></entry><entry><title>Managing Dotfiles with Stow and Git</title><link href="http://lizard-spock.co.uk/dotfiles-setup-stow-git.html" rel="alternate"/><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-07:/dotfiles-setup-stow-git.html</id><summary type="html">&lt;p&gt;How to centralise and manage Unix dotfiles in a Git repository, using source directives and GNU Stow to link them into place.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Configuration files (dotfiles) for tools like Zsh, Vim, tmux, and Git tend to accumulate across machines over time. Without a strategy, they diverge: one machine has tuned aliases, another has an old vimrc, and a new machine starts from scratch. Keeping dotfiles in a single Git repository solves this. Clone once, link into place, and every machine stays consistent.&lt;/p&gt;
&lt;p&gt;My dotfiles are on GitHub at &lt;a href="https://github.com/morganp/dotfiles"&gt;morganp/dotfiles&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why Centralise Dotfiles&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A single source of truth for all shell and tool configuration.&lt;/li&gt;
&lt;li&gt;Version history lets you see what changed and roll back if something breaks.&lt;/li&gt;
&lt;li&gt;Setting up a new machine reduces to cloning the repo and running a handful of commands.&lt;/li&gt;
&lt;li&gt;Sharing config across macOS and Linux with minimal platform-specific branching.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cloning the Repo&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/morganp/dotfiles.git&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Linking Config Files&lt;/h2&gt;
&lt;p&gt;There are two approaches depending on the tool.&lt;/p&gt;
&lt;h3&gt;Sourcing (Zsh and Vim)&lt;/h3&gt;
&lt;p&gt;Some tools are simplest to handle by having the home directory config file source the dotfiles version directly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zsh&lt;/strong&gt; -- add to &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config/shell/dot-zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Vim&lt;/strong&gt; -- add to &lt;code&gt;~/.vimrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;so&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;/dotfiles/&lt;/span&gt;config&lt;span class="sr"&gt;/vim/&lt;/span&gt;dot&lt;span class="p"&gt;-&lt;/span&gt;vimrc_clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;GNU Stow (Everything Else)&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt; manages symlinks. It takes a package directory and creates symlinks in a target directory that mirror the structure. With the &lt;code&gt;--dotfiles&lt;/code&gt; flag, files named &lt;code&gt;dot-foo&lt;/code&gt; are linked as &lt;code&gt;.foo&lt;/code&gt; in the target, keeping the repo free of files that would be hidden by default.&lt;/p&gt;
&lt;p&gt;Install Stow and other packages via Homebrew:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;bundle&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--file&lt;span class="o"&gt;=&lt;/span&gt;~/dotfiles/config/brew
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then link the remaining configs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config
stow&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;--dotfiles&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;~/
stow&lt;span class="w"&gt; &lt;/span&gt;input&lt;span class="w"&gt; &lt;/span&gt;--dotfiles&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;~/
stow&lt;span class="w"&gt; &lt;/span&gt;screen&lt;span class="w"&gt; &lt;/span&gt;--dotfiles&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;~/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A &lt;code&gt;run_stow&lt;/code&gt; script in the repo automates this.&lt;/p&gt;
&lt;h2&gt;Tmux&lt;/h2&gt;
&lt;p&gt;Tmux looks for its config via the &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt; variable, set in &lt;code&gt;config/shell/dot-profile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$XDG_CONFIG_HOME/tmux/tmux.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For the tmux plugin manager, clone TPM separately:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/tmux-plugins/tpm&lt;span class="w"&gt; &lt;/span&gt;~/.tmux/plugins/tpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Shell Features&lt;/h2&gt;
&lt;p&gt;The dotfiles configure the following shell aliases across Bash and Zsh:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alias&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File list with colour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ll&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Long list with human-readable sizes and colour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;la&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;As &lt;code&gt;ll&lt;/code&gt; but includes hidden files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Directory tree view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;..&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Up one directory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Other configurations included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Screen:&lt;/strong&gt; Virtual tabs across the bottom in standard colours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inputrc:&lt;/strong&gt; Case-insensitive tab completion, including hidden files.&lt;/li&gt;
&lt;/ul&gt;</content><category term="Unix &amp; Tools"/><category term="dotfiles"/><category term="shell"/><category term="stow"/><category term="zsh"/><category term="tmux"/><category term="vim"/></entry><entry><title>Emotional Intelligence in the Workplace</title><link href="http://lizard-spock.co.uk/emotional-intelligence-in-the-workplace.html" rel="alternate"/><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-07:/emotional-intelligence-in-the-workplace.html</id><summary type="html">&lt;p&gt;A summary of high and low emotional intelligence (EQ) traits in leaders and employees, and how EQ shapes team dynamics and decision making.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Emotional intelligence (EQ) is the ability to recognise, understand, and manage your own emotions, as well as the emotions of those around you. In professional settings, EQ significantly influences how individuals lead, collaborate, and navigate difficult situations.&lt;/p&gt;
&lt;h2&gt;High EQ: What It Looks Like&lt;/h2&gt;
&lt;p&gt;Leaders and employees that demonstrate high EQ:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigate not just motivating and empowering employees and team members, but also navigate complex and challenging decision making with the mastery of emotional response.&lt;/li&gt;
&lt;li&gt;Are realistic but not negative and overcome with their emotions.&lt;/li&gt;
&lt;li&gt;Are enthusiastic and confident in themselves and the team.&lt;/li&gt;
&lt;li&gt;Demonstrate trust and respect.&lt;/li&gt;
&lt;li&gt;Are communicative and cooperative.&lt;/li&gt;
&lt;li&gt;Volunteer for assignments and decisions.&lt;/li&gt;
&lt;li&gt;Include and engage others as much as possible.&lt;/li&gt;
&lt;li&gt;Share openly with the team.&lt;/li&gt;
&lt;li&gt;Demonstrate empathy.&lt;/li&gt;
&lt;li&gt;Actively listen.&lt;/li&gt;
&lt;li&gt;Are flexible.&lt;/li&gt;
&lt;li&gt;Develop their teams.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Low EQ: Warning Signs&lt;/h2&gt;
&lt;p&gt;Low EQ may show up as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Emotional outbursts, typically out of proportion to the situation at hand.&lt;/li&gt;
&lt;li&gt;Not listening to others.&lt;/li&gt;
&lt;li&gt;Becoming argumentative.&lt;/li&gt;
&lt;li&gt;Blaming others.&lt;/li&gt;
&lt;li&gt;Believing that others are overly sensitive, because the person with low EQ cannot understand how others feel.&lt;/li&gt;
&lt;li&gt;Difficulty maintaining friendships and other relationships.&lt;/li&gt;
&lt;li&gt;Stonewalling, or refusing to see other points of view.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why It Matters&lt;/h2&gt;
&lt;p&gt;High EQ is not about suppressing emotion -- it is about channelling emotion constructively. Teams led by high-EQ individuals tend to communicate more openly, recover from setbacks more quickly, and make better collective decisions. Recognising low EQ behaviours early, in yourself and others, is the first step toward building a healthier team culture.&lt;/p&gt;</content><category term="Engineering"/><category term="Leadership"/><category term="Management"/><category term="Soft Skills"/><category term="Teams"/></entry><entry><title>MakerWorld Parametric Models with OpenSCAD</title><link href="http://lizard-spock.co.uk/makerworld-parametric-models-openscad.html" rel="alternate"/><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-07:/makerworld-parametric-models-openscad.html</id><summary type="html">&lt;p&gt;How to create customizable parametric models on MakerWorld using OpenSCAD scripts.&lt;/p&gt;</summary><content type="html">&lt;p&gt;MakerWorld listings with a "Customize" button let users tweak model parameters, preview changes live, and download a ready-to-print file. Under the hood, these are OpenSCAD scripts. MakerWorld runs the script inside their Parametric Model Maker app and presents the configurable variables as a UI for the end user.&lt;/p&gt;
&lt;p&gt;If you can write an OpenSCAD script that generates your model and expose the key dimensions as variables, MakerWorld handles the rest: a polished customizer UI and a downloadable 3mf file.&lt;/p&gt;
&lt;p&gt;OpenSCAD is a free, code-based CAD tool. Rather than clicking tools and dragging dimensions, you describe geometry as code. For example, instead of drawing a sphere and setting its diameter to 20mm, you write:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Think of it as scripting the feature timeline you would normally follow in Fusion 360 or Onshape.&lt;/p&gt;
&lt;h2&gt;Getting Started with OpenSCAD&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Learning resources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;YouTube tutorials are the quickest way to get a feel for what OpenSCAD can do.&lt;/li&gt;
&lt;li&gt;The official &lt;a href="https://openscad.org/documentation.html"&gt;OpenSCAD documentation&lt;/a&gt; and &lt;a href="https://openscad.org/cheatsheet/"&gt;cheatsheet&lt;/a&gt; are available online.&lt;/li&gt;
&lt;li&gt;LLMs like ChatGPT are surprisingly effective: describe the object you want, ask for an OpenSCAD script, and iterate from there.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Testing your script:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run it locally in OpenSCAD on your own machine as you develop.&lt;/li&gt;
&lt;li&gt;Or use MakerWorld's Parametric Model Maker directly in your browser without installing anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Which version of OpenSCAD does MakerWorld support?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MakerWorld is compatible with the 2021 official release. The project has daily development builds with additional features, but stick with the stable 2021 release to ensure compatibility.&lt;/p&gt;
&lt;h2&gt;Publishing to MakerWorld&lt;/h2&gt;
&lt;p&gt;To add a Customize button to your listing, create a new MakerWorld listing and upload the &lt;code&gt;.scad&lt;/code&gt; file instead of a &lt;code&gt;.3mf&lt;/code&gt; file. MakerWorld will automatically detect it and enable the customizer. You also earn MakerWorld points when users download via the Customize button.&lt;/p&gt;
&lt;h2&gt;Controlling the Customizer UI&lt;/h2&gt;
&lt;p&gt;Any variable declared at the top of your script appears as an editable field in the UI automatically. To control the widget type (slider, dropdown, colour picker, help text), use comments following the OpenSCAD Customiser standard. The easiest way to explore the options is to click the "Sample Code" icon in the bottom-left of the Parametric Model Maker. It loads example code demonstrating all available widgets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: dropdown with named options&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Select which size you want&lt;/span&gt;
&lt;span class="n"&gt;Box_Size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// [10:Large, 5:Medium, 1:Small]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Font selector:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Label_Font&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Arial&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// font&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Available fonts are those pre-loaded by MakerWorld, which includes most of Google Fonts. The full list is in the Parametric Model Maker under the "Book" icon then "Third-party fonts".&lt;/p&gt;
&lt;h2&gt;Libraries&lt;/h2&gt;
&lt;p&gt;MakerWorld supports a curated set of OpenSCAD libraries:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BOSL2&lt;/td&gt;
&lt;td&gt;General-purpose functions and geometry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UB&lt;/td&gt;
&lt;td&gt;Miscellaneous utilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KeyV2&lt;/td&gt;
&lt;td&gt;Keyboard keycap generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gridfinity-rebuilt-openscad&lt;/td&gt;
&lt;td&gt;Gridfinity bin generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;threads-scad&lt;/td&gt;
&lt;td&gt;Threaded parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Getriebe&lt;/td&gt;
&lt;td&gt;Gear generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;knurledFinishLib_v2&lt;/td&gt;
&lt;td&gt;Knurled surface textures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Largely based on this &lt;a href="https://www.reddit.com/r/BambuLab/comments/1jl6ypa/how_to_create_customizable_models_on_maker_world/"&gt;Reddit post&lt;/a&gt; in r/BambuLab.&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="OpenSCAD"/><category term="3D Print"/><category term="MakerWorld"/><category term="CAD"/></entry><entry><title>OpenSCAD on Apple Silicon</title><link href="http://lizard-spock.co.uk/openscad-apple-silicon.html" rel="alternate"/><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-07:/openscad-apple-silicon.html</id><summary type="html">&lt;p&gt;Installing OpenSCAD on Apple Silicon Macs using Homebrew.&lt;/p&gt;</summary><content type="html">&lt;p&gt;OpenSCAD is a script-based 3D CAD modeller, well suited to parametric and printable part design. The snapshot release has native Apple Silicon support.&lt;/p&gt;
&lt;h2&gt;Install Homebrew&lt;/h2&gt;
&lt;p&gt;If &lt;a href="https://brew.sh"&gt;Homebrew&lt;/a&gt; is not already installed, run the following in Terminal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/bin/bash&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Install OpenSCAD&lt;/h2&gt;
&lt;p&gt;The snapshot cask provides the latest development build with native ARM support:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;--cask&lt;span class="w"&gt; &lt;/span&gt;openscad@snapshot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once installed, OpenSCAD will be available in &lt;code&gt;/Applications&lt;/code&gt; and can be launched from Spotlight or the Applications folder.&lt;/p&gt;</content><category term="Engineering"/><category term="OpenSCAD"/><category term="3D Print"/><category term="macOS"/><category term="Homebrew"/></entry><entry><title>reMarkable: Local File Transfer Without the Cloud</title><link href="http://lizard-spock.co.uk/remarkable-local-file-transfer-without-the-cloud.html" rel="alternate"/><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-07:/remarkable-local-file-transfer-without-the-cloud.html</id><summary type="html">&lt;p&gt;How to use the reMarkable tablet without cloud sync by unpairing the device and enabling the built-in USB file server at http://10.11.99.1/.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The reMarkable tablet is excellent for reading and annotating PDFs, but by default it routes
everything through reMarkable's cloud. If you prefer to keep files local -- for privacy, or
simply because you do not want a subscription -- you can unpair the device and use the
built-in USB file server instead.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="#1-unpairing-from-the-cloud"&gt;Unpairing from the Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2-enabling-the-usb-web-interface"&gt;Enabling the USB Web Interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#3-transferring-files"&gt;Transferring Files via http://10.11.99.1/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#4-tips-and-limitations"&gt;Tips and Limitations&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;1. Unpairing from the Cloud&lt;/h2&gt;
&lt;p&gt;Unpairing stops the device from syncing to reMarkable's servers. Your existing documents
remain on the device; they are simply no longer backed up or accessible from the reMarkable
apps on other devices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On the tablet:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings&lt;/strong&gt; (gear icon, bottom-left of the home screen)&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Storage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Unpair device&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Confirm when prompted&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once unpaired, the tablet no longer requires a reMarkable account or internet connection for
normal use. The cloud sync icon will disappear from the top bar.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unpairing is reversible. You can re-pair the device at any time from the same
Settings screen by signing back in to your reMarkable account.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;2. Enabling the USB Web Interface&lt;/h2&gt;
&lt;p&gt;The reMarkable has a built-in HTTP file server that becomes accessible over USB. When enabled,
the tablet presents itself as a USB network adapter and serves files at &lt;code&gt;http://10.11.99.1/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On the tablet:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Storage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;USB transfer&lt;/strong&gt; (the toggle may be labelled "USB web interface" depending on
   firmware version)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Connect the tablet to your computer&lt;/strong&gt; using the supplied USB-C cable. The tablet will
appear as a network device (USB Ethernet adapter) rather than a mass storage drive.&lt;/p&gt;
&lt;p&gt;On Linux the interface typically appears as &lt;code&gt;usb0&lt;/code&gt; or &lt;code&gt;enp0s20u1&lt;/code&gt; and receives an address in
the &lt;code&gt;10.11.99.0/24&lt;/code&gt; subnet automatically via DHCP from the tablet.&lt;/p&gt;
&lt;p&gt;Verify connectivity:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ping&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;.11.99.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;3. Transferring Files&lt;/h2&gt;
&lt;p&gt;Open a browser and navigate to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;http://10.11.99.1/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The web interface shows all documents and notebooks currently on the device, organised in
folders matching the tablet's home screen layout.&lt;/p&gt;
&lt;h3&gt;Uploading PDFs&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Import&lt;/strong&gt; (or the upload button, top-right)&lt;/li&gt;
&lt;li&gt;Select one or more PDF files from your computer&lt;/li&gt;
&lt;li&gt;The files appear on the tablet home screen immediately -- no restart required&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Supported formats: &lt;strong&gt;PDF&lt;/strong&gt; and &lt;strong&gt;EPUB&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Downloading annotated files&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Click on any document in the web interface&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Download&lt;/strong&gt; to save the annotated PDF to your computer&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The downloaded file includes all pencil annotations, highlights, and notes made on the tablet.&lt;/p&gt;
&lt;h3&gt;Command-line upload with curl&lt;/h3&gt;
&lt;p&gt;For scripting or batch uploads:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;http://10.11.99.1/documents/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;file=@/path/to/document.pdf&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To upload into a specific folder, first find the folder UUID from the web interface URL, then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-X&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://10.11.99.1/documents/{folder-uuid}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-F&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;file=@/path/to/document.pdf&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;4. Tips and Limitations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Wi-Fi required.&lt;/strong&gt; The USB file server works entirely over the USB cable with no network
  connection needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cable must stay connected.&lt;/strong&gt; The web interface is only available while the tablet is
  plugged in. Unplugging immediately drops the &lt;code&gt;10.11.99.1&lt;/code&gt; address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One client at a time.&lt;/strong&gt; The built-in server is minimal; avoid opening multiple browser
  tabs uploading simultaneously.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Folder structure is flat internally.&lt;/strong&gt; The reMarkable stores documents with UUIDs
  internally; the folder hierarchy you see in the web interface is a virtual view maintained
  by the device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firmware updates still work offline.&lt;/strong&gt; Updates are downloaded over Wi-Fi separately from
  the cloud sync; unpairing does not block firmware updates if Wi-Fi is available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Annotations on existing PDFs&lt;/strong&gt; are exported as a new annotated PDF -- the original on-
  device file is not modified.&lt;/li&gt;
&lt;/ul&gt;</content><category term="Hardware &amp; Homelab"/><category term="reMarkable"/><category term="eink"/><category term="privacy"/></entry><entry><title>SoC Article 01: From Room to Silicon — The Story of the Computer System</title><link href="http://lizard-spock.co.uk/soc-article-01-from-room-to-silicon.html" rel="alternate"/><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-07:/soc-article-01-from-room-to-silicon.html</id><summary type="html">&lt;p&gt;How computing evolved from room-filling mainframes to a sliver of silicon in your pocket, and why that history shapes modern SoC design.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Series: Introduction to SoC Design | Article 1 of 11&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-07_SoC_Article_01_From_Room_to_Silicon/e94c024c96e0d5c9d4abd4ff88d2b54b0c6220e6da4821532a97d5e0972e0297-901w.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;There is a photograph from 1964 that captures the scale of computing at the time. It shows two engineers installing an &lt;a href="https://en.wikipedia.org/wiki/IBM_System/360"&gt;IBM System/360&lt;/a&gt; Model 50. They are not sitting at desks. They are standing in a large, air-conditioned room, surrounded by cabinets the size of wardrobe closets, connected by thick bundles of cabling snaking under a raised false floor. The computer weighs approximately two tonnes. It required specialist riggers with cranes and fork trucks to move into the building. It draws 30 kilowatts of power — enough to heat several homes.&lt;/p&gt;
&lt;p&gt;That machine cost around $3.5 million in 1964 dollars. It performed roughly 500,000 operations per second.&lt;/p&gt;
&lt;p&gt;The phone in your pocket performs about 15 trillion operations per second, thirty million times more, on a sliver of silicon roughly 100 mm² in area, weighing a fraction of a gram, drawing less than three watts. It fits comfortably in a jacket pocket.&lt;/p&gt;
&lt;p&gt;This article is the story of how we got from there to here. It is not just a history lesson. Understanding the shape of that journey — what drove miniaturisation at each step, and what trade-offs were made along the way — is essential context for understanding why modern SoC design is structured the way it is.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article01/01-era-scale-comparison-HQ.png"&gt;&lt;img alt="The miniaturisation journey: five eras of computing from room-sized mainframe to a fingernail-sized SoC die, showing the same computational capability in a dramatically shrinking footprint" src="http://lizard-spock.co.uk/images/SoC/Article01/01-era-scale-comparison-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What is a "system"?&lt;/h2&gt;
&lt;p&gt;Before discussing a &lt;em&gt;System&lt;/em&gt; on Chip, we should understand what the word "system" means in this context.&lt;/p&gt;
&lt;p&gt;In engineering, a &lt;strong&gt;system&lt;/strong&gt; is a collection of components that work together to perform a function greater than any single component could achieve alone. A computer &lt;em&gt;system&lt;/em&gt;, in the classical sense, means all the hardware necessary to store, retrieve, and process information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;processor&lt;/strong&gt;: the arithmetic and logic engine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory&lt;/strong&gt;: where programs and data live&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;storage&lt;/strong&gt;: where data persists when power is off&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;input and output&lt;/strong&gt;: keyboards, displays, printers, serial ports&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;interconnect&lt;/strong&gt;: the buses and cables that tie it all together&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;power supply&lt;/strong&gt;: the electrical infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For decades, each of these was a separate physical unit, often made by different vendors, assembled on a raised floor in a dedicated machine room. The history of computing is largely the story of these components shrinking, converging, and ultimately merging.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Era 1: The Room-Sized Machine (1950s–1960s)&lt;/h2&gt;
&lt;h3&gt;Valves and Racks&lt;/h3&gt;
&lt;p&gt;The earliest electronic computers used &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Vacuum_tube"&gt;vacuum tubes&lt;/a&gt;&lt;/strong&gt; (valves) as their switching elements. A vacuum tube is roughly the size of a light bulb. A computer that needs hundreds of thousands of switching elements built from vacuum tubes fills a building.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/ENIAC"&gt;ENIAC&lt;/a&gt;&lt;/strong&gt; (1945) is the canonical example: 17,468 vacuum tubes, 70,000 resistors, 10,000 capacitors, filling a room 2.4 m × 1.8 m × 30 m. It consumed 150 kilowatts of power and broke down regularly — with so many valves, a failure every few hours was normal. Operators spent more time repairing it than computing with it.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Manchester_Baby"&gt;Manchester Baby&lt;/a&gt;&lt;/strong&gt; (1948), while tiny by comparison, still occupied a substantial rack of equipment and required skilled operators to program it using switches and plugboards.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Approximate Physical Scale — 1950s Computer Systems

  ┌──────────────────────────────────────────────────────────────────┐
  │                                                                  │
  │  ENIAC (1945)                                                    │
  │                                                                  │
  │  [██████████████████████████████████████████████████████████]    │  ← 30 metres
  │  [█ Accumulator █][█ Multiplier █][█ Divider █][█ I/O █][█...█]  │
  │                                                                  │
  │  Weight: ~27 tonnes   Power: 150 kW   Speed: 5,000 ADD/sec       │
  │                                                                  │
  └──────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;The transistor changes everything&lt;/h3&gt;
&lt;p&gt;In 1947, &lt;a href="https://en.wikipedia.org/wiki/Bell_Labs"&gt;Bell Labs&lt;/a&gt; invented the &lt;a href="https://en.wikipedia.org/wiki/Transistor"&gt;transistor&lt;/a&gt;. It did the same job as a vacuum tube — acting as an electronic switch — but was smaller, faster, cooler, more reliable, and consumed far less power. By the late 1950s, computers began to be built from transistors instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IBM System/360&lt;/strong&gt; (1964) was a landmark: the first family of computers with a common instruction set, so that programs written for one model would run on any other. But it was still a room-scale machine. The System/360 Model 50 filled multiple cabinets, required dedicated air conditioning, and had to be craned into the computer room through specially widened doorways or lowered through the roof.&lt;/p&gt;
&lt;p&gt;The economics were equally extreme: only universities, large corporations, government agencies, and national laboratories could afford to own one. Everyone else bought time-shares, submitting jobs on punched cards and waiting hours for results.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article01/02-ibm360-room-HQ.png"&gt;&lt;img alt="Floor plan of the IBM System/360 computer room showing four cabinets — CPU, Memory, I/O Control, and Tape Drives — connected by cables under a raised floor, with a human figure showing scale" src="http://lizard-spock.co.uk/images/SoC/Article01/02-ibm360-room-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img alt="image" src="http://lizard-spock.co.uk/posts/2026-03-07_SoC_Article_01_From_Room_to_Silicon/75c93b948fd4c7e4d5d2bd57310eae25d63ba86933920bd1896ee562c266c3a4-900w.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Era 2: The Minicomputer (1965–1975)&lt;/h2&gt;
&lt;h3&gt;Thinking Small&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;minicomputer&lt;/strong&gt; was not called "mini" because it was small by today's standards. It was called mini because it was dramatically smaller than the mainframes it accompanied — small enough to fit in a single large cabinet, deliverable in a standard lift, operable without a dedicated air-conditioned room.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Digital_Equipment_Corporation"&gt;Digital Equipment Corporation (DEC)&lt;/a&gt;&lt;/strong&gt; pioneered this category. Their &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/PDP-8"&gt;PDP-8&lt;/a&gt;&lt;/strong&gt; (1965) was a 12-bit machine that cost $18,000 — expensive, but accessible to a laboratory, university department, or medium-sized company without a dedicated data centre.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/PDP-11"&gt;PDP-11&lt;/a&gt;&lt;/strong&gt; (1970) is one of the most important computers ever designed. It was a 16-bit machine that fit in a single rack about the size of a tall filing cabinet. It had a clean, elegant instruction set that influenced virtually every processor architecture that followed — including &lt;a href="https://en.wikipedia.org/wiki/Unix"&gt;Unix&lt;/a&gt;, which was first developed on a &lt;a href="https://en.wikipedia.org/wiki/PDP-7"&gt;PDP-7&lt;/a&gt; and PDP-11.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DEC PDP-11 (1970) — Physical Footprint:

  ┌─────────────────────┐
  │  ┌───────────────┐  │
  │  │  Backplane    │  │  ← Processor and memory cards slot in here
  │  │  (UNIBUS)     │  │
  │  │               │  │
  │  │  CPU card     │  │  ← A single card, 30 cm × 25 cm
  │  │  Memory cards │  │
  │  │  I/O cards    │  │
  │  └───────────────┘  │
  │  Power supply       │
  └─────────────────────┘
    ~ 60 cm × 45 cm × 60 cm
    Weight: ~30 kg
    Power: ~300 W
    Speed: ~1 million ops/sec
    Cost: ~$10,000 (1970)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The PDP-11 introduced several ideas that persist in modern SoC design: &lt;a href="https://en.wikipedia.org/wiki/Memory-mapped_I/O"&gt;memory-mapped I/O&lt;/a&gt; (where peripherals appear as addresses in the memory map), a bus standard (&lt;a href="https://en.wikipedia.org/wiki/Unibus"&gt;UNIBUS&lt;/a&gt;), and the concept of a clean separation between the processor and the rest of the system through a well-defined bus interface.&lt;/p&gt;
&lt;h3&gt;The minicomputer era's legacy&lt;/h3&gt;
&lt;p&gt;The minicomputer era produced ideas that underpin every SoC today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bus standards&lt;/strong&gt;: the UNIBUS, &lt;a href="https://en.wikipedia.org/wiki/Multibus"&gt;Multibus&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/S-100_bus"&gt;S-100&lt;/a&gt; buses established the principle that components from different vendors could interoperate on a common bus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operating systems&lt;/strong&gt;: Unix was born on the PDP-11; its model of processes, file descriptors, and device drivers lives on in &lt;a href="https://en.wikipedia.org/wiki/Linux"&gt;Linux&lt;/a&gt; running on modern SoCs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modular architecture&lt;/strong&gt;: CPU, memory, and I/O were separate cards in the same backplane, a precursor to the IP block model&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Era 3: The Microprocessor (1971–1980)&lt;/h2&gt;
&lt;h3&gt;Everything on one chip&lt;/h3&gt;
&lt;p&gt;In 1971, Intel introduced the &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Intel_4004"&gt;4004&lt;/a&gt;&lt;/strong&gt; — the first microprocessor. It placed the entire CPU — arithmetic unit, registers, control logic — onto a single chip about 12 mm². It ran at 740 kHz and processed 4-bit numbers.&lt;/p&gt;
&lt;p&gt;This was a revolutionary idea: previously, a "CPU" was a cabinet full of boards. Now it was a package you could hold between your fingers.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article01/03-pdp11-vs-4004-HQ.png"&gt;&lt;img alt="Side-by-side comparison: the PDP-11/20 CPU (1970) as a rack of four boards for ALU, registers, control, and bus interface versus the Intel 4004 (1971) as a single tiny chip package — the entire CPU compressed onto 12 mm²" src="http://lizard-spock.co.uk/images/SoC/Article01/03-pdp11-vs-4004-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Intel_8080"&gt;8080&lt;/a&gt;&lt;/strong&gt; (Intel, 1974), &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/MOS_Technology_6502"&gt;6502&lt;/a&gt;&lt;/strong&gt; (MOS Technology, 1975), and &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Zilog_Z80"&gt;Z80&lt;/a&gt;&lt;/strong&gt; (Zilog, 1976) followed in rapid succession. These 8-bit processors became the engines of the personal computer revolution.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Intel_8086"&gt;Intel 8086&lt;/a&gt;&lt;/strong&gt; (1978) and &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Motorola_68000"&gt;Motorola 68000&lt;/a&gt;&lt;/strong&gt; (1979) raised the bar to 16-bit, with the 68000 in particular earning a reputation for elegance and performance that made it the choice for the &lt;a href="https://en.wikipedia.org/wiki/Macintosh"&gt;Apple Macintosh&lt;/a&gt;, the &lt;a href="https://en.wikipedia.org/wiki/Amiga"&gt;Amiga&lt;/a&gt;, the &lt;a href="https://en.wikipedia.org/wiki/Atari_ST"&gt;Atari ST&lt;/a&gt;, and countless workstations.&lt;/p&gt;
&lt;p&gt;But the microprocessor alone was just the CPU. Everything else — memory, storage, I/O — was still provided by separate chips on a board. The "system" was still a board, just a smaller one.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Era 4: The Personal Computer (1977–1990)&lt;/h2&gt;
&lt;h3&gt;The desktop system&lt;/h3&gt;
&lt;p&gt;The first personal computers were kits for hobbyists. The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Altair_8800"&gt;Altair 8800&lt;/a&gt;&lt;/strong&gt; (1975) was an 8080-based machine with no keyboard, no display, and no software beyond a bootloader — users programmed it by toggling switches on the front panel.&lt;/p&gt;
&lt;p&gt;Within a few years, &lt;strong&gt;Apple&lt;/strong&gt;, &lt;strong&gt;Commodore&lt;/strong&gt;, and &lt;strong&gt;Tandy&lt;/strong&gt; produced complete personal computers: CPU, RAM, ROM, keyboard, display output, and storage on a single motherboard in a desktop enclosure. The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Apple_II"&gt;Apple II&lt;/a&gt;&lt;/strong&gt; (1977) sold as a complete system you could put on a desk.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;II&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Motherboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1977&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Computer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;One&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Board&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌────────────────────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="n"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;II&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Motherboard&lt;/span&gt;&lt;span class="w"&gt;                     &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                                                                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6502&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RAM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chips&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;×&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chips&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;×&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Video&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                                                                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Keyboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Cassette&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Speaker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                                                                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;┌────┐&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="n"&gt;Slot&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;└────┘&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expansion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                                                                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Board&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;×&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;chips&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;66&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="n"&gt;Speed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MHz&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└────────────────────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/IBM_Personal_Computer"&gt;IBM PC&lt;/a&gt;&lt;/strong&gt; (1981) standardised the desktop architecture that dominated computing for three decades: a processor, a chipset handling memory and I/O, expansion slots for peripherals, and a shared bus (first &lt;a href="https://en.wikipedia.org/wiki/Industry_Standard_Architecture"&gt;ISA&lt;/a&gt;, later &lt;a href="https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect"&gt;PCI&lt;/a&gt;). By the late 1980s, a PC could sit on a desk, consume 50–200 W, and perform millions of operations per second for a few thousand dollars.&lt;/p&gt;
&lt;p&gt;The chip count had fallen from thousands (mainframe) to hundreds (minicomputer) to dozens on a single motherboard. But a PC motherboard was still a system assembled from many chips: CPU, north bridge, south bridge, graphics chip, sound chip, network chip, storage controller, and many more.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Era 5: The laptop and mobile computing&lt;/h2&gt;
&lt;h3&gt;Batteries change everything&lt;/h3&gt;
&lt;p&gt;The first truly portable computers appeared around 1981. The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/GRiD_Compass"&gt;Grid Compass&lt;/a&gt;&lt;/strong&gt; (1982) was used by NASA and the US military — it was functional but cost $8,000 and ran for barely four hours on battery. The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Compaq_LTE"&gt;Compaq LTE&lt;/a&gt;&lt;/strong&gt; (1987) was the first laptop to use a 3.5" hard drive and internal battery in a genuinely portable form factor.&lt;/p&gt;
&lt;p&gt;Mobile computing imposed a constraint that desktop design had never faced: &lt;strong&gt;batteries&lt;/strong&gt;. A desktop computer plugged into the wall can draw as much power as needed. A laptop must run for hours on a battery with finite energy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;Power&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Budget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;—&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Desktop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;vs&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Laptop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;late&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1980&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Desktop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1988&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nv"&gt;Compaq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;LTE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1987&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Power&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;W&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;Power&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;W&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Battery&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;none&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nv"&gt;Battery&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NiCd&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;hr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;life&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Weight&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kg&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nv"&gt;Weight&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kg&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Size&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tower&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;case&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nv"&gt;Size&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;×&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;×&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cm&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Problem&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chips&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;designed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;desktop&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;drew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;—&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;far&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;too&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;much&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;battery&lt;/span&gt;.
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;approach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;chip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;design&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;needed&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Power pressure drove two responses:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Low-voltage CMOS&lt;/strong&gt; — designers switched from bipolar logic (powerful but power-hungry) to &lt;a href="https://en.wikipedia.org/wiki/CMOS"&gt;CMOS&lt;/a&gt; (Complementary Metal-Oxide-Semiconductor), which consumes power only when transistors switch, not when they are idle. This dramatically reduced both active and standby power.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration&lt;/strong&gt; — every chip-to-chip interface wastes energy driving signals off-chip and back. Merging two chips into one removes those interfaces. The laptop era was the first time integration was driven primarily by power rather than just performance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Era 6: The mobile phone and the ARM architecture&lt;/h2&gt;
&lt;h3&gt;An architecture built for efficiency&lt;/h3&gt;
&lt;p&gt;In 1983, &lt;a href="https://en.wikipedia.org/wiki/Acorn_Computers"&gt;Acorn Computers&lt;/a&gt; in Cambridge designed their own processor: the &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/ARM_architecture_family"&gt;Acorn RISC Machine&lt;/a&gt;&lt;/strong&gt;, or &lt;strong&gt;ARM&lt;/strong&gt;. It was a 32-bit RISC processor designed to be simple, low-power, and fast enough for interactive computing in an inexpensive product. The original ARM1 ran at 6 MHz and consumed a fraction of a watt — remarkable for 1985.&lt;/p&gt;
&lt;p&gt;Acorn spun off &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Arm_Holdings"&gt;Advanced RISC Machines Ltd&lt;/a&gt;&lt;/strong&gt; in 1990 as a joint venture with Apple and VLSI Technology. Rather than manufacturing chips itself, ARM licenced its architecture to other companies — a business model that would eventually see ARM cores inside virtually every mobile device on the planet.&lt;/p&gt;
&lt;p&gt;The first mobile phones were large, power-hungry, and limited. As &lt;a href="https://en.wikipedia.org/wiki/GSM"&gt;GSM&lt;/a&gt; digital mobile telephony spread in the 1990s, phones needed to perform signal processing (decoding the radio channel), handle the telephone UI, and manage a small battery. They could afford perhaps 200 mW of sustained power.&lt;/p&gt;
&lt;p&gt;A phone of the early 2000s had several separate chips: a &lt;strong&gt;baseband processor&lt;/strong&gt; (running the radio protocols), an &lt;strong&gt;application processor&lt;/strong&gt; (running the UI and apps), a &lt;strong&gt;power management IC&lt;/strong&gt;, a &lt;strong&gt;display controller&lt;/strong&gt;, and various analog front-ends for audio and radio. These chips communicated over a shared PCB — the "system" was a small board inside a plastic case.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article01/04-smartphone-pcb-HQ.png"&gt;&lt;img alt="A pre-SoC smartphone PCB (c. 2003) showing six discrete chips — Application Processor ARM9, Baseband DSP, PMIC, SDRAM, Flash NOR, and LCD Controller — connected by PCB traces, with an annotation showing all of this becomes one SoC" src="http://lizard-spock.co.uk/images/SoC/Article01/04-smartphone-pcb-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This was the moment before SoC. All the pieces existed — they just were not yet on the same die.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Era 7: The System on Chip (1995–present)&lt;/h2&gt;
&lt;h3&gt;Integration crosses a threshold&lt;/h3&gt;
&lt;p&gt;As process technology advanced through the 1990s — from 350 nm to 250 nm to 180 nm — the number of transistors that could be fabricated reliably on a single die crossed 100 million. That is enough transistors to implement not just a CPU, but everything around it: memory controllers, DSPs, display engines, USB, audio, and power management.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/OMAP"&gt;Texas Instruments OMAP&lt;/a&gt;&lt;/strong&gt; series (early 2000s) was among the first true smartphone SoCs: ARM application processor, DSP, camera interface, display controller, and power management all on one die. This appeared in early Nokia smartphones and PDAs.&lt;/p&gt;
&lt;p&gt;Apple's acquisition of &lt;a href="https://en.wikipedia.org/wiki/P.A._Semi"&gt;PA Semi&lt;/a&gt; in 2008 and its launch of the &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Apple_A4"&gt;A4&lt;/a&gt;&lt;/strong&gt; SoC in 2010 (the first iPhone 4 chip) signalled that the world's most valuable consumer electronics company was betting everything on the SoC model. Qualcomm, Samsung, MediaTek, and HiSilicon followed with their own vertical SoC programmes.&lt;/p&gt;
&lt;p&gt;The convergence was complete:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Integration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Journey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Computational&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Power&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Shrinking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Footprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Year&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="nx"&gt;Physical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Size&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;Power&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Transistors&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;─────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1964&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;IBM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tonnes&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kW&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1970&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;DEC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PDP&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="nx"&gt;Filing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cabinet&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1977&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;II&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;full&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Desk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="err"&gt;×&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PCB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1982&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Intel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80286&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CPU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alone&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DIP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;1993&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Intel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Pentium&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;273&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PGA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chip&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m m-Double"&gt;3.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;M&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2003&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Nokia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chips&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;PCB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inside&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mW&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SoC&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;×&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;die&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;M&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SoC&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mm²&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;die&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m m-Double"&gt;11.8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;B&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;A18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SoC&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mm²&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;die&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;B&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;─────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Approximate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;discrete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transistors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SSI&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;MSI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chips&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Moore's Law: the engine of miniaturisation&lt;/h2&gt;
&lt;p&gt;No account of this journey is complete without &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Moore%27s_law"&gt;Moore's Law&lt;/a&gt;&lt;/strong&gt;. In 1965, &lt;a href="https://en.wikipedia.org/wiki/Gordon_Moore"&gt;Gordon Moore&lt;/a&gt; (co-founder of Intel) observed that the number of transistors on a commercially practical integrated circuit doubled approximately every two years. This was an empirical observation about the economics of the semiconductor industry, but it became a self-fulfilling prophecy: the industry organised itself to deliver that doubling, and did so for more than fifty years.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://lizard-spock.co.uk/images/SoC/Article01/05-moores-law-HQ.png"&gt;&lt;img alt="Transistor count chart from 1971 to 2024 on a log scale showing the exponential growth from the Intel 4004 at 2,300 transistors to the Apple A18 at 16 billion, with a dotted line showing the idealized Moore's Law doubling every two years" src="http://lizard-spock.co.uk/images/SoC/Article01/05-moores-law-900w.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Each doubling meant that the same design could be shrunk to half the area (reducing cost), or that twice as much logic could fit in the same area (enabling integration). Both drove the SoC story.&lt;/p&gt;
&lt;p&gt;Moore's Law has slowed in recent years — physical limits make further miniaturisation increasingly difficult and expensive below 3 nm. The industry is responding with new techniques: &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/3D_integrated_circuit"&gt;3D stacking&lt;/a&gt;&lt;/strong&gt; (chips stacked vertically), &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Chiplet"&gt;chiplets&lt;/a&gt;&lt;/strong&gt; (multiple dies in one package), and new materials. But the principle — relentless pressure toward more integration per unit cost — continues.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What "System" Means Now&lt;/h2&gt;
&lt;p&gt;Return to where we started: the IBM System/360, craned into a dedicated machine room, requiring a team of operators and a purpose-built building to run.&lt;/p&gt;
&lt;p&gt;Today's SoC is a &lt;em&gt;more capable&lt;/em&gt; system — more memory, faster clock, more peripherals, better networking — occupying an area smaller than a fingernail, drawing power measurable in milliwatts, costing a few dollars to manufacture at scale.&lt;/p&gt;
&lt;p&gt;But it is still a &lt;em&gt;system&lt;/em&gt;: processor, memory controller, interconnect, peripherals, power management, security hardware. The same logical building blocks exist. They have simply been reimplemented in silicon, brought together on a single die, separated by wires measured in nanometres rather than copper cables measured in metres.&lt;/p&gt;
&lt;p&gt;Understanding that lineage — why the blocks exist, what problems they solve, where the trade-offs come from — is the context for everything that follows in this series.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Computer systems began as room-filling collections of vacuum tubes. The transistor enabled the minicomputer — a cabinet-scale machine accessible to laboratories and companies. The microprocessor collapsed the CPU onto a single chip and made the personal computer possible. Mobile computing added battery constraints that drove integration for power efficiency. The System on Chip emerged when transistor densities crossed the threshold that made fitting an entire system — CPU, memory, peripherals, and more — onto a single die both technically feasible and economically compelling. Moore's Law underpinned the entire journey, doubling integration density roughly every two years for five decades.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Series Roadmap&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Article&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;01&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;From Room to Silicon — History of the Computer System (this article)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="soc-article-02-anatomy-and-motivation.html"&gt;02&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="soc-article-02-anatomy-and-motivation.html"&gt;What is a System on Chip? — Anatomy and Motivation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="soc-article-03-design-stack.html"&gt;03&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="soc-article-03-design-stack.html"&gt;The SoC Design Stack: From Transistors to Software&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="soc-article-04-processor-cores.html"&gt;04&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="soc-article-04-processor-cores.html"&gt;Processor Cores: CPU, DSP, GPU and Hardware Accelerators&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="soc-article-05-memory-architecture.html"&gt;05&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="soc-article-05-memory-architecture.html"&gt;Memory Architecture: Caches, DRAM, and On-chip Storage&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="soc-article-06-interconnects-and-bus-protocols.html"&gt;06&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="soc-article-06-interconnects-and-bus-protocols.html"&gt;Interconnects and Bus Protocols: AXI, AHB, and APB&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="soc-article-07-clocking-reset-and-power-domains.html"&gt;07&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="soc-article-07-clocking-reset-and-power-domains.html"&gt;Clocking, Reset, and Power Domains&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08&lt;/td&gt;
&lt;td&gt;Peripherals and I/O: Connecting the SoC to the World&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09&lt;/td&gt;
&lt;td&gt;Hardware Description Languages and RTL Design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;The SoC Design Flow: From Specification to Silicon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;HW/SW Co-Design: Bridging Software and Silicon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Next: &lt;a href="http://lizard-spock.co.uk/soc-article-02-anatomy-and-motivation.html"&gt;Article 02 -- What is a System on Chip? Anatomy and Motivation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="Engineering"/><category term="SoC"/><category term="Hardware"/><category term="Computer Architecture"/><category term="History"/><category term="Electronics"/></entry><entry><title>Requirements Writing</title><link href="http://lizard-spock.co.uk/requirements-writing.html" rel="alternate"/><published>2026-03-06T18:00:00+00:00</published><updated>2026-03-06T18:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-06:/requirements-writing.html</id><summary type="html">&lt;p&gt;Good requirements engineering is fundamental to delivering products that meet customer needs within cost and schedule. A well-formed requirement identifies …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Good requirements engineering is fundamental to delivering products that meet customer needs within cost and schedule. A well-formed requirement identifies a specific user or stakeholder, defines a positive end result, and states a measurable success criterion -- for example: &lt;em&gt;"The internet user shall be able to access their current account balance in less than 5 seconds."&lt;/em&gt; Requirements must be unambiguous, singular (one requirement per statement), and free of escape clauses such as "unless", "except", or "if possible". They should avoid designing the solution, speculating about future needs, or using vague qualitative terms like "user-friendly" or "flexible" that cannot be verified. The normative terms shall, should, and may carry specific and distinct meanings -- using them precisely is critical to a traceable and verifiable requirement set.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The IBM Requirements Writing Training document uses 'shall' or 'will' interchangeably to signal a mandatory requirement. The IEEE Standards Style Manual (also referenced below) deprecates the use of 'will' for mandatory requirements, reserving it for statements of fact only. The IEEE definition is the preferred usage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Normative Terms&lt;/h2&gt;
&lt;p&gt;Normative requirements are normally phrased using one of the three terms below: shall, should, or may. All normative elements that are requirements (shall) are to be followed in all cases in order to be in conformity.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;'Shall'&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;'Must-have' for minimum viable product (MVP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Should'&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;'Desirable' in product and to be considered for phase 1, including any impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'May'&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;'Nice-to-have' and could be included if no impact to key metrics. Should log decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Use of Special Terms (from IEEE Standards Style Manual)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The word &lt;strong&gt;shall&lt;/strong&gt; is used to indicate mandatory requirements strictly to be followed in order to conform to the Specification and from which no deviation is permitted (shall equals is required to).&lt;/p&gt;
&lt;p&gt;The use of the word &lt;strong&gt;must&lt;/strong&gt; is deprecated and shall not be used when stating mandatory requirements; must is used only to describe unavoidable situations.&lt;/p&gt;
&lt;p&gt;The use of the word &lt;strong&gt;will&lt;/strong&gt; is deprecated and shall not be used when stating mandatory requirements; will is only used in statements of fact.&lt;/p&gt;
&lt;p&gt;The word &lt;strong&gt;should&lt;/strong&gt; is used to indicate that among several possibilities one is recommended as particularly suitable, without mentioning or excluding others; or that a certain course of action is preferred but not necessarily required; or that (in the negative form) a certain course of action is deprecated but not prohibited (should equals is recommended that).&lt;/p&gt;
&lt;p&gt;The word &lt;strong&gt;may&lt;/strong&gt; is used to indicate a course of action permissible within the limits of the Specification (may equals is permitted to).&lt;/p&gt;
&lt;p&gt;The word &lt;strong&gt;can&lt;/strong&gt; is used for statements of possibility and capability, whether material, physical, or causal (can equals is able to).&lt;/p&gt;
&lt;p&gt;All sections are normative, unless they are explicitly indicated to be informative.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Requirement Categories&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;The following categories reflect a common industry classification used in semiconductor product development.&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Customer Requirement&lt;/strong&gt; -- A customer need that the product needs to meet. Gathered from customer interaction, field channels or market research. May not be possible to meet all CRs on a product due to risk or resources.&lt;/td&gt;
&lt;td&gt;Marketing, Systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;System Requirement&lt;/strong&gt; -- Describes the required black-box behaviour of the device. Customer visible SRs are the primary basis for the datasheet. Often, an SR translates a CR into the plan-of-record for a product. Captures the negotiated attribute. May not fully meet the ideal CR.&lt;/td&gt;
&lt;td&gt;Systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Design Requirement&lt;/strong&gt; -- Result of distillation of SR, driven by implementation choices that do not affect the black-box behaviour but need to be defined for the design or verification team. Capture constraints required to implement the SRs.&lt;/td&gt;
&lt;td&gt;Design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Verification Requirement&lt;/strong&gt; -- Description of what needs to be done to ensure an SR and/or DR is implemented correctly. Linked to at least one SR and/or DR. Checks and coverage information is recorded in VRs.&lt;/td&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Validation Requirement&lt;/strong&gt; -- A concise description of how an SR or DR of any requirement type will be covered with a test case during the emulation or post-silicon phase of development. Validation requirements check whether we implemented the right thing through means of functional testing or parametric silicon measurements. Test conditions are recorded in a VAR. Linked to at least one SR.&lt;/td&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Requirement Types&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Functional&lt;/td&gt;
&lt;td&gt;Normative functional requirements that can be verified and/or validated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parametric&lt;/td&gt;
&lt;td&gt;Normative parametric requirements that can be verified and/or validated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Register Field&lt;/td&gt;
&lt;td&gt;Normative register field requirements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Informative&lt;/td&gt;
&lt;td&gt;Informative requirement content, not actionable, but provides context for better interpretation of normative requirements. Informative elements are illustrations, examples, or suggestions that explain the meaning and implications of requirements, as well as case studies on their application. May include diagrams and other attachments which are not to be treated as normative.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Case&lt;/td&gt;
&lt;td&gt;System-level use cases. Commonly captured as diagrams and set context (informative) but can require verification and distillation into further requirements (normative).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/pdf/IBM_Requirements_Writing_Training.pdf"&gt;IBM Requirements Writing Training (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lizard-spock.co.uk/pdf/2021-IEEE-Standards-Style-Manual.pdf"&gt;2021 IEEE Standards Style Manual (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Engineering"/><category term="Design"/><category term="Verilog"/><category term="Verification"/></entry><entry><title>Scotland Outdoor Routes</title><link href="http://lizard-spock.co.uk/scotland-outdoor-routes.html" rel="alternate"/><published>2026-03-06T12:00:00+00:00</published><updated>2026-03-06T12:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-03-06:/scotland-outdoor-routes.html</id><summary type="html">&lt;h2&gt;Misc&lt;/h2&gt;
&lt;p&gt;Historic Scotland (Family £106/year includes entry to Edinburgh Castle):
&lt;a href="https://www.historicenvironment.scot/"&gt;https://www.historicenvironment.scot/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://members.historic-scotland.gov.uk/file/HS_Map.pdf"&gt;Site Map (PDF)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Mapping&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Memory …&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;</summary><content type="html">&lt;h2&gt;Misc&lt;/h2&gt;
&lt;p&gt;Historic Scotland (Family £106/year includes entry to Edinburgh Castle):
&lt;a href="https://www.historicenvironment.scot/"&gt;https://www.historicenvironment.scot/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://members.historic-scotland.gov.uk/file/HS_Map.pdf"&gt;Site Map (PDF)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Mapping&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Memory Map (£25/year)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://memory-map.com/maps/uk-os-topo-maps/"&gt;https://memory-map.com/maps/uk-os-topo-maps/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bing Maps, switch from Road to Ordnance Survey (free)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.bing.com/maps"&gt;https://www.bing.com/maps&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordnance Survey Maps (7 day free trial, £24/year)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://osmaps.ordnancesurvey.co.uk/"&gt;https://osmaps.ordnancesurvey.co.uk/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rucksack Readers for long distance routes (~£15 per map)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.rucsacs.com/book-category/scotland/"&gt;https://www.rucsacs.com/book-category/scotland/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spokes Cycle Maps (~£7 per map)&lt;/td&gt;
&lt;td&gt;&lt;a href="http://www.spokes.org.uk/spokes-maps/"&gt;http://www.spokes.org.uk/spokes-maps/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Urban Nature Maps (Edinburgh &amp;amp; Glasgow, £10 each)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://edinburgh-urban-nature-map"&gt;edinburgh-urban-nature-map&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scots Ways&lt;/td&gt;
&lt;td&gt;&lt;a href="https://scotways.com/maps-and-leaflets/"&gt;https://scotways.com/maps-and-leaflets/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;All Scotland&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.walkhighlands.co.uk/"&gt;Walk Highlands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forestryandland.gov.scot/visit/activities/mountain-biking/7stanes"&gt;7Stanes Mountain Biking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scotways.com/maps-and-leaflets/"&gt;Scots Ways Maps and Leaflets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Edinburgh&lt;/h2&gt;
&lt;h3&gt;Seven Hills&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Castle Rock&lt;/li&gt;
&lt;li&gt;Calton Hill&lt;/li&gt;
&lt;li&gt;Arthur's Seat&lt;/li&gt;
&lt;li&gt;Blackford Hill&lt;/li&gt;
&lt;li&gt;Braid Hills&lt;/li&gt;
&lt;li&gt;Corstorphine Hill&lt;/li&gt;
&lt;li&gt;Craiglockhart Hills&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Midlothian&lt;/h2&gt;
&lt;h2&gt;Pentland Hills&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pentlandhills.org/"&gt;www.pentlandhills.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pentlandhills.org/downloads/file/16/2019-map"&gt;Pentland Hills Map (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;East Lothian&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.eastlothian.gov.uk/info/210569/countryside_and_wildlife/12044/core_paths/2"&gt;Core Paths Map links&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map&lt;/th&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23120/map_a_gullane_aberlady_and_surrounding_area"&gt;Map A&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Gullane, Aberlady and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23137/map_b_north_berwick_and_surrounding_area"&gt;Map B&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;North Berwick and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23127/map_c_whitekirk_and_surrounding_area"&gt;Map C&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Whitekirk and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23121/map_d_musselburgh_north"&gt;Map D&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Musselburgh North&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23126/map_e_prestonpans_port_seton_tranent_north"&gt;Map E&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Prestonpans, Port Seton, Tranent North&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23128/map_f_longniddry_and_haddington_west"&gt;Map F&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Longniddry and Haddington West&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23130/map_g_haddington_east_and_surrounding_area"&gt;Map G&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Haddington East and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23133/map_h_east_linton_stenton_and_surrounding_area"&gt;Map H&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;East Linton, Stenton and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23129/map_j_dunbar_and_surrounding_area"&gt;Map J&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Dunbar and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23131/map_k_lnnerwick_and_surrounding_area"&gt;Map K&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Innerwick and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23132/map_l_musselburgh_south_and_whitecraig"&gt;Map L&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Musselburgh South and Whitecraig&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23134/map_m_tranent_south_macmerry_ormiston"&gt;Map M&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Tranent South, Macmerry, Ormiston&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23122/map_n_pencaitland_and_east_saltoun"&gt;Map N&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Pencaitland and East Saltoun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23135/map_o_gifford_and_surrounding_area"&gt;Map O&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Gifford and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23125/map_p_garvald_and_surrounding_area"&gt;Map P&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Garvald and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23136/map_q_east_lammermuir_hills"&gt;Map Q&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;East Lammermuir Hills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23138/map_r_oldhamstocks_and_surrounding_area"&gt;Map R&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oldhamstocks and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23123/map_s_glenkinchie_and_south_west_area"&gt;Map S&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Glenkinchie and South West area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23139/map_t_humbie_and_surrounding_area"&gt;Map T&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Humbie and surrounding area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23140/map_u_western_lammermuir_hills"&gt;Map U&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Western Lammermuir Hills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23124/map_v_central_lammermuir_hills"&gt;Map V&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Central Lammermuir Hills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.eastlothian.gov.uk/downloads/file/23141/map_w_whiteadder_and_south_east_lammermuir_hills"&gt;Map W&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Whiteadder and South East Lammermuir Hills&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Aviemore&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.cairngormmountain.co.uk/"&gt;https://www.cairngormmountain.co.uk/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Rothiemurchus&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://rothiemurchus.net/outdoor-activities-at-aviemore/"&gt;https://rothiemurchus.net/outdoor-activities-at-aviemore/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Loch-an-Eilein: &lt;a href="https://rothiemurchus.net/visit/loch-an-eilein/"&gt;https://rothiemurchus.net/visit/loch-an-eilein/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Laggan Wolftrax&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forestryandland.gov.scot/visit/laggan-wolftrax"&gt;https://forestryandland.gov.scot/visit/laggan-wolftrax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cirrus.com/download/attachments/290193638/LagganWolftraxMap.pdf?version=1&amp;amp;modificationDate=1626943260000&amp;amp;api=v2"&gt;Laggan Wolftrax Map (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Insh Marshes Nature Reserve&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rspb.org.uk/reserves-and-events/reserves-a-z/insh-marshes/"&gt;https://www.rspb.org.uk/reserves-and-events/reserves-a-z/insh-marshes/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rspb.org.uk/globalassets/downloads/documents/reserves/insh-marshes-trail-guide.pdf"&gt;Insh Marshes Trail Guide (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;North Ayrshire&lt;/h2&gt;
&lt;h2&gt;Arran&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.coastalway.co.uk/"&gt;Arran Coastal Way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.arranbikeclub.com/routes"&gt;Arran Bike Routes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Aberdeenshire&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.tarland-trails.com/"&gt;Tarland Trails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Outdoor"/><category term="Outdoors"/><category term="Wildlife"/><category term="Family"/><category term="Garden"/><category term="Bike"/></entry><entry><title>tmux Training Manual: Ghostty, xterm and PVE LXC Terminals</title><link href="http://lizard-spock.co.uk/tmux-training-manual-ghostty-xterm-and-pve-lxc-terminals.html" rel="alternate"/><published>2026-02-27T12:00:00+00:00</published><updated>2026-02-27T12:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-02-27:/tmux-training-manual-ghostty-xterm-and-pve-lxc-terminals.html</id><summary type="html">&lt;p&gt;tmux is a terminal multiplexer: it lets you run multiple terminal sessions inside a single window, split panes side by …&lt;/p&gt;</summary><content type="html">&lt;p&gt;tmux is a terminal multiplexer: it lets you run multiple terminal sessions inside a single window, split panes side by side, and crucially, keep sessions alive after you disconnect. For headless Proxmox VE (PVE) LXC containers accessed over SSH, this is essential. If your SSH connection drops mid-task, tmux keeps the job running and lets you reattach exactly where you left off. This manual walks through tmux in practical exercises, covering Ghostty on macOS, xterm, and terminal sessions inside PVE LXC containers.&lt;/p&gt;
&lt;h2&gt;Getting Back to a Known State&lt;/h2&gt;
&lt;p&gt;This section comes first because it is the most important concept to internalise. Just as pressing &lt;code&gt;Escape&lt;/code&gt; repeatedly in vim returns you to a safe state, tmux has equivalent recovery paths. Learn these before anything else.&lt;/p&gt;
&lt;h3&gt;The Prefix Key&lt;/h3&gt;
&lt;p&gt;Every tmux command starts with the &lt;strong&gt;prefix key&lt;/strong&gt;, which is &lt;code&gt;Ctrl+b&lt;/code&gt; by default. You press &lt;code&gt;Ctrl+b&lt;/code&gt;, release both keys, then press the command key. Nothing happens if you just hold &lt;code&gt;Ctrl+b&lt;/code&gt; without following up.&lt;/p&gt;
&lt;p&gt;If you press &lt;code&gt;Ctrl+b&lt;/code&gt; and then the wrong key, tmux usually just ignores it. You are not stuck.&lt;/p&gt;
&lt;h3&gt;Exiting Any Mode&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;How to exit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stuck in copy mode&lt;/td&gt;
&lt;td&gt;Press &lt;code&gt;q&lt;/code&gt; or &lt;code&gt;Escape&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stuck in command mode (&lt;code&gt;:&lt;/code&gt; prompt)&lt;/td&gt;
&lt;td&gt;Press &lt;code&gt;Escape&lt;/code&gt; or &lt;code&gt;Ctrl+c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pane running something&lt;/td&gt;
&lt;td&gt;Press &lt;code&gt;Ctrl+c&lt;/code&gt; to interrupt it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything looks wrong&lt;/td&gt;
&lt;td&gt;Detach with &lt;code&gt;Ctrl+b d&lt;/code&gt; and reattach fresh&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Emergency Exit Sequences&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Detach from session (session keeps running):&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;List all sessions from outside tmux:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reattach to a named session:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;session-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reattach to the most recent session:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;attach
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kill a specific pane (with confirmation prompt):&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kill a specific window (with confirmation prompt):&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kill a session entirely from the command line:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;kill-session&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;session-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kill all tmux sessions:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;kill-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The pattern to remember: &lt;code&gt;Ctrl+b d&lt;/code&gt; detaches safely, &lt;code&gt;tmux ls&lt;/code&gt; shows what is running, and &lt;code&gt;tmux attach -t &amp;lt;name&amp;gt;&lt;/code&gt; gets you back. These three commands will rescue you from almost any situation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;h3&gt;Debian and Ubuntu LXC Containers&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;update
sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;tmux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;macOS (Ghostty Host)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;tmux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Verify Installation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;-V
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Expected output: &lt;code&gt;tmux 3.x&lt;/code&gt; (version 3.3a or later is common as of 2026).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Terminal Compatibility Notes&lt;/h2&gt;
&lt;h3&gt;Ghostty&lt;/h3&gt;
&lt;p&gt;Ghostty works excellently with tmux. It supports true colour, full mouse reporting, and passes modifier keys correctly. No special configuration is needed.&lt;/p&gt;
&lt;p&gt;If colours look wrong inside tmux running in Ghostty, set this in your shell profile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm-256color
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or add this to &lt;code&gt;~/.tmux.conf&lt;/code&gt; to force 256 colour mode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;default-terminal&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;screen-256color&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;xterm&lt;/h3&gt;
&lt;p&gt;xterm is a reliable fallback. It handles tmux correctly but defaults to a limited colour palette. Set the terminal type explicitly if you see rendering artefacts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm-256color&lt;span class="w"&gt; &lt;/span&gt;tmux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or add to your shell profile permanently:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm-256color
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;PVE LXC Console: noVNC vs SSH&lt;/h3&gt;
&lt;p&gt;PVE's web console (noVNC) can cause key capture problems with tmux. The &lt;code&gt;Ctrl+b&lt;/code&gt; prefix may not pass through correctly because the browser intercepts some key combinations. In practice, &lt;code&gt;Ctrl+b&lt;/code&gt; often works, but scrolling, mouse support, and certain modifier combinations behave inconsistently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Recommendation: always use SSH into the LXC for tmux work.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;user@&amp;lt;lxc-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;SSH gives you a proper PTY, correct terminal type negotiation, and reliable key forwarding. noVNC is fine for quick root access or emergency recovery, but not for regular tmux sessions.&lt;/p&gt;
&lt;p&gt;If you must use the PVE noVNC console with tmux, set a longer escape time to reduce timeout frustration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-sg&lt;span class="w"&gt; &lt;/span&gt;escape-time&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Exercise 1: First Session&lt;/h2&gt;
&lt;p&gt;This exercise covers starting, using, detaching, and reattaching to a named session.&lt;/p&gt;
&lt;h3&gt;Start a Named Session&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;training
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You are now inside a tmux session called &lt;code&gt;training&lt;/code&gt;. The green (or coloured) bar at the bottom is the &lt;strong&gt;status bar&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Reading the Status Bar&lt;/h3&gt;
&lt;p&gt;The status bar shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Left side: session name in brackets, e.g. &lt;code&gt;[training]&lt;/code&gt;, followed by window index and name, e.g. &lt;code&gt;0:bash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Right side: hostname and time (depending on your config)&lt;/li&gt;
&lt;li&gt;An asterisk &lt;code&gt;*&lt;/code&gt; next to the current window name&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Run a Command&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;top
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Watch it run. Now detach without killing it.&lt;/p&gt;
&lt;h3&gt;Detach from the Session&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You are back at your normal shell prompt. The &lt;code&gt;top&lt;/code&gt; command is still running inside tmux.&lt;/p&gt;
&lt;h3&gt;Verify the Session is Still Running&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;training: 1 windows (created Thu Feb 27 12:00:00 2026)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Reattach to the Session&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;training
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You are back inside the session. &lt;code&gt;top&lt;/code&gt; is still running. Press &lt;code&gt;q&lt;/code&gt; to quit &lt;code&gt;top&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Quick Reconnect: Attach or Start in One Command&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tmux attach&lt;/code&gt; has the short form &lt;code&gt;tmux a&lt;/code&gt;. With no target it attaches to the most
recently used session, which is exactly what you want when only one session exists:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To attach if a session exists, or start a fresh one if none does, chain the two:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tmux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A cleaner built-in alternative is &lt;code&gt;new-session -A&lt;/code&gt;, which means "attach to this named
session, creating it first if needed". It is idempotent, so it is safe as a login
one-liner or shell alias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both patterns make reconnecting to a headless LXC a single muscle-memory command: SSH
in, type it, and you are either back where you left off or in a fresh session.&lt;/p&gt;
&lt;h3&gt;Recovery Notes for Exercise 1&lt;/h3&gt;
&lt;p&gt;If you lose track of session names, &lt;code&gt;tmux ls&lt;/code&gt; always shows what is running. If &lt;code&gt;tmux ls&lt;/code&gt; shows nothing, there are no sessions and you start fresh with &lt;code&gt;tmux new -s &amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Exercise 2: Windows&lt;/h2&gt;
&lt;p&gt;Windows in tmux are like browser tabs. Each window fills the full terminal area and can run a different program.&lt;/p&gt;
&lt;h3&gt;Create a New Window&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A new window appears. The status bar now shows two windows: &lt;code&gt;0:bash&lt;/code&gt; and &lt;code&gt;1:bash&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Name the Current Window&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b ,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A rename prompt appears at the bottom. Type a name, for example &lt;code&gt;logs&lt;/code&gt;, then press &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Switch Between Windows&lt;/h3&gt;
&lt;p&gt;Switch to window by number:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b 0
Ctrl+b 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Switch to next window:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Switch to previous window:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b p
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;List All Windows (Known State Check)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b w
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;An interactive list of all windows appears. Use arrow keys to navigate and &lt;code&gt;Enter&lt;/code&gt; to select. Press &lt;code&gt;Escape&lt;/code&gt; to cancel without switching. This gives you a clear view of what is open.&lt;/p&gt;
&lt;h3&gt;Close a Window&lt;/h3&gt;
&lt;p&gt;The cleanest way is to exit the shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or force close the current window (with confirmation):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt; to confirm. If you have multiple windows, tmux moves you to the next one. If it was the last window, the session ends.&lt;/p&gt;
&lt;h3&gt;Recovery Notes for Exercise 2&lt;/h3&gt;
&lt;p&gt;If you are confused about which window you are in, &lt;code&gt;Ctrl+b w&lt;/code&gt; shows the full list. If a window is running something stuck, &lt;code&gt;Ctrl+b &amp;amp;&lt;/code&gt; closes it after confirmation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Exercise 3: Panes&lt;/h2&gt;
&lt;p&gt;Panes split a single window into multiple terminal areas visible at the same time.&lt;/p&gt;
&lt;h3&gt;Split Horizontally (Left and Right)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b %
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The window is now split into two vertical columns (left pane and right pane).&lt;/p&gt;
&lt;h3&gt;Split Vertically (Top and Bottom)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b &amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The current pane is split into two horizontal rows.&lt;/p&gt;
&lt;h3&gt;Navigate Between Panes&lt;/h3&gt;
&lt;p&gt;Use the prefix followed by an arrow key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b &amp;lt;arrow key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For example, &lt;code&gt;Ctrl+b&lt;/code&gt; then the right arrow moves focus to the right pane.&lt;/p&gt;
&lt;h3&gt;Show Pane Numbers (Known State Check)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b q
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Large numbers appear briefly over each pane. Press the number while it is visible to jump to that pane directly.&lt;/p&gt;
&lt;h3&gt;Resize a Pane&lt;/h3&gt;
&lt;p&gt;Hold the prefix and then repeatedly press arrow keys. In most terminals:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b :resize-pane -D 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This resizes the current pane down by 5 rows. Directions: &lt;code&gt;-U&lt;/code&gt; (up), &lt;code&gt;-D&lt;/code&gt; (down), &lt;code&gt;-L&lt;/code&gt; (left), &lt;code&gt;-R&lt;/code&gt; (right).&lt;/p&gt;
&lt;p&gt;Alternatively, if mouse mode is enabled (covered in the config section), you can drag pane borders.&lt;/p&gt;
&lt;h3&gt;Zoom a Pane to Full Screen&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b z
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The current pane expands to fill the entire window. Press &lt;code&gt;Ctrl+b z&lt;/code&gt; again to return to the split layout. The status bar shows &lt;code&gt;[Z]&lt;/code&gt; when a pane is zoomed.&lt;/p&gt;
&lt;h3&gt;Close a Pane&lt;/h3&gt;
&lt;p&gt;The cleanest way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or force close with confirmation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt; to confirm. If it is the last pane in the window, the window closes too.&lt;/p&gt;
&lt;h3&gt;Recovery Notes for Exercise 3&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Ctrl+b q&lt;/code&gt; shows pane numbers so you can see where you are. &lt;code&gt;Ctrl+b z&lt;/code&gt; is useful if you accidentally zoomed and the layout looks wrong: toggle it again to return to the split view.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Exercise 4: Copy Mode (and How to Exit It)&lt;/h2&gt;
&lt;p&gt;Copy mode lets you scroll back through terminal output and copy text. Knowing how to exit it reliably is as important as knowing how to enter it.&lt;/p&gt;
&lt;h3&gt;Enter Copy Mode&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b [
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The status bar shows &lt;code&gt;[Copy]&lt;/code&gt; in the top right corner. You are now in copy mode.&lt;/p&gt;
&lt;h3&gt;Navigate in Copy Mode&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arrow keys&lt;/td&gt;
&lt;td&gt;Move cursor one line/column&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PgUp&lt;/code&gt; / &lt;code&gt;PgDn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scroll up or down a page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;g&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go to the top of the scrollback buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go to the bottom (current output)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search backward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Next search match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Previous search match&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Exit Copy Mode&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Press &lt;code&gt;q&lt;/code&gt; or &lt;code&gt;Escape&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the key thing to remember. If you are in copy mode and the terminal seems unresponsive to normal commands, you are still in copy mode. Press &lt;code&gt;q&lt;/code&gt; or &lt;code&gt;Escape&lt;/code&gt; to return to normal mode.&lt;/p&gt;
&lt;h3&gt;Copy Text&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Enter copy mode: &lt;code&gt;Ctrl+b [&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Navigate to the start of the text you want&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Space&lt;/code&gt; to begin selection&lt;/li&gt;
&lt;li&gt;Move to the end of the text&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Enter&lt;/code&gt; to copy and exit copy mode&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Paste Copied Text&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This pastes the tmux clipboard into the current pane at the cursor position.&lt;/p&gt;
&lt;h3&gt;Recovery Notes for Exercise 4&lt;/h3&gt;
&lt;p&gt;If your keyboard input is being swallowed and commands are not running, the most likely cause is that you are in copy mode. Press &lt;code&gt;q&lt;/code&gt; first, then &lt;code&gt;Escape&lt;/code&gt; if &lt;code&gt;q&lt;/code&gt; did not work.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Exercise 5: PVE LXC Workflow&lt;/h2&gt;
&lt;p&gt;This exercise demonstrates the core use case for tmux in a Proxmox VE environment: running long tasks in an LXC container and safely reconnecting after a disconnect.&lt;/p&gt;
&lt;h3&gt;Step 1: SSH into the LXC Container&lt;/h3&gt;
&lt;p&gt;From your workstation (Ghostty or any terminal):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;user@&amp;lt;lxc-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Replace &lt;code&gt;&amp;lt;lxc-ip&amp;gt;&lt;/code&gt; with the IP address of your LXC container. You can find this in the PVE web interface under the container's Network tab, or by running &lt;code&gt;ip a&lt;/code&gt; inside the container.&lt;/p&gt;
&lt;h3&gt;Step 2: Start a Persistent Session&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;work
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Step 3: Run a Long Job&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This may take several minutes. Let it run.&lt;/p&gt;
&lt;h3&gt;Step 4: Detach and Close SSH&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You are back at the container's shell prompt (outside tmux). Now close the SSH connection entirely:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or close the terminal window. The &lt;code&gt;apt upgrade&lt;/code&gt; is still running inside the tmux session on the LXC container.&lt;/p&gt;
&lt;h3&gt;Step 5: Reconnect and Reattach&lt;/h3&gt;
&lt;p&gt;Open a new terminal and SSH back in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;user@&amp;lt;lxc-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;List running sessions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;work: 1 windows (created Thu Feb 27 12:05:00 2026)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Reattach:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;work
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You are back watching &lt;code&gt;apt upgrade&lt;/code&gt; complete, exactly as you left it.&lt;/p&gt;
&lt;h3&gt;Why tmux Beats GNU Screen for LXC Work&lt;/h3&gt;
&lt;p&gt;Both tmux and screen provide session persistence, but tmux has clearer defaults, better pane support, and easier scripting. The &lt;code&gt;tmux ls&lt;/code&gt; command gives a clean session overview. Pane splitting is built in without workarounds. Configuration via &lt;code&gt;~/.tmux.conf&lt;/code&gt; is straightforward. For new setups, start with tmux.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Quick Reference Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Key or Command&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New named session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tmux new -s &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run from shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List sessions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tmux ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run from shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attach to session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tmux attach -t &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run from shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detach from session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Session keeps running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b ,&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch to window N&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b &amp;lt;N&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0-9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Previous window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Interactive picker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b &amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Confirmation required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split pane left/right&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b %&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split pane top/bottom&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b "&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigate panes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b &amp;lt;arrow&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show pane numbers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom pane toggle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Status bar shows [Z]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close pane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Confirmation required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enter copy mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b [&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit copy mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt; or &lt;code&gt;Escape&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start selection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;While in copy mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy selection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Enter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;While in copy mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b ]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reload config&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+b :source-file ~/.tmux.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kill session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tmux kill-session -t &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run from shell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;Config Tweaks&lt;/h2&gt;
&lt;p&gt;tmux reads &lt;code&gt;~/.tmux.conf&lt;/code&gt; on startup. Create or edit this file to customise behaviour. Changes take effect in new sessions, or you can reload without restarting.&lt;/p&gt;
&lt;h3&gt;Location&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~/.tmux.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Change the Prefix Key to Ctrl+a&lt;/h3&gt;
&lt;p&gt;Many users prefer &lt;code&gt;Ctrl+a&lt;/code&gt; because it is easier to reach and was the &lt;code&gt;screen&lt;/code&gt; default:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;unbind&lt;span class="w"&gt; &lt;/span&gt;C-b
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;prefix&lt;span class="w"&gt; &lt;/span&gt;C-a
&lt;span class="nb"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;C-a&lt;span class="w"&gt; &lt;/span&gt;send-prefix
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After this, all commands use &lt;code&gt;Ctrl+a&lt;/code&gt; instead of &lt;code&gt;Ctrl+b&lt;/code&gt;. The &lt;code&gt;bind C-a send-prefix&lt;/code&gt; line lets you type a literal &lt;code&gt;Ctrl+a&lt;/code&gt; in programs like bash by pressing the prefix twice.&lt;/p&gt;
&lt;h3&gt;Enable Mouse Support&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;mouse&lt;span class="w"&gt; &lt;/span&gt;on
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With mouse enabled, you can click to select panes, drag borders to resize, and scroll with the mouse wheel. Scroll wheel enters copy mode automatically. To exit copy mode after scrolling, press &lt;code&gt;q&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Increase Scrollback Buffer&lt;/h3&gt;
&lt;p&gt;The default scrollback is 2000 lines, which fills up quickly. Increase it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;history-limit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Reduce Escape Time (Useful for PVE noVNC)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-sg&lt;span class="w"&gt; &lt;/span&gt;escape-time&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This reduces the delay tmux waits after pressing &lt;code&gt;Escape&lt;/code&gt; before deciding it is not a prefix sequence. Lower values make &lt;code&gt;Escape&lt;/code&gt; feel more responsive in vim and other tools inside tmux.&lt;/p&gt;
&lt;h3&gt;Start Window and Pane Index at 1&lt;/h3&gt;
&lt;p&gt;By default windows are numbered from 0. Starting at 1 maps better to the number keys:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;base-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
setw&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;pane-base-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Reload Config Without Restarting&lt;/h3&gt;
&lt;p&gt;After editing &lt;code&gt;~/.tmux.conf&lt;/code&gt;, reload it inside a running tmux session:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b :source-file ~/.tmux.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or add a keybinding for this in &lt;code&gt;~/.tmux.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;r&lt;span class="w"&gt; &lt;/span&gt;source-file&lt;span class="w"&gt; &lt;/span&gt;~/.tmux.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;display-message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Config reloaded&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then use &lt;code&gt;Ctrl+b r&lt;/code&gt; (or &lt;code&gt;Ctrl+a r&lt;/code&gt; if you changed the prefix) to reload.&lt;/p&gt;
&lt;h3&gt;Minimal Recommended Config&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Change prefix to Ctrl+a&lt;/span&gt;
unbind&lt;span class="w"&gt; &lt;/span&gt;C-b
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;prefix&lt;span class="w"&gt; &lt;/span&gt;C-a
&lt;span class="nb"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;C-a&lt;span class="w"&gt; &lt;/span&gt;send-prefix

&lt;span class="c1"&gt;# Enable mouse&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;mouse&lt;span class="w"&gt; &lt;/span&gt;on

&lt;span class="c1"&gt;# Increase scrollback&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;history-limit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;

&lt;span class="c1"&gt;# Reduce escape time&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-sg&lt;span class="w"&gt; &lt;/span&gt;escape-time&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;

&lt;span class="c1"&gt;# Start numbering from 1&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;base-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
setw&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;pane-base-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# Reload config&lt;/span&gt;
&lt;span class="nb"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;r&lt;span class="w"&gt; &lt;/span&gt;source-file&lt;span class="w"&gt; &lt;/span&gt;~/.tmux.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;display-message&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Config reloaded&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;Known State Cheatsheet&lt;/h2&gt;
&lt;p&gt;Bookmark this section. When something goes wrong, work through it from top to bottom.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Exit any mode:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In copy mode: press &lt;code&gt;q&lt;/code&gt;, then &lt;code&gt;Escape&lt;/code&gt; if needed&lt;/li&gt;
&lt;li&gt;In command mode: press &lt;code&gt;Escape&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Program stuck: press &lt;code&gt;Ctrl+c&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Check what windows and panes are open:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b w    (list windows)
Ctrl+b q    (show pane numbers)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Detach safely (session keeps running):&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. From the shell, list all sessions:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;ls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Reattach to a session:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;name&amp;gt;
tmux&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;# most recent session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;6. Kill a stuck pane:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b x    (confirmation prompt, then y)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;7. Kill a stuck window:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Ctrl+b &amp;amp;    (confirmation prompt, then y)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;8. Kill a session entirely:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;kill-session&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;9. Nuclear option: kill all tmux sessions:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;kill-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;10. Start fresh:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmux&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Following this list top to bottom will resolve the vast majority of situations where tmux feels stuck or confusing. The core principle: &lt;code&gt;Ctrl+b d&lt;/code&gt; gets you out, &lt;code&gt;tmux ls&lt;/code&gt; shows what is there, and &lt;code&gt;tmux attach&lt;/code&gt; gets you back in.&lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="tmux"/><category term="Terminal"/><category term="Ghostty"/><category term="xterm"/><category term="Proxmox"/><category term="LXC"/><category term="Linux"/><category term="Vim"/><category term="Bash"/><category term="Ubuntu"/></entry><entry><title>Installing Homebrew on a Debian 13 (Trixie) LXC Container</title><link href="http://lizard-spock.co.uk/installing-homebrew-on-a-debian-13-trixie-lxc-container.html" rel="alternate"/><published>2026-02-25T00:00:00+00:00</published><updated>2026-02-25T00:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-02-25:/installing-homebrew-on-a-debian-13-trixie-lxc-container.html</id><summary type="html">&lt;p&gt;A verified step-by-step guide to installing Homebrew (Linuxbrew) on a Debian 13 "trixie" LXC unprivileged container, including group setup, prefix pre-creation, and multi-user configuration.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This tutorial documents a working, verified installation of Homebrew (Linuxbrew) on a
Debian 13 "trixie" LXC container. Every step reflects what was actually done on this system;
nothing has been invented or copied from generic guides without verification.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tested environment:&lt;/strong&gt;
- Debian GNU/Linux 13 (trixie), amd64
- LXC unprivileged container
- Homebrew 5.0.15
- Primary non-root user: &lt;code&gt;morgan&lt;/code&gt; (uid=1000)&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="#1-prerequisites"&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2-installation-steps"&gt;Installation Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#3-per-user-shell-setup"&gt;Per-User Shell Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#4-verification"&gt;Verification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#5-adding-future-users"&gt;Adding Future Users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#6-troubleshooting"&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;1. Prerequisites&lt;/h2&gt;
&lt;h3&gt;System packages&lt;/h3&gt;
&lt;p&gt;The Homebrew installer requires these packages. Install them as root before running the
installer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;update
apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;build-essential&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;procps
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Verify all five are present:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;ii&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;essential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;12.12&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nx"&gt;amd64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Informational&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;essential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;
&lt;span class="nx"&gt;ii&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;curl&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m m-Double"&gt;8.14.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;deb13u2&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;amd64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transferring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;syntax&lt;/span&gt;
&lt;span class="nx"&gt;ii&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m m-Double"&gt;5.46&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nx"&gt;amd64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Recognize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;magic&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;
&lt;span class="nx"&gt;ii&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m m-Double"&gt;2.47.3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;deb13u1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;amd64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scalable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;distributed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;revision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;
&lt;span class="nx"&gt;ii&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;procps&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m m-Double"&gt;4.0.4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;amd64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;utilities&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;PATH gotcha: /usr/sbin must be in root's PATH&lt;/h3&gt;
&lt;p&gt;On Debian 13, &lt;code&gt;/usr/sbin&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; automatically in the PATH of non-login shells (including
those invoked by &lt;code&gt;su&lt;/code&gt; without &lt;code&gt;-l&lt;/code&gt;). The installer calls &lt;code&gt;useradd&lt;/code&gt;, which lives at
&lt;code&gt;/usr/sbin/useradd&lt;/code&gt;. If &lt;code&gt;/usr/sbin&lt;/code&gt; is missing from your PATH the installer will fail with a
confusing "command not found" error.&lt;/p&gt;
&lt;p&gt;Before running the installer, confirm your PATH includes &lt;code&gt;/usr/sbin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If it does not, prepend it for the session:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/sbin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or simply run the installer from a full login shell (&lt;code&gt;su -&lt;/code&gt; rather than &lt;code&gt;su&lt;/code&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. Installation Steps&lt;/h2&gt;
&lt;p&gt;All steps in this section are run as &lt;strong&gt;root&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Step 1 — Create the dedicated &lt;code&gt;linuxbrew&lt;/code&gt; group&lt;/h3&gt;
&lt;p&gt;Homebrew on Linux uses a shared group so that multiple users can write to the installation
prefix. Create it before the installer runs so you control its GID:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;groupadd&lt;span class="w"&gt; &lt;/span&gt;linuxbrew
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Step 2 — Pre-create the Homebrew prefix directory&lt;/h3&gt;
&lt;p&gt;The Homebrew installer expects to own &lt;code&gt;/home/linuxbrew/.linuxbrew&lt;/code&gt;. In an LXC container the
installer sometimes cannot create &lt;code&gt;/home/linuxbrew&lt;/code&gt; itself due to ownership constraints on
&lt;code&gt;/home&lt;/code&gt;. Pre-create it and set the correct ownership &lt;strong&gt;before&lt;/strong&gt; running the installer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew/.linuxbrew
chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;morgan:linuxbrew&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2775&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2775&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew/.linuxbrew
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;2775&lt;/code&gt; mode sets the &lt;strong&gt;setgid bit&lt;/strong&gt; (&lt;code&gt;s&lt;/code&gt;) on the directory. This causes all files and
subdirectories created inside to inherit the &lt;code&gt;linuxbrew&lt;/code&gt; group automatically, which is what
allows multiple users to install and update packages.&lt;/p&gt;
&lt;p&gt;After this step, &lt;code&gt;ls -la /home/linuxbrew/&lt;/code&gt; should show:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;drwxrwxr-x  3 morgan linuxbrew 4096 Feb 25 15:31 .
drwxr-xr-x  4 root   root      4096 Feb 25 15:31 ..
drwxrwsr-x 14 morgan linuxbrew 4096 Feb 25 15:31 .linuxbrew
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And &lt;code&gt;stat /home/linuxbrew/.linuxbrew&lt;/code&gt; confirms the setgid bit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Access: (2775/drwxrwsr-x)  Uid: ( 1000/  morgan)   Gid: ( 1001/linuxbrew)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Step 3 — Add the primary user to the linuxbrew group&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;usermod&lt;span class="w"&gt; &lt;/span&gt;-aG&lt;span class="w"&gt; &lt;/span&gt;linuxbrew&lt;span class="w"&gt; &lt;/span&gt;morgan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-a&lt;/code&gt; flag is critical — without it &lt;code&gt;usermod -G&lt;/code&gt; &lt;strong&gt;replaces&lt;/strong&gt; all supplementary groups
instead of appending to them.&lt;/p&gt;
&lt;h3&gt;Step 4 — Run the Homebrew installer as the primary user&lt;/h3&gt;
&lt;p&gt;Switch to the non-root user and run the official installer. Do &lt;strong&gt;not&lt;/strong&gt; run it as root:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;morgan
/bin/bash&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Because the prefix directory already exists and is writable by the &lt;code&gt;linuxbrew&lt;/code&gt; group (and
&lt;code&gt;morgan&lt;/code&gt; is a member), the installer will populate it without needing to create it or change
system-level ownership.&lt;/p&gt;
&lt;h3&gt;Step 5 — Create the system-wide profile.d script&lt;/h3&gt;
&lt;p&gt;To activate Homebrew automatically for every user who logs in, create a shell script in
&lt;code&gt;/etc/profile.d/&lt;/code&gt; as root:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/profile.d/homebrew.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="s"&gt;if [ -d &amp;quot;/home/linuxbrew/.linuxbrew&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s"&gt;    eval &amp;quot;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;fi&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;/etc/profile.d/homebrew.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is exactly the file present on this system:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# /etc/profile.d/homebrew.sh&lt;/span&gt;
&lt;span class="c1"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/linuxbrew/.linuxbrew&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/home/linuxbrew/.linuxbrew/bin/brew&lt;span class="w"&gt; &lt;/span&gt;shellenv&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;if&lt;/code&gt; guard makes the script safe on systems where the prefix does not exist (e.g., before
installation, or if the directory is removed).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. Per-User Shell Setup&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;/etc/profile.d/homebrew.sh&lt;/code&gt; script runs automatically for &lt;strong&gt;login shells&lt;/strong&gt;. This covers
&lt;code&gt;su - username&lt;/code&gt;, SSH logins, and console logins.&lt;/p&gt;
&lt;p&gt;For interactive non-login shells (e.g., a new terminal tab that sources &lt;code&gt;~/.bashrc&lt;/code&gt; instead of
&lt;code&gt;~/.bash_profile&lt;/code&gt;), a user may need to add the following to their &lt;code&gt;~/.bashrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Homebrew (Linuxbrew)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/linuxbrew/.linuxbrew&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/home/linuxbrew/.linuxbrew/bin/brew&lt;span class="w"&gt; &lt;/span&gt;shellenv&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;brew shellenv&lt;/code&gt; sets these environment variables:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HOMEBREW_PREFIX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/home/linuxbrew/.linuxbrew&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HOMEBREW_CELLAR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/home/linuxbrew/.linuxbrew/Cellar&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HOMEBREW_REPOSITORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/home/linuxbrew/.linuxbrew/Homebrew&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PATH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;prepends &lt;code&gt;/home/linuxbrew/.linuxbrew/bin&lt;/code&gt; and &lt;code&gt;.../sbin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MANPATH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;prepends &lt;code&gt;.../share/man&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INFOPATH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;prepends &lt;code&gt;.../share/info&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;4. Verification&lt;/h2&gt;
&lt;h3&gt;Confirm the installation prefix layout&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-la&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew/.linuxbrew/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Expected output (all entries owned by &lt;code&gt;morgan:linuxbrew&lt;/code&gt;, setgid &lt;code&gt;s&lt;/code&gt; visible):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;drwxrwxr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Caskroom&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cellar&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Frameworks&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Homebrew&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sbin&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;
&lt;span class="n"&gt;drwxrwsr&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morgan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linuxbrew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Feb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Check the Homebrew version&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;morgan&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brew --version&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Homebrew 5.0.15
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Run brew doctor&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;morgan&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brew doctor&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Your system is ready to brew.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Confirm group membership&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;getent&lt;span class="w"&gt; &lt;/span&gt;group&lt;span class="w"&gt; &lt;/span&gt;linuxbrew
&lt;span class="c1"&gt;# linuxbrew:x:1001:morgan&lt;/span&gt;

id&lt;span class="w"&gt; &lt;/span&gt;morgan
&lt;span class="c1"&gt;# uid=1000(morgan) gid=1000(morgan) groups=1000(morgan),100(users),1001(linuxbrew)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Test installing a package (dry run)&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;morgan&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brew install --dry-run hello&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;==&amp;gt; Would install 1 formula:
hello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;5. Adding Future Users&lt;/h2&gt;
&lt;p&gt;When a new user needs Homebrew access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# As root — add the new user to the linuxbrew group&lt;/span&gt;
usermod&lt;span class="w"&gt; &lt;/span&gt;-aG&lt;span class="w"&gt; &lt;/span&gt;linuxbrew&lt;span class="w"&gt; &lt;/span&gt;newuser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The user must log out and back in (or start a new login shell) for the group change to take
effect. The &lt;code&gt;/etc/profile.d/homebrew.sh&lt;/code&gt; script will activate &lt;code&gt;brew&lt;/code&gt; for them automatically
on their next login. No other changes are needed.&lt;/p&gt;
&lt;p&gt;To verify the new user can reach Homebrew:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;newuser&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brew --version&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;h2&gt;6. Troubleshooting&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;brew: command not found&lt;/code&gt; in a non-login shell&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;/etc/profile.d/homebrew.sh&lt;/code&gt; script only runs in login shells. In a non-login shell
(typical terminal emulators, &lt;code&gt;su username&lt;/code&gt; without the &lt;code&gt;-&lt;/code&gt;), Homebrew will not be on the PATH.&lt;/p&gt;
&lt;p&gt;Fix: source the script manually, or add the &lt;code&gt;eval&lt;/code&gt; line to &lt;code&gt;~/.bashrc&lt;/code&gt; as shown in
&lt;a href="#3-per-user-shell-setup"&gt;Section 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Quick test — if this works but &lt;code&gt;brew&lt;/code&gt; does not:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/home/linuxbrew/.linuxbrew/bin/brew&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;then the issue is purely PATH/environment, not the installation.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;useradd: command not found&lt;/code&gt; during installation&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;useradd&lt;/code&gt; lives at &lt;code&gt;/usr/sbin/useradd&lt;/code&gt;. On Debian 13, &lt;code&gt;/usr/sbin&lt;/code&gt; is not in the PATH of
non-login root shells. Run the installer from a full login shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# note the hyphen — this gives a login shell with /usr/sbin in PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or prepend it explicitly before running the installer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/sbin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Installer fails to create &lt;code&gt;/home/linuxbrew&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In LXC containers, the installer may not be able to create the prefix directory itself because
&lt;code&gt;/home&lt;/code&gt; is owned by &lt;code&gt;root:root&lt;/code&gt; with mode &lt;code&gt;755&lt;/code&gt;. The installer does not have write access to
&lt;code&gt;/home&lt;/code&gt; when running as a non-root user.&lt;/p&gt;
&lt;p&gt;Solution: pre-create the directory as root before running the installer (see
&lt;a href="#step-2--pre-create-the-homebrew-prefix-directory"&gt;Step 2&lt;/a&gt;).&lt;/p&gt;
&lt;h3&gt;Permission denied errors when running &lt;code&gt;brew install&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The user must be in the &lt;code&gt;linuxbrew&lt;/code&gt; group &lt;strong&gt;and&lt;/strong&gt; must have started a new session after being
added. Check:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;# linuxbrew should appear in the groups list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If &lt;code&gt;linuxbrew&lt;/code&gt; is absent, either the &lt;code&gt;usermod -aG linuxbrew username&lt;/code&gt; step was skipped, or
the user has not logged out and back in since being added.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;brew doctor&lt;/code&gt; reports warnings about &lt;code&gt;/usr/local&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;On Linux, Homebrew uses &lt;code&gt;/home/linuxbrew/.linuxbrew&lt;/code&gt; as its prefix, not &lt;code&gt;/usr/local&lt;/code&gt;. Warnings
about &lt;code&gt;/usr/local&lt;/code&gt; being absent or not owned by the current user are normal and harmless on a
Linux installation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Quick-Reference: Full Root-Side Install Sequence&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Install dependencies&lt;/span&gt;
apt-get&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;build-essential&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;procps

&lt;span class="c1"&gt;# 2. Ensure /usr/sbin is in PATH (run as login shell or set manually)&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/sbin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Create the linuxbrew group&lt;/span&gt;
groupadd&lt;span class="w"&gt; &lt;/span&gt;linuxbrew

&lt;span class="c1"&gt;# 4. Pre-create the prefix and set ownership/permissions&lt;/span&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew/.linuxbrew
chown&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;morgan:linuxbrew&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2775&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2775&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/linuxbrew/.linuxbrew

&lt;span class="c1"&gt;# 5. Add the primary user to the group&lt;/span&gt;
usermod&lt;span class="w"&gt; &lt;/span&gt;-aG&lt;span class="w"&gt; &lt;/span&gt;linuxbrew&lt;span class="w"&gt; &lt;/span&gt;morgan

&lt;span class="c1"&gt;# 6. Run the installer as the non-root user&lt;/span&gt;
su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;morgan&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/bin/bash -c &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&amp;quot;&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# 7. Install the system-wide profile.d activation script&lt;/span&gt;
cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/profile.d/homebrew.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="s"&gt;if [ -d &amp;quot;/home/linuxbrew/.linuxbrew&amp;quot; ]; then&lt;/span&gt;
&lt;span class="s"&gt;    eval &amp;quot;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)&amp;quot;&lt;/span&gt;
&lt;span class="s"&gt;fi&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;/etc/profile.d/homebrew.sh

&lt;span class="c1"&gt;# 8. Verify&lt;/span&gt;
su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;morgan&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;brew --version &amp;amp;&amp;amp; brew doctor&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Hardware &amp; Homelab"/><category term="Homebrew"/><category term="Linux"/><category term="Debian"/><category term="LXC"/><category term="Proxmox"/><category term="Git"/><category term="Bash"/></entry><entry><title>FSM Diagrams in Pelican and Claude Code</title><link href="http://lizard-spock.co.uk/fsm-diagrams-in-pelican-and-claude-code.html" rel="alternate"/><published>2026-02-22T18:00:00+00:00</published><updated>2026-02-22T18:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-02-22:/fsm-diagrams-in-pelican-and-claude-code.html</id><summary type="html">&lt;p&gt;Finite state machines turn up everywhere: protocol implementations, hardware controllers, UI flows, parsers. Drawing them well is useful but tedious …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Finite state machines turn up everywhere: protocol implementations, hardware controllers, UI flows, parsers. Drawing them well is useful but tedious. This post covers two tools I've built: a Pelican plugin that renders FSM diagrams at build time so they appear as images in published posts, and a Claude Code skill that generates diagrams from plain-English descriptions.&lt;/p&gt;
&lt;p&gt;The pattern is identical to the &lt;a href="http://lizard-spock.co.uk/wavedrom-timing-diagrams-in-pelican-with-claude-code.html"&gt;WaveDrom plugin and skill&lt;/a&gt;: describe what you want, get a fenced code block, paste it into a post, and the build renders it to SVG automatically.&lt;/p&gt;
&lt;h2&gt;The Pelican plugin&lt;/h2&gt;
&lt;p&gt;The plugin is at &lt;a href="https://github.com/morganp/pelican-fsm"&gt;github.com/morganp/pelican-fsm&lt;/a&gt;, cloned to &lt;code&gt;~/Code/pelican-fsm&lt;/code&gt; (a sibling directory to the blog repo). It intercepts fenced &lt;code&gt;```mermaid ```&lt;/code&gt; and &lt;code&gt;```dot ```&lt;/code&gt; code blocks before Pelican's standard Markdown processing, renders each one to an SVG using the appropriate CLI tool, caches the result by content hash in &lt;code&gt;content/images/fsm/&lt;/code&gt;, and replaces the block with a standard image reference. Pelican then copies the SVG to &lt;code&gt;output/images/fsm/&lt;/code&gt; as a static asset.&lt;/p&gt;
&lt;p&gt;SVGs are cached across builds, so only diagrams whose source has changed are re-rendered.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mermaid-js/mermaid-cli"&gt;Mermaid CLI&lt;/a&gt; for &lt;code&gt;mermaid&lt;/code&gt; blocks:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;@mermaid-js/mermaid-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://graphviz.org/"&gt;Graphviz&lt;/a&gt; for &lt;code&gt;dot&lt;/code&gt; blocks:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;graphviz&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# macOS&lt;/span&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;graphviz&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Both tools are optional. If one is missing, blocks for that type fall back gracefully to a fenced &lt;code&gt;text&lt;/code&gt; block without failing the build.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;Clone the plugin alongside your Pelican project and install it into the Pelican virtualenv in editable mode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/morganp/pelican-fsm&lt;span class="w"&gt; &lt;/span&gt;../pelican-fsm
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;venv/bin/activate
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;../pelican-fsm&lt;span class="w"&gt; &lt;/span&gt;--config-settings&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editable_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;compat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;editable_mode=compat&lt;/code&gt; flag is required. Without it, modern setuptools editable installs use a path-hook mechanism that prevents Pelican's namespace plugin auto-discovery from finding the plugin.&lt;/p&gt;
&lt;p&gt;Pelican 4.5+ auto-discovers namespace plugins, so no changes to &lt;code&gt;pelicanconf.py&lt;/code&gt; are needed.&lt;/p&gt;
&lt;h3&gt;Optional config&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;mmdc&lt;/code&gt; or &lt;code&gt;dot&lt;/code&gt; are not on your &lt;code&gt;PATH&lt;/code&gt; during the build, set their full paths in &lt;code&gt;pelicanconf.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;FSM_MERMAID_CLI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/opt/homebrew/bin/mmdc&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;FSM_DOT_CLI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/opt/homebrew/bin/dot&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Using it in a post&lt;/h3&gt;
&lt;p&gt;Write a fenced code block with the language set to &lt;code&gt;mermaid&lt;/code&gt; or &lt;code&gt;dot&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```mermaid&lt;/span&gt;
&lt;span class="s"&gt;stateDiagram-v2&lt;/span&gt;
&lt;span class="s"&gt;    [*] --&amp;gt; Idle&lt;/span&gt;
&lt;span class="s"&gt;    Idle --&amp;gt; Running : start&lt;/span&gt;
&lt;span class="s"&gt;    Running --&amp;gt; Idle : stop&lt;/span&gt;
&lt;span class="s"&gt;    Running --&amp;gt; Error : fault&lt;/span&gt;
&lt;span class="s"&gt;    Error --&amp;gt; Idle : reset&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At build time this becomes an SVG embedded in the page:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_ea7d29ca092a08571aa2cfe0ccc1600b.svg"&gt;&lt;/p&gt;
&lt;p&gt;Graphviz DOT works the same way, and is a better fit for hardware and RTL documentation where the compact box-and-arrow style is conventional:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_d33619e8d6e9ae49f343c57a8f6db1e1.svg"&gt;&lt;/p&gt;
&lt;p&gt;If the CLI is not found or rendering fails, the block falls back to fenced &lt;code&gt;text&lt;/code&gt; — the post still builds, you just see the raw source instead of a diagram.&lt;/p&gt;
&lt;h2&gt;The Claude Code skill&lt;/h2&gt;
&lt;p&gt;The skill lives at &lt;a href="https://github.com/morganp/dotfiles/tree/main/config/claude/skills/fsm"&gt;github.com/morganp/dotfiles/tree/main/config/claude/skills/fsm&lt;/a&gt;. Claude Code auto-discovers skills from &lt;code&gt;~/.claude/skills/&lt;/code&gt; and loads them on demand.&lt;/p&gt;
&lt;p&gt;The skill triggers automatically when you describe anything involving states, transitions, or control flow: protocol implementations, UI flows, hardware FSMs, parsers, game logic. You describe the system in plain English and Claude generates the diagram.&lt;/p&gt;
&lt;p&gt;The skill knows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full Mermaid &lt;code&gt;stateDiagram-v2&lt;/code&gt; syntax: plain states, display labels, transitions with events/guards/actions, composite (nested) states, choice pseudostates, fork/join for parallel regions, concurrent regions (&lt;code&gt;--&lt;/code&gt;), notes, direction, and classDef styling&lt;/li&gt;
&lt;li&gt;Graphviz DOT FSM conventions: Moore vs Mealy annotation, initial pseudostate arrow, accepting states as double circles&lt;/li&gt;
&lt;li&gt;When to prefer each format: Mermaid for software/web docs, DOT for hardware/RTL&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Example workflow&lt;/h3&gt;
&lt;p&gt;Describe the system:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Draw the states for a TCP connection"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Claude produces:&lt;/p&gt;
&lt;p&gt;&lt;img alt="FSM diagram" src="http://lizard-spock.co.uk/images/fsm/fsm_03bf39c43e71e98bd12e737558c3f9e3.svg"&gt;&lt;/p&gt;
&lt;p&gt;Because the skill outputs a &lt;code&gt;mermaid&lt;/code&gt; fenced block, you can paste it directly into a blog post and the Pelican plugin renders it automatically.&lt;/p&gt;
&lt;h3&gt;Installing the skill&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;~/.claude/skills/fsm
curl&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;~/.claude/skills/fsm/SKILL.md&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;https://raw.githubusercontent.com/morganp/dotfiles/main/config/claude/skills/fsm/SKILL.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or clone the dotfiles repo and symlink:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/morganp/dotfiles&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles
ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;~/dotfiles/config/claude/skills/fsm&lt;span class="w"&gt; &lt;/span&gt;~/.claude/skills/fsm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Syntax quick reference&lt;/h2&gt;
&lt;p&gt;Both formats can express the same FSM concepts, but use different syntax. The table below maps them side by side.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Mermaid &lt;code&gt;stateDiagram-v2&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Graphviz DOT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Graph declaration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stateDiagram-v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;digraph FSM { ... }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default node style&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node [shape=circle]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left-to-right layout&lt;/td&gt;
&lt;td&gt;&lt;code&gt;direction LR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rankdir=LR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%% comment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;// comment&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StateName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StateName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State with display label&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state "Label" as Name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Name [label="Label"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initial pseudostate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[*] --&amp;gt; State&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;__start [shape=point width=0.2]&lt;/code&gt; + &lt;code&gt;__start -&amp;gt; State&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final/accepting state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;State --&amp;gt; [*]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;State [shape=doublecircle]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transition&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A --&amp;gt; B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A -&amp;gt; B&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transition with event&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A --&amp;gt; B : event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A -&amp;gt; B [label="event"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transition with action&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A --&amp;gt; B : event / action()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A -&amp;gt; B [label="event / action()"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guarded transition&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A --&amp;gt; B : [guard]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A -&amp;gt; B [label="[guard]"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composite (nested) state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state S { ... }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;subgraph cluster_S { ... }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Choice pseudostate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state C &amp;lt;&amp;lt;choice&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;C [shape=diamond]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fork pseudostate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state F &amp;lt;&amp;lt;fork&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;invisible node + multiple outgoing edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Join pseudostate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state J &amp;lt;&amp;lt;join&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;invisible node + multiple incoming edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent regions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--&lt;/code&gt; inside composite&lt;/td&gt;
&lt;td&gt;parallel &lt;code&gt;subgraph&lt;/code&gt; blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Project structure&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morganp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;# blog source (main branch)&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fsm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;# SVG cache (persists across make clean)&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;fsm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;# github.com/morganp/pelican-fsm&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fsm_renderer&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="c1"&gt;# plugin entry point, signal registration&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preprocessor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Markdown extension + preprocessor&lt;/span&gt;

&lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;claude&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fsm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SKILL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;# Claude Code skill definition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The combination is useful for any documentation that involves control flow: describe the behaviour, get the diagram from Claude, drop it into a post, and the plugin renders it at build time. No manual editing of diagram syntax, no copy-pasting between browser tabs.&lt;/p&gt;</content><category term="Engineering"/><category term="FSM"/><category term="Pelican"/><category term="Mermaid"/><category term="Graphviz"/><category term="claude"/><category term="ai"/><category term="Python"/><category term="Git"/><category term="Bash"/><category term="Homebrew"/><category term="Ubuntu"/><category term="RTL"/></entry><entry><title>Verilog Lint Skill for Claude Code</title><link href="http://lizard-spock.co.uk/verilog-lint-skill-for-claude-code.html" rel="alternate"/><published>2026-02-22T12:00:00+00:00</published><updated>2026-02-22T12:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-02-22:/verilog-lint-skill-for-claude-code.html</id><summary type="html">&lt;p&gt;I've been using Claude Code for RTL generation lately, and the missing piece was a tight feedback loop between code …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using Claude Code for RTL generation lately, and the missing piece was a tight feedback loop between code generation and linting. Writing Verilog by hand is tedious; having Claude generate it and immediately validate it with real tools closes that gap nicely.&lt;/p&gt;
&lt;h2&gt;What it does&lt;/h2&gt;
&lt;p&gt;The skill connects two open-source linters into an agentic write → lint → fix → re-lint loop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Verilator&lt;/strong&gt; (&lt;code&gt;--lint-only -Wall&lt;/code&gt;) catches semantic errors: undeclared signals, bit-width mismatches, blocking assignments in &lt;code&gt;always_ff&lt;/code&gt; blocks, multi-driven nets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verible&lt;/strong&gt; (&lt;code&gt;verible-verilog-lint&lt;/code&gt;) enforces style: naming conventions, whitespace, port alignment, &lt;code&gt;always_ff&lt;/code&gt;/&lt;code&gt;always_comb&lt;/code&gt; discipline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When Claude generates RTL, the skill automatically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Writes the code to a temp file&lt;/li&gt;
&lt;li&gt;Runs both linters&lt;/li&gt;
&lt;li&gt;Parses error messages with line numbers&lt;/li&gt;
&lt;li&gt;Applies fixes and re-lints — up to 3 times&lt;/li&gt;
&lt;li&gt;Reports clean status or surfaces a diagnostic table if errors remain&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Why two tools&lt;/h2&gt;
&lt;p&gt;They complement each other. Verilator thinks like a synthesizer — it catches things that will fail elaboration or produce incorrect silicon. Verible doesn't simulate; it reads the source and checks against Google's SystemVerilog style guide. Together they cover both "will this work" and "is this readable".&lt;/p&gt;
&lt;h2&gt;Both are free and open source&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Verilator: &lt;code&gt;brew install verilator&lt;/code&gt; / &lt;code&gt;apt install verilator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Verible: &lt;code&gt;brew tap chipsalliance/verible &amp;amp;&amp;amp; brew install verible&lt;/code&gt; (macOS), or grab a binary from the &lt;a href="https://github.com/chipsalliance/verible/releases"&gt;GitHub releases page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RTL patterns baked in&lt;/h2&gt;
&lt;p&gt;The skill includes reference patterns for mistake-free SystemVerilog: synchronous-reset flip-flops, &lt;code&gt;always_comb&lt;/code&gt; combinational blocks, parameterized FSMs with &lt;code&gt;typedef enum&lt;/code&gt;. Claude defaults to these when generating new modules, which avoids most of the common lint warnings before the first pass even runs.&lt;/p&gt;
&lt;h2&gt;Same pattern as WaveDrom&lt;/h2&gt;
&lt;p&gt;This follows the same skill format as the &lt;a href="http://lizard-spock.co.uk/wavedrom-timing-diagrams-in-pelican-with-claude-code.html"&gt;WaveDrom timing diagram skill&lt;/a&gt; — a &lt;code&gt;SKILL.md&lt;/code&gt; file that gives Claude structured instructions, reference tables, and workflow steps. Both skills live in &lt;code&gt;~/.claude/skills/&lt;/code&gt; and are picked up automatically by Claude Code.&lt;/p&gt;
&lt;p&gt;The combination is handy for hardware work: describe a protocol, get a timing diagram, then generate and lint the RTL that implements it.&lt;/p&gt;</content><category term="Engineering"/><category term="Verilog"/><category term="SystemVerilog"/><category term="RTL"/><category term="claude"/><category term="ai"/><category term="Hardware"/><category term="Linting"/></entry><entry><title>WaveDrom Timing Diagrams in Pelican with Claude Code</title><link href="http://lizard-spock.co.uk/wavedrom-timing-diagrams-in-pelican-with-claude-code.html" rel="alternate"/><published>2026-02-20T12:00:00+00:00</published><updated>2026-02-20T12:00:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2026-02-20:/wavedrom-timing-diagrams-in-pelican-with-claude-code.html</id><summary type="html">&lt;p&gt;&lt;a href="https://wavedrom.com"&gt;WaveDrom&lt;/a&gt; is a JavaScript library that renders digital timing diagrams from a simple JSON-based format called WaveJSON. It's widely used …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://wavedrom.com"&gt;WaveDrom&lt;/a&gt; is a JavaScript library that renders digital timing diagrams from a simple JSON-based format called WaveJSON. It's widely used in hardware documentation — you describe signal transitions in a compact string notation, and WaveDrom draws the waveform. This post covers two tools I've built around it: a Pelican plugin that renders diagrams at build time, and a Claude Code skill that generates WaveJSON from plain English.&lt;/p&gt;
&lt;h2&gt;The Pelican plugin&lt;/h2&gt;
&lt;p&gt;The plugin is at &lt;a href="https://github.com/morganp/pelican-wavedrom"&gt;github.com/morganp/pelican-wavedrom&lt;/a&gt;, cloned to &lt;code&gt;~/Code/pelican-wavedrom&lt;/code&gt; — a sibling directory to the blog repo. It intercepts fenced &lt;code&gt;```wavedrom ```&lt;/code&gt; code blocks before Pelican's standard Markdown processing, renders each one to an SVG using &lt;code&gt;wavedrom-cli&lt;/code&gt;, caches the result by content hash in &lt;code&gt;content/images/wavedrom/&lt;/code&gt;, and replaces the block with a standard image reference. Pelican then copies the SVG to &lt;code&gt;output/images/wavedrom/&lt;/code&gt; as a static asset.&lt;/p&gt;
&lt;p&gt;SVGs are cached across builds — only diagrams whose source has changed are re-rendered.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;The plugin requires &lt;code&gt;wavedrom-cli&lt;/code&gt; globally via npm:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npm&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;wavedrom-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then clone the plugin alongside your blog repo and install it into the Pelican virtualenv in editable mode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/morganp/pelican-wavedrom&lt;span class="w"&gt; &lt;/span&gt;../pelican-wavedrom
&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;venv/bin/activate
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;../pelican-wavedrom&lt;span class="w"&gt; &lt;/span&gt;--config-settings&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editable_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;compat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;editable_mode=compat&lt;/code&gt; flag is required. Without it, modern setuptools editable installs use a path-hook mechanism that prevents Pelican's namespace plugin auto-discovery from finding the plugin.&lt;/p&gt;
&lt;p&gt;Pelican 4.5+ auto-discovers namespace plugins — no changes to &lt;code&gt;pelicanconf.py&lt;/code&gt; are needed.&lt;/p&gt;
&lt;h3&gt;Optional config&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;wavedrom-cli&lt;/code&gt; is not on your &lt;code&gt;PATH&lt;/code&gt; during the build (e.g. in a CI environment), set its full path in &lt;code&gt;pelicanconf.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;WAVEDROM_CLI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/opt/homebrew/bin/wavedrom-cli&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Using it in a post&lt;/h3&gt;
&lt;p&gt;Write a fenced code block with the language set to &lt;code&gt;wavedrom&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```wavedrom&lt;/span&gt;
&lt;span class="s"&gt;{ &amp;quot;signal&amp;quot;: [&lt;/span&gt;
&lt;span class="s"&gt;  { &amp;quot;name&amp;quot;: &amp;quot;CLK&amp;quot;,     &amp;quot;wave&amp;quot;: &amp;quot;p.....|...&amp;quot; },&lt;/span&gt;
&lt;span class="s"&gt;  { &amp;quot;name&amp;quot;: &amp;quot;Data&amp;quot;,    &amp;quot;wave&amp;quot;: &amp;quot;x.345x|=.x&amp;quot;, &amp;quot;data&amp;quot;: [&amp;quot;head&amp;quot;, &amp;quot;body&amp;quot;, &amp;quot;tail&amp;quot;, &amp;quot;data&amp;quot;] },&lt;/span&gt;
&lt;span class="s"&gt;  { &amp;quot;name&amp;quot;: &amp;quot;Request&amp;quot;, &amp;quot;wave&amp;quot;: &amp;quot;0.1..0|1.0&amp;quot; }&lt;/span&gt;
&lt;span class="s"&gt;]}&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At build time (&lt;code&gt;make html&lt;/code&gt; or &lt;code&gt;make github&lt;/code&gt;) this becomes an SVG embedded in the page:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_da1bb22d431f672f9f36b629cf1f4152.svg"&gt;&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;wavedrom-cli&lt;/code&gt; is not found or rendering fails, the block falls back to a fenced &lt;code&gt;json&lt;/code&gt; block — the post still builds, you just see the raw JSON instead of a diagram.&lt;/p&gt;
&lt;h2&gt;The Claude Code skill&lt;/h2&gt;
&lt;p&gt;The skill lives at &lt;code&gt;~/.claude/skills/wavedrom/SKILL.md&lt;/code&gt;. Claude Code auto-discovers skills from &lt;code&gt;~/.claude/skills/&lt;/code&gt; and loads them on demand.&lt;/p&gt;
&lt;p&gt;The skill triggers automatically when you describe anything related to timing diagrams, waveforms, or digital protocols — SPI, I2C, UART, AXI handshakes, clock enables, request/acknowledge patterns. You describe the signals in plain English; Claude generates the WaveJSON.&lt;/p&gt;
&lt;p&gt;The skill includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The full WaveJSON wave-character reference (&lt;code&gt;p&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;z&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;–&lt;code&gt;9&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Signal properties (&lt;code&gt;phase&lt;/code&gt;, &lt;code&gt;period&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Top-level properties (&lt;code&gt;edge&lt;/code&gt;, &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;foot&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Group and spacer syntax&lt;/li&gt;
&lt;li&gt;Edge annotation syntax for timing arrows between signals&lt;/li&gt;
&lt;li&gt;Common patterns: SPI transactions, request/acknowledge handshakes, clock-with-enable, grouped signals&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Example workflow&lt;/h3&gt;
&lt;p&gt;Describe the protocol:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Draw an I2C start condition followed by a 7-bit address byte with ACK"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Claude generates the WaveJSON and explains the timing. Because the skill outputs a &lt;code&gt;wavedrom&lt;/code&gt; fenced block (not just JSON), you can paste it directly into a blog post and the Pelican plugin renders it automatically.&lt;/p&gt;
&lt;h2&gt;WaveJSON quick reference&lt;/h2&gt;
&lt;p&gt;The wave string for each signal is a sequence of characters:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Char&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p&lt;/code&gt; / &lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Positive / negative clock (with tick mark)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt; / &lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logic low / high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unknown / undefined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High impedance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Continue previous state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multi-bit data (label from &lt;code&gt;data&lt;/code&gt; array)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;–&lt;code&gt;9&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coloured data states&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\|&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gap / break in time axis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A minimal diagram:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_47846f1d1444efee9e793db00192ea4d.svg"&gt;&lt;/p&gt;
&lt;p&gt;An SPI transaction with chip select:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_41ab2e5fe0cdd7dde2f60fec31f05df5.svg"&gt;&lt;/p&gt;
&lt;p&gt;A request/acknowledge handshake with an edge annotation showing propagation delay:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WaveDrom timing diagram" src="http://lizard-spock.co.uk/images/wavedrom/wavedrom_df96e15d2ad371db91e44c66212ec566.svg"&gt;&lt;/p&gt;
&lt;h2&gt;Project structure&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;morganp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;# blog source (main branch)&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;wavedrom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# SVG cache (persists across make clean)&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wavedrom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;# github.com/morganp/pelican-wavedrom&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;wavedrom_generator&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;# plugin entry point, signal registration&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;preprocessor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Markdown extension + preprocessor&lt;/span&gt;

&lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;claude&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wavedrom&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SKILL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;# Claude Code skill definition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The combination is useful for hardware documentation: describe a protocol in English, get WaveJSON from Claude, drop the block into a post, and the plugin renders it at build time. No manual JSON editing, no copy-pasting between browser tabs.&lt;/p&gt;
&lt;p&gt;The same skill format is used by the &lt;a href="http://lizard-spock.co.uk/verilog-lint-skill-for-claude-code.html"&gt;Verilog lint skill&lt;/a&gt; — describe a module, get generated RTL, and the lint loop validates it automatically.&lt;/p&gt;</content><category term="Engineering"/><category term="WaveDrom"/><category term="Timing"/><category term="Electronics"/><category term="Pelican"/><category term="claude"/><category term="ai"/><category term="Verilog"/><category term="Python"/><category term="Git"/><category term="Bash"/><category term="Homebrew"/><category term="RTL"/></entry><entry><title>Top Museums for Families</title><link href="http://lizard-spock.co.uk/top-museums-for-families.html" rel="alternate"/><published>2025-12-05T15:06:00+00:00</published><updated>2025-12-05T15:06:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-12-05:/top-museums-for-families.html</id><content type="html">&lt;p&gt;&lt;a href="https://www.beamish.org.uk/"&gt;Beamish&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.life.org.uk"&gt;Life Science Centre&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://play.eureka.org.uk/"&gt;Eureka: Children’s Museum&lt;/a&gt;
&lt;a href="https://discover.eureka.org.uk/"&gt;Eureka: Science Museum&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://edencamp.co.uk"&gt;Eden Camp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dynamicearth.org.uk"&gt;Dynamic Earth&lt;/a&gt;&lt;/p&gt;</content><category term="Home &amp; Garden"/><category term="Family"/></entry><entry><title>Reduce Rack Drum Kit Size</title><link href="http://lizard-spock.co.uk/reduce-rack-drum-kit-size.html" rel="alternate"/><published>2025-12-05T14:50:00+00:00</published><updated>2025-12-05T14:50:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-12-05:/reduce-rack-drum-kit-size.html</id><summary type="html">&lt;p&gt;For the Yamaha DTX8 the cymbals mount on to the horizontal drum rack tubes. A more compact setup can be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For the Yamaha DTX8 the cymbals mount on to the horizontal drum rack tubes. A more compact setup can be achieved if the cymbal mounts are onto of the vertical tubes. Some drum racks already have this setup, typically on the lower end as it requires less harder to be supplied. It seems like a great way to save space if trying to fit a drum kit into your home!  &lt;a href="https://www.musicstore.com/en_GB/GBP/Gibraltar-Rack-Tube-Attachment-SC-RMAA-/art-DRU0025312-000?campaign=GShopping/GB&amp;amp;ProgramUUID=fp4KAQMEqOEAAAGSjgtiaHEt&amp;amp;gad_source=1"&gt;Gibraltar have this clamp&lt;/a&gt;, to convert other drum racks with (1.5”) 38.1mm tubing.&lt;/p&gt;</content><category term="Music"/><category term="Drum"/></entry><entry><title>Expanding Yamaha DTX8 Drum Kit</title><link href="http://lizard-spock.co.uk/expanding-yamaha-dtx8-drum-kit.html" rel="alternate"/><published>2025-12-05T14:43:00+00:00</published><updated>2025-12-05T14:43:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-12-05:/expanding-yamaha-dtx8-drum-kit.html</id><summary type="html">&lt;h2&gt;TOM&lt;/h2&gt;
&lt;p&gt;Parts Required for adding 4th Tom (second floor tom) to a Yamaha DTX8 kit.&lt;/p&gt;
&lt;p&gt;Mounting bracket for rack: Yamaha …&lt;/p&gt;</summary><content type="html">&lt;h2&gt;TOM&lt;/h2&gt;
&lt;p&gt;Parts Required for adding 4th Tom (second floor tom) to a Yamaha DTX8 kit.&lt;/p&gt;
&lt;p&gt;Mounting bracket for rack: Yamaha TPCL500 Tom Holder. 
&lt;a href="https://www.yamahamusiclondon.com/TPCL500-Tom-Holder/pidJTPCL500"&gt;UK supplier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;10” tom, on the DTX8 they are all 10”: Yamaha XP125T M.  
Note are the Mesh heads(M), not the (X) heads.   &lt;a href="https://www.yamahamusiclondon.com/XP125T-M-Real-Wood-Finish-Tom-Pack/pidJXP125TMRW-TOMPACK"&gt;UK supplier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;eBay might also be a good source for used components.&lt;/p&gt;
&lt;h2&gt;Adding a Cymbal&lt;/h2&gt;
&lt;p&gt;Cable splitting tom1 for tom1 single zone and a single zone splash cymbal.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yamaha PCY10010-inch cymbal pad&lt;/li&gt;
&lt;li&gt;Yamaha CYAT500 Cymbal Attachment  &lt;a href="https://www.yamahamusiclondon.com/DTX6-and-8-Splash-Pack/pidDTX68-SPLASHPACK"&gt;Splash £180 UK Supplier&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternatively for another crash or simply larger 13” pad a Yamaha PCY135A pad could be used.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.yamahamusiclondon.com/DTX6-Crash-Pack/pidDTX6-CRASHPACK"&gt;Crash £200 UK Supplier&lt;/a&gt;.&lt;/p&gt;</content><category term="Music"/><category term="Drum"/></entry><entry><title>Yamaha Drum App</title><link href="http://lizard-spock.co.uk/yamaha-drum-app.html" rel="alternate"/><published>2025-12-05T14:38:00+00:00</published><updated>2025-12-05T14:38:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-12-05:/yamaha-drum-app.html</id><summary type="html">&lt;p&gt;For Yamaha electronic drum kits (DTX) there is a &lt;a href="https://uk.yamaha.com/en/products/musical_instruments/drums/el_drums/apps/index.html"&gt;‘DTX Touch app’&lt;/a&gt; for iPhone, iPad, Android that lets you more …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For Yamaha electronic drum kits (DTX) there is a &lt;a href="https://uk.yamaha.com/en/products/musical_instruments/drums/el_drums/apps/index.html"&gt;‘DTX Touch app’&lt;/a&gt; for iPhone, iPad, Android that lets you more easily manage the kit sounds.&lt;/p&gt;</content><category term="Music"/><category term="Drum"/></entry><entry><title>Command Line Disk Usage</title><link href="http://lizard-spock.co.uk/command-line-disk-usage.html" rel="alternate"/><published>2025-09-06T13:19:00+01:00</published><updated>2025-09-06T13:19:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-09-06:/command-line-disk-usage.html</id><summary type="html">&lt;h1&gt;NCurses Disk Usage&lt;/h1&gt;
&lt;p&gt;A useful Command Line Iterface (CLI) for exploring disk usage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ncdu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Example output.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ncdu 2.9.1 …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;h1&gt;NCurses Disk Usage&lt;/h1&gt;
&lt;p&gt;A useful Command Line Iterface (CLI) for exploring disk usage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ncdu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Example output.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ncdu 2.9.1 ~ Use the arrow keys to navigate, press ? for help
--- ... github.io --------------------------
  115.6 MiB [##################] /venv
   59.7 MiB [#########         ] /output
   47.6 MiB [#######           ] /.git
   46.1 MiB [#######           ] /content
    8.0 KiB [                  ]  .DS_Store
    8.0 KiB [                  ]  tasks.py
    8.0 KiB [                  ] /__pycache__
    4.0 KiB [                  ]  Makefile
    4.0 KiB [                  ]  readme.md
    4.0 KiB [                  ]  pelicanconf.py
    4.0 KiB [                  ]  python_search_category.py
    4.0 KiB [                  ]  python_search_and_replace.py
    4.0 KiB [                  ]  create_new_post.py
    4.0 KiB [                  ]  publishconf.py
    4.0 KiB [                  ]  .gitignore
    4.0 KiB [                  ]  CNAME
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you are using MacOS then &lt;a href="https://brew.sh"&gt;brew&lt;/a&gt; can be used to install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install ncdu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Disk Usage&lt;/h1&gt;
&lt;p&gt;The disk usage tool &lt;code&gt;du&lt;/code&gt; is a simpler non interactive way of listing file sizes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;du -h -d 1
  60M   ./output
  46M   ./content
 8.0K   ./__pycache__
 116M   ./venv
  48M   ./.git
 313M   .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-h&lt;/code&gt; gives human readable output, ie sizes in k, M, G TBytes.&lt;br&gt;
thr &lt;code&gt;-d 1&lt;/code&gt; on MacOS limits listing to 1 directory deep. &lt;code&gt;-s&lt;/code&gt;can be used to summarise the requested directory.&lt;/p&gt;
&lt;h1&gt;Tree&lt;/h1&gt;
&lt;p&gt;Another alternative is to use &lt;code&gt;tree&lt;/code&gt; to list folde contents with the &lt;code&gt;-h&lt;/code&gt;option to give human readable file sizes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tree -L 1 -h
[ 608]  .
├── [ 128]  __pycache__
├── [  19]  CNAME
├── [ 224]  content
├── [1.2K]  create_new_post.py
├── [2.8K]  Makefile
├── [ 28K]  output
├── [2.1K]  pelicanconf.py
├── [ 528]  publishconf.py
├── [1.3K]  python_search_and_replace.py
├── [1.7K]  python_search_category.py
├── [2.2K]  readme.md
├── [4.1K]  tasks.py
├── [ 224]  venv
└── [ 224]  venv_old
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;MacOS install tree with &lt;a href="https://brew.sh"&gt;brew&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install tree
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Unix &amp; Tools"/><category term="OS X"/><category term="CLI"/><category term="Git"/></entry><entry><title>Book: Mastering the command line</title><link href="http://lizard-spock.co.uk/book-mastering-the-command-line.html" rel="alternate"/><published>2025-05-31T10:11:00+01:00</published><updated>2025-05-31T10:11:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-05-31:/book-mastering-the-command-line.html</id><summary type="html">&lt;!--to publish change draft to published--&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/masteringcli"&gt;Mastering the command line like a hacker by Xiaodong Xu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The book focuses on bash and zsh, which is great …&lt;/p&gt;</summary><content type="html">&lt;!--to publish change draft to published--&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/masteringcli"&gt;Mastering the command line like a hacker by Xiaodong Xu&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The book focuses on bash and zsh, which is great since I use both of those shells.&lt;/p&gt;
&lt;p&gt;The useful sections from the book include:&lt;/p&gt;
&lt;h1&gt;Zsh Completion&lt;/h1&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="n"&gt;autoload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compinit&lt;/span&gt;
&lt;span class="n"&gt;compinit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;dotfiles&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;zsh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;autosuggestions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;zsh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;autosuggestions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zsh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The zsh-autosuggestions.zsh needs to be &lt;a href="https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md"&gt;installed/downloaded from Github&lt;/a&gt;, adding as a seperate depo or adding as a submodule to your dotfiles depo.&lt;/p&gt;
&lt;h1&gt;History&lt;/h1&gt;
&lt;h2&gt;BASH History&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;.&lt;span class="nv"&gt;bashrc&lt;/span&gt;
#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Save&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;history&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;scroll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;back&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;terminal&lt;/span&gt;
&lt;span class="nv"&gt;HISTFILESIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="nv"&gt;HISTSIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;

#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;save&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;duplicate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;commands&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;space&lt;/span&gt;
&lt;span class="nv"&gt;HISTCONTROL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;erasedups:ignorespace&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;ZSH History&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; ~/.zshrc 
&lt;span class="gh"&gt;#&lt;/span&gt; Save 5000 lins of history, with the same scroll back in the terminal
SAVEHIST=5000
HISTSIZE=5000

&lt;span class="gh"&gt;#&lt;/span&gt; Do not save duplicate commands, ignore commands starting with a space
setopt HIST_IGNORE_ALL_DUPS
setopt HIST_IGNORE_SPACE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Using History&lt;/h2&gt;
&lt;p&gt;View the last 5 commands&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# Bash
$ history 5
# Zsh
$ history -5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For zsh we can see the last time the command was run and the time taken with :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;history -i -D
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alternative command to history:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Searching History&lt;/h2&gt;
&lt;p&gt;The most common way is piping history through grep to search.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cmd&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ctrl+r is another option which starts a reverse search through the history.&lt;/p&gt;
&lt;p&gt;Ctrl+p and Ctrl+n can be used to scroll forwards and backwards through previous commands. Although for me the up down cursors workk mor enaturally.&lt;/p&gt;
&lt;h2&gt;Repeating Commands&lt;/h2&gt;
&lt;p&gt;Repeat the last command used (Bang Bang)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;!!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;great example is when you run a command but it fails because of require Superuser access.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cmd_that_needs_root
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;fail
$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;!!
&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;yay!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Execute command 2 back.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;!-2
#Note
!! =&amp;gt; !-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Execute command 100 from history&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sx"&gt;!100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Last Argument&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;!$&lt;/code&gt; recalls the last Argument. typicall usage could be with mkdir and cd:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;notes
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/><category term="Bash"/></entry><entry><title>Better Python Iterators</title><link href="http://lizard-spock.co.uk/better-python-iterators.html" rel="alternate"/><published>2025-03-01T12:24:00+00:00</published><updated>2025-03-01T12:24:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-03-01:/better-python-iterators.html</id><summary type="html">&lt;!--to publish change draft to published--&gt;

&lt;p&gt;Pyhton can convert most (all) lists/collections into iterator objects, this is done by calling &lt;code&gt;iter()&lt;/code&gt; ie&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;num_list = [1, 2 …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;!--to publish change draft to published--&gt;

&lt;p&gt;Pyhton can convert most (all) lists/collections into iterator objects, this is done by calling &lt;code&gt;iter()&lt;/code&gt; ie&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;num_list = [1, 2, 3] 
num_iter = iter(num_list)  # Returns Iterator
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Python in the background is calling the objects &lt;code&gt;__iter__()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;A typical method for returning an Iterator is using a for loop to compile/create the elements. However the draw back to this is that the for loop can not begin executing until the full iterator is created.&lt;br&gt;
This could make it the program look like it has stalled while it compiles long and or complex iterator objects.&lt;/p&gt;
&lt;h2&gt;Generator Functions&lt;/h2&gt;
&lt;p&gt;Generator function allow an optimisation that it allows the use of the iterator after each element has been created. Generator functions return elemnts using &lt;code&gt;yield&lt;/code&gt; instead of &lt;code&gt;return&lt;/code&gt; on the complete list. The a benefit of this approach is that the full iterator never has to be held in memory at the same time, allowing much larger data sets to be analysed.&lt;/p&gt;
&lt;h2&gt;Generator Example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def my_generator(n):

    # initialize counter
    value = 0

    # loop until counter is less than n
    while value &amp;lt; n:

        # produce the current value of the counter
        yield value

        # increment the counter
        value += 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Non-Generator Example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def my_nongenerator(n):

    # initialize counter
    value = 0
    this_list = list()
    # loop until counter is less than n
    while value &amp;lt; n:

        # produce the current value of the counter
        this_list.append( value )

        # increment the counter
        value += 1
    return this_list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Packaging Python Projects</title><link href="http://lizard-spock.co.uk/packaging-python-projects.html" rel="alternate"/><published>2025-02-28T15:22:00+00:00</published><updated>2025-02-28T15:22:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-02-28:/packaging-python-projects.html</id><summary type="html">&lt;!--to publish change draft to published--&gt;

&lt;p&gt;How to package a pythin script or project for distribution. Offical Docs are [here][python_package].
Packaging for pip requires use …&lt;/p&gt;</summary><content type="html">&lt;!--to publish change draft to published--&gt;

&lt;p&gt;How to package a pythin script or project for distribution. Offical Docs are [here][python_package].
Packaging for pip requires use of a [build backend][], [Hatchling][] is the default for this example.&lt;/p&gt;
&lt;p&gt;This run through uses the testPyPI. &lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://test.pypi.org/account/register/"&gt;Register for a test.pypi account&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Initialise Python project area:
note: the build process also uses the gitignore to excluded the venv folder.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir packaging_tutorial
cd packagin_tutorial
python3 -m venv venv
source venv/bin/activate
curl --output .gitignore &amp;quot;https://raw.githubusercontent.com/github/gitignore/refs/heads/main/Python.gitignore&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create the folder structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;packaging_tutorial&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LICENSE&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pyproject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toml&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;README&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example_package_YOUR_USERNAME_HERE&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;__init__.py&lt;/code&gt; can be empty, but allows project to be imported in standard way.&lt;/p&gt;
&lt;p&gt;pyproject.toml - directs pip how to build the project&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[build-system]&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;hatchling&amp;quot;]&lt;/span&gt;
&lt;span class="na"&gt;build-backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hatchling.build&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;example_package_YOUR_USERNAME_HERE&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0.0.1&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;authors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;{ name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Example Author&amp;quot;&lt;/span&gt;&lt;span class="na"&gt;, email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;author@example.com&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;A small example package&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;readme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;README.md&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;requires-python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;gt;=3.8&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;classifiers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;&amp;quot;Programming Language&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: Python :: 3&amp;quot;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;&amp;quot;Operating System&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;: OS Independent&amp;quot;,&lt;/span&gt;
&lt;span class="na"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MIT&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;license-files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&amp;quot;LICEN[CS]E*&amp;quot;]&lt;/span&gt;

&lt;span class="k"&gt;[project.urls]&lt;/span&gt;
&lt;span class="na"&gt;Homepage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://github.com/pypa/sampleproject&amp;quot;&lt;/span&gt;
&lt;span class="na"&gt;Issues&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://github.com/pypa/sampleproject/issues&amp;quot;&lt;/span&gt;


&lt;span class="na"&gt;[python_package]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://packaging.python.org/en/latest/tutorials/packaging-projects/&lt;/span&gt;
&lt;span class="na"&gt;[build backend]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://packaging.python.org/en/latest/glossary/&lt;/span&gt;&lt;span class="c1"&gt;#term-Build-Backend&lt;/span&gt;
&lt;span class="na"&gt;[Hatchling]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;https://hatch.pypa.io/latest/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Generate Distrubution Archive&lt;/h2&gt;
&lt;p&gt;Update &lt;code&gt;build&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install --upgrade build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This should create&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example_package_YOUR_USERNAME_HERE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m m-Double"&gt;0.0.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;whl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Built&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example_package_YOUR_USERNAME_HERE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m m-Double"&gt;0.0.1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Distibution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Upload!&lt;/h2&gt;
&lt;p&gt;Create and store API token. &lt;a href="https://test.pypi.org/manage/account/#api-tokens"&gt;Create Token&lt;/a&gt;, set scope to entire account.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;twine&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install --upgrade twine
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Upload the files in dist&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;twine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;testpypi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once Complete the uploaded package can be viewed here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//test.pypi.org/project/example_package_YOUR_USERNAME_HERE.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Test install&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//test.pypi.org/simple/ --no-deps example-package-YOUR-USERNAME-HERE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Python Parsing XML</title><link href="http://lizard-spock.co.uk/python-parsing-xml.html" rel="alternate"/><published>2025-01-05T13:31:00+00:00</published><updated>2025-01-05T13:31:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-05:/python-parsing-xml.html</id><summary type="html">&lt;p&gt;Parsing XML with Python using the standard library &lt;code&gt;xml.etree.ElementTree&lt;/code&gt;. It converts XML text into a tree of &lt;code&gt;Element …&lt;/code&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Parsing XML with Python using the standard library &lt;code&gt;xml.etree.ElementTree&lt;/code&gt;. It converts XML text into a tree of &lt;code&gt;Element&lt;/code&gt; objects you can traverse and query.&lt;/p&gt;
&lt;h2&gt;Basic Parsing&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;xml.etree.ElementTree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ET&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;lt;person&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;name&amp;gt;Chuck&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;phone type=&amp;quot;intl&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;    +1 734 303 4456&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;/phone&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;email hide=&amp;quot;yes&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;&amp;lt;/person&amp;gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Name:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Attr:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hide&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Parsing from a File&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;tree = ET.parse(&amp;#39;data.xml&amp;#39;)&lt;/span&gt;
&lt;span class="n"&gt;root = tree.getroot()&lt;/span&gt;
&lt;span class="n"&gt;print(root.tag)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Iterating over Children&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt;.&lt;span class="nv"&gt;tag&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt;.&lt;span class="nv"&gt;attrib&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;iter()&lt;/code&gt; to walk the entire tree recursively:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;.&lt;span class="nv"&gt;iter&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;phone&amp;#39;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;elem&lt;/span&gt;.&lt;span class="nv"&gt;text&lt;/span&gt;.&lt;span class="nv"&gt;strip&lt;/span&gt;&lt;span class="ss"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Accessing Attributes and Text&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;
&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrib&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Finding Multiple Elements&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;direct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matching&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;XPath&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expressions&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./items/item&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./items/item[@id=&amp;quot;1&amp;quot;]&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Handling Namespaces&lt;/h2&gt;
&lt;p&gt;XML namespaces appear as &lt;code&gt;{uri}tag&lt;/code&gt;. Register a prefix to keep queries readable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ns = {&amp;#39;ns&amp;#39;: &amp;#39;http://example.com/schema&amp;#39;}
root.find(&amp;#39;ns:item&amp;#39;, ns)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For larger or more complex XML, consider &lt;code&gt;lxml&lt;/code&gt; which is faster and supports full XPath:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install lxml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Python Web Parsing</title><link href="http://lizard-spock.co.uk/python-web-parsing.html" rel="alternate"/><published>2025-01-05T11:15:00+00:00</published><updated>2025-01-05T11:15:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-05:/python-web-parsing.html</id><summary type="html">&lt;p&gt;For parsing web pages with Python, BeautifulSoup is a helpful library. To install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You also need a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For parsing web pages with Python, BeautifulSoup is a helpful library. To install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You also need a way to fetch the page. The standard library &lt;code&gt;urllib&lt;/code&gt; works fine for simple cases; &lt;code&gt;requests&lt;/code&gt; is more ergonomic for anything complex:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install requests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Basic Usage: Extract all links&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.parse&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;urllib.error&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;bs4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ssl&lt;/span&gt;

&lt;span class="c1"&gt;# Ignore SSL certificate errors&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_default_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verify_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CERT_NONE&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Enter URL: &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;html.parser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve all anchor tags&lt;/span&gt;
&lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Finding Elements&lt;/h2&gt;
&lt;p&gt;BeautifulSoup provides several ways to locate content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; First matching element
soup.find(&amp;#39;h1&amp;#39;)
soup.find(&amp;#39;div&amp;#39;, class_=&amp;#39;content&amp;#39;)
soup.find(&amp;#39;a&amp;#39;, id=&amp;#39;main-link&amp;#39;)

&lt;span class="gh"&gt;#&lt;/span&gt; All matching elements (returns a list)
soup.find_all(&amp;#39;p&amp;#39;)
soup.find_all(&amp;#39;a&amp;#39;, class_=&amp;#39;external&amp;#39;)

&lt;span class="gh"&gt;#&lt;/span&gt; CSS selector syntax
soup.select(&amp;#39;div.article &amp;gt; p&amp;#39;)
soup.select_one(&amp;#39;#footer a&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Extracting Text and Attributes&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tag = soup.find(&amp;#39;a&amp;#39;)

tag.text          # visible text content (strips tags)
tag.get_text()    # same, with optional separator
tag[&amp;#39;href&amp;#39;]       # attribute access (raises KeyError if missing)
tag.get(&amp;#39;href&amp;#39;)   # safe attribute access (returns None if missing)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Using requests Instead of urllib&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;bs4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;html.parser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note: always check &lt;code&gt;response.status_code == 200&lt;/code&gt; before parsing, and respect &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/p&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Pelican New Post script</title><link href="http://lizard-spock.co.uk/pelican-new-post-script.html" rel="alternate"/><published>2025-01-03T16:44:00+00:00</published><updated>2025-01-03T16:44:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-03:/pelican-new-post-script.html</id><summary type="html">&lt;p&gt;In the aim of keeping the navigation of the Blog posts clean, I have limited my self to a few …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the aim of keeping the navigation of the Blog posts clean, I have limited my self to a few general categories, but remeberign them while editing in vim and not creating typos is a pain. Therefore I have added the category selection to my pyhton script for creating the new markdown files.&lt;/p&gt;
&lt;p&gt;When running this file from the command line, top level of my pelican project, it first prompts for a category selection then requests The title of the post. The new file is created and correctly formated for a Pelican markdown post.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;&lt;span class="normal"&gt; 1&lt;/span&gt;
&lt;span class="normal"&gt; 2&lt;/span&gt;
&lt;span class="normal"&gt; 3&lt;/span&gt;
&lt;span class="normal"&gt; 4&lt;/span&gt;
&lt;span class="normal"&gt; 5&lt;/span&gt;
&lt;span class="normal"&gt; 6&lt;/span&gt;
&lt;span class="normal"&gt; 7&lt;/span&gt;
&lt;span class="normal"&gt; 8&lt;/span&gt;
&lt;span class="normal"&gt; 9&lt;/span&gt;
&lt;span class="normal"&gt;10&lt;/span&gt;
&lt;span class="normal"&gt;11&lt;/span&gt;
&lt;span class="normal"&gt;12&lt;/span&gt;
&lt;span class="normal"&gt;13&lt;/span&gt;
&lt;span class="normal"&gt;14&lt;/span&gt;
&lt;span class="normal"&gt;15&lt;/span&gt;
&lt;span class="normal"&gt;16&lt;/span&gt;
&lt;span class="normal"&gt;17&lt;/span&gt;
&lt;span class="normal"&gt;18&lt;/span&gt;
&lt;span class="normal"&gt;19&lt;/span&gt;
&lt;span class="normal"&gt;20&lt;/span&gt;
&lt;span class="normal"&gt;21&lt;/span&gt;
&lt;span class="normal"&gt;22&lt;/span&gt;
&lt;span class="normal"&gt;23&lt;/span&gt;
&lt;span class="normal"&gt;24&lt;/span&gt;
&lt;span class="normal"&gt;25&lt;/span&gt;
&lt;span class="normal"&gt;26&lt;/span&gt;
&lt;span class="normal"&gt;27&lt;/span&gt;
&lt;span class="normal"&gt;28&lt;/span&gt;
&lt;span class="normal"&gt;29&lt;/span&gt;
&lt;span class="normal"&gt;30&lt;/span&gt;
&lt;span class="normal"&gt;31&lt;/span&gt;
&lt;span class="normal"&gt;32&lt;/span&gt;
&lt;span class="normal"&gt;33&lt;/span&gt;
&lt;span class="normal"&gt;34&lt;/span&gt;
&lt;span class="normal"&gt;35&lt;/span&gt;
&lt;span class="normal"&gt;36&lt;/span&gt;
&lt;span class="normal"&gt;37&lt;/span&gt;
&lt;span class="normal"&gt;38&lt;/span&gt;
&lt;span class="normal"&gt;39&lt;/span&gt;
&lt;span class="normal"&gt;40&lt;/span&gt;
&lt;span class="normal"&gt;41&lt;/span&gt;
&lt;span class="normal"&gt;42&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/python3&lt;/span&gt;
&lt;span class="c1"&gt;# coding=utf-8&lt;/span&gt;

&lt;span class="c1"&gt;# CLI for creating a new markdown post&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;html&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="c1"&gt;# datetime object containing current date and time&lt;/span&gt;
&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# dd/mm/YY H:M&lt;/span&gt;
&lt;span class="n"&gt;dt_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; %H:%M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dt_string_simple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create dictionary of categories, ask for user selection.&lt;/span&gt;
&lt;span class="n"&gt;cat_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cooking&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Engineering&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Home&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Outdoor&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Photography&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Tech&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;cat_dict&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post_cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Enter Post Category :&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post_cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#Request title of blog post&lt;/span&gt;
&lt;span class="n"&gt;post_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Enter Post Title : &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;post_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# sanitize&lt;/span&gt;
&lt;span class="n"&gt;post_title_safe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post_title&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;new_post_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt_string_simple&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;post_title_safe&lt;/span&gt;

&lt;span class="n"&gt;this_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;./content/posts/&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;new_post_name&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.md&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this_file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Title: &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;post_title&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Date: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;dt_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Category: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;cat_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post_cat&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Tags: python&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Author: morganp&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Status: draft&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;!--to publish change draft to published--&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this_file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/><category term="Vim"/><category term="Pelican"/></entry><entry><title>Python Base Types</title><link href="http://lizard-spock.co.uk/python-base-types.html" rel="alternate"/><published>2025-01-02T18:09:00+00:00</published><updated>2025-01-02T18:09:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-02:/python-base-types.html</id><summary type="html">&lt;p&gt;Making notes as I work through &lt;a href="https://www.dr-chuck.com"&gt;Dr Chucks&lt;/a&gt; Python for Everyone course on &lt;a href="https://www.coursera.org/specializations/python"&gt;Coursera&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are 3 main types in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Making notes as I work through &lt;a href="https://www.dr-chuck.com"&gt;Dr Chucks&lt;/a&gt; Python for Everyone course on &lt;a href="https://www.coursera.org/specializations/python"&gt;Coursera&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are 3 main types in Python:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lists []&lt;/li&gt;
&lt;li&gt;Dictionaries {}&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Tuple"&gt;Tuples&lt;/a&gt; ()&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;List&lt;/h2&gt;
&lt;p&gt;Lists are mutable, ie elements can be changed.&lt;br&gt;
Creation of a list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;emptylist = []
thislist  = [&amp;quot;A&amp;quot;,&amp;quot;B&amp;quot;,&amp;quot;C&amp;quot;]
print( thislist )
  [&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;]
print( thislist[1] )
  B
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Dictionary&lt;/h2&gt;
&lt;p&gt;Dictionaries are key value pairs.&lt;br&gt;
Creation of Dictionary&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;emptydict = {}
thisdict  = {0:&amp;quot;A&amp;quot;, 1:&amp;quot;B&amp;quot;, 2:&amp;quot;C&amp;quot;} 
print( thisdict )
  {0: &amp;#39;A&amp;#39;, 1: &amp;#39;B&amp;#39;, 2: &amp;#39;C&amp;#39;}
print( thisdict[1] )
  B
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Tuple&lt;/h2&gt;
&lt;p&gt;Tuples are unmodifiable lists. elements can not be changed or reordered.&lt;br&gt;
Creation of Tuple.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;emptytuple = () # This is of no real use as imutable
thistuple  = (&amp;quot;A&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;C&amp;quot;)
print( thistuple )
  (&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;)
print( thistuple[1] )
  B
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Modify Pelican theme</title><link href="http://lizard-spock.co.uk/modify-pelican-theme.html" rel="alternate"/><published>2025-01-02T16:53:00+00:00</published><updated>2025-01-02T16:53:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-02:/modify-pelican-theme.html</id><summary type="html">&lt;p&gt;First figure out which theme you are using.
List installed themes via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;% pelican-themes -l&lt;/span&gt;
&lt;span class="n"&gt;simple&lt;/span&gt;
&lt;span class="s"&gt;notmyidea&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;pelicanconf.py does not …&lt;/p&gt;</summary><content type="html">&lt;p&gt;First figure out which theme you are using.
List installed themes via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;% pelican-themes -l&lt;/span&gt;
&lt;span class="n"&gt;simple&lt;/span&gt;
&lt;span class="s"&gt;notmyidea&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;pelicanconf.py does not set a THEME, therfore defaulting to notmyidea.&lt;/p&gt;
&lt;p&gt;The template locaions are (update your python version as appropriate):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python3&lt;/span&gt;&lt;span class="m m-Double"&gt;.13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;themes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;notmyidea&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;templates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At a minimum a theme must contain these files, which could be altered in your local copy of the theme.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;└── templates
    ├── archives.html         // to display archives
    ├── period_archives.html  // to display time-period archives
    ├── article.html          // processed for each article
    ├── author.html           // processed for each author
    ├── authors.html          // must list all the authors
    ├── categories.html       // must list all the categories
    ├── category.html         // processed for each category
    ├── index.html            // the index (list all the articles)
    ├── page.html             // processed for each page
    ├── tag.html              // processed for each tag
    └── tags.html             // must list all the tags. Can be a tag cloud.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/><category term="Pelican"/></entry><entry><title>Pelican Site add Tags and Categories list</title><link href="http://lizard-spock.co.uk/pelican-site-add-tags-and-categories-list.html" rel="alternate"/><published>2025-01-02T15:39:00+00:00</published><updated>2025-01-02T15:39:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-02:/pelican-site-add-tags-and-categories-list.html</id><summary type="html">&lt;p&gt;I think it improves website usability when they are navigatable via the URL.&lt;/p&gt;
&lt;p&gt;For example when viewing Posts under the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I think it improves website usability when they are navigatable via the URL.&lt;/p&gt;
&lt;p&gt;For example when viewing Posts under the caetegory 'Tech' ie https://lizard-spock.co.uk/category/tech.html, &lt;/p&gt;
&lt;p&gt;removing the category and viewing  https://lizard-spock.co.uk/category/ should list the possible categories.&lt;/p&gt;
&lt;p&gt;The page is created by pelican but it is placed as :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;https://lizard-spock.co.uk/categories.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;https://lizard-spock.co.uk/category/index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To remedy this, modify your pelicanconf.py Adding in some page mappings.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;##&lt;/span&gt; Adding template pages creating /tag/index.html from tags.html
TEMPLATE_PAGES = {&amp;#39;tags.html&amp;#39;: &amp;#39;tag/index.html&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Above takes the tags.html template and makes it available via example.com/tag&lt;/p&gt;</content><category term="Programming"/><category term="Python"/><category term="Pelican"/></entry><entry><title>Japanese Garden 10 Years on</title><link href="http://lizard-spock.co.uk/japanese-garden-10-years-on.html" rel="alternate"/><published>2025-01-02T13:02:00+00:00</published><updated>2025-01-02T13:02:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2025-01-02:/japanese-garden-10-years-on.html</id><summary type="html">&lt;p&gt;An update to the Japanese Garden, aorund 10 years after it was built.&lt;br&gt;
The ground cover is being removed in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;An update to the Japanese Garden, aorund 10 years after it was built.&lt;br&gt;
The ground cover is being removed in some of these images for the next iteration.  &lt;/p&gt;
&lt;p&gt;The large panorama is 270 degree view of 3 sides of it.
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_4220.jpeg"&gt;
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_4221.jpeg"&gt;
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_4223.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;Various iterations of ground cover, last one was partially paved.
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_2181.jpeg"&gt;
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_2495.jpeg"&gt;
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_2496.jpeg"&gt;
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_2612.jpeg"&gt;&lt;/p&gt;
&lt;h2&gt;Older pictures during Construction&lt;/h2&gt;
&lt;p&gt;Then main structure nearly complete
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_0988.jpeg"&gt;
&lt;img alt="photo" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_1120.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;Leveling up the roofs for the seating area.
The large panorama is a 270 degree view of 3 sides of it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="270 degree panorama of the garden" src="http://lizard-spock.co.uk/images/Gardening/JapaneseGarden10yr/IMG_9401.jpeg"&gt;&lt;/p&gt;</content><category term="Home &amp; Garden"/><category term="Garden"/><category term="DIY"/></entry><entry><title>Python Regular Expressions</title><link href="http://lizard-spock.co.uk/python-regular-expressions.html" rel="alternate"/><published>2024-12-23T10:21:00+00:00</published><updated>2024-12-23T10:21:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-12-23:/python-regular-expressions.html</id><summary type="html">&lt;h2&gt;Python Regular Expressions Quick Guide:&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;beginning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;h2&gt;Python Regular Expressions Quick Guide:&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;beginning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;whitespace&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;whitespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Repeats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;Repeats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;greedy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Repeats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="vm"&gt;?&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;Repeats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;greedy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aeiou&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;single&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;^XYZ&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;single&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;listed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a-z0-9&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;characters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Indicates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extraction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;start&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Indicates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extraction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Based on Dr Chucks from Python for Everyone course.&lt;/p&gt;
&lt;h2&gt;Usage:&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;list_of_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Adaptive Filter Theory Notes 1</title><link href="http://lizard-spock.co.uk/adaptive-filter-theory-notes-1.html" rel="alternate"/><published>2024-12-19T11:04:00+00:00</published><updated>2024-12-19T11:04:00+00:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-12-19:/adaptive-filter-theory-notes-1.html</id><summary type="html">&lt;p&gt;Notes From Reading Adaptive Signal Theory (5th Ed) by Simon Haykin&lt;/p&gt;
&lt;h2&gt;Three Basic Kinds Of Estimation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Filtering  - Extraction of Current …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Notes From Reading Adaptive Signal Theory (5th Ed) by Simon Haykin&lt;/p&gt;
&lt;h2&gt;Three Basic Kinds Of Estimation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Filtering  - Extraction of Current and previous information. RealTime operation.&lt;/li&gt;
&lt;li&gt;Smoothing  - Data after the time of interest is used. Posteriori operation.&lt;/li&gt;
&lt;li&gt;Prediction - forcasting for some time in the future. RealTime operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Filter optimization is useful to think of minimising the mean-square-error. &lt;/p&gt;
&lt;p&gt;For stationary inputs the &lt;strong&gt;Wiener&lt;/strong&gt; filter is considered optimal in mean-square-error sense.&lt;/p&gt;
&lt;p&gt;Plots of the mean-square value of the error signal versus the adjustable parameters of the linear filter is known as the &lt;strong&gt;error-performance-surface&lt;/strong&gt;. The min point on this is the Wiener solution.&lt;/p&gt;
&lt;p&gt;Wiener Filter is no good with moving signals, or precense of noise. Kalman Filters are useful in this sitation.&lt;/p&gt;</content><category term="Engineering"/><category term="DSP"/></entry><entry><title>Node Red vs Home Assistant</title><link href="http://lizard-spock.co.uk/node-red-vs-home-assistant.html" rel="alternate"/><published>2024-10-24T15:58:00+01:00</published><updated>2024-10-24T15:58:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-24:/node-red-vs-home-assistant.html</id><summary type="html">&lt;p&gt;Node Red vs Home Assistant&lt;/p&gt;
&lt;p&gt;Which is better* ?&lt;/p&gt;
&lt;p&gt;*better: CPU/RAM efficient, easier to learn and or offer better support …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Node Red vs Home Assistant&lt;/p&gt;
&lt;p&gt;Which is better* ?&lt;/p&gt;
&lt;p&gt;*better: CPU/RAM efficient, easier to learn and or offer better support of devices&lt;/p&gt;
&lt;p&gt;Materials used for comparison &lt;a href="https://letsautomate.net/nodered/home-assistant-vs-node-red-which-is-the-better-choice/"&gt;video this review&lt;/a&gt;, end with the conclusion:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Home Assistant and Node-RED have their merits when it comes to automating your home. The choice ultimately depends on your preferences and specific requirements. While Home Assistant offers a user-friendly interface and integration within a single system, Node-RED provides versatility, the ability to connect to multiple Home Assistant instances and other APIs. Consider your needs, technical proficiency, and desired level of customization to determine which platform suits you best. Let us know your thoughts in the comments.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="Hardware &amp; Homelab"/><category term="HomeAssistant"/></entry><entry><title>Proxmox Plex Container</title><link href="http://lizard-spock.co.uk/proxmox-plex-container.html" rel="alternate"/><published>2024-10-24T13:10:00+01:00</published><updated>2024-10-24T13:10:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-24:/proxmox-plex-container.html</id><summary type="html">&lt;p&gt;With Proxmox installed time to setup Plex in a container, and migrate from Synology NAS.&lt;/p&gt;
&lt;p&gt;I am following these guides …&lt;/p&gt;</summary><content type="html">&lt;p&gt;With Proxmox installed time to setup Plex in a container, and migrate from Synology NAS.&lt;/p&gt;
&lt;p&gt;I am following these guides for &lt;a href="https://www.derekseaman.com/2023/04/proxmox-plex-lxc-with-alder-lake-transcoding.html"&gt;plex setup&lt;/a&gt;, and there following guide for &lt;a href="https://www.derekseaman.com/2023/04/migration-guide-plex-on-synology-to-proxmox-lxc.html"&gt;synology to proxmox migration&lt;/a&gt;.&lt;/p&gt;</content><category term="Hardware &amp; Homelab"/><category term="Proxmox"/><category term="Plex"/></entry><entry><title>Starting with ProxMox</title><link href="http://lizard-spock.co.uk/starting-with-proxmox.html" rel="alternate"/><published>2024-10-24T12:56:00+01:00</published><updated>2024-10-24T12:56:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-24:/starting-with-proxmox.html</id><summary type="html">&lt;p&gt;Having decided that &lt;a href="https://www.proxmox.com/en/"&gt;Proxmox&lt;/a&gt; is the way forward to manage docker and ivrtualisation outside of the synology NAS. i need …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Having decided that &lt;a href="https://www.proxmox.com/en/"&gt;Proxmox&lt;/a&gt; is the way forward to manage docker and ivrtualisation outside of the synology NAS. i need to figure out how to install , use and manage proxmox.&lt;/p&gt;
&lt;p&gt;For the first instance I will be following the &lt;a href="https://noted.lol/proxmox-for-beginners/"&gt;guide to Proxmox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is another good tutorial &lt;a href="https://www.derekseaman.com/2023/10/home-assistant-proxmox-ve-8-0-quick-start-guide-2.html"&gt;here&lt;/a&gt; which covers installing proxmox and then creating a virtual machine for home assistant.&lt;/p&gt;
&lt;p&gt;Note from &lt;a href="https://pve.proxmox.com/pve-docs/chapter-pve-faq.html#:~:text=You%20manage%20a%20Docker%20instance,inside%20a%20Proxmox%20QEMU%20VM."&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;manage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;interface&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;recommended&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;directly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Proxmox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;containers&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;example&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;images&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;best&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;them&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;inside&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Proxmox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;QEMU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VM&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Hardware &amp; Homelab"/><category term="Proxmox"/><category term="Docker"/></entry><entry><title>Beelink N200 Proxmox and Plex</title><link href="http://lizard-spock.co.uk/beelink-n200-proxmox-and-plex.html" rel="alternate"/><published>2024-10-24T11:43:00+01:00</published><updated>2024-10-24T11:43:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-24:/beelink-n200-proxmox-and-plex.html</id><summary type="html">&lt;p&gt;To improve the Plex transcoding options and off load the computation from the NAS, the plan is purchase a BeeLink …&lt;/p&gt;</summary><content type="html">&lt;p&gt;To improve the Plex transcoding options and off load the computation from the NAS, the plan is purchase a BeeLink EQ13 Alder Lake N200 16GB Ram and 500GB Drive, currently for sale at £215.&lt;/p&gt;
&lt;p&gt;The Beelink will then have &lt;a href="https://www.proxmox.com/en/"&gt;proxmox&lt;/a&gt; installed as the main operating system. 
A docker container for Plex and the other dockers that are currently hosted on the NAS box.&lt;/p&gt;
&lt;p&gt;Theguide for plex install I will be following &lt;a href="https://forum.level1techs.com/t/plex-on-proxmox-with-igpu-hardware-transcode-best-practices/210491"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A virtual machine for Hssistant OS can also be installed to run &lt;a href="https://www.home-assistant.io/"&gt;HomeAssistant&lt;/a&gt;.&lt;/p&gt;</content><category term="Hardware &amp; Homelab"/><category term="Networking"/><category term="Plex"/><category term="Proxmox"/><category term="Docker"/></entry><entry><title>Cat8 T-568</title><link href="http://lizard-spock.co.uk/cat8-t-568.html" rel="alternate"/><published>2024-10-21T13:31:00+01:00</published><updated>2024-10-21T13:31:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-21:/cat8-t-568.html</id><summary type="html">&lt;p&gt;Cat 8 Wiring, comes in 2 variants T-568A and T-568B.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Wiring Diagram" src="http://lizard-spock.co.uk/images/Homelab/T-568AB.png"&gt;&lt;/p&gt;
&lt;p&gt;Cat8 replaces the need for crimping tools with field termination …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cat 8 Wiring, comes in 2 variants T-568A and T-568B.  &lt;/p&gt;
&lt;p&gt;&lt;img alt="Wiring Diagram" src="http://lizard-spock.co.uk/images/Homelab/T-568AB.png"&gt;&lt;/p&gt;
&lt;p&gt;Cat8 replaces the need for crimping tools with field termination kits. Both ends need to wired the same otherwise there is no perfomance difference.&lt;/p&gt;</content><category term="Hardware &amp; Homelab"/><category term="Ethernet"/><category term="Networking"/></entry><entry><title>Synology Connecting Across VLAN with active VPN</title><link href="http://lizard-spock.co.uk/synology-connecting-across-vlan-with-active-vpn.html" rel="alternate"/><published>2024-10-16T11:30:00+01:00</published><updated>2024-10-16T11:30:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-16:/synology-connecting-across-vlan-with-active-vpn.html</id><summary type="html">&lt;p&gt;When a Synology NAS creates a VPN connection it overides the default gateway, except for devices listed as contained within …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When a Synology NAS creates a VPN connection it overides the default gateway, except for devices listed as contained within its subnet. According to the &lt;a href="https://community.synology.com/enu/forum/1/post/138785"&gt;a post&lt;/a&gt; on the synology forum even if the subnet is changed to 255.255.0.0 it will not see devices on another VLAN. It feels like a subnet mask of 255.255.255.0 is hardcoded.&lt;/p&gt;
&lt;p&gt;For Allowing my Synology NAS to create a VPN connection and see device on my VLANS I had to add a &lt;em&gt;static route&lt;/em&gt; defining a gatweway for the VLAN IP/subnet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;Network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Destination&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;.&lt;span class="mi"&gt;168&lt;/span&gt;.&lt;span class="mi"&gt;107&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VLAN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;
&lt;span class="nv"&gt;Netmask&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;.&lt;span class="mi"&gt;255&lt;/span&gt;.&lt;span class="mi"&gt;255&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Standard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;netmask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VLAN&lt;/span&gt;
&lt;span class="nv"&gt;Gateway&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192&lt;/span&gt;.&lt;span class="mi"&gt;168&lt;/span&gt;.&lt;span class="mi"&gt;100&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;#&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Gateway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Synology&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NASs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VLAN&lt;/span&gt;
&lt;span class="nv"&gt;Interface&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;physical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Synology&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;NAS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;uses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;VLAN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="screen grab of route" src="/images/Tech/Synology_static_route.png"&gt;&lt;/p&gt;</content><category term="Hardware &amp; Homelab"/><category term="Synology"/><category term="Networking"/><category term="VLAN"/><category term="VPN"/></entry><entry><title>TeX hyphenation</title><link href="http://lizard-spock.co.uk/tex-hyphenation.html" rel="alternate"/><published>2024-10-14T14:45:00+01:00</published><updated>2024-10-14T14:45:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-14:/tex-hyphenation.html</id><summary type="html">&lt;p&gt;When TeX/LaTeX is fully justifying text there are times when it could do better but it does not know …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When TeX/LaTeX is fully justifying text there are times when it could do better but it does not know how to hyphenate certain words. Here we can give it guidnace on how to split words.&lt;/p&gt;
&lt;p&gt;For example (insert into the pre-amble):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\hyphenation{acro-nym}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Multiple hyphenation points can also be defined, and for related words:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\hyphenation{ac-ro-nym ac-ro-nym-ic a-cro-nym-i-cal-ly}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The hyphenation point can be inserted manually with a &lt;code&gt;\-&lt;/code&gt;, but it is best to setup in the pre-amble and keep all the layout rules together.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;acro\-nym
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Preventing Hyphenation&lt;/h2&gt;
&lt;p&gt;Preventing hyphenation can be done by decalring in the pre-amble without a hyphen or inline with an &lt;code&gt;\mbox&lt;/code&gt; command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\hyphenation{automobile}

this will stop hyphenation of inline text \mbox{automobile}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Global Hyphenation Control&lt;/h2&gt;
&lt;p&gt;Use the hphenat package to prevent all hyphenation&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;usepackage&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hyphenat&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are some better ideas on this &lt;a href="https://tex.stackexchange.com/q/5036/165015"&gt;TeX SO Question&lt;/a&gt;.&lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="TeX"/></entry><entry><title>TeX Paragraph Box</title><link href="http://lizard-spock.co.uk/tex-paragraph-box.html" rel="alternate"/><published>2024-10-11T14:18:00+01:00</published><updated>2024-10-11T14:18:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-11:/tex-paragraph-box.html</id><summary type="html">&lt;p&gt;&lt;code&gt;parbox&lt;/code&gt; is a LaTeX command used to place a box around text. &lt;/p&gt;
&lt;p&gt;Remeber that optional arguments are in [] (Square brackets …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;parbox&lt;/code&gt; is a LaTeX command used to place a box around text. &lt;/p&gt;
&lt;p&gt;Remeber that optional arguments are in [] (Square brackets)&lt;/p&gt;
&lt;p&gt;Typical usage is :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;parbox&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;alignment&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="err"&gt;}{&lt;/span&gt;&lt;span class="nc"&gt;text&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The options are:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;alignment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Top&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;alligned&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vertically&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;centered&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bottom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;alligned&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Stretch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vertically&lt;/span&gt;

&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fractional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;followed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ISO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;units&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mm&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;supported&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;\documentclass&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;article&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;\parbox&lt;/span&gt;&lt;span class="na"&gt;[b]&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;2.3cm&lt;span class="nb"&gt;}{&lt;/span&gt;b for bottom aligned&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If a large amount of text is to be placed in the box &lt;code&gt;minipage&lt;/code&gt; is preffered over &lt;code&gt;parbox&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;\documentclass&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;article&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;minipage&lt;span class="nb"&gt;}{&lt;/span&gt;2.3cm&lt;span class="nb"&gt;}&lt;/span&gt;
    minipage not parbox
  &lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;minipage&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;minipage&lt;/code&gt; supports all the arguments of &lt;code&gt;\parbox&lt;/code&gt;, as well as &lt;code&gt;\footnote&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;\documentclass&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;article&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;minipage&lt;span class="nb"&gt;}{&lt;/span&gt;5cm&lt;span class="nb"&gt;}&lt;/span&gt;
    minipage not parbox &lt;span class="k"&gt;\footnote&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;this is a foot note&lt;span class="nb"&gt;}&lt;/span&gt;.
    Second Line with another foot note &lt;span class="k"&gt;\footnote&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;Another foot note&lt;span class="nb"&gt;}&lt;/span&gt;.
  &lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;minipage&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="TeX minipage with footnotes example output" src="/images/Tech/Tex/tex_minipage_footnote.png"&gt;&lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="TeX"/></entry><entry><title>LaTeX macros</title><link href="http://lizard-spock.co.uk/latex-macros.html" rel="alternate"/><published>2024-10-10T14:17:00+01:00</published><updated>2024-10-10T14:17:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-10-10:/latex-macros.html</id><summary type="html">&lt;p&gt;LaTeX macros can be used as simple text replacments for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;\documentclass&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;article&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\newcommand&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="k"&gt;\LUG&lt;/span&gt;&lt;span class="nb"&gt;}{&lt;/span&gt;LEGO User Group&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;LaTeX macros can be used as simple text replacments for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;\documentclass&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;article&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\newcommand&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="k"&gt;\LUG&lt;/span&gt;&lt;span class="nb"&gt;}{&lt;/span&gt;LEGO User Group&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;\section&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;Welcome to the &lt;span class="k"&gt;\LUG&lt;/span&gt;&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;More Advanced usage allows LaTeX macros to be used like functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;\documentclass&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;article&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\newcommand&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="k"&gt;\LUG&lt;/span&gt;&lt;span class="nb"&gt;}&lt;/span&gt;[1]&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="k"&gt;\textbf&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;#1&lt;span class="nb"&gt;}&lt;/span&gt; LEGO User Group&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;\section&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;Welcome to the &lt;span class="k"&gt;\LUG&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;tartan&lt;span class="nb"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;document&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Unix &amp; Tools"/><category term="TeX"/></entry><entry><title>Tools for Technical Writing</title><link href="http://lizard-spock.co.uk/tools-for-technical-writing.html" rel="alternate"/><published>2024-09-10T10:17:00+01:00</published><updated>2024-09-10T10:17:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-09-10:/tools-for-technical-writing.html</id><summary type="html">&lt;p&gt;WYSIWYG editor for LaTeX &lt;a href="https://www.lyx.org/"&gt;lyx&lt;/a&gt;, and a manager for Bibliography &lt;a href="https://www.jabref.org/"&gt;JabRef&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lyx Tutorial: &lt;a href="https://wiki.lyx.org/Mac/Mac"&gt;Lyx mac Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;JabRef Tutorial: &lt;a href="https://docs.jabref.org/getting-started"&gt;Getting Started …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;WYSIWYG editor for LaTeX &lt;a href="https://www.lyx.org/"&gt;lyx&lt;/a&gt;, and a manager for Bibliography &lt;a href="https://www.jabref.org/"&gt;JabRef&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lyx Tutorial: &lt;a href="https://wiki.lyx.org/Mac/Mac"&gt;Lyx mac Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;JabRef Tutorial: &lt;a href="https://docs.jabref.org/getting-started"&gt;Getting Started&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tools avialable via home brew:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install lyx
brew install jabref
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Unix &amp; Tools"/><category term="TeX"/></entry><entry><title>Bike Chain Cleaning</title><link href="http://lizard-spock.co.uk/bike-chain-cleaning.html" rel="alternate"/><published>2024-06-14T12:27:00+01:00</published><updated>2024-06-14T12:27:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-06-14:/bike-chain-cleaning.html</id><summary type="html">&lt;p&gt;Chain Cleaning method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; Clean the chain with WD40 (water dispersant).
 Wipe dry with kitchen roll. 
 Spray with Comma White Grease …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Chain Cleaning method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; Clean the chain with WD40 (water dispersant).
 Wipe dry with kitchen roll. 
 Spray with Comma White Grease and wipe clean again.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The chain should be lubricated on the moving parts of the chain, not the surface in contact with cogs.&lt;/p&gt;</content><category term="Outdoor"/><category term="Outdoors"/><category term="Bike"/></entry><entry><title>Pyhton_virtual_env</title><link href="http://lizard-spock.co.uk/pyhton_virtual_env.html" rel="alternate"/><published>2024-05-30T16:03:00+01:00</published><updated>2024-05-30T16:03:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-05-30:/pyhton_virtual_env.html</id><summary type="html">&lt;p&gt;Creating/Setup a pyhton virtual enviroment called 'venv'&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m venv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Load the virtual env called venv&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;source venv …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Creating/Setup a pyhton virtual enviroment called 'venv'&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 -m venv venv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Load the virtual env called venv&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://realpython.com/python-virtual-environments-a-primer/"&gt;RealPython Guide&lt;/a&gt;, &lt;a href="https://docs.python.org/3/library/venv.html"&gt;Docs&lt;/a&gt;.&lt;/p&gt;</content><category term="Programming"/><category term="Python"/><category term="Command Line"/></entry><entry><title>Convolution in Python</title><link href="http://lizard-spock.co.uk/convolution-in-python.html" rel="alternate"/><published>2024-05-03T14:25:00+01:00</published><updated>2024-05-03T14:25:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-05-03:/convolution-in-python.html</id><summary type="html">&lt;p&gt;Convolution in Python, for merging filter responses, creating pascals triangle ...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;b …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Convolution in Python, for merging filter responses, creating pascals triangle ...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;convolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/><category term="Signal"/><category term="Matlab"/></entry><entry><title>Perforce Update files in a Shelve</title><link href="http://lizard-spock.co.uk/perforce-update-files-in-a-shelve.html" rel="alternate"/><published>2024-04-30T11:04:00+01:00</published><updated>2024-04-30T11:04:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-04-30:/perforce-update-files-in-a-shelve.html</id><summary type="html">&lt;p&gt;Perforce can temporarily checkins of changes that you might want to share with others before fully commiting them to the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Perforce can temporarily checkins of changes that you might want to share with others before fully commiting them to the code base. These P4 Shelves have some properties that seem undesirable for a revision control system. They are not imuttable. &lt;/p&gt;
&lt;p&gt;From there nature they are temporary but they can also be altered and maintain the same changelist number. Solution based on &lt;a href="https://stackoverflow.com/a/23109698/97073"&gt;this SO answer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lets create file a.txt with some default content, and create a shelve.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;File a - line 1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;
&lt;span class="nv"&gt;p4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;
&lt;span class="nv"&gt;p4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shelve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;...

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3265397&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;open&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Shelving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3265397&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Remeber the Changelist number for the shelve, we will refer to it as \&amp;lt;change#&amp;gt;. 
Now lets modify a and create a b.txt. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;p4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;
&lt;span class="nv"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;File a - line 2 after intial shelve&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;
&lt;span class="nv"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;File b - line 1 Missed in first shelve&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;
&lt;span class="nv"&gt;p4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;

&lt;span class="nv"&gt;p4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;reopen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;change&lt;/span&gt;#&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;&lt;span class="sc"&gt;#1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="c1"&gt;; change 3265397&lt;/span&gt;

&lt;span class="nv"&gt;p4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shelve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;change&lt;/span&gt;#&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Shelving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3265397&lt;/span&gt;.
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;.&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;&lt;span class="sc"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;.&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;&lt;span class="sc"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3265397&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;shelved&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Revision Control"/><category term="Command Line"/></entry><entry><title>Python merge PDFs</title><link href="http://lizard-spock.co.uk/python-merge-pdfs.html" rel="alternate"/><published>2024-04-22T18:49:00+01:00</published><updated>2024-04-22T18:49:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-04-22:/python-merge-pdfs.html</id><summary type="html">&lt;p&gt;Using python script to merge pdfs together.&lt;/p&gt;
&lt;p&gt;Install package&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install pypdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pypdf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PdfWriter&lt;/span&gt;

&lt;span class="n"&gt;pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file1.pdf …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Using python script to merge pdfs together.&lt;/p&gt;
&lt;p&gt;Install package&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install pypdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pypdf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PdfWriter&lt;/span&gt;

&lt;span class="n"&gt;pdfs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;file1.pdf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;file2.pdf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;file3.pdf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;file4.pdf&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;#merger = PdfMerger()&lt;/span&gt;
&lt;span class="n"&gt;merger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;merger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;merger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result.pdf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/3444645/merge-pdf-files"&gt;source&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Pythonista pip install pelican</title><link href="http://lizard-spock.co.uk/pythonista-pip-install-pelican.html" rel="alternate"/><published>2024-04-20T12:04:00+01:00</published><updated>2024-04-20T12:04:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-04-20:/pythonista-pip-install-pelican.html</id><summary type="html">&lt;p&gt;On the journey towards publish static site generator blog posts from iPadOS, my next step after installing stash on pythonista …&lt;/p&gt;</summary><content type="html">&lt;p&gt;On the journey towards publish static site generator blog posts from iPadOS, my next step after installing stash on pythonista is uisng pip to install the pelican library.&lt;/p&gt;</content><category term="Programming"/><category term="Python"/><category term="Pelican"/></entry><entry><title>Pythonista using stash for pip</title><link href="http://lizard-spock.co.uk/pythonista-using-stash-for-pip.html" rel="alternate"/><published>2024-04-19T17:04:00+01:00</published><updated>2024-04-19T17:04:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-04-19:/pythonista-using-stash-for-pip.html</id><summary type="html">&lt;p&gt;Installing stash on pythonista&lt;/p&gt;</summary><content type="html">&lt;p&gt;To run pip in pythonista we need to install &lt;a href="https://github.com/ywangd/stash"&gt;stash&lt;/a&gt; first.&lt;/p&gt;
&lt;p&gt;See stash page sfor latest details, at time of install I had to run this from the pythinista terminal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://bit.ly/get-stash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="installing stash" src="img/install_stash.png" title="Installing Stash"&gt;&lt;/p&gt;
&lt;p&gt;Then to start stash from a new (restart pyhtonista app) find and run the launch_stash.py script&lt;/p&gt;
&lt;p&gt;&lt;img alt="start stash" src="img/launch_stash.png" title="Starting Stash"&gt;&lt;/p&gt;
&lt;p&gt;there will then be a second terminal with the stash command line open up&lt;/p&gt;
&lt;p&gt;&lt;img alt="running stash" src="img/running_stash.png" title="Running Stash"&gt;&lt;/p&gt;</content><category term="Programming"/><category term="Python"/></entry><entry><title>Python read a CSV file</title><link href="http://lizard-spock.co.uk/python-read-a-csv-file.html" rel="alternate"/><published>2024-04-19T12:34:00+01:00</published><updated>2024-04-19T12:34:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-04-19:/python-read-a-csv-file.html</id><summary type="html">&lt;p&gt;Python example for reading data from a csv file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, newline=&amp;#39;&amp;#39;) as csvfile:&lt;/span&gt;
    &lt;span class="n"&gt;spamreader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Python example for reading data from a csv file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, newline=&amp;#39;&amp;#39;) as csvfile:&lt;/span&gt;
    &lt;span class="n"&gt;spamreader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quotechar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;|&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;spamreader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Import csv into a numpy array.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# data_array = np.array(data)  # Conversion for strings&lt;/span&gt;
&lt;span class="n"&gt;data_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Conversion for doubles&lt;/span&gt;

&lt;span class="c1"&gt;# Print first 10 data elements.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;data_array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Outputs: &lt;/p&gt;
&lt;p&gt;&lt;a href="https://saturncloud.io/blog/loading-csv-data-into-a-numpy-array-a-comprehensive-guide/"&gt;source&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"/><category term="Pelican"/><category term="Python"/></entry><entry><title>Python Sinewave</title><link href="http://lizard-spock.co.uk/python-sinewave.html" rel="alternate"/><published>2024-04-19T12:34:00+01:00</published><updated>2024-04-19T12:34:00+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2024-04-19:/python-sinewave.html</id><summary type="html">&lt;p&gt;Create and plot a sinewave in python&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python example for creatign and plotting a sinewave:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# coding: utf-8&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;fs&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;48000&lt;/span&gt;
&lt;span class="n"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="c1"&gt;#Hz&lt;/span&gt;
&lt;span class="n"&gt;amp&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="c1"&gt;# Linear Amplitude&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt; &lt;span class="c1"&gt;# Seconds sample length&lt;/span&gt;

&lt;span class="n"&gt;amp_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sine_&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;Hz_&lt;/span&gt;&lt;span class="si"&gt;%4.2f&lt;/span&gt;&lt;span class="s1"&gt;_amp&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amp_db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;label_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sinewave &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;Hz &lt;/span&gt;&lt;span class="si"&gt;%5.2f&lt;/span&gt;&lt;span class="s2"&gt;dB&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amp_db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#samples = np.linspace(0, t, int(fs*t), endpoint=False)&lt;/span&gt;
&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;

&lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kp"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplots&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;label_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sinewave&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;savefig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Generates and saves this plot&lt;/p&gt;
&lt;p&gt;&lt;img alt="1kHz Sinewave" src="img/sine_1000Hz_0.00_amp.png" title="Generate Sinewave Plot"&gt;&lt;/p&gt;
&lt;p&gt;To export the 'signal' data as a CSV file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;csv&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;f_writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;quotechar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;|&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quoting&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QUOTE_MINIMAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;f_writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;*.csv output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;span class="mf"&gt;0.13052619222005157&lt;/span&gt;
&lt;span class="mf"&gt;0.25881904510252074&lt;/span&gt;
&lt;span class="mf"&gt;0.3826834323650898&lt;/span&gt;
&lt;span class="mf"&gt;0.49999999999999994&lt;/span&gt;
&lt;span class="mf"&gt;0.6087614290087207&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Programming"/><category term="Python"/><category term="Signal"/><category term="DSP"/></entry><entry><title>"Home Directory File Structure"</title><link href="http://lizard-spock.co.uk/home-directory-file-structure.html" rel="alternate"/><published>2015-10-22T10:32:44+01:00</published><updated>2015-10-22T10:32:44+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2015-10-22:/home-directory-file-structure.html</id><summary type="html">&lt;p&gt;Apples default directory structure is :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Desktop&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# Hidden by default&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Movies&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Music&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pictures&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My current …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Apples default directory structure is :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Desktop&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;# Hidden by default&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Movies&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Music&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pictures&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My current setup After adding Dropbox, dotfiles stored on github, Code, and user level applications folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Applications&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Desktop&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Dropbox&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Movies&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Music&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pictures&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotfiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At present though my Dropbox is a mess of some documents, some code and lots of ebooks. Free Dropbox does not have enough space for me to the entire home directory. Considering a BtSync pro account which allows selective sync. BtSync will allow me to set the root as my home directory and select which folders from that to sync. &lt;/p&gt;
&lt;p&gt;I have started using USB flash drives again, and have found &lt;a href="http://www.cis.upenn.edu/~bcpierce/unison"&gt;unison&lt;/a&gt; to be a useful tool for syncing local drives to each other.&lt;/p&gt;
&lt;p&gt;Me example unison sync (USB drive is called 'Dropbox'):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;unison&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Volumes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Dropbox&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Unison&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;fat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;links&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Code&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;\
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Documents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Dropbox&lt;/span&gt;

#&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;fat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Work&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;With&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;filesystem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ThumbDrive&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
#&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Accept&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;merges&lt;/span&gt;
#&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;links&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;follow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;symlinks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now that the 'Dropbox' root is my home directory I can reorganise. Books should become a top level folder, and share a place along side Music and Movies.&lt;/p&gt;
&lt;p&gt;Dropbox is kept but mainly used for sharing temporary files with others.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Applications&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Books&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Desktop&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Dropbox&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Movies&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Music&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pictures&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotfiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dropbox: sending large files
BT Sync: Synchronising files (eBook collection)
Unison: Synchronising local drives (Thumbdrive to local documents/ebooks)
ReadyNAS: Remote access to larger volume drives (Movies and TV Collections)
 Above could be a BT Sync non populated folder  &lt;/p&gt;</content><category term="Unix &amp; Tools"/><category term="Filesystem"/></entry><entry><title>"JapaneseGarden"</title><link href="http://lizard-spock.co.uk/japanesegarden.html" rel="alternate"/><published>2015-10-10T12:50:55+01:00</published><updated>2015-10-10T12:50:55+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2015-10-10:/japanesegarden.html</id><content type="html">&lt;p&gt;First step creating the retaining wall, to become a raised bed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Japanese garden, June 2014" src="/images/Gardening/JapaneseGarden/morganp-20140616--_MG_9132.jpg"&gt;
&lt;img alt="Japanese garden, October 2014" src="/images/Gardening/JapaneseGarden/morganp-20141025-Garden-IMG_9608.jpg"&gt;
&lt;img alt="Japanese garden, March 2015" src="/images/Gardening/JapaneseGarden/morganp-20150306--IMG_0145.jpg"&gt;
&lt;img alt="Japanese garden, March 2015" src="/images/Gardening/JapaneseGarden/morganp-20150306--IMG_0149.jpg"&gt;
&lt;img alt="Japanese garden, March 2015" src="/images/Gardening/JapaneseGarden/morganp-20150309--IMG_0158.jpg"&gt;
&lt;img alt="Japanese garden, April 2015" src="/images/Gardening/JapaneseGarden/morganp-20150420--IMG_0417.jpg"&gt;
&lt;img alt="Japanese garden, April 2015" src="/images/Gardening/JapaneseGarden/morganp-20150420--IMG_0418.jpg"&gt;
&lt;img alt="Japanese garden, April 2015" src="/images/Gardening/JapaneseGarden/morganp-20150420--IMG_0419.jpg"&gt;&lt;/p&gt;</content><category term="Home &amp; Garden"/><category term="DIY"/><category term="Garden"/></entry><entry><title>"Potting Bench"</title><link href="http://lizard-spock.co.uk/potting-bench.html" rel="alternate"/><published>2015-10-10T12:23:15+01:00</published><updated>2015-10-10T12:23:15+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2015-10-10:/potting-bench.html</id><summary type="html">&lt;p&gt;Homemade potting bench, using 15 2.4M Fence boards (£45).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Homemade potting bench" src="/images/Gardening/PottingBench/morganp-20150414--IMG_0330.jpg"&gt;
&lt;img alt="Homemade potting bench" src="/images/Gardening/PottingBench/morganp-20150414--IMG_0332.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Note for ease of building the height was 1.2M …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Homemade potting bench, using 15 2.4M Fence boards (£45).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Homemade potting bench" src="/images/Gardening/PottingBench/morganp-20150414--IMG_0330.jpg"&gt;
&lt;img alt="Homemade potting bench" src="/images/Gardening/PottingBench/morganp-20150414--IMG_0332.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Note for ease of building the height was 1.2M, half a 2.4 meter board, this is too high.&lt;/p&gt;</content><category term="Home &amp; Garden"/><category term="DIY"/><category term="Garden"/></entry><entry><title>"Self Watering Propagator"</title><link href="http://lizard-spock.co.uk/self-watering-propagator.html" rel="alternate"/><published>2015-10-10T12:04:12+01:00</published><updated>2015-10-10T12:04:12+01:00</updated><author><name>morganp</name></author><id>tag:lizard-spock.co.uk,2015-10-10:/self-watering-propagator.html</id><content type="html">&lt;p&gt;Propagator hack using some seed trays and capillary matting.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Propagator" src="/images/Gardening/Propogator/morganp-20150328--IMG_0176.jpg"&gt;
&lt;img alt="Propagator" src="/images/Gardening/Propogator/morganp-20150328--IMG_0178.jpg"&gt;
&lt;img alt="Propagator" src="/images/Gardening/Propogator/morganp-20150328--IMG_0179.jpg"&gt;
&lt;img alt="Propagator" src="/images/Gardening/Propogator/morganp-20150328--IMG_0180.jpg"&gt;&lt;/p&gt;</content><category term="Home &amp; Garden"/><category term="DIY"/><category term="Garden"/></entry></feed>