Skip to content

dreamsourcelab-dslogic: V2 simple-trigger encoding (follow-up to #292)#293

Open
huehuehuehueing wants to merge 9 commits into
sigrokproject:masterfrom
huehuehuehueing:dslogic-plus-0034-pr-b
Open

dreamsourcelab-dslogic: V2 simple-trigger encoding (follow-up to #292)#293
huehuehuehueing wants to merge 9 commits into
sigrokproject:masterfrom
huehuehuehueing:dslogic-plus-0034-pr-b

Conversation

@huehuehuehueing

Copy link
Copy Markdown

Summary

Adds real trigger encoding to the V2 (DSLogic Plus Pango) path. Before this, v2_set_trigger was a no-op stub and the DSL_setting trig_* fields were hardcoded to "always true" — so -t flags on V2 devices were silently dropped and captures started immediately on arm. With this, sigrok-cli -t '3=f' (CS-falling-edge) works the same way it does in DSView.

Stacked on #292 (and transitively #291). The diff here will narrow to one commit once the bases land.

What's in this PR (1 commit)

V2 simple-trigger encoding — walks sr_session_trigger_get()'s stages, packs per-channel matches (ZERO / ONE / RISING / FALLING / EDGE) into stage 0 of the DSL_setting's trig_mask0/value0/edge0 (mirrored to mask1/value1/edge1), and enables the FPGA's trigger comparator via mode bit 0 (TRIG_EN). Without TRIG_EN, the FPGA ignores the trig_* fields entirely; this was the missing piece behind the first iteration looking like the encoding was wrong.

Trigger position from capture_ratio, aligned to the FPGA's 64-sample atomic unit, clamped to 10% in streaming mode and 90% in buffered mode (matches DSView's DS_MAX_TRIG_PERCENT). trig_glb packs the enabled-channel count + stage count per DSView's layout.

Encoding mirrors DSView's SIMPLE_TRIGGER path: only stage 0 carries the user condition; remaining stages stay as mask=0xffff / logic=2 ("always true"). Sufficient for the single-condition / single-channel triggers sigrok-cli's -t exposes (SR_TRIGGER_MATCH list in the driver advertises ZERO / ONE / RISING / FALLING / EDGE).

Verification

End-to-end: 100 MHz buffered RLE on channels 0..3 with -t '3=f' (CS-falling) captures the actual SPI traffic on a live SX1276 bus. With a plain spi decoder downstream, transactions decode cleanly — burst register reads/writes and FIFO accesses across the post-trigger window.

Test plan

  • make builds clean
  • sigrok-cli -d dreamsourcelab-dslogic -C 0,1,2,3 -c samplerate=100000000 -c rle=on --samples 10000000 -t '3=f' -O bits waits for the trigger then captures
  • Captures without a trigger still start immediately (no behaviour change for untriggered runs)
  • -t '0=r', -t '0=f', -t '0=0', -t '0=1', -t '0=e' each gate correctly on the corresponding edge / level / change

huehuehuehueing and others added 9 commits May 30, 2026 18:52
…0034)

Adds support for the DSLogic Plus hardware revision that enumerates
as USB ID 2a0e:0034 ("USB-based DSL Instrument v2") and ships with a
Pango FPGA bitstream. This revision speaks a newer envelope-style
control protocol than the existing DSLogic family; the driver gains a
parallel V2 path while leaving the V1 (flat-opcode) path untouched.

Architecture
------------

- struct dslogic_protocol_ops vtable in protocol.h with one slot per
  operation whose wire format differs between protocol versions
  (fpga_firmware_upload, fpga_config, acquisition_start/stop,
  set_samplerate/voltage_threshold/trigger/external_clock/clock_edge,
  security_check).
- enum dslogic_protocol_version per profile (DSL_PROTO_V1 or
  DSL_PROTO_V2). All existing profiles tagged V1; PID 0x0034 tagged V2.
- dev_context gains a cached ops pointer resolved at dev_open.
- protocol_v1.c (new): thin facades over the existing V1 functions,
  bound into dslogic_v1_ops. Pure refactor with no wire changes.
- protocol_v2.{c,h} (new): the envelope-protocol implementation.

Wire format (V2)
----------------

- Three control opcodes only: CMD_CTL_WR=0xb0, CMD_CTL_RD_PRE=0xb1,
  CMD_CTL_RD=0xb2. The packed struct ctl_header (dest, offset, size)
  carries the real destination via a DSL_CTL_* enum
  (HW_STATUS, INTRDY, WORDWIDE, START, STOP, BULK_WR, NVM, I2C_REG,
  I2C_STATUS, PROG_B, LED, FW_VERSION).
- command_ctl_rd_v2 is two-phase: PRE (OUT) writes the header, 10 ms
  sleep, then RD (IN) collects the requested bytes.
- Register R/W (dsl_wr_reg_v2 / dsl_rd_reg_v2) wraps the envelope with
  I2C_REG (write) / I2C_STATUS (read) destination and a 1-byte address
  in offset. NVM reads use DSL_CTL_NVM.

Bitstream upload (V2)
---------------------

Mirrors DSView's dsl_fpga_config sequence:
PROG_B low -> LED off -> PROG_B high -> wait FPGA_INIT_B -> INTRDY
low -> DSL_CTL_BULK_WR with 3-byte filesize -> bulk transfer bitstream
on ep2 OUT -> INTRDY high -> wait GPIF_DONE -> INTRDY low -> wait
FPGA_DONE -> LED green -> WORDWIDE high.

Security challenge (V2)
-----------------------

The 0x0034 firmware gates capture on an 8-step challenge-response over
an I2C register block (SEC_CTRL_ADDR=0x73, SEC_DATA_ADDR=0x75). The
encryption blob is read from device NVM at SECU_EEP_ADDR=0x3C00 via
DSL_CTL_NVM. Implemented as v2_security_check; called from dev_open
after the FPGA bitstream is loaded. Mirrors DSView's dsl_secuCheck.

FPGA arm (V2)
-------------

WORDWIDE -> DSL_CTL_BULK_WR (3-byte word count = sizeof(setting)/2 =
186) -> poll bmSYS_CLR -> bulk transfer struct DSL_setting (372 bytes)
on ep2 OUT -> DSL_CTL_INTRDY -> read HW_STATUS once and check
bmGPIF_DONE. The DSL_setting layout (with its (register_index << 8) |
word_count header encoding) and the samplerate divider math
(hw_max=500 MHz, pre_div=5 for DSLogic Plus pgl12) mirror DSView's
dsl_fpga_arm. Sample count is shifted right by 4 because the FPGA's
minimum capture unit is 16 samples.

V2 acquisition stop is two-stage like DSView's: write CTR0_ADDR :=
bmFORCE_RDY first (soft FPGA abort that releases the GPIF capture
engine and resets the green LED to solid), then DSL_CTL_STOP.

dev_open changes
----------------

- Extends has_firmware product-string probe to recognise "USB-based
  DSL Instrument v2" so the V2 device skips the legacy FX2 firmware
  upload (which would renumerate it to 0x0020 with the V1 firmware).
- V2 path performs DSL_CTL_FW_VERSION + DSL_CTL_HW_STATUS reads at the
  start of dev_open (matches DSView's hw_dev_open + dsl_dev_open
  initialisation). The pre-existing V1 firmware version probe at
  bRequest 0xb0 collides with V2's CMD_CTL_WR opcode and is now
  V1-gated.
- After bitstream upload + security check, an initial voltage threshold
  is written to VTH_ADDR via the new vtable slot.

Tested on real PID 0x2a0e:0x0034 hardware: scan, FPGA bitstream upload,
security challenge, FPGA arm, sample capture (100 samples / 16 channels
at 1 MHz returns 1959 bytes), and clean stop with idle-LED state.

Co-authored-by: Larry Hernandez <l.gr@dartmouth.edu>
After a close/reopen cycle (e.g. pulseview Stop then Run again) the
kernel-side endpoint state can take a few ms to settle. The next
dev_open's libusb_claim_interface then returns LIBUSB_ERROR_BUSY even
though no other process holds the interface.

Retry up to 10 times at 50 ms intervals (500 ms worst case) before
giving up with the original error message.

Independently testable: cycle PulseView's Stop and Run buttons
repeatedly on the same device without replugging; the BUSY error
should no longer surface.
…al open

Two related fixes for the PulseView Stop->Run cycle:

1. v2_fpga_firmware_upload now reads HW_STATUS first and skips the
   PROG_B/bitstream sequence if bmFPGA_DONE is already set. In that
   case it just writes CTR0_ADDR=0 ("dessert clear"), mirroring
   DSView's behaviour in dsl_dev_open's already-configured branch
   (dsl.c). Re-running the full PROG_B cycle on a live FPGA
   wedges the post-INTRDY FPGA_DONE poll because the previous capture
   engine has not been torn down on the host side. Symptom was:

     sr: dreamsourcelab-dslogic: Timeout waiting for HW_STATUS bit 0x40

2. dev_open now releases + closes the USB handle on any post-claim
   failure (fpga_firmware_upload or security_check returning non-OK).
   libsigrok does not call dev_close on a failed dev_open, so without
   this unwind the kernel still saw the interface as claimed by us;
   the next dev_open's libusb_claim_interface then returned
   LIBUSB_ERROR_BUSY ("Another program or driver has already claimed
   it") even with the retry loop added in 2ee3757. The retry loop
   stays as a belt-and-braces for genuinely transient cases.

Together these turn the "Stop, then Run again" PulseView cycle into a
reliable no-op (the device is already configured, we skip the upload,
arm runs against the warm FPGA).
DSLogic Plus exposes seven distinct channel-count/samplerate presets in
DSView (DSL_BUFFER100x16 / DSL_BUFFER200x8 / DSL_BUFFER400x4 plus four
stream modes from 20 to 100 MHz). Each preset has its own FPGA base
clock and pre-divider, so the samplerate divider math has to key off
the active preset. This commit introduces the table and uses the
profile's default preset (DSL_BUFFER100x16: 16 channels buffered, max
100 MHz, hw_max=100 MHz, pre_div=1).

Foundation for a follow-up commit that exposes SR_CONF_CHANNEL_MODE
so users can pick a different preset (more channels at lower rates,
or fewer channels at higher rates / streaming).

This also fixes a latent bug: the previous implementation hardcoded
hw_max=500 MHz and pre_div=5 (values that belong to the _3DN2 mode
variants the DSLogic Plus does not enable), producing a div_h value
that almost certainly resulted in a sample rate different from the
one the user requested. After this change, the divider matches
DSView's computation for the active channel mode.

V1 hardware is untouched: the ch_mode_id field on dev_context is V2
only, and the V1 path does not look at it.

Adds the relevant DS_MODE_* bit positions to protocol_v2.h and sets
DS_MODE_STREAM_MODE_BIT in setting.mode for stream channel modes.
ch_en mask is derived from the active mode's num_channels (16-bit only;
the DSLogic Plus has no channels above 15).
Wire dslogic_plus_auto_pick_mode_id into the V2 set_samplerate path
and re-pick at arm time. The auto-pick chooses the smallest-channel
mode whose max_samplerate covers the requested rate (minimising USB
bandwidth so high samplerates can stream cleanly).

sigrok-cli orders -c flags independently of -C, so the enabled-
channel mask may still be stale at config_set time; the arm-time
re-pick in v2_build_default_setting sees the FINAL mask just before
the FPGA arm.

When no mode covers (requested samplerate x enabled channels), clamp
cur_samplerate down to the picked mode's max_samplerate and warn so
the user knows to drop a channel or switch off continuous mode.
Avoids a silent "Device only sent N samples" USB-bandwidth abort
mid-acquisition.
…wer mode

V2 captures at 50/100/200 MHz buffered with all 16 channels enabled
were sending ~100-200 MB/s on the USB IN endpoint - twice USB 2.0 HS's
~50 MB/s ceiling. The FPGA-side buffer filled with pre-arm idle data
and real bursts never made it across.

Two interlocking changes:

1. v2_build_default_setting derives setting.ch_en_l from the sigrok
   enabled_channel_mask intersected with the active mode's capability
   cap, instead of unconditionally enabling channels 0..num_channels-1.
   With sigrok-cli's -C 0,1,2,3 (or PulseView's channel checkboxes),
   the FPGA now only captures those channels, dropping the IN-rate.

2. dslogic_plus_auto_pick_mode_id now takes need_channels =
   max_enabled_index+1 and picks the smallest num_channels mode that
   satisfies BOTH the requested samplerate AND need_channels. At
   50 MHz with -C 0,1,2,3 this picks DSL_BUFFER400x4 (4-channel mode,
   max 400 MHz) which streams at 25 MB/s instead of 100 MB/s.

Promotes enabled_channel_count and enabled_channel_mask from file-
static to SR_PRIV so V2 code can use them.

Independently testable:
  sigrok-cli -d dreamsourcelab-dslogic -C 0,1,2,3 \
      -c samplerate=50000000 --samples 100000000 -O bits
captures real SPI activity (verified against DSView CSV reference of
the same DUT: SCK on ch0, MOSI on ch1, MISO on ch2, CS on ch3 - all
four lines show the expected DUT-reset burst pattern).
Wire three V2-only configuration knobs through the libsigrok config
API and into the DSL_setting blob's mode bitfield at arm time:

- SR_CONF_RLE        -> mode bit 3 (RLE_MODE)
- SR_CONF_FILTER     -> mode bit 8 (FILTER)
- SR_CONF_EXTERNAL_CLOCK -> mode bit 1 (CLK_TYPE)

These give applications access to the DSLogic Plus FPGA's run-length
encoding (compresses idle samples to fit USB 2.0 HS bandwidth at
high samplerates), the 1T glitch filter (suppresses single-sample
spikes in noisy environments), and external clocking.

SR_CONF_FILTER is wired as SR_T_BOOL (matches its registration in
libsigrok's config table), not a string enum; passing a string would
make PulseView's libsigrokcxx bindings throw std::bad_cast when the
runtime type didn't match the declared boolean type.
The FPGA's trigger-position header reports remain_cnt in two halves
(remain_cnt_l/h, 64-bit total). In BUFFERED mode with RLE this is
the count of samples the FPGA was short of limit_samples (the
compressed buffer ran out of room before reaching the requested
capture length); without honoring it, the acquisition-stop check
never trips, sent_samples never reaches limit_samples, and the
capture hangs until the empty-transfer timeout aborts.

Stash the FPGA-actual count in dev_context.actual_samples and use
it (when non-zero) as the acquisition-stop budget. Falls back to
limit_samples for non-RLE captures.

Gate the shortening to !continuous_mode. In streaming mode
remain_cnt is an in-flight "samples remaining to send" counter
that updates continuously as the FPGA streams; subtracting it
from limit_samples gives a meaningless small number that would
end streaming captures almost immediately on the first trigger
header. In streaming mode let limit_samples remain the budget.
Wire libsigrok's sr_session_trigger to the V2 DSL_setting trig_*
fields and enable the FPGA's trigger comparator. Before this patch
the V2 driver had a no-op v2_set_trigger stub and hardcoded
"always-true" trig defaults, so -t flags were silently dropped and
captures started immediately on arm.

Encoding mirrors DSView's SIMPLE_TRIGGER path:

- v2_encode_trigger walks the session trigger stages and packs
  per-channel ZERO/ONE/RISING/FALLING/EDGE matches into
  trig_mask0/value0/edge0[0] (mirrored to the *1 halves), with
  unused stages left as mask=0xffff/logic=2 ("always true").

- trig_glb gets the enabled-channel count in its upper 5 bits and
  (num_stages - 1) in the low byte.

- trig_pos is computed from capture_ratio (percentage of
  limit_samples that should sit before the trigger), clamped to
  10% in streaming mode and 90% in buffered mode and aligned to
  the FPGA's 64-sample atomic unit.

- mode bit 0 (TRIG_EN) is set when at least one stage has matches.
  Without this bit the FPGA ignores the trig_* fields entirely;
  this was the missing piece that made the first iteration look
  like the encoding was wrong.

Verified end-to-end: 100 MHz buffered RLE on channels 0..3 with
-t '3=f' (CS-falling) captures the SX1276's actual SPI traffic,
sigrok_pd:chip=sx1276 decodes 21 register transactions including
Burst R/W RegOpmode and FIFO reads in a 0.1s window.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant