Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/embeddedci-mcp/tests/test_e2e_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def test_current_loop_terminals(connected):
assert rng["current"] is None and 0.0039 < rng["min_current"] < 0.0042 < 0.0199 < rng["max_current"]
held = call("current_out", current=0.012)
assert abs(held["current"] - 0.012) < 2e-6
if connected["firmware"].get("board_rev") == "v3r2":
pytest.skip("quad DAC: the 5 V loopback does not see the 4-20 mA output; needs the J9 fixture")
want = (0.012 - rng["min_current"]) / (rng["max_current"] - rng["min_current"]) * 65535 / 65536 * 4.99
time.sleep(0.1)
assert abs(call("adc_read", source="cal1")["voltage"] - want) < 0.1
Expand Down Expand Up @@ -212,7 +214,7 @@ def test_rev3_tools(connected):
if not connected["firmware"].get("nrst_pin"):
pytest.skip("not a rev3 pod")
caps = connected["capabilities"]
assert caps["board_rev"] == "v3" and caps["nrst_pin"] and caps["usb_cc"], caps
assert caps["board_rev"] in ("v3", "v3r2") and caps["nrst_pin"] and caps["usb_cc"], caps
try:
assert call("set_la_voltage", voltage=1.8) == {"voltage": 1.8, "readback": 1.8}
finally:
Expand Down
16 changes: 16 additions & 0 deletions packages/embeddedci/tests/e2e/e2e_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@
ANALOG = pytest.mark.benchpod_capability("analog")


def has_dac_mux(pod: BenchPod) -> bool:
"""One DAC behind output muxes (v3 and earlier). False on the quad DAC (analog v3r2 and later):
every output has its own channel, so the 5 V loopback does not see the 4-20 mA output."""
try:
pod.command({"cmd": "dac_mux"})
except FirmwareError:
return False
return True


def require_shared_dac(pod: BenchPod) -> None:
"""Skip where the 4-20 mA output has its own DAC channel: only the J9 fixture can read it."""
if not has_dac_mux(pod):
pytest.skip("quad DAC: the 5 V loopback does not see the 4-20 mA output; needs the J9 fixture")


def require_analog(pod: BenchPod) -> None:
"""Skip on the digital-only board, for tests on a pod the ``ANALOG`` marker does not see (the
cloud tier's own connection)."""
Expand Down
44 changes: 37 additions & 7 deletions packages/embeddedci/tests/e2e/test_e2e_pod.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
from embeddedci.benchpod.decode import decode_uart

from e2e_helpers import (ANALOG, HOST_CLOCK_REL, SETTLE, adc_levels, events_during, host_clock_rate, p2p,
split_levels)
split_levels, has_dac_mux, require_shared_dac,
)

pytestmark = pytest.mark.hardware

Expand Down Expand Up @@ -197,8 +198,9 @@ def test_current_out_holds_a_current_and_the_dac_follows(pod):
want_code = q.code + (0.012 - q.current) / per_code
assert mid.current == pytest.approx(0.012, abs=2e-6) and abs(mid.code - want_code) <= 4, mid
time.sleep(0.1)
readback = pod.adc_read("cal1").voltage # routes the 5 V output: about 2.49 V
assert abs(readback - mid.code / 65536 * 4.99) < 0.06, (mid, readback)
if has_dac_mux(pod):
readback = pod.adc_read("cal1").voltage # routes the 5 V output: about 2.49 V
assert abs(readback - mid.code / 65536 * 4.99) < 0.06, (mid, readback)

top = pod.current_out(0.020) # switches the 5 V output off again
assert top.current == pytest.approx(0.020, abs=2e-6)
Expand All @@ -220,6 +222,7 @@ def test_waveforms_on_the_current_output_reach_the_dac(pod):
output while it is read). Stopping returns the DAC to the live zero."""
if not pod.capabilities.current_out:
pytest.skip("this pod's firmware has no current_out command")
require_shared_dac(pod)
rng = pod.current_out_range()

def dac_volts(amps: float) -> float:
Expand Down Expand Up @@ -315,6 +318,7 @@ def test_a_current_waveform_starts_on_the_capture_t0(pod):
capture's t0, so the capture opens on the first level and steps to the second."""
if not pod.capabilities.current_out:
pytest.skip("this pod's firmware has no current_out command")
require_shared_dac(pod)
if not pod.capabilities.dac_cotrig:
pytest.skip("the running gateware has no DAC co-trigger")
hi, lo = _loop_dac_volts(pod, 0.012), _loop_dac_volts(pod, 0.006)
Expand All @@ -339,6 +343,7 @@ def test_stop_dac_after_freezes_the_loop_current(pod):
so the loop HOLDS a current from the waveform. It does not fall back to 4 mA by itself."""
if not pod.capabilities.current_out:
pytest.skip("this pod's firmware has no current_out command")
require_shared_dac(pod)
lo, hi = _loop_dac_volts(pod, 0.006), _loop_dac_volts(pod, 0.012)
try:
handle = pod.generate("square", freq_hz=200, amplitude=0.003, offset=0.009, dac_path="current_out")
Expand All @@ -365,6 +370,7 @@ def test_dac_stop_alone_leaves_the_loop_where_it_was(pod):
4 mA takes `current_out(0.004)`, which is what the handles and the web Stop do."""
if not pod.capabilities.current_out:
pytest.skip("this pod's firmware has no current_out command")
require_shared_dac(pod)
try:
pod.current_out(0.012)
pod.dac_stop()
Expand All @@ -384,6 +390,7 @@ def test_a_current_switches_the_voltage_outputs_off(pod):
and it leaves the ADC relays alone (J8 stays routed)."""
if not pod.capabilities.current_out:
pytest.skip("this pod's firmware has no current_out command")
require_shared_dac(pod)
try:
pod.dac_output("5v", volts=0.0)
pod.analog_path("current_in")
Expand All @@ -396,6 +403,26 @@ def test_a_current_switches_the_voltage_outputs_off(pod):
pod.analog_path("off")


@ANALOG
def test_a_current_leaves_the_voltage_outputs_alone_on_the_quad_dac(pod):
"""Quad DAC: the 4-20 mA output has its own channel, so a current does not move the 5 V output."""
if not pod.capabilities.current_out:
pytest.skip("this pod's firmware has no current_out command")
if has_dac_mux(pod):
pytest.skip("one shared DAC: a current switches the voltage outputs off")
try:
pod.dac_output("5v", volts=2.0)
before = pod.adc_read("cal1").voltage
assert abs(before - 2.0) < 0.06, before
pod.current_out(0.012)
time.sleep(0.1)
assert abs(pod.adc_read("cal1").voltage - before) < 0.01
finally:
pod.current_out(0.004)
pod.dac_stop()
pod.analog_path("off")


@ANALOG
def test_current_in_reads_zero_with_j8_open(pod):
"""The 4-20 mA input (J8) with nothing wired: about 0 mA, and the current matches the voltage."""
Expand Down Expand Up @@ -722,7 +749,7 @@ def test_deep_replay_switches_to_the_deep_replay_image(loop_image):
np = pytest.importorskip("numpy")
pod = loop_image
period = [0.8] * 100 + [2.2] * 100 # 100 Hz at 20 kS/s
with pod.replay(period * 41, dac_path="5v", sample_rate_hz=20_000, route=False) as h:
with pod.replay(period * 41, dac_path="5v", sample_rate_hz=20_000) as h:
assert h.deep and h.switched_image is not None
assert h.switched_image.image == FpgaImage.DEEP_REPLAY
pod.analog_path("cal1") # after the switch, which resets the FPGA
Expand Down Expand Up @@ -775,9 +802,12 @@ def test_control_loop_sweep_advances(loop_image):
pytest.skip("the running gateware has no selectable loop input")
pod.analog_path("dac_3v3")
with pod.control_loop(curve=build_linear_curve(60000), source="sweep", step=256) as loop:
first = loop.probe().loop_input
time.sleep(0.05)
assert loop.probe().loop_input != first
# The sweep wraps every 256 ticks, so one probe can land on the same input by chance.
seen = {loop.probe().loop_input}
for pause in (0.013, 0.029, 0.05):
time.sleep(pause)
seen.add(loop.probe().loop_input)
assert len(seen) > 1, seen


@ANALOG
Expand Down
12 changes: 8 additions & 4 deletions packages/embeddedci/tests/e2e/test_e2e_rev3.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,16 @@ def _level(pod, ch: int) -> int:

# -- identity ----------------------------------------------------------------------------

def test_board_strap_reads_as_v3(v3):
def test_board_strap_reads_as_rev3_or_later(v3):
status = v3.status()
# R164 1 k over R163 10 k from +3V3: ~3.0 V. Near a bucket edge means a wrong part or a bad joint.
assert status["board_rev"] == "v3" and 2700 <= status["board_rev_mv"] <= 3250, status
# R164 over R163 10 k from +3V3: 1 k ~3.0 V (v3), 2.2 k ~2.7 V (v3r2). Near a bucket edge means
# a wrong part or a bad joint.
bands = {"v3": (2900, 3250), "v3r2": (2550, 2849)}
assert status["board_rev"] in bands, status
low, high = bands[status["board_rev"]]
assert low <= status["board_rev_mv"] <= high, status
caps = v3.refresh_capabilities()
assert caps.board_rev == "v3" and caps.nrst_pin and caps.usb_cc, caps
assert caps.board_rev == status["board_rev"] and caps.nrst_pin and caps.usb_cc, caps


def test_firmware_and_gateware_are_current(v3):
Expand Down