Skip to content

Add a device verification command - #37

Open
glopesdev wants to merge 40 commits into
mainfrom
verify-command
Open

Add a device verification command#37
glopesdev wants to merge 40 commits into
mainfrom
verify-command

Conversation

@glopesdev

@glopesdev glopesdev commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

harp.toolkit verify checks a device against the Harp specification and reports where its behavior departs from what the standard requires, printing to the console as the run proceeds and optionally writing a shareable HTML report. Conformance in this ecosystem has rested on word of mouth and on each maintainer reading the specification text, so a core or device developer has had no way to check a device independently. A report is an artifact that can be attached to an issue or a release to help back a conformance claim with evidence.

The command originates in #16, authored by @bruno-f-cruz, and those commits are preserved here.

What it checks

The suite declares 50 checks, 45 of them grouped one suite per core register, covering readability, payload length and type, read-only enforcement, operation control behavior and the register dump. Alongside those it measures round trip latency and synchronization clock alignment against a reference device, and validates a device against its own device.yml by generating an interface and parsing live replies with it. That set spans all three specification documents, so this is a conformance tool in substance rather than the benchmark the original name suggested.

docs/articles/verify.md documents the command, its options and the report.

The declared baseline

The original suite declared no target version while encoding several at once, so a failure could mean a device defect, an unreleased rule, or a rule that no longer exists.

A device is now held to the revision it declares in R_VERSION, falling back to v1 when that register is absent, unreadable or reads all zeros, since a device predating the register predates the version field. Checks encoding text outside the stable baseline are hidden unless the --prerelease option is supplied, and the excluded count is reported. Of the 50 checks, 28 run by default and 22 require --prerelease, which applies only to a device declaring the matching major version.

The report also names the harp-tech/protocol commit behind the checks, currently harp-tech/protocol@11b584b, linked to the tree at that commit. We can use this to decide disagreements with the specification text currently under ratification. A device matching the text at that commit while a check fails is a toolkit defect, and a check matching a text that is wrong is a specification issue. Bumping it is intended to be a deliberate act with its own commit, since it can change results.

The core register set comes from the core.yml asset embedded in Harp.Generators, so the package version fully determines the registers a run expects, and no copy of the schema is vendored here.

What it found

Measured against a Behavior device, WhoAmI 1216, hardware 2.0, firmware 3.3. The default run reports 24 passed, 0 failed and 4 skipped, the skips being the checks that need --clock-port or --metadata. With --prerelease it reports 42 passed, 4 failed and 4 skipped. Both runs were taken on hardware after the final change, with the report rendered and its header checked, and the project and documentation site both build at zero warnings.

All four failures are genuine device non-conformances against the v2 text the device itself declares, rather than test defects:

  • R_CORE_VERSION_H and R_CORE_VERSION_L report core 1.15 while R_VERSION reports PROTOCOL 2.0, where Device Registers and Operation requires the deprecated pair to equal the major and minor protocol version.
  • Writing BOOT_DEF or BOOT_EE to R_RESET_DEV is answered with a write acknowledgement, where both bits are read-only state that must be answered with an error reply. The ATxmega handler tests four exact equalities and then falls through to success, so the write matches nothing, changes nothing, and is reported as having succeeded.

Reliability

A conformance tool has to be reproducible before anyone can act on its output, and two properties get it there.

The run opens one connection rather than one per check, taking 49 port transitions down to one, which halved the runtime and removed an intermittent disagreement between back-to-back runs of an identical build.

Requests carry a 2000 ms reply timeout and fail with a TimeoutException. Without it a device that never answers a register stalls the whole run rather than failing one check, which is reachable on real firmware: core.pico drops any request above its core register set with a bare return and no reply.

New dependencies

Four package references are added to a tool shipped through the dotnet tool channel, and Harp.Generators moves from 0.6.0 to 0.7.0 for the embedded core schema. RazorLight renders the HTML report, Spectre.Console renders the console output, and Microsoft.CodeAnalysis.CSharp with Microsoft.Extensions.DependencyModel compile the generated interface at runtime for the --metadata check. PreserveCompilationContext is required by RazorLight.

Known limitations

The synchronization clock measurement is usable but still developing, and the open question of acceptable alignment tolerances requires further tests and discussion.

Verification always mutates the device. Conformance cannot be established without exercising writes, read-only enforcement and event streams, so there is no read-only mode, and the run assumes a freshly powered device.

bruno-f-cruz and others added 30 commits March 25, 2026 08:56
The command, its folder, namespace and class are renamed from benchmark
to verify, so the name describes conformance against the Harp
specification rather than just the smaller part that measures timing.

The measurement types keep the benchmark name, since
NumericBenchmarkResult, BenchmarkSummary and BenchmarkRoundTrip do
measure latency.
Replaces the literal register addresses and lengths across the
suites with the constants declared by the client register classes.
The four addresses the client cannot name are written in decimal to
match the numbering used by the specification and by core.yml, and
the UID address becomes internal so the serial number suite reads
it instead of repeating the value.

R_VERSION has no client constant either, so it gains Version,
VersionPayload and SemanticVersion, mirroring the interface the code
generator produces for a register carrying a payload specification.
Each version field is read as three bytes, major, minor and patch,
rather than as HarpVersion, which has no patch component. The
Version suite gains a test reporting the parsed PROTOCOL, FIRMWARE,
HARDWARE, CORE_ID and INTERFACE_HASH fields.

Renames the PPS option to --pps-event, replacing a partial rename
that left --pps-address in the option and --reg-clock in the skip
message, so the skip message named a flag the command rejected. The
same file also drops a conditional access on a non-nullable result
property, which was the only build warning on the branch.
The progress line and the line clear that precedes each result are
now written only when output is not redirected. Piping the command
to a file or running it under CI would otherwise fail on
Console.WindowWidth, which is unavailable in that case. Redirected
output now carries only the result lines.
The read-only probes for R_HEARTBEAT, R_TAG and R_VERSION now send a
payload matching the register type and length. The timestamp
writability test compares the absolute difference, so a device that
ignores the write no longer passes when its clock reads below the value
written. The register dump requires all twenty allocated core addresses
rather than the first eighteen, and its core boundary now excludes
address 32, the first application register.

The three operation control event tests now write distinct bit
combinations: heartbeat only, alive only, and both. The heartbeat test
previously also set ALIVE_EN and the indicator bits. Restores are
wrapped so a failed restore no longer replaces the test result.

On the command, --clock-samples is rejected when not positive, since
zero yielded NaN statistics, and the unreachable report filename
fallback is removed.
Tests now receive a shared connection instead of a port name, so a run
opens the device once rather than once per test. A sweep goes from 49
port openings to one, or two when a reference clock device is
configured, and completes in about half the time.

Repeated runs of the same build now produce the same result for every
test that does not depend on host timing. A denied open is retried
against a ten second deadline rather than failing immediately.
The benchmark now times each command with a stopwatch instead of
differencing consecutive device reply timestamps, removing the
dependency on the internal timestamp resolution of the device, which
the protocol quantizes to 32 microseconds. It reports the resulting
statistics and always passes. Mean, median, standard deviation,
minimum, maximum and percentiles still appear in the console table
and the HTML report.
A failed read of the register would leave the restore writing zero,
putting the device in Standby with every flag cleared. The original
value is now nullable and the restore is skipped when it is absent.
The reset device suite now checks that a read reports exactly one boot
provenance bit with every command bit cleared, so the only legal values
are 0x40 and 0x80, and that writes setting BOOT_DEF or BOOT_EE are
refused, since both are read-only state.

Both write probes fail on current ATxmega firmware, which answers them
with a write reply rather than an error.
The register dump test now resolves the expected addresses and payload
types from the core register metadata embedded in Harp.Generators,
rather than from a hardcoded range of twenty addresses. It requires the
fifteen declared addresses, ignores any beyond them, and now also fails
when a reply carries a payload type the schema does not declare.

The generators reference moves to 0.7.0, which is what pins the schema
the test reads.
Tests asserting specification text outside the stable baseline are
marked with Prerelease on HarpTestAttribute and excluded during
collection, so the progress counter and the report cover only what
runs. The new --prerelease option includes them. Twenty-two of the
fifty-one tests are marked, and the Behavior device now passes the
whole default run.
Verification now reads the protocol version declared by the device
and holds it to that revision. A device with no R_VERSION falls back
to v1, and a version beyond coverage is reported before the run
continues against v1. The --prerelease option also requires a device
declaring v2, so on a v1 device it reports having no effect rather
than silently including nothing. The console states the declared
version and the revision applied, and the report header adds both
plus the generator version supplying the register set.
The serial number test asserted a derivation from R_UID that was
added after the v1.13.0 tag and removed again in protocol 225, so it
failed on every conformant device. It is replaced by a readability
test, which the deprecated register rules still require. The
timestamp offset writability test asserted the opposite of every
version of the specification, where the register is marked writable
at the tag and on main, and is removed.

The two operation control heartbeat tests are marked prerelease,
since they write a bit and await events from a register the tagged
specification does not define. A device without R_VERSION now passes
the whole default run, which is 28 tests.
The verify option naming the device interface file was --device-yml,
which named a serialization format rather than the content, and
diverged from the word the toolkit already uses for that file in
DeviceMetadata, ReadDeviceMetadata and MetadataPathArgument.
The shared connection now requests standby mode with register dumps
disabled, so the board no longer streams application events during a
run. The identity read gains a two-second deadline per attempt. Each
phase now starts its own retry budget rather than sharing one, so the
connection always gets its full retry window. Both phases also retry
transport errors and timeouts.

The run no longer hangs. Stale bytes on a reopened port still produce
an occasional parse warning, but the parser resynchronizes and the
run completes.
Writing the register sets the integer seconds and leaves the
sub-second counter running, so the clock can cross a second boundary
before it is read. The bound now allows one full crossing and is
directional, so a clock reading below the written value fails instead
of passing. The failure message reports the reply timestamp against
the expected range.
The device identity is read once before the suite runs, each read
bounded by its own 2000 ms deadline, so an unanswered read shows as
not reported rather than stalling the run. The console prints one
identity line and the report header carries WhoAmI, serial port,
hardware version and firmware version. The report is now titled with
the device name, with a Harp conformance report subtitle in place of
the toolkit badge.
Requests sent through CommandAsync now fail with a TimeoutException
when the device does not reply within 2000 ms, so an unanswered
request fails only that test instead of stalling the whole run. The
reply timeout, the identity reads and the connection probe share one
constant.

The protocol version read moves onto the connection as
ReadProtocolVersionAsync, taking the payload length check and the
all-zero normalization with it, which leaves ProtocolTarget a plain
record struct over its declared version and the prerelease flag.
The report header now names the harp-tech/protocol commit behind the
checks, linked to the repository tree at that commit.
Writing a past timestamp is now accepted only when the clock reads
forward of the written value, where the previous absolute difference
also accepted a clock behind it. The one second tolerance is
unchanged, since this check compares integer seconds and a boundary
crossing shows as exactly one.

The round trip suite description no longer claims a set of tests that
write registers, since it is one test that reads WhoAmI and reports
latency statistics. The boot provenance failure message no longer
cites the rule that a device without non-volatile memory must set
BOOT_DEF. That rule postdates the stable baseline, where the check
itself runs.
A new article covers running a verification, how the specification
revision is determined, the report structure and how to act on a
reported failure, the synchronization clock checks and the declared
interface checks, with every option documented. The readme gains a
section pointing at it, and the article joins the table of contents
after code generation.
@glopesdev glopesdev added the feature New planned feature label Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New planned feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants