Measurement Traceability #

Litmus provides ATML-style traceability for every measurement, enabling compliance reporting, root cause analysis, and calibration tracking.

The framework automatically captures ALL metadata when a measurement is produced. No user effort required.

What is ATML? #

ATML (Automatic Test Markup Language) is an IEEE standard (IEEE 1671) for exchanging test information. It defines:

  • Standard vocabulary for test outcomes (IEEE-1671 uses PASS / FAIL / SKIP / ERROR; Litmus maps these onto its lowercase passed / failed / skipped / errored / done / terminated / aborted enum)
  • Standard comparator types (GELE, GTLT, EQ, NE, etc.)
  • Signal routing concepts (how measurements trace back to DUT pins and instruments)

Litmus adopts ATML terminology and concepts to enable interoperability with other test systems and compliance with industry standards.

Traceability Fields #

Every Measurement in Litmus includes traceability fields:

Measurement Signal Path #

FieldDescriptionExample
spec_refReference to specification"output_voltage"
dut_pinWhich DUT pin was measured"J1.3", "TP_VOUT"
instrument_nameStation config instrument name"dmm", "dmm_main"
instrument_resourceVISA address or connection"TCPIP::192.168.1.100::INSTR"
instrument_channelChannel on the instrument"CH1", "ai0", "1"
fixture_connectionFixture connection name"VOUT", "VIN_SENSE"

Stimulus Signal Path (Dynamic) #

For each input parameter, Litmus captures the full signal path:

Column PatternDescriptionExample
in_{param}Value commandedin_vin = 12.0
in_{param}_instrumentInstrument namein_vin_instrument = "psu_main"
in_{param}_resourceVISA addressin_vin_resource = "TCPIP::..."
in_{param}_channelChannelin_vin_channel = "CH1"
in_{param}_dut_pinDUT pin drivenin_vin_dut_pin = "VIN"
in_{param}_fixture_connectionFixture routingin_vin_fixture_connection = "vin_supply"

The Traceability Chain #

Every measurement can be traced from result back to source:

flowchart LR
    meas["Measurement Output"]
    spec["Product Spec<br/>(products/id.yaml)<br/><sub>Characteristic ID from datasheet</sub>"]
    pin["Product Pin Definition<br/><sub>Physical location: J1.3, net: VOUT</sub>"]
    fix["Fixture Config<br/>(fixture.yaml)<br/><sub>Maps DUT pin to instrument</sub>"]
    sta["Station Config<br/>(station.yaml)<br/><sub>Logical name: dmm_main</sub>"]
    res["Physical Connection<br/><sub>VISA: TCPIP::192.168.1.100::INSTR</sub>"]
    ch["Instrument Channel<br/><sub>Specific input: CH1, ai0</sub>"]
 
    meas -- spec_ref --> spec
    meas -- dut_pin --> pin
    meas -- fixture_connection --> fix
    meas -- instrument_name --> sta
    meas -- instrument_resource --> res
    meas -- instrument_channel --> ch
 
    stim["Stimulus Inputs<br/>(in_&#123;param&#125;)"]
    sinstr["Source instrument"]
    sres["VISA address"]
    sch["Channel on instrument"]
    spin["DUT pin driven"]
    sfix["Fixture routing"]
 
    stim -- in_{param}_instrument --> sinstr
    stim -- in_{param}_resource --> sres
    stim -- in_{param}_channel --> sch
    stim -- in_{param}_dut_pin --> spin
    stim -- in_{param}_fixture_connection --> sfix

Setting Traceability in Tests #

Automatic (via Fixture) #

When you use the pins fixture, traceability is captured automatically:

def test_output_voltage(pins, logger):
    # pins["VOUT"] knows:
    # - dut_pin (from product spec)
    # - instrument_name (from fixture)
    # - instrument_resource (from station)
    # - instrument_channel (from fixture)
    logger.measure("output_voltage", pins["VOUT"].measure_voltage())

Manual (Direct Instruments) #

When using instruments directly, set traceability manually:

def test_output_voltage(dmm, logger):
    voltage = dmm.measure_dc_voltage()
 
    logger.measure(
        "output_voltage",
        voltage,
        dut_pin="J1.3",
        instrument_name="dmm",
        instrument_channel="CH1",
    )

Using ProductContext #

For spec-driven traceability:

def test_output_voltage(dmm, verify):
    # verify resolves the limit and traceability from the active
    # ProductContext (configured via --product=products/power_board.yaml)
    verify("output_voltage", dmm.measure_dc_voltage())

Hierarchical Context #

The harness (Litmus's runner-agnostic execution wrapper) provides hierarchical context with scoped inheritance:

from litmus.execution.harness import TestHarness
 
harness = TestHarness(step_name="my_test")
 
# Run-level: visible to all steps and vectors
harness.run_context.configure("operator", "jane")
 
with harness.step():
    # Step-level: visible to all vectors in this step
    harness.context.configure("fixture.id", "FIX-01")
 
    with harness.run_vector(vector) as tv:
        # Vector-level: inherits from step and run
        harness.context.observe("temp_probe.temp", 24.8)
 
        # tv.params includes: operator, fixture.id, temp

Custom Metadata with run_context #

Add custom traceability fields that become Parquet columns:

def test_with_context(run_context, psu, dmm, logger):
    # Custom fields for your organization's needs
    run_context.set("operator_badge", "EMP-12345")
    run_context.set("fixture_serial", "FIX-001")
    run_context.set("ambient_temp", 23.5)
    run_context.set("calibration_due", "2026-06-15")
 
    # Normal test code...
    psu.set_voltage(5.0)
    logger.measure("output_voltage", dmm.measure_dc_voltage())

Comparators (ATML/IEEE 1671) #

The comparator field defines how values are compared against limits:

Range Comparators #

ComparatorMeaningPass Condition
GELEGreater-equal, less-equal (default)low <= value <= high
GELTGreater-equal, less-thanlow <= value < high
GTLEGreater-than, less-equallow < value <= high
GTLTGreater-than, less-thanlow < value < high

Single-Bound Comparators #

ComparatorMeaningPass Condition
GEGreater-equalvalue >= low
GTGreater-thanvalue > low
LELess-equalvalue <= high
LTLess-thanvalue < high

Equality Comparators #

ComparatorMeaningPass Condition
EQEqualvalue == nominal
NENot equalvalue != nominal

Setting Comparators in tests/test_<module>.yaml #

tests:
  test_output_voltage:
    limits:
      output_voltage:
        low: 3.135
        high: 3.465
        nominal: 3.3
        comparator: GELE  # Default: inclusive range
        units: V
        spec_ref: "output_voltage @ tolerance_pct=5"
 
  test_minimum_current:
    limits:
      load_current:
        low: 0.1
        comparator: GE  # Only lower bound: must be >= 0.1A
        units: A
 
  test_exact_value:
    limits:
      calibration_ref:
        nominal: 1.000
        comparator: EQ  # Exact match required
        units: V

Querying Traceable Results #

Results are stored in Parquet files at <data_dir>/runs/{date}/{timestamp}_{serial}.parquet (UTC timestamps).

By DUT Pin #

import pandas as pd
 
df = pd.read_parquet("data/runs/2026-01-15/20260115T143025Z_SN001.parquet")
 
# Find all measurements on pin J1.3
j1_3_measurements = df[df["dut_pin"] == "J1.3"]
 
# Find failures on specific pin
failures = df[(df["dut_pin"] == "J1.3") & (df["measurement_outcome"] == "failed")]

By Instrument #

# Find all measurements from the main DMM
dmm_measurements = df[df["instrument_name"] == "dmm_main"]
 
# Find measurements from specific VISA address
visa_measurements = df[df["instrument_resource"] == "TCPIP::192.168.1.100::INSTR"]

By Spec Reference #

# Find all measurements for output_voltage characteristic
output_v = df[df["spec_ref"] == "output_voltage"]

By Input Conditions #

# Find measurements at specific input voltage
high_vin = df[df["in_vin"] == 12.0]
 
# Find measurements across input conditions
print(df.groupby(["in_vin", "in_load"])["measurement_value"].mean())

Cross-Run Queries (DuckDB) #

-- Query all runs with full traceability
SELECT
    dut_serial,
    measurement_name,
    value,
    instrument_name,
    dut_pin,
    in_vin,
    in_load
FROM read_parquet('data/runs/**/*.parquet')
WHERE measurement_outcome = 'failed';

Compliance Reporting #

Traceability enables compliance reports that link:

  1. MeasurementSpec Requirement (via spec_ref)
  2. MeasurementTest Equipment (via instrument_name* fields)
  3. MeasurementDUT (via dut_pin and dut_serial)
  4. StimulusSource Equipment (via in_* fields)

Example compliance report structure:

Test Report: SN12345
──────────────────────────────────────────────────────
Requirement: output_voltage
  Source: products/power_board.yaml
  DUT Pin: J1.3 (VOUT_3V3)
  Instrument: dmm_main (Keithley 2000)
  Resource: TCPIP::192.168.1.100::INSTR
  Channel: CH1
 
  Input Conditions:
    VIN: 12.0V via psu_main (TCPIP::192.168.1.101::INSTR)
    Load: 0.5A via eload_main (USB0::0x1234::INSTR)
 
  Measured: 3.31 V
  Limits: 3.135 V to 3.465 V
  Outcome: passed
──────────────────────────────────────────────────────

Benefits of Traceability #

  1. Root Cause Analysis — When a test fails, identify exactly which instrument and channel were involved, plus the input conditions that triggered the failure

  2. Calibration Tracking — Link measurements to instrument calibration records via instrument_resource

  3. Fixture Debugging — Verify signal routing through the fixture via fixture_connection

  4. Specification Compliance — Prove that measurements satisfy specific spec requirements via spec_ref

  5. Audit Trail — Complete chain from measurement to DUT pin to datasheet reference

  6. Stimulus Correlation — Understand how input conditions affect outputs via in_* columns

Best Practices #

  1. Let the framework capture traceability — Most fields are auto-captured when using fixtures and ProductContext

  2. Use run_context for custom fields — Add organization-specific metadata that becomes queryable columns

  3. Use fixtures for complex routing — Let the framework handle signal path traceability automatically

  4. Use meaningful DUT pin names — Match your schematic/PCB designators in product spec

  5. Query with DuckDB for big data — Use glob patterns to analyze across all runs:

    SELECT * FROM read_parquet('data/runs/**/*.parquet')

See also #