Circuit review & bench-test guide
AXL-003 — Fast waveform generator PMOD module (8-bit R-2R DAC)
Document purpose#
This document explains the axl-003-wavegen prototype at component level and turns the design evidence into a practical manual-review and bench-test plan. It is based on the generated schematic (generate_design.py, rev 0.1-schematic, 2026-07-12), the README, and the TI OPA356 datasheet.
The board is a design-stage prototype. The schematic is generated and ERC clean per reports/erc.rpt (0 errors / 0 warnings, 2026-07-12) and the netlist has been reviewed, but PCB layout has not started and no board has been fabricated or assembled. There is no first-article bench result of any kind, and the companion dds_fast HDL core does not exist yet. Every expected value below is a design target to verify, not a measurement.
One finding of this review is carried throughout: the README describes the OPA356 as "RRIO", but the datasheet gives an input common-mode range that stops 1.5 V below the positive rail. On a 3.3 V supply the buffer input is only in specification up to about 1.8 V, while the ladder produces up to about 3.29 V. See Sections 6, 7, 9.F, and 12.
1. What the board does#
The board is a 12-pin PMOD (Type 1 GPIO, all eight signal pins used) module carrying an 8-bit voltage-mode R-2R ladder DAC (R = 1 kΩ, 2R = 2 kΩ, 1% 0603, 16 resistors) driven directly by the host FPGA on D0–D7, buffered by a TI OPA356 (200 MHz CMOS, unity follower), and brought out through a 100 Ω series resistor to a 2-pin header and a BNC in parallel. Full scale is nominally 0–3.3 V (in practice 0 to 255/256 x VOH). There is no register, latch, or serial interface on the module: the DAC code is whatever the host drives on the PMOD pins at that instant, which is what lets the future dds_fast core update the output at the 25 MHz fabric clock for DDS up to ~10 MHz. The intended lab lessons are update-rate/settling limits and R-2R accuracy versus resistor tolerance, measured against the AXL-002 scope module. It is catalog #3, level L4 (instrument) in the AruviX lab line.
Functional block diagram#
PMOD J1 R-2R ladder U1 OPA356
1..4 D0..D3 ── 2k legs ──> LAD0..LAD3 ─┐ (1k chain +in = LAD7
7..10 D4..D7 ── 2k legs ──> LAD4..LAD7 ─┤ LAD0→...→LAD7) −in = out
5/11 GND ── R16 2k (LSB terminator, LAD0→GND) (unity buffer)
6/12 3V3 ── C1 100n + C2 10u ── U1 supply │ WAVE_BUF
v
WAVE_OUT <── R17 100R ────────────┘
├──> J2 pin 1 (header; pin 2 GND)
└──> J3 BNC center (shield GND)
2. Safety and scope boundaries#
- 3.3 V host only. The OPA356 absolute-maximum supply is 7.5 V and the operating range 2.5–5.5 V, but the ladder full scale, the CM-range analysis, and every number in this guide assume a 3.3 V PMOD rail. Never plug into a 5 V PMOD port.
- D0–D7 are host-driven inputs into a passive resistor network; the ladder cannot damage the host. There are deliberately no series resistors on D0–D7 (the line rule applies to module-driven lines only, per the README). The only module-driven line is WAVE_OUT, protected by R17.
- WAVE_OUT is an output. Do not drive it from an external source: R17 (100 Ω) limits the fault current, but a stiff external source contends with U1's output stage (short-circuit current +250/−200 mA typ — hot, not safe).
- U1's inputs are diode-clamped to the rails (abs max ±0.5 V beyond, 10 mA); the ladder itself can never violate this on a shared 3.3 V rail.
- Earth-referenced oscilloscope grounds clip only to board GND. The BNC shield is board GND — a grounded 50 Ω instrument on J3 is fine.
- Standalone bench work uses a current-limited 3.3 V supply (limit ~50 mA; expected module draw is ~9–13 mA, dominated by U1's quiescent current).
- A clean staircase on a scope is not a calibration. This is an instrument board: it must be validated against a calibrated DMM (Section 9.C) before any measurement made with it is trusted.
3. Power and control sequence#
There is no power sequencing, enable, or configuration on this module:
- 3V3 and GND arrive on PMOD pins 6/12 and 5/11. C1 (100 nF) decouples U1 and C2 (10 µF) provides bulk charge for fast edges into capacitive loads.
- U1 is always on; quiescent current is 8.3 mA typ / 11 mA max. It has internal thermal shutdown at ~160 °C junction (recovers at ~140 °C).
- The output is valid as soon as the rail is up and D0–D7 are driven. With an unconfigured FPGA the PMOD pins float or are weakly pulled; the ladder output (and therefore WAVE_OUT) is then undefined but harmless — the ladder pulls floating inputs toward a mid-ish level through the network.
- Whatever byte the host drives appears at LAD7 after the ladder settles (output resistance R = 1 kΩ into node capacitance — nanoseconds), is buffered by U1, and reaches J2/J3 through R17.
4. Interfaces and pin maps#
| PMOD pin | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Net | D0 | D1 | D2 | D3 | GND | 3V3 | D4 | D5 | D6 | D7 | GND | 3V3 |
D0 is the LSB, D7 the MSB. Note the GND/3V3 pins split the byte: bits 0–3 are on the top row, bits 4–7 on the bottom row of the PMOD connector.
| J2 pin | 1 | 2 |
|---|---|---|
| Net | WAVE_OUT | GND |
| J3 (BNC) | center | shield |
|---|---|---|
| Net | WAVE_OUT | GND |
J2 and J3 are electrically in parallel. U1 pinout in the SOIC-8 (D) package, for assembly checks: 2 = −In (WAVE_BUF), 3 = +In (LAD7), 4 = V− (GND), 6 = Out (WAVE_BUF), 7 = V+ (3V3); 1, 5, 8 = NC.
5. Component-by-component review#
Every reference designator in generate_design.py appears below. This board has no DNP parts and no jumpers; #FLG01 and #FLG02 are ERC power-flag pseudo-components on 3V3 and GND with no physical footprint, listed only for completeness. Identical-value ladder resistors are grouped, with every ref-des listed explicitly.
| Ref. | Part / value | Function and why needed | If absent/open | If shorted, wrong, or misassembled |
|---|---|---|---|---|
| J1 | 12-pin PMOD plug (Type 1 GPIO) | Host power and the eight DAC bits D0–D7 | No power or data | Offset insertion puts 3V3 on data pins (ladder survives; check before repowering) |
| R1–R8 | 2 kΩ 1% 0603 (Yageo RC0603FR-072KL), 8 pcs | R-2R bit legs (2R): R1 = D0→LAD0, R2 = D1→LAD1, R3 = D2→LAD2, R4 = D3→LAD3, R5 = D4→LAD4, R6 = D5→LAD5, R7 = D6→LAD6, R8 = D7→LAD7 | That bit's weight vanishes: missing codes, giant DNL step at that bit's transitions | Wrong value (1 k for 2 k) doubles that bit's weight — gross INL/DNL; a short makes the digital level appear directly on the ladder node |
| R9–R15 | 1 kΩ 1% 0603 (Yageo RC0603FR-071KL), 7 pcs | R-2R chain (R): R9 = LAD0→LAD1, R10 = LAD1→LAD2, R11 = LAD2→LAD3, R12 = LAD3→LAD4, R13 = LAD4→LAD5, R14 = LAD5→LAD6, R15 = LAD6→LAD7 | Ladder splits: all bits below the break stop contributing (large missing chunk of the transfer curve) | Wrong value (2 k for 1 k) rescales every bit weight below it; a short merges two ladder nodes and corrupts two bit weights |
| R16 | 2 kΩ 1% 0603 (RC0603FR-072KL) | R-2R LSB terminator, LAD0→GND; makes the LSB-end Thevenin resistance equal 1 kΩ so every stage divides by exactly 2 | Every bit weight shifts (the binary weighting breaks); output scale and linearity wrong | Short grounds LAD0: D0 leg loads to a fixed divider, LSB nearly dead |
| U1 | TI OPA356AIDR, SOIC-8 (D) | 200 MHz CMOS unity buffer (+in = LAD7, −in tied to out); isolates the 1 kΩ ladder output impedance from the load so cable/termination loading does not corrupt the code voltage | Output floats; J2/J3 see nothing (ladder is not connected to R17 except through U1) | Rotated package swaps supply onto inputs — likely fatal to U1; +in/−in swap makes a comparator-like latch, not a buffer |
| R17 | 100 Ω 1% 0603 (RC0603FR-07100RL) | Series back-termination and short-circuit protection between WAVE_BUF and WAVE_OUT; isolates U1 from cable capacitance (U1 is only comfortable with small direct C loads) | U1 drives the cable directly: ringing/instability into open coax, no short protection | Wrong high value forms a divider with 50 Ω terminated loads far from the intended 100:50; note 100 Ω is deliberately NOT a matched 50 Ω back-termination (see Section 12) |
| J2 | 1x2 2.54 mm header (MPN TBD in the script) | Breadboard/jumper access to WAVE_OUT and GND | No header output (BNC still works) | Pin swap puts GND where the signal is expected — harmless but confusing |
| J3 | Amphenol RF B6252HB-NPP3G-50 BNC, horizontal | Coax output to a scope / AXL-002; footprint verified against stock library per README | No coax output (header still works) | Center–shield solder bridge shorts WAVE_OUT to GND through R17 (U1 survives, output stuck low ×1/3... actually stuck at 0 V at the connector) |
| C1 | 100 nF 16 V X7R 0603 (Murata GRM188R71C104KA01D) | U1 high-frequency decoupling | Supply bounce during fast edges; visible ringing/HD degradation | Short kills the rail (host or bench limit should catch it) |
| C2 | 10 µF 10 V X5R 0805 (Murata GRM21BR61A106KE19L) | U1 bulk decoupling — charge reservoir for fast edges into capacitive/50 Ω loads | Rail droop on large fast steps; worse settling at MHz update rates | Short kills the rail |
| #FLG01/#FLG02 | ERC power flags (3V3, GND) | Schematic ERC bookkeeping only | ERC warnings | Not applicable; no physical part |
One table-cell correction for honesty: a J3 center-to-shield bridge grounds WAVE_OUT itself, so both J2 and J3 read 0 V while U1 works into 100 Ω — a current of about V_BUF/100 Ω flows in R17 (33 mA at full scale; R17's 0603 rating makes this a "find it quickly" fault, not a fire).
6. Datasheet summary and design interpretation#
Consulted: TI OPA356/OPA2356 datasheet SBOS212A (November 2001), <https://www.ti.com/lit/ds/symlink/opa356.pdf>, product page <https://www.ti.com/product/OPA356>. The PDF was fetched and read directly for this review; specs below are from the SBOS212A electrical tables (V_S = 2.7–5.5 V single supply, T_A = 25 °C unless noted). Check for a newer revision before procurement.
| Manufacturer fact (SBOS212A) | Board-specific interpretation |
|---|---|
| 200 MHz GBW, 450 MHz unity-gain small-signal bandwidth, unity-gain stable; slew rate 300–360 V/µs | Vastly faster than the 25 MHz update rate needs; a full-scale 3.3 V step slews in ~10 ns, so the ladder RC and FPGA edge skew dominate settling, not U1 |
| Supply 2.5–5.5 V operating (2.7–5.5 specified); abs max 7.5 V | 3.3 V PMOD rail is legal but near the low end; all AC specs are characterized at 5 V — expect somewhat less swing/drive margin at 3.3 V |
| Quiescent current 8.3 mA typ / 11 mA max per amplifier (14 mA max over temp) | Dominates module power: ~27–36 mW from the 3.3 V rail with no load |
| Input common-mode range (V−) − 0.1 V to (V+) − 1.5 V ("input range includes ground"); CMRR specified for −0.1 V < V_CM < +4.0 V at V_S = 5.5 V | At 3.3 V the buffer input is only in spec from 0 to ~1.8 V. The ladder produces 0 to ~3.29 V, so codes above ~0x8B put U1's +input beyond its specified CM range. The README's "RRIO" claim contradicts the datasheet: the OPA356 is rail-to-rail OUTPUT only (TI's own related-products table points to the OPA350 for rail-to-rail input). This is the design's principal open risk — see Sections 7, 9.F, and 12 |
| Rail-to-rail output: swing within 0.2 V typ / 0.3 V max of each rail at R_L = 150 Ω; 0.1 V typ at R_L = 1 kΩ; 0.8 V typ / 1 V max at ±100 mA | Into a high-impedance scope the output reaches within tens of mV of the rails; into a 50 Ω-terminated BNC (U1 sees 150 Ω) budget 0.2–0.3 V of headroom loss at each rail |
| V_OS ±2 mV typ / ±9 mV max (±15 mV over temp); I_B 3 pA typ / ±50 pA max | Offset is ±0.16 LSB typ, ±0.7 LSB max (LSB = 12.9 mV); bias current into the 1 kΩ ladder impedance is sub-µV — negligible |
| Output current ±60 mA continuous min, ±100 mA peak; short-circuit +250/−200 mA; thermal shutdown 160 °C | A terminated 50 Ω load at 1.8 V draws 12 mA — comfortable; a hard short of WAVE_BUF (before R17) is survivable but hot |
| Settling 30 ns to 0.1% / 120 ns to 0.01% (2 V step, G = +2 at 5 V); HD2 −81 dBc / HD3 −93 dBc at 1 MHz, 2 Vp-p, R_L = 200 Ω | At a 40 ns update period (25 MHz) U1 settles to ~8-bit accuracy within one sample; U1's own distortion is far below the 8-bit quantization floor (~−50 dB), so measured THD reflects the ladder and glitches, not the buffer |
| Stable driving small capacitive loads; series isolation recommended for larger C loads (typical-characteristics section) | R17 (100 Ω) is that isolation for cables; do not bypass it to "improve amplitude" |
| Inputs diode-clamped to the rails; signals beyond ±0.5 V of a rail must be limited to 10 mA | The ladder cannot generate such a signal from a shared rail; only relevant for external abuse of J2/J3 |
Additional design interpretation not from the datasheet but from the topology, needed for Section 7:
- The "reference" is the FPGA's VOH, not a reference. Each bit leg is driven by an ECP5 LVCMOS33 pin with output resistance R_O on the order of 25–50 Ω (drive-strength dependent). This adds to the 2 kΩ leg: a weight error of roughly R_O/2000 ≈ 1.3–2.5% per bit, code- and drive-strength- dependent — the same order as the 1% resistor tolerance. This is a gain error plus an INL contribution, and it is the textbook reason real DACs buffer their reference. Measure it (Section 9.C) rather than assume 3.300 V.
- README-vs-script cross-check. The ladder (16 resistors, values, nets), OPA356 wiring, R17, J2/J3, C1/C2, and the PMOD map in the README all match
generate_design.pyexactly. The two discrepancies found: (1) the README's "RRIO" description of the OPA356 versus the datasheet CM range above; (2) "Full scale 0–3.3 V" — the unloaded ladder tops out at 255/256 x VOH ≈ 3.287 V, and the buffered path is only in spec to ~1.8 V.
7. Expected values before bench testing#
Targets assume VOH = VDD = 3.300 V exactly and ideal resistors unless noted. All are design targets, not measured results.
LSB and static levels (at the ladder node LAD7, unloaded, into U1's pA input):
- LSB = VOH / 256 = 3.300 / 256 = 12.891 mV
- V(code) = code x LSB. Selected codes:
| Code | V(LAD7) ideal | Note |
|---|---|---|
| 0x00 | 0.000 V | Buffer output adds V_OS and output-stage floor |
| 0x01 | 12.9 mV | One LSB |
| 0x20 | 412.5 mV | |
| 0x40 | 825.0 mV | |
| 0x7F | 1.6371 V | Just below mid-scale |
| 0x80 | 1.6500 V | Mid-scale (MSB only) |
| 0x8B | 1.7918 V | Last code with U1's +input inside its ~1.8 V CM spec |
| 0xC0 | 2.4750 V | Beyond U1 CM spec — ladder node fine, buffer unspecified |
| 0xFF | 3.2871 V | Full scale = 255/256 x VOH; beyond U1 CM spec |
Worst-case DNL from 1% resistors (the monotonicity math):
The MSB (D7) weight is set by the divider of its 2 kΩ leg (R8) against the rest-of-ladder Thevenin resistance at LAD7, which is exactly 2 kΩ when the network is ideal: weight = 2k/(2k+2k) x VOH = VOH/2 = 128 LSB. The weight's sensitivity to each of those two resistances is ±0.5 x (fractional error), and the "rest of ladder" 2 kΩ is itself an aggregate of ±1% parts, so the MSB weight can be wrong by roughly ±1%:
- MSB weight error ≈ ±1% x 128 LSB = ±1.28 LSB
- The sum of all lower bits (127 LSB) can err the opposite way by a similar ~1%: ∓1.27 LSB
- Step at the 0x7F→0x80 transition = 128 LSB − 127 LSB ± ~2.5 LSB, i.e. worst case ≈ −1.5 LSB: non-monotonic, or ≈ +3.5 LSB.
Monotonicity at the MSB requires the effective weight error to stay below 1 LSB out of 128, i.e. aggregate matching better than ~0.78%, or roughly 0.2–0.4% per resistor. 1% E24 parts therefore do not guarantee an 8-bit-monotonic R-2R — a typical board (RMS-combined errors ~0.3–0.5 LSB) usually is monotonic, and finding out which you got is the point of the lab. The same math at the D6 carry (0x3F→0x40) halves everything: worst case ~±1.3 LSB, still marginal; D5 and below are safe with 1% parts.
Other expected values:
| Quantity | Design target / calculated | What to measure |
|---|---|---|
| Supply current, static code | ~8.3–11 mA (U1 I_Q) + 0–~1.5 mA ladder (code-dependent; 0.825 mA at 0x80: 3.3 V into 2k leg + 2k Thevenin) | Bench supply, standalone context |
| Buffer offset, code 0x00 | V(WAVE_OUT) − V(LAD7) = ±2 mV typ, ±9 mV max, plus output-stage low-side floor into light load | DMM both nodes, Section 9.F |
| Buffer tracking, codes ≤ 0x8B | WAVE_OUT = LAD7 ± offset; gain error ≪ 0.1% (unity follower) | Section 9.C/9.F |
| Buffer behavior, codes > 0x8B | Unspecified (CM range exceeded); characterize, do not assume | Section 9.F |
| Ladder output resistance (all D pins grounded) | 1.00 kΩ ± ~1% at LAD7 | Ohmmeter, Section 9.A |
| D7-to-GND resistance (other D pins grounded) | 4.0 kΩ (2k leg + 2k rest-of-ladder) | Ohmmeter, Section 9.A |
| 50 Ω-terminated BNC amplitude | 1/3 of WAVE_BUF (100 Ω R17 into 50 Ω); high-Z scope: ~1x | Scope on J3 |
| Sine fundamental, 256-entry table at 25 MHz, phase step 1 | 25 MHz / 256 = 97.656 kHz | Scope FFT, Section 9.G |
| 8-bit ideal SINAD | 6.02 x 8 + 1.76 = 49.9 dB; harmonics roughly ≥ 40 dB down is the sanity bound, not a spec | Scope FFT |
| Sinc roll-off at 10 MHz out / 25 MHz update | 20 log10(sinc(0.4)) ≈ −2.4 dB, plus images at 15/35 MHz | AXL-002 / spectrum view |
| Ladder settling time constant | ~1 kΩ x (node + trace + U1 input ~1.5 pF) ≈ few ns | Only visible as edge shaping at 25 MHz |
8. Manual schematic and assembly review checklist#
- Confirm U1 orientation (pin 1 = NC, pin 4 = V− toward GND) and the AIDR suffix (SOIC-8, D package — not the SOT23-5 DBV variant, whose pinout differs).
- Verify the ladder pattern against Section 5: 2 k legs on R1–R8, 1 k chain on R9–R15, 2 k terminator R16. A single 1 k/2 k swap is the most likely assembly error and is invisible to ERC — measure, don't trust markings (both are E24 0603 from the same Yageo series).
- Verify R17 = 100 Ω between U1 pin 6 and the J2/J3 node.
- Buzz U1 pin 2 to pin 6 (−in tied to out — the unity-follower strap).
- Buzz J2 pin 1 to the J3 center pin (parallel outputs) and J2 pin 2 to the BNC shield.
- Check C1 lands close to U1 pin 7 during layout review, and that the ladder legs are matched-length per the README's layout note (layout not yet started — carry both forward).
- Confirm PMOD pin 1 is D0 and pin 10 is D7 (bit order split across the two rows; an off-by-one here scrambles every measurement).
9. Ordered bench-test procedure#
Stop at the first abnormal result. Record board serial, host identity and bitstream/drive-strength settings, measured VDD, and the DMM/scope IDs and calibration dates. Two bench contexts are used throughout:
- Context (a) — PMOD host: plugged into a 3.3 V PMOD port on the ECP5/ULX3S-class FPGA host, with a small bitstream driving D0–D7 (static code register, up/down counter for walks, sine table for 9.G). The
dds_fastcore does not exist yet; a trivial code-register design is enough for everything except 9.G/9.H. - Context (b) — standalone: 3V3/GND from a current-limited bench supply (50 mA limit), D0–D7 strapped to 3V3 or GND with jumper wires for static codes. All static tests (A–C, F) can run standalone; dynamic tests (D, E, G, H) need the FPGA host.
A. Unpowered inspection and resistance tests#
- Complete the Section 8 checklist under magnification.
- Measure 3V3-to-GND resistance in both polarities; investigate < 1 kΩ (C2 charging tail is normal).
- With all eight D pins strapped to GND: LAD7-to-GND (probe U1 pin 3) should read 1.00 kΩ ± ~1% — this single number checks the whole ladder topology at once.
- Lift the D7 strap only: D7-to-GND should read 4.0 kΩ.
- WAVE_OUT-to-WAVE_BUF (J2 pin 1 to U1 pin 6) reads 100 Ω; WAVE_OUT to GND reads open (megaohms) with U1 unpowered.
B. First power, no signal#
- Standalone context, all D pins strapped to GND: current should sit in the ~8–11 mA band (U1 I_Q; ladder draws nothing at code 0x00). Well outside that band, stop: > 20 mA suggests a supply short or U1 misorientation; ~0 mA suggests U1 not powered (check pins 4/7).
- Measure VDD at U1 pin 7. Measure WAVE_OUT: expect ~0 V (0x00 plus offset).
- Context (a): plug into the FPGA host unconfigured, confirm the rail and that nothing heats. WAVE_OUT is undefined until the FPGA drives D0–D7 — that is expected, not a fault.
C. Instrument validation against a known source (static codes vs calibrated DMM)#
This is an instrument board: before it is used to generate anything, its static levels must be validated against a calibrated DMM and gain/offset corrections derived.
- Measure the actual drive level: with code 0xFF, DMM each D pin (at the PMOD connector). In context (b) this is the bench rail; in context (a) it is the ECP5 VOH under ladder load — expect a few tens of mV below 3.300 V, and record it as VOH_meas. The true LSB is VOH_meas/256, not 12.891 mV.
- Set codes 0x00, 0x20, 0x40, 0x80, 0x8B, 0xC0, 0xFF. At each, record V(LAD7) (U1 pin 3) and V(WAVE_OUT) (J2, unloaded) on the calibrated DMM.
- Fit V(WAVE_OUT) = G x code x LSB_ideal + O over the codes ≤ 0x8B (two-point 0x20/0x80 is acceptable; least-squares over all is better). Record gain correction G and offset O. Expect G within ~±2% of unity (driver R_O + resistor tolerance) and O within ±9 mV (U1 V_OS max).
- Codes above 0x8B are recorded but excluded from the fit until 9.F shows how the buffer behaves beyond its CM range.
- In context (a), repeat step 1–3 at two FPGA drive-strength settings; the gain difference directly demonstrates the driver-R_O error of Section 6.
D. Monotonicity walk, 0x00–0xFF#
- Context (a): the FPGA steps an up-counter through all 256 codes slowly (e.g. 10 codes/s for DMM logging, or ~1 kHz with a scope in roll mode).
- DMM-log or scope-capture WAVE_OUT (unloaded/high-Z). Every step must increase. Expected ideal step = LSB_meas ≈ 12.9 mV.
- Inspect the three biggest carries explicitly: 0x3F→0x40, 0x7F→0x80, 0xBF→0xC0. Compute DNL at each: (measured step − LSB)/LSB. Per Section 7, |DNL| up to ~2.5 LSB at the major carry is possible with 1% parts, and a negative step (non-monotonic) at 0x7F→0x80 is a legitimate outcome of the tolerance math, not automatically an assembly fault — but a DNL beyond ±3 LSB, or non-monotonicity at minor carries, means a wrong-value or wrong-position resistor (recheck Section 8).
- Note that above ~0x8B the walk is measuring ladder-through-buffer with the buffer out of CM spec; cross-check any anomaly there against the LAD7 node directly before blaming the ladder.
E. Mid-scale glitch capture (0x7F→0x80)#
The major carry switches all eight bits at once (01111111 → 10000000). Two mechanisms make a glitch: (1) FPGA output timing skew — if D7 rises a nanosecond before D0–D6 fall, the ladder momentarily sees 0xFF (or 0x00 for the opposite skew), a half-scale excursion; (2) the unequal, tolerance-error bit weights mean even simultaneous edges don't cancel exactly. The ladder's ~ns settling and U1's 450 MHz bandwidth will happily pass it.
- Context (a): toggle the code between 0x7F and 0x80 at a few hundred kHz.
- Scope on WAVE_OUT, high-Z or 50 Ω as intended for real use, trigger on D7 (PMOD pin 10) rising; use single-shot / infinite persistence, ≥200 MHz bandwidth.
- Expect a narrow spike riding the 1-LSB step: with ~1 ns skew the worst case is a ~1.65 V excursion lasting ~1 ns, low-pass-shaped by the ladder RC and probe — in practice expect a few hundred mV tall and a few ns wide. Record its impulse area (V·ns), the honest figure of merit.
- Acceptability is application-defined: for the DDS lab, a narrow (few ns) glitch is normal for an unlatched, unsegmented R-2R and shows up as the elevated spur floor budgeted in 9.G. A wide (tens of ns) or slow- settling disturbance is not skew — suspect ladder/buffer settling or a wrong resistor.
- Repeat 0x3F→0x40 for comparison (should be roughly half the area).
F. Buffer offset, swing, and CM-range characterization#
- Code 0x00: DMM V(LAD7) and V(WAVE_OUT). The difference is U1's offset plus its low-side output floor; expect within ±9 mV (V_OS max) plus a few mV of output-stage saturation floor into a light load. Record it.
- Code 0x8B (~1.79 V): difference should still be just the offset — the input is at the edge of its specified CM range.
- Codes 0xA0, 0xC0, 0xE0, 0xFF: record V(LAD7) vs V(WAVE_OUT) at each. The datasheet does not specify behavior here. Possible outcomes: the buffer keeps tracking with growing error (benign), plateaus, or misbehaves. Whatever is observed becomes the documented usable range of this instrument. If tracking fails above ~1.8 V, the module's honest spec is a 0–1.8 V output instrument until the buffer is redesigned (see Section 12).
- Swing check at the top: code 0xFF, compare WAVE_OUT unloaded vs with the BNC 50 Ω-terminated (U1 loaded by 150 Ω). Datasheet output stage allows within 0.2–0.3 V of the rail at 150 Ω — but only if the input stage is still functioning; this measurement and step 3 are read together.
- Load-current sanity: terminated at 50 Ω, code at the top of the usable range; U1 output current = V/150 Ω (12 mA at 1.8 V) — confirm no heating, no supply-current anomaly.
G. Sine table output and FFT sanity#
- Context (a): FPGA plays a 256-entry, full-scale-to-usable-range sine table (recommend centering on mid-scale with amplitude limited to the 9.F-validated range) at one entry per 25 MHz clock.
- Expected fundamental: f = 25 MHz/256 = 97.656 kHz (scaled by the actual fabric clock; verify the clock separately). Capture WAVE_OUT on a real oscilloscope, 50 Ω into the scope if it terminates, FFT with enough record length for a few kHz of resolution.
- Verify the fundamental frequency matches the table-rate math to within the host clock tolerance.
- Harmonic sanity bound (not a spec): ideal 8-bit SINAD is 49.9 dB, so with a good ladder expect harmonics/spurs roughly ≥ 40 dB below the fundamental, with the deficit vs 49.9 dB coming from DNL (Section 7 math) and glitch energy (9.E). Spurs only ~20–30 dB down indicate a ladder fault or a table/bit-order bug; chasing the last few dB toward 49.9 is not meaningful on an unlatched 1% R-2R.
- Look for the update-rate images at 25 MHz ± 97.7 kHz; their presence at the sinc-predicted level is correct behavior, not distortion.
- Optionally repeat with the AXL-002 scope module as the capture instrument once it exists in validated form — cross-validating the two instruments.
H. Update-rate ceiling (stretch, needs dds_fast)#
- Raise the output frequency (larger phase step) toward 10 MHz; measure amplitude roll-off vs the −2.4 dB sinc prediction at 0.4 x f_s and watch the images close in. This confirms the README's ~10 MHz usable-ceiling claim; it is a characterization goal, not a pass/fail gate.
10. Troubleshooting map#
| Symptom | First measurements | Likely areas |
|---|---|---|
| No output at J2/J3, ladder OK at LAD7 | U1 pins 4/7 supply, pin 2-to-6 continuity, R17 | U1 unpowered/rotated, follower strap open, R17 open, BNC bridge |
| Output stuck at ~0 V | J3 center-shield short, WAVE_OUT-to-GND resistance | Solder bridge at BNC or J2 |
| Ladder resistance ≠ 1.00 kΩ in 9.A | Walk node-by-node: D_i-to-GND with others grounded | 1 k/2 k swap, wrong-position resistor, cold joint |
| One code bit has no effect | That bit's D pin voltage at the connector and at the resistor | PMOD pin numbering (bit split across rows), open leg resistor, FPGA constraint file |
| Giant step or reversal at a minor carry | DNL at every carry of the suspect bit | Wrong-value resistor at that bit (tolerance math cannot explain minor-carry non-monotonicity) |
| Small negative step only at 0x7F→0x80 | DNL magnitude vs the ±2.5 LSB Section 7 bound | Legitimate 1%-tolerance outcome — document it; swap in 0.1% resistors to confirm |
| Output tracks to ~1.8 V then misbehaves | LAD7 vs WAVE_OUT divergence point | Expected OPA356 CM-range limit (Section 6), not a fault — record the usable range |
| Half/one-third expected amplitude on the scope | Scope input impedance setting | 50 Ω termination forms 100:50 divider with R17 — arithmetic, not a fault |
| Big wide glitches at every code change | D0–D7 edges on a logic analyzer, glitch width | FPGA output timing/drive settings, unmatched trace lengths (layout), not the ladder |
| FFT fundamental at wrong frequency | Fabric clock, table length, phase step | Host clocking/HDL, not the board |
| Spurs only 20–30 dB down | Repeat 9.D monotonicity walk | Ladder fault, bit-order swap in HDL, table error |
| Supply current ≫ 11 mA + ladder | Code dependence of the excess | U1 damaged/oscillating (scope WAVE_BUF for MHz oscillation with no input change), C1 missing |
11. Bench record template#
| Field | Record |
|---|---|
| Board revision / serial | |
| Context ((a) FPGA host + bitstream ID / (b) standalone) | |
| Measured VDD and VOH_meas (9.C.1); LSB_meas | |
| DMM / scope IDs and calibration dates | |
| Ladder resistance checks (9.A: 1.00 k / 4.0 k) | |
| Supply current at 0x00 / 0x80 / 0xFF | |
| Static-code table (9.C) and fitted G / O | |
| Monotonicity walk result; DNL at 0x3F/0x7F/0xBF carries | |
| Buffer offset at 0x00; divergence code and behavior above CM range (9.F) | |
| Documented usable output range | |
| Mid-scale glitch capture (V·ns), scope BW stated | |
| Sine FFT: fundamental, worst spur (dBc), image levels | |
| Deviations, captures, raw-file paths | |
| Reviewer / date / disposition |
12. Review conclusion#
The strengths: the ladder netlist is exactly the textbook voltage-mode R-2R (verified resistor-by-resistor against generate_design.py), the terminator and chain are correct, the follower strap and output network are simple and right, decoupling is appropriate for a fast buffer, the BNC footprint is a verified stock part, and the whole board is honest about being a lab instrument whose imperfections (1% DNL math, glitch energy, driver-R_O gain error) are the curriculum. ERC is clean (0/0).
The open risks, in order:
- OPA356 input common-mode range. The datasheet (SBOS212A) limits the input to (V+) − 1.5 V — about 1.8 V on this 3.3 V board — while the ladder demands 0–3.29 V. The README's "RRIO" description is wrong: the part is rail-to-rail output only. Unless bench characterization (9.F) shows benign out-of-spec behavior, the buffered output is a ~0–1.8 V instrument, roughly half the intended range. The clean fixes are a true RRIO part (TI's own table suggests the OPA350, 38 MHz — slower; or another fast RRIO buffer) or a gain-of-1 configuration from a ladder scaled to half amplitude. This should be resolved before layout.
- No PCB exists: matched-length ladder legs, decoupling placement, and ground return for the BNC are all unverified layout intentions.
- 1% resistors do not guarantee MSB monotonicity (Section 7 math, ~±2.5 LSB worst-case DNL at the major carry); acceptable for a teaching instrument, but the guide's correction-factor procedure (9.C) is what makes it usable as one.
- The DAC "reference" is the FPGA VOH through ~25–50 Ω of driver resistance: absolute accuracy is host- and drive-strength-dependent by design.
- R17 is short-circuit protection and cable isolation, not a matched 50 Ω back-termination; reflections on unterminated coax are damped, not absorbed.
- The
dds_fastHDL, and therefore every dynamic result in Sections 9.D–9.H, does not exist yet; and every expected value in this guide is calculated from the schematic and datasheet rather than measured on hardware.