Skip to the content
PCBKitNew board
4 layers88.6 × 85.1 mmOut, the printed circuit board in 3D

opensource combination of agent driven hardware development tools

Size
88.6 × 85.1 mm
Copper layers
4
Thickness
1.6 mm
Parts
60
Nets
32
Pads
191
Tracks, vias
592, 167
Pours
27
Routed
Yes
KiCad
KiCad 0.0.1 (file 20241229)
Created
8 Oct 2026
Updated
28 Oct 2026

Overview

Out is an open-source printed circuit board by maximmalin, from the maximmalin/simul repository on GitHub, published under the MIT licence. It is a 4-layer board of 88.6 × 85.1 mm with 60 parts on 32 nets, routed with 592 tracks and 167 vias.

It is built around the STM32F103C8T6, OPA2134UA and AMS1117-3.3. Its parts include 1 IC and 1 crystal. It belongs with the STM32 boards of this library.

Main components

3

README

README.md

FULLY AUTOMATED HARDWARE DEVELOPMENT PIPELINE.

lAST TESTED PROJECT IS DEVELOPMENT OF 2 WATT LED DRIVER

Start here


1. What it is

ProductZero-IF (direct-conversion) SDR receiver
MCUSTM32F103C8T6 Blue Pill (Cortex-M3)
Frequency range0.5 – 30 MHz
ArchitectureDirect conversion, quadrature I/Q, USB I/Q to host
Circuit source of truthSKiDL (sim/scripts/sdr_skidl_circuit.py) — 60 parts, 38 nets
Analog simulatorngspice 44.2
Digital/co-simFMI 2.0 (pythonfmu / fmpy), QEMU (qemu-system-arm), PicSimLab
PCB / 3DKiCad 9, tscircuit, FreeCAD, Elmer FEM, openEMS FDTD, NEC-2
LicenseMIT

The MCU is never "inside" ngspice. The analog/digital boundary is explicit at the STM32 pins, and that contract (PA0/PA1 = ADC I/Q, PA6 = LO, PA7 = GAIN) is asserted both by the firmware and by verify_pin_contract.py.


2. The spine — signal/flow of truth

text
sim/scripts/sdr_skidl_circuit.py   SKiDL = the single source of truth for connectivity
        │
        ▼
sim/scripts/export_ngspice.py      → sim/netlists/sdr_skidl_ngspice.cir (ngspice deck)
        │
        ▼
sim/scripts/build_fmu.py           → sim/fmu/SdrNgspiceFMU.fmu  (FMI 2.0 slave)
        │
        ▼
sim/scripts/cosim_fmu_stm32.py     hard-lockstep: firmware model  <->  ngspice
sim/scripts/test_fmu_cosim.py      regression through fmpy's FMI master
sim/scripts/cosim_real_stm32.py    the same, but the MCU is the real firmware.elf on QEMU

The system in one picture:

text
                        ┌─────────────────────────────────────────────┐
                        │            HOST / OFF-BOARD                  │
  sim/scripts/          │  SKiDL → ngspice deck → FMI FMU              │
  sdr_skidl_circuit.py  │        └── sim/netlists/sdr_skidl_ngspice.cir │
  export_ngspice.py     │                    │                         │
  build_fmu.py          │                    ▼                         │
                        │        sim/fmu/SdrNgspiceFMU.fmu             │
                        │                    │  FMI 2.0 setReal/doStep │
                        └────────────────────┼─────────────────────────┘
                                             │
             ┌───────────────────────────────┼───────────────────────────────┐
             │                               │                               │
    cosim_fmu_stm32.py           cosim_real_stm32.py            sdr_picsimlab_cosim.py
    (firmware MODEL in Python)   (REAL firmware.elf on QEMU)    (PicSimLab rcontrol)
             │                               │                               │
             │                     qemu-system-arm + GDB RSP          PicSimLab NOGUI
             │                     firmware/src/cosim.h mailbox       src/picsimlab_fmu/*
             ▼                               ▼                               ▼
      ┌──────────────────────────────── analog boundary (pins) ──────────────────┐
      │ PA0 = ADC_I   PA1 = ADC_Q   PA6 = LO (TIM3_CH1)   PA7 = GAIN             │
      └──────────────────────────────────────────────────────────────────────────┘

Antennas have their own physics path:

text
sim/inputs/antenna_*.yaml → sim/scripts/antenna_fdtd.py → S11/Zin
                          → sim/scripts/export_antenna_ngspice.py → Touchstone (ngspice)

3. Functionality index

Every piece of functionality in the repository, grouped by subsystem.

3.1 Circuit source of truth — SKiDL

  • sim/scripts/sdr_skidl_circuit.py — the unified SKiDL netlist generator. The only place connectivity is defined: 60 parts, 38 nets. Writes the KiCad schematic and the netlist consumed downstream. Never hand-edit generated netlists; change this file and regenerate.

3.2 Analog netlist & ngspice

  • sim/scripts/export_ngspice.py — emits the behavioral zero-IF receiver chain to sim/netlists/sdr_skidl_ngspice.cir:
    • LNA stages as transconductance macromodels (not ideal VCVS),
    • quadrature LO derived from a real 90° all-pass (H = 1 − 2/(1+sRC)),
    • zero-IF mixer with mid-rail-biased limiting baseband buffers,
    • RF chokes as L+R (the ferrite bead uses its DC resistance),
    • the STM32 pin boundary (see §3.4).
  • Other decks: sim/netlists/sdr_receiver.cir, sim/netlists/sdr_simplified.cir, sim/netlists/bridge_check.cir.
  • XSPICE bridges: analog→digital (adc_bridge) with datasheet CMOS thresholds; digital→analog modelled explicitly as a host-alterable source behind the pin's output resistance and load capacitance (an ngspice dac_bridge input cannot be driven from outside ngspice).

3.3 Direct-conversion physics (verified)

  • LO is a real 7.2 MHz timer waveform; the beat lands at 48.1 kHz with a 50 kHz IF offset.
  • I/Q amplitude ratio 0.965, phase +91.6°.
  • The Q channel is driven by a real 90° all-pass on the LO, not wired to zero.
  • Five original analog defects (dead Q, DC LO, 0 V-centred ADC, DC-biased LNA gain, bare-resistor chokes) were found and fixed; the fix log lives in sim/COSIM_STATUS.md.
  • Two measurement traps (ngspice print 80-column truncation and non-uniform adaptive timesteps) are documented there too.

3.4 STM32 pin model — datasheet-derived boundary

ParameterValueSource
Output impedance65 ΩDS5319 Table 30, IIO = 20 mA
Pin capacitance CIO5 pFDS5319 Table 29
Input low VIL0.35·VDD = 1.155 VDS5319 Table 29
Input high VIH0.65·VDD = 2.145 VDS5319 Table 29
Input leakage1 µADS5319 Table 29

Numbers are taken from the datasheet, not chosen for convenience.

3.5 Verification suite

ScriptWhat it proves
sim/scripts/test_sdr_netlist.py22 regression checks on the SKiDL→ngspice export
sim/scripts/verify_pin_contract.py31 closed-loop analog + pin-contract checks
sim/scripts/test_ngspice_nets.pySmoke tests for the ngspice decks
sim/scripts/test_fmu_cosim.pyDrives the FMU through fmpy's FMI master, checks the pin boundary
tests/requirements/**Requirement-matrix tests (RF, antenna, AO, system)
tests/stress/test_co_simulation.pyBragg FMU + ngspice antenna 2-port stress test

3.6 FMI 2.0 co-simulation (the FMU)

  • sim/scripts/sdr_ngspice_fmu.py — a self-contained FMI 2.0 co-simulation slave bundling an embedded ngspice deck (self-contained on purpose: pythonfmu bundles only this one file).
  • sim/scripts/build_fmu.py — regenerates the netlist from SKiDL and splices it into the FMU source, so the FMU and the deck are the same circuit by construction.
  • sim/fmu/SdrNgspiceFMU.fmu — the built slave.
  • sim/scripts/cosim_fmu_stm32.py — hard-lockstep driver: STM32 firmware model ↔ ngspice FMU. Real FMI calls (setReal/doStep/getReal) every step; pulses cross the boundary at half an LO period; PA6 drives the mixer through the 65 Ω + 5 pF pin model.
  • Build: <venv>/bin/pythonfmu build -f sim/scripts/sdr_ngspice_fmu.py -d sim/fmu (or python3 sim/scripts/build_fmu.py).

3.7 Real-firmware co-simulation on QEMU

The actual firmware.elf on an emulated Cortex-M, in lockstep with real ngspice.

  • sim/scripts/cosim_real_stm32.py — orchestrator.
  • sim/scripts/gdb_remote.py — a minimal GDB remote-serial-protocol client (QEMU's human monitor can read memory with xp but has no write command; the GDB stub can read and write and single-step).
  • firmware/src/cosim.h — the RAM mailbox both sides agree on (magic, host_lo/host_gain in; mcu_stamp/adc_i/adc_q/adc_stamp out).
  • sim/scripts/build_cosim_firmware.py — builds the COSIM_MODE ELF.
  • sim/scripts/diag_*.py — forensic diagnostics that found the two setup() blockers (USB CDC enumeration, PLL wait) and the current stop point.

Status: the core reaches ADC1_2_IRQHandler; QEMU's STM32F103 peripheral model starves loop() (an ADC/DMA emulator-fidelity limit). Everything around it — mailbox, exchange, netlist, FMU — is verified. See sim/COSIM_STATUS.md.

3.8 PicSimLab bridge package (src/picsimlab_fmu/)

  • wrapper.py — PicSimLabFMU co-simulation wrapper (step() / run()).
  • cosim_bridge.py — TCP rcontrol ↔ ngspice lockstep loop (+ parse_pinsl, MAX_SAMPLES).
  • ngspice_bridge.py — prompt-synced ngspice CLI pipe (-p) bridge.
  • pin_mapper.py — board pin allocation (ARDUINO_UNO, STM32_BLUE_PILL, ESP32_DEVKITC).
  • live_scope.py — live co-sim oscilloscope (POSIX SHM + semaphores + matplotlib).
  • examples/ — small usage examples.

Drivers and probe scripts:

  • sim/scripts/start_picsimlab.py — launches PicSimLab NOGUI and polls until the rcontrol port accepts (the board comes from the .ini; there is no board command in rcontrol).
  • sim/scripts/sdr_picsimlab_cosim.py — the SDR-specific driver.
  • sim/scripts/rcontrol.py, rcontrol_analog.py, rcontrol_session.py — low-level rcontrol probing / analog pin driving / one-shot evidence session.
  • Config: firmware/picsimlab_sdr.ini.

Blocked upstream: with the QEMU backend running, rcontrol never binds (0.9.2); 0.9.3 has rcontrol but ships no libqemu-stm32.so. Full evidence in sim/COSIM_STATUS.md.

3.9 Firmware (firmware/)

  • firmware/src/firmware.ino — minimal firmware that reaches loop() and services the mailbox.
    • Pins: PA0 = ADC_I, PA1 = ADC_Q, PA6 = LO (TIM3_CH1), PA7 = GAIN.
    • Under COSIM_MODE, overrides SystemClock_Config() to skip the PLL wait (QEMU never sets PLLRDY).
  • firmware/src/cosim.h — mailbox layout shared with the co-sim host.
  • firmware/platformio.ini — two environments:
    • bluepill_f103c8 — the shipped image (USB CDC on);
    • cosim — USB compiled out (a bench image and a co-sim image can never be confused).
  • firmware/scripts/hex.py — post-build objcopy to emit Intel HEX for PicSimLab's loadhex.

3.10 Antenna physics

  • sim/inputs/antenna_*.yaml — one file per antenna; adding an antenna = add a YAML file, no code edits. Present: dipole 7 MHz, meander 200 MHz, patch 2.45 GHz.
  • sim/scripts/antenna_fdtd.py — YAML → S11/Zη → Touchstone. All three shipped antennas run the real openEMS FDTD solver (fdtd: true); setting fdtd: false falls back to an analytic thin-wire / transmission-line estimate (marked model: analytic in the JSON).
  • sim/scripts/mesh.py — openEMS mesh sizing with a hard memory budget.
  • sim/scripts/export_antenna_ngspice.py — results → ngspice S-parameter networks.
  • sim/scripts/antenna_sim.py, smoke_tutorial.py — openEMS workflow and tutorial smoke test.
  • sim/scripts/radiation.py, sim/nec/** — NEC-2 radiation patterns for the three channels (sim/nec/channel_{a,b,c}.nec, run_nec.py).
  • hardware/antenna/** — FreeCAD panel geometry (150 × 150 mm FR-4 0.8 mm):
    • Channel A: meander 200 MHz, Channel B: 2×2 patch @ 2.45 GHz, Channel C: 4×4 patch @ 5.8 GHz; geometry/build_antennas_fc.py rebuilds and exports STEP/STL.
  • Results (sim/outputs/*.json, all model: fdtd):
    • dipole 7 MHz — fres 6.90 MHz, S11 −15.7 dB, ηrad 98 %.
    • patch 2.45 GHz — fres 2442.5 MHz, S11 ≈ −3.6 dB, ηrad 30.8 %.
    • meander 200 MHz — no usable match anywhere in 50–800 MHz: S11 ≈ 0 dB and Zin ≈ 5 − 63j Ω at 50 MHz, with |Re Z| only ≈ 0.6–2 Ω at the in-band reactance zeros. The printed meander over its full ground plane is a low-Q, low-resistance resonator, so the old analytic −12.0 dB estimate was optimistic; the FDTD reports the element as effectively non-radiating (η = 0 %, eta_source: unavailable_no_radiated_power). A genuine full-wave result, not a solver failure.

3.11 Web GUI

  • sim/web_gui/app.py — Flask app that runs SdrNgspiceFMU.fmu via fmpy, plots I/Q live, exposes stim_level / lo_frequency, and exports an sdr_cosim.xcos diagram for Xcos.
  • sim/web_gui/templates/index.html — Plotly dark dashboard.
  • Run: python3 sim/web_gui/app.py → http://localhost:5000.

3.12 Diagnostics

sim/scripts/diag_*.py (infinite, literal, mailbox, pc, regs, watch, patch) are the forensic scripts used to find the QEMU blockers and validate the patch antenna. Kept as the reproducible evidence trail, not scratch.

3.13 Hardware / PCB / 3D toolchain

  • hardware/tscircuit/build_pcb.mjs — the PCB stage. Reads the SKiDL netlist (sim/scripts/sdr_skidl_circuit.py → hardware/kicad/sdr_receiver.kicad_sch), places parts, autoroutes with explicit Altium-grade design rules and exports out/: board.kicad_pcb, board.kicad_sch, gerbers.zip, board.step, board.{glb,gltf}, board.net, board_schematic.pdf, circuit.json. Run node hardware/tscircuit/build_pcb.mjs (or make pcb).
  • The earlier hardware/kicad/build_board.py (kinet2pcb + kicad-cli) path is superseded by the tscircuit emitter.
  • Reliability — sim/octave/reliability_model.m (Octave) reads datasheet parameters from sim/octave/reliability_parts.tsv (base FIT, Ea, T_ref/T_use, π_Q) and computes system MTBF as parts-count + Arrhenius. Requirement REQ-REL-01 (MTBF ≥ 10 000 ч). Run make reliability → out/reliability.json; tests in tests/requirements/reliability/.
  • Thermal — sim/scripts/thermal_pcb.py couples ngspice (DC operating point → dissipated power) with the PCB CAD (circuit.json → outline, stackup, placement). Two levels: fast T_j = T_a + P·θ_JA and a deep 2D FEM of heat conduction/spreading over the board. Run make thermal → out/thermal_{fast,fem}.json; tests in tests/requirements/thermal/.
  • Reception distance (link budget) — sim/scripts/link_budget.py computes the receiver noise floor (REQ-PERF-02), MDS (REQ-PERF-02) and the Friis range per antenna channel (A/B/C). Run make link-budget → out/link_budget.{json,svg}; «дальность приёма» сводится по разделам в requirements/reception_range.md (и столбец Reception_Distance_m в requirements_matrix.csv).
  • Direction finding — sim/scripts/direction_finding.py implements a phase interferometer (Δφ = 2π·d·sinθ/λ), draws the combined bearing + range diagram, and emits a NEC-2 .nec that opens in EA1FUO/AntennaSim (--antennasim-url posts to its /api/v1/simulate). Run make direction → out/direction_finding.{json,svg}.
  • EMC — tests/requirements/emc/ verifies REQ-EMC-01…04: skin depth and shielding effectiveness, immunity to 3 V/m (IEC 61000-4-3 level 2), CISPR 22 Class B emission, supply filters and enclosure zoning.
  • tools/spec.py, tools/blocks.py — the project spec (6 blocks, 24 parts) used by the generators.
  • hardware/pipeline.py — hardware build-pipeline orchestrator.
  • hardware/kicad/kicad_lib.py — KiCad symbol / schematic helper; hardware/kicad/generate_schematic.py — full single-sheet .kicad_sch generation from code; test_any_schematic.py — prove the generator is schematic-agnostic; verify_nets.py — cross-check the KiCad netlist against the generator tables.
  • hardware/tscircuit/build_pcb.mjs — netlist → TSX, placement, model offsets; build_derived.py (build local derived tables from the JLCPCB part DB), resolve_parts.py (part substitution by value/footprint), step2stl.py (STEP → STL with orientation normalization), check_models3d.py (glb vs circuit.json), make_viewer.py + site_assets.py (offline 3D viewer), fetch_vendor_models.mjs / fetch_3dmodels*.sh (vendor 3D models), check_net.py (verify the viewer makes no external requests).
  • Part selection / datasheets: scripts/download-datasheets.js, scripts/convert-kicad-to-circuit-json.js, scripts/post-build-datasheets.js, scripts/lib/*.
  • Manufacturing output dir: out/ (board.kicad_pcb, gerbers, BOM, PnP, GLB, STEP).

3.14 Engineering drawings & power budget

  • gost_pipeline.py — STEP → cleaned → GOST drawing.
  • block0_bus_power_calc.py — 5 V bus / buck / LC-filter calculation.
  • gen_spice_from_kicad.py — KiCad .kicad_sch → SPICE netlist.
  • picsimlab_rt_wrapper.py — PicSimLab real-time wrapper (from brig-receiver).

3.15 Bragg-cell / AOPP toolbox (transferred from brig-receiver)

The acousto-optic branch is kept as a toolbox, not part of the SDR signal path:

  • sim/scripts/bragg_sim.py — Gmsh → Elmer FEM pipeline (Δn = dn_scale·ε_zz).
  • sim/scripts/bragg_fmu.py, test_fmu.py, test_antenna_fmu.py, realtime_loop_fmu.py — FMI blocks and real-time FMU loop.
  • sim/scripts/msh_to_elmer.py — Gmsh .msh → Elmer mesh.
  • sim/inputs/bragg_inputs.yaml, config/params.yaml — parameters.
  • sim/octave/bragg_formulas.m + README_formulas.md — formulas and an analytic self-check (Hooke + photoelasticity).
  • sim/cad/ — FreeCAD parametric Bragg-cell scripts: make_bragg_cell_fc.py (build), refresh_cad.py (regenerate), import_elmer_mesh_fc.py, cad_dims.json. No .FCStd/.step is checked in — they are generated.
  • sim/outputs/elmer_results.json, sim/outputs/nec_results.json, openems_results.json.

3.16 Requirements, plan, tests & CI

  • Requirements matrix — requirements/requirements_matrix.csv (37 requirements + header) and per-subsystem requirements/*/REQ-*.md; SNR and noise floor are project-wide requirements REQ-PERF-02 (SNR, noise floor, MDS).
  • Plan — plan/plan.md, plan/blocks.md, plan/README.md, plan/checks.py.
  • Technical task — technical_task.md (RU), technical_task_en.md (EN), technical_task_ua.md (UA), plus per-block TZ in work/tz-{a..e}/.
  • Tests — pytest suites under tests/requirements/{antenna,rf,ao,system,reliability,thermal}/, tests/stress/test_co_simulation.py (co-simulation, needs fmpy); checklist at tests/requirements/checklist.csv.
  • CI — .forgejo/workflows/push-{general,fix,feature,docs}.yml (Elmer-FEM physical-constraint jobs on push).
  • references/formulas_and_sources.md, issues/ (issue template folder).

3.17 Tooling

  • tools/dump_manifest.py — writes manifest.json.
  • tools/init_project.py — create a fresh project from tracked files.

4. Quick start

bash
cd <repo>

# --- Everything (netlist → PCB → docs → tests → reliability/thermal) ---
make all

# --- Circuit → netlist ---
.venv/bin/python sim/scripts/sdr_skidl_circuit.py     # regenerate SKiDL netlist
.venv/bin/python sim/scripts/export_ngspice.py        # regenerate ngspice deck

# --- Verify (fastest health check) ---
.venv/bin/python sim/scripts/test_sdr_netlist.py      # 22/22 checks
.venv/bin/python sim/scripts/verify_pin_contract.py   # 31/31 checks
.venv/bin/python sim/scripts/test_ngspice_nets.py

# --- FMU co-simulation ---
python3 sim/scripts/build_fmu.py                      # rebuild FMU from SKiDL
.venv/bin/python sim/scripts/cosim_fmu_stm32.py       # lockstep firmware-model ↔ ngspice
.venv/bin/python sim/scripts/test_fmu_cosim.py        # fmpy regression

# --- Real firmware on QEMU ---
.venv/bin/python sim/scripts/build_cosim_firmware.py  # COSIM_MODE ELF
.venv/bin/python sim/scripts/cosim_real_stm32.py      # real firmware ↔ ngspice

# --- PicSimLab ---

.venv/bin/python is the project venv (skidl, pythonfmu, fmpy, numpy, matplotlib). Pure-stdlib tools run fine under system python3; the web GUI uses flask (system python3), and antenna FDTD needs a separate openEMS-enabled interpreter.


5. Repository layout

Roles: source = keep and edit; derived = regenerated by scripts; data = external/binary.

PathRoleWhat it is
sim/scripts/sourceAll physics, netlist, co-sim, antenna, diagnostic scripts
sim/netlists/derivedngspice decks (.cir) produced by export_ngspice.py
sim/inputs/sourceYAML parameter files (one per antenna)
sim/outputs/derivedPhysics result JSON/PNG
sim/fmu/dataBuilt FMUs (SdrNgspiceFMU.fmu, antenna/Bragg blocks)
sim/work/derivedSolver scratch (openEMS, Elmer, gmsh)
sim/web_gui/sourceFlask I/Q web GUI for the FMU
firmware/sourceSTM32 Arduino sketch, cosim mailbox, PlatformIO config
firmware/src/sourcefirmware.ino, cosim.h
src/picsimlab_fmu/sourcePicSimLab rcontrol ↔ ngspice bridge package
hardware/sourceKiCad generation, PCB/3D pipeline, antenna geometry, FEM
hardware/kicad/sourceSchematic generator + KiCad symbol/S-expression library
hardware/tscircuit/sourcePCB emitter, 3D model resolution, part selection, viewer
tools/sourceSpec (spec.py, blocks.py), manifest/init tooling
tests/sourceRequirement-matrix + stress tests
requirements/, plan/, work/, tz/, references/, config/, issues/sourceRequirements, plan, TZ, formulas, parameters
local_footprints/externalОпциональный клон KiCad/kicad-footprints для ручной правки плат в KiCad. Не входит в репозиторий и не нужен для сборки: плату генерирует tscircuit, футпринты берутся из resolved-таблицы деталей.
out/derivedGenerated board artifacts (gerbers/BOM/GLB/STEP)
node_modules/, .venv/derivedDependency installs
.forgejo/sourceCI workflow definitions
scripts/sourceNode datasheet/part-selection helpers

6. Environment

System tools: python3 (3.13), ngspice (44.2), kicad-cli (9.0.2), node/npm (22 / 10), pio (PlatformIO 6.2), qemu-system-arm (10.0), gdb-multiarch (16.3), gmsh (4.13), Elmer FEM (26.2), openEMS, NEC-2 (nec2c), FreeCAD.

Python venv (.venv): skidl 2.3.0, pythonfmu 0.7.0, fmpy 0.3.32, numpy 2.5.3, matplotlib. flask is not installed here — the web GUI runs under system python3 (flask 3.1); pytest is installed separately for the tests/ suites. Antenna FDTD needs a separate openEMS-enabled interpreter.

External data / installs:

  • /usr/share/kicad/{symbols,footprints}
  • $HOME/opt/openEMS — openEMS install (used via LD_LIBRARY_PATH)
  • /mnt/ext4data/jlcparts — JLCPCB parts DB used by the part resolver
  • Elmer / gmsh for the FEM path

PicSimLab: NOGUI AppImages in /mnt/ext4data/downloads/ (PICSimLab_NOGUI-0.9.2 has the STM32 backend; 0.9.3 has a working rcontrol but ships no libqemu-stm32.so). See sim/COSIM_STATUS.md.


7. Verified status

StageEvidence
SKiDL netlist60 parts, 38 nets, KiCad schematic written
ngspice decksim/netlists/sdr_skidl_ngspice.cir, 0 warnings / 0 errors
Direct conversionLO is a real 7.2 MHz timer waveform; beat lands at 48.1 kHz (IF offset 50 kHz); I/Q ratio 0.965; phase +91.6°
Q channeldriven by a real 90° all-pass on the LO, not wired to zero
STM32 pin model65 Ω @ 20 mA; Cio 5 pF; VIL/VIH 0.35/0.65·VDD
Regression teststest_sdr_netlist.py — 22/22
Analog + pin contractverify_pin_contract.py — 31/31
Firmwarecosim build artifacts present (firmware/.pio/build/cosim/): firmware.elf ≈ 23 KB, firmware.bin ≈ 4.6 KB; .hex via pio run -t hex
Antennasall real openEMS FDTD (model: fdtd): dipole 6.90 MHz / S11 −15.7 dB / ηrad 98 %; patch 2442.5 MHz / −3.6 dB / 31 %; meander reactive with no in-band match (S11 ≈ 0 dB, ηrad 0 %) — sim/outputs/*.json
PicSimLab NOGUI rcontrolanswers version, pinsl, sim, loadhex
STM32 backendqemu-stm32 -M stm32-f103c8-picsimlab-new, 48 pins

8. Known limitations / open items

  1. QEMU real-firmware path stops at ADC1_2_IRQHandler — QEMU's STM32F103 ADC/DMA model is not faithful enough for loop() to be serviced. Emulator fidelity limit, not a project defect.
  2. PicSimLab rcontrol never binds while QEMU runs (0.9.2), and 0.9.3 has no STM32 backend. Needs an upstream fix / a 0.9.3-built libqemu-stm32.so.
  3. out/ artifacts are text templates, not manufacturing outputs.
  4. Several transferred brig scripts assume brig's paths/data (e.g. config/params.yaml points at /home/mirrage/brig-receiver/...); adapt before use. Likewise sim/README.md is the brig Bragg-cell module README, kept as reference.
  5. docs/ is empty; AGENT_MAP.md + sim/COSIM_STATUS.md + manifest.json are the authoritative documentation.
  6. Meander 200 MHz does not match in full-wave simulation. Modelled over its full ground plane (matching hardware/antenna/geometry/build_antennas_fc.py) it is a low-Q reactive resonator — S11 ≈ 0 dB across 50–800 MHz, no net radiation (η = 0 %). The project's own definitions also disagree: the YAML/CAD use 3 × 63.5 mm arms (220 mm), while sim/nec/run_nec.py uses 3 × 130 mm arms (430 mm) and its GN 1 0 0 0 1.0 0.0 ground card is rejected by nec2c. Treat the meander figures as a design warning, not a finished antenna.

9. Conventions

  • One source of truth per thing. Connectivity = SKiDL; physics = the sim/scripts/* entry points; parameters = YAML in sim/inputs/.
  • Don't hand-edit derived files (sim/netlists/, sim/outputs/, out/).
  • Pin boundary is a contract. PA0/PA1 = ADC I/Q, PA6 = LO, PA7 = GAIN. Asserted by verify_pin_contract.py and by firmware.ino.
  • Datasheet numbers, not convenience. Pin impedance and thresholds come from DS5319.
  • Prove it. Any claim about the analog chain should be backed by test_sdr_netlist.py / verify_pin_contract.py / test_fmu_cosim.py.
  • Regenerate, don't patch. build_fmu.py exists precisely so the FMU can't drift from SKiDL.

See AGENT_MAP.md for the deep functional map and sim/COSIM_STATUS.md for the co-simulation narrative and evidence.

🤖 Агентно-функциональный граф

Заказ приходит текстом, проходит граф функций-агентов и превращается в сборку. Движок не знает про Krystal Radio — всё, что касается конкретного изделия, лежит в agents.project.yaml.

text
POST /api/order "посчитай дальность и пеленгацию"
        │
        ▼
   ┌──────────┐  модель получает инструменты и решает:
   │supervisor│  list_targets → submit_plan → run_target → read_artifacts
   └────┬─────┘  (или ask_human, когда нужен человек)
        ▼
   ┌──────┐  выполняет вызовы, возвращает модели результат
   │ tools│
   └──┬───┘
      ├─ нужна помощь ─► await_human ─► interrupt() ─► ответ оператора ─┐
      ├─ есть модель ──► supervisor (цикл: думаю → действую → смотрю)  │
      └─ нет модели ───► plan_rules → execute_rules → review          │
                                                                        │
   ┌────────┐ ◄───────────────────────────────────────────────────────┘
   │ report │  факты из артефактов → структурный отчёт (+ резюме модели)
   └────────┘

Почему LangGraph, а не что-то другое

Проверено по документации LangChain: LangGraph — это agent runtime, LangChain — framework, Deep Agents — harness. Runtime даёт наибольший контроль над тем, что именно выполняется.

Здесь контроль — главное требование. Модель выбирает из объявленных целей, а исполняет система сборки. Harness вроде CrewAI или Deep Agents даёт ReAct-агента с доступом к файловой системе — то есть ровно то, чего в инженерном проекте быть не должно: агент с правом писать что угодно был бы не помощью, а источником порчи.

Что взято из рантайма по-современному:

ВозможностьКак реализовано
Вызов инструментовТипизированные схемы в backend/agents/tools.py, ответ приходит разобранным. Прежний разбор JSON регуляркой не нужен
Цикл «думаю — действую — смотрю»Узлы supervisor ↔ tools, не один план на всё
Прерывание человекаinterrupt() — граф встаёт, состояние сохранено, после ответа продолжается с места остановки
Долговременное состояниеSqliteSaver в .run/graph.db; переживает перезапуск моста
Устойчивость к обрывуНедоступный провайдер уходит в паузу на KR_LLM_COOLDOWN секунд, чтобы не ждать таймаут на каждом вызове

Без langgraph движок работает на запасном пути (run_plain): те же узлы, те же проверки — теряются только чекпоинты и прерывание.

Модели: свободные веса, любой рантайм

Authorization не отправляется, если ключа нет — поэтому один и тот же код работает и с облаком, и с локальным сервером. Локальные серверы находятся сами: опрашиваются типовые порты (11434, 8080, 1234, 8000, 5000…), так что не важно, запущен Ollama, llama.cpp, LM Studio или vLLM.

bash
make llm-check                            # что включено, что отвечает, какие модели есть
KR_LLM_PRESET=hermes make llm-check       # то же для другой открытой модели
KR_LLM_ENABLED=0 make llm-check           # проверить, что будет при выключенной модели
ПеременнаяСмысл
KR_LLM_ENABLEDГлавный выключатель. 0 — план по правилам, сводки структурные
KR_LLM_PRESEThermes, qwen, llama, gemma, mistral
KR_LLM_PROVIDERollama, llamacpp, lmstudio, vllm, openai, deepseek, groq, custom
KR_LLM_MODELМодель. Если её нет на сервере — подставится та, что есть
KR_LLM_MODEL_FASTОтдельная модель для сводок (обычно локальная и дешёвая)
KR_LLM_FALLBACKauto — откат на живой локальный сервер, либо имя провайдера

Роли разделены намеренно: планирование требует аккуратного разбора формулировки, сводка — нет. Сводить второе на слабую локальную модель безопасно, потому что модель не выдумывает числа: она читает их из артефактов (backend/agents/reporter.py).

Про эту машину: 8 ядер, 11 ГБ RAM, GPU нет. Hermes 3 Llama 3.1 8B (4.9 ГБ) здесь даёт единицы токенов в секунду — для агентного цикла непригодно. Практичен qwen2.5:3b-instruct; Hermes имеет смысл включать на машине с видеокартой.

Никакой конкретики в движке

Это проверяется тестом, а не обещанием: tests/requirements/agents создаёт посторонние проекты (make, npm, just, пустой) и требует, чтобы движок с ними работал. Отдельный тест разбирает AST движка и падает, если в коде появятся имена целей или файлы артефактов этого проекта.

OpenCode на тех же бесплатных моделях

OpenCode умеет любой OpenAI-совместимый провайдер, поэтому работает на том же ключе и тех же моделях, что и наш движок:

bash
./scripts/oc.py --write     # обновить opencode.json
./scripts/oc.py             # обновить и запустить
./scripts/oc.py run "собери всё и проверь тестами"

scripts/oc.py не зашивает список моделей и не доверяет каталогу:

  • спрашивает /v1/models, оставляет помеченные :free;
  • проверяет каждую живым запросом. Суффикс :free — не гарантия: из семи моделей в момент проверки шесть отвечали, одна отвечала «This model is no longer free», несколько отдавали 429 по fair-share. Без проверки модель по умолчанию могла оказаться нерабочей, а ошибка выглядела бы как «Payment required» — будто ключ не тот;
  • ключ берёт из ~/.hermes/auth.json и кладёт в .env (в .gitignore), а не в конфиг: он живёт около часа, и захардкоженный завтра даст 401;
  • подставляет его через settings.apiKey. Поле env управляет хранилищем учётных данных самого OpenCode и пакету openai-compatible ключа не передаёт.

Там же прописан наш MCP-сервер, поэтому связка замкнута:

bash
opencode run "посчитай дальность и пеленгацию"
# → бесплатная модель в облаке → MCP → граф → make → артефакты

Изоляция заказов

Каждый заказ выполняется в work/orders/<id>: исходники проекта копируются, .venv и node_modules подключаются символическими ссылками, out/ создаётся свой. Проект — опора работы агентов, и заказ не имеет возможности их переписать. Артефакты забираются наружу только при успешном выполнении.

Отключается, если песочница не нужна:

bash
KR_SANDBOX=0 make run

Команды

bash
make agents-check                       # что движок увидел в проекте
make agents-graph                       # узлы графа
make agents-ask  TEXT="нужен водопад"   # разобрать заказ, ничего не запуская
make agents-run  TEXT="собери всё"      # выполнить заказ
make run                                # мост + интерфейс по одному адресу

Порядок живого прогона (что поднять, куда постить заказ, где песочница и архив, какие env-ручки) — в docs/RUN_LIVE_ORDER.md.


License

MIT — see LICENSE.

Parts

60
QtyValueFootprintReferences
11100ptscircuit:100pC_AF_I, C_AF_Q, C_BS1, C_IN, C_LNA1, C_LO, C_MIX1, C_MIX2, C_OPI, C_OPQ, C_QWC
610ktscircuit:10kR_AF_I, R_AF_Q, R_G1, R_G2, R_OPI, R_OPQ
6TESTPOINTtscircuit:TESTPOINTTP_ADC_I, TP_ADC_Q, TP_ANT, TP_FIL, TP_LNA1_D, TP_LNA2_D
5100ntscircuit:100nC_ANA2, C_LDO2, C_MCU1, C_MCU2, C_MIX3
41ktscircuit:1kR_BS, R_QWC, R_S1, R_S2
2100utscircuit:100uC_LDO1, L_BS1
218utscircuit:18uL_LNA1, L_LNA2
222tscircuit:22R_USB1, R_USB2
222ptscircuit:22pC_OSC1, C_OSC2
22N7002tscircuit:2N7002Q_LNA1, Q_LNA2
250tscircuit:50R_LO, R_TERM
2600R@100MHztscircuit:600R_100MHzFB1, L_MIX
110ptscircuit:10pC_BS2
110utscircuit:10uC_ANA1
12k2tscircuit:2k2R_G4
14k7tscircuit:4k7R_G3
14u7tscircuit:4u7C_MCU3
15V_INtscircuit:5V_INJ_PWR
18MHztscircuit:8MHzY1
1AMS1117-3.3tscircuit:AMS1117-3_3U_LDO
1LPJ-1tscircuit:LPJ-1X_QWC1
1LT5560tscircuit:LT5560U_MIX1
1OPA2134tscircuit:OPA2134U_OP1
1SMA_EDGEtscircuit:SMA_EDGEJ_ANT
1STM32F103C8T6tscircuit:STM32F103C8T6U1
1USB_CDCtscircuit:USB_CDCJ_USB

Questions

How big is the Out PCB?

It measures 88.6 × 85.1 mm and has 4 copper layers, 1.6 mm thick.

What parts does the Out use?

60 parts, built around the STM32F103C8T6, OPA2134UA and AMS1117-3.3. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Out KiCad files?

From the maximmalin/simul repository on GitHub, at the commit this page was made from: the board is out/board.kicad_pcb. PCBKit opens the board in the browser, with nothing to install.

Can I use the Out design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

Similar boards

  • 4 layers100 × 85.5 mmPCM3168APAP breakout, the printed circuit board in 3D

    PCM3168APAP breakout

    fsanzeni/PCM3168APAP-breakout

    A breakout board for the Texas Instruments PCM3168APAP audio codec, featuring 6 analog inputs and 8 analog outputs with high-quality buffering and filtering circuitry.

    206 parts5MIT
  • 2 layers68 × 40 mmStm32-io extend, the printed circuit board in 3D

    Stm32-io extend

    hardenedlinux/cheap-pcb

    Cheap PCB: Better understanding the current status of hardware supply chain

    44 parts170CC0-1.0
  • 2 layers99.1 × 58.4 mmHEAT, the printed circuit board in 3D

    HEAT

    milar111/HEAT

    An open-source STM32 SMD assembly station for precision soldering. Uses a custom Smart Pulse & Coast algorithm to drive 400W mains heaters via SSR. Features OLED UI, NTC feedback, and safety cooling.

    57 parts9
  • 2 layers71.3 × 25.4 mmUSB DALI, the printed circuit board in 3D

    USB DALI

    even-notodd/USB-DALI

    USB-DALI interface converter

    43 parts4
  • 2 layers78 × 60 mmDC Motor Controller, the printed circuit board in 3D

    DC Motor Controller

    minhle4397/Line-Follower-Robot-v2

    Improved Hardware and Firmware for Line Follower Robot

    53 parts3
  • 4 layers36 × 36 mmTrackMAG, the printed circuit board in 3D

    TrackMAG

    mupfdev/TrackMAG

    An open hardware development board primarily intended for the development of accessible input devices.

    58 parts3
  • 2 layers154.7 × 113 mmDeskMixer, the printed circuit board in 3D

    DeskMixer

    RaduAndre/DeskMixer

    DeskMixer is a software that allows control of application volumes with real sliders, and action mapping on buttons using arduino like platforms. The project was inspired originally by deej.

    113 parts3
  • 2 layers42 × 33 mmSTM32 course, the printed circuit board in 3D

    STM32 course

    abda-s/STM32-Phill-s-Lab-PCB-Course

    A beginner-friendly introduction to STM32 hardware design and KiCad

    32 parts2