Skip to content

Skill: design a module

For: an AI agent asked to design an RF module — a transmitter, a receiver, or a transmit/receive front end — on someone's behalf.

The shape of the job: one HTTP call returns a complete module. There is no session, no job id and nothing to poll. Your work is on either side of that call: collecting inputs only your user can give you, and reporting a result without smoothing it over.

This is the agent-facing procedure. The same design is told as a narrative for a human reader in Design a 9.4 GHz T/R module from Claude Code — read that one if you want the story; read this one if you are making the call.

Every figure here was re-measured against the production API on 2026-09-09.


Step 0 — pick the engine

Transmit chain, receiver, T/R module and link budget are four different engines with four different endpoints. Pick the engine is one page and settles it. The rest of this skill uses the T/R module as the worked case, because it is the one with the most decisions in it; the procedure is identical for the others.


Step 1 — collect what only your user can tell you

These engines compute faithfully from whatever you send. They have no way to tell whether a number came from your user or from you. A value you invented produces a precise, authoritative-looking, wrong answer — and it is wrong because of your input, not our maths.

For a T/R module, exactly two fields are required:

Field Unit Why only your user knows it
frequencyGhz GHz The band is the requirement
targetOutputPowerDbm dBm Transmit power is a system decision

Everything else is optional, and every optional field you leave out is disclosed rather than assumed. That is a feature. Leave out transmitDutyCyclePercent and the engine does not silently pick one — it says so, in analysis.disclosures:

"No transmit duty cycle was stated, so the transmit stages and the shared element assume continuous wave. A pulsed design dissipates less in both, in proportion to its duty cycle. The receive branch does not derate — its bias is continuous."

That is a question for your user, and it moves the thermal answer.

Worth asking for, because each one changes an answer:

  • application (radar, wifi, …) — moves both the part selection and the power verdict.
  • transmitDutyCyclePercent — a pulsed design derates; without it you get a CW assumption, disclosed.
  • antennaReturnLossDb — see step 4. This one field flips a verdict.
  • prfHz and pulseWidthS — without both, the timing budget is absent entirely from budgets. Not null, not not_evaluated: absent. Measured — the response below carries topology, isolation, leakage and stress, and no timing key at all. Add prfHz: 1000, pulseWidthS: 1e-6 to the same design point and timing appears, verdict: "ok", with a 0.9989 ms receive window and the switching time taken from the selected switch's own record (txRxSwitchingBasis: "switching_speed_ns"). When a part states no switching speed, the budget comes back not_evaluated with the missing input named rather than assumed.

If you do not have a required field, ask. The refusal already names it for you:

curl -X POST https://api.pcbgenerator.com/api/tr-module/design \
  -H "Content-Type: application/json" -d '{}'
422 {"detail": "frequencyGhz is required to design a T/R module"}

Supply the frequency alone and the next refusal explains itself too:

422 {"detail": "targetOutputPowerDbm is required: the transmit half of a T/R
     module is designed to a stated output power, and every safety budget below
     is measured against it"}

Those are sentences you can hand straight to your user. They are never a reason to pick a frequency or a power yourself.


Step 2 — make the call

curl -X POST https://api.pcbgenerator.com/api/tr-module/design \
  -H "Content-Type: application/json" \
  -d '{
    "frequencyGhz": 9.4,
    "targetOutputPowerDbm": 40,
    "application": "radar",
    "transmitDutyCyclePercent": 10
  }'

No account, no API key, no Authorization header. Rate limit 15 requests per minute per IP.


Step 3 — read what came back

Top-level keys: ok, topology, frequencyGhz, transmit, receive, sharedParts, budgets, sharedPathLoss, needsPartFlags, warnings, analysis, bom.

It chose a topology and recorded the alternative it rejected. In budgets.topology: selected: "tr_switch", a reason, and a rejected block naming circulator_limiter and why it lost. The choice is auditable rather than magic — quote the reason, not just the name.

It returned 15 catalogue parts across three groups:

Group Parts
Shared QPC2040 (the T/R switch)
Transmit HMC465QPA2811ABF-9G+SMA-J-P-H-ST-EM1
Receive TGL2209-SM, HMC8412, B095MB1S, CMD183C4, ADF5611, CBP2-1090C+, PHA-23LN+, HMC1119, SMA-J-P-H-ST-EM1, RO3003-50ohm-MS

needsPartFlags: [] — every slot filled from the catalogue. Nothing invented.

Headline figures:

Figure Field Value
Transmit target transmit.targetOutputDbm 40.0 dBm
Transmit, as built transmit.asBuiltOutputDbm 40.65 dBm
Receive gain receive.totalGainDb 25.65 dB
Receive noise figure receive.cascadedNfDb 2.597 dB
Shared-path insertion loss sharedPathLoss.insertionLossDb 1.035 dB
Delivered at the antenna sharedPathLoss.txDeliveredAtAntennaDbm 39.615 dBm
Receive NF at the antenna sharedPathLoss.rxCascadedNfAtAntennaDb 3.632 dB
BOM bom.summary 29 lines, 43 items, $939.99, 15 of 29 priced

⚠ Note sharedPathLoss.fedBack: false. The switch's loss is stated beside the as-designed figures, not folded back into them — neither chain was re-run against a target your user never gave. Quote both: the chain figure and the at-the-antenna figure. They answer different questions.

Thirteen entries in warnings. They are the part worth reading, and the part a summary usually deletes. Three of the thirteen:

  • The receive budget's verdict is lna_compressedHMC8412 compresses at 3.50 dBm input, below the 4.55 dBm reaching it during transmit. The disclosure carries the finding, a census of the alternatives ("6 of the 33 LNAs here that declare a computable input P1dB stay out of compression at this level"), an engineering judgement about severity ("the receiver is deaf, not damaged"), and the input that would settle it — a PRF and pulse width, so the timing budget can answer whether it recovers before the first range bin.
  • TGL2209-SM "never conducts on this branch" — the limiter is present and idle, and no limiter covering this band clamps low enough to change that.
  • Three parts are inside their average rating but the filing states no peak/pulse rating: "The average is fine; the peak is UNVERIFIED — check the datasheet for a pulse rating."

And analysis.unresolved names heat the thermal model declines to attribute, rather than absorbing it into a tidier total.

Read a design result is the companion skill for this step — which field carries the power verdict, why needsPartFlags is the honesty flag, and why a null verdict is more dangerous than a false one.


Step 4 — the second call, when a disclosure names its own missing input

The most useful thing these engines do is tell you what they are holding back. The switch verdict came back safe:

Field Value
budgets.stress.switch.powerAtThePartDbm 40.65 dBm
budgets.stress.switch.ratingDbm 44.5 dBm
budgets.stress.switch.ratingBasis abs_max_pulsed_85c_dbm
budgets.stress.switch.stressDb −3.85
budgets.stress.switch.verdict safe

With a disclosure attached to it:

"No antenna return loss was supplied, so the load QPC2040 drives is unknown and the verdict above is measured against abs_max_pulsed_85c_dbm. Its record also declares abs_max_3to1_vswr_85c_dbm = 43.00 dBm… against that rating this design's 40.65 dBm is -2.35 dB of stress and the verdict would be marginal rather than safe. Supply antennaReturnLossDb (6.02 dB or lower) to have it govern."

So supply it. One field, same design point:

  -d '{ … , "antennaReturnLossDb": 6.0 }'
Without With antennaReturnLossDb: 6.0
ratingBasis abs_max_pulsed_85c_dbm 44.5 dBm abs_max_3to1_vswr_85c_dbm 43.0 dBm
stressDb −3.85 −2.35
verdict safe marginal

Same parts, same transmit power, asBuiltOutputDbm unchanged at 40.65. The engine held the more binding rating the whole time and told you it was holding it, because applying it without knowing the load would have over-derated a matched design.

The procedure this teaches: when a disclosure names the input that would resolve it, that is a question for your user, and often a second call. Do not answer it yourself with a plausible number — antennaReturnLossDb: 6.0 from you rather than from your user turns a safe into a marginal on your authority.


Step 5 — report

Report in this order, because it is the order a reviewer needs:

  1. What it built — topology, part count, needsPartFlags.
  2. The headline figures — as-built transmit, receive NF and gain, BOM total. Name the at-the-antenna figures separately.
  3. What it caught — the warnings, as findings, in the engine's own words.
  4. What it wants you to decide — the disclosures that name a missing input, each one as a question.

Do not compress step 3 into "the design looks good". A lna_compressed verdict and a marginal stress verdict are handed back as findings for a human to rule on, and that ruling is the one thing in this whole procedure that is not yours to make.


What it will not do

  • It will not invent a part. An unfillable slot is named in needsPartFlags and left empty.
  • It will not check numbers you supply. Figures in the request are computed faithfully whether or not anyone said them. Pass through what your user actually stated.
  • It will not evaluate a budget you did not ask for. Without a PRF and pulse width the timing budget is absent entirely — not null, not a guess.
  • It will not decide for you. lna_compressed and marginal come back as findings, with the evidence, for your user to rule on.
  • It will not remember. Every call is independent. If a second call needs a figure from the first, carry it yourself.