Skip to content

dreamsourcelab-dslogic: V2 channel modes, RLE/filter/clock, robustness (follow-up to #291)#292

Open
huehuehuehueing wants to merge 8 commits into
sigrokproject:masterfrom
huehuehuehueing:dslogic-plus-0034-pr-a
Open

dreamsourcelab-dslogic: V2 channel modes, RLE/filter/clock, robustness (follow-up to #291)#292
huehuehuehueing wants to merge 8 commits into
sigrokproject:masterfrom
huehuehuehueing:dslogic-plus-0034-pr-a

Conversation

@huehuehuehueing

Copy link
Copy Markdown

Summary

Follow-up to #291 (DSLogic Plus Pango variant baseline). Adds the V2 channel-mode table, samplerate-aware auto-pick, channel-enable wiring, RLE/glitch-filter/external-clock exposure, and a few robustness fixes. Strictly V2-path additions; the V1 path is untouched.

Stacked on top of #291. Until #291 merges, the diff here will include #291's baseline commit too. After #291 merges, this PR's diff shrinks to the 7 commits added by this PR.

What's in this PR (7 commits)

  1. retry claim_interface on transient BUSY — udev / autosuspend can hold the interface for a few hundred ms after open; retry up to 10× × 50ms before failing.
  2. gate V2 bitstream re-upload + unwind on partial open — skip the FPGA bitstream upload when bmFPGA_DONE is already set (warm re-open), and release the interface + close the handle on mid-init failure so the next open isn't stuck on LIBUSB_ERROR_BUSY.
  3. introduce DSLogic Plus channel-modes table — DSL_BUFFER100x16 / DSL_BUFFER200x8 / DSL_BUFFER400x4 / DSL_STREAM{20,25,50,100}x{16,12,6,3} as struct dslogic_channel_mode rows, mirroring DSView's channel_modes[].
  4. V2 channel-mode auto-pick + samplerate clamp — pick the smallest-channel mode whose max_samplerate covers the requested rate; re-pick at arm time so the final enabled-channel mask is seen; clamp + warn when no mode fits.
  5. wire V2 channel-enable to sigrok masksetting.ch_en from enabled_channel_mask(sdi) & mode_cap_mask, so the FPGA only emits the user-enabled channels and high samplerates fit USB 2.0 HS's ~50 MB/s ceiling.
  6. expose V2 RLE, glitch filter, external clock — three new SR_CONF knobs feeding mode bits 3 / 8 / 1 of the DSL_setting blob. FILTER is SR_T_BOOL (matches libsigrok's config-table registration).
  7. honor remain_cnt from trigger header (RLE) — in buffered RLE mode, the FPGA can capture fewer samples than requested when the compressed buffer fills; use remain_cnt_l/h from the trigger header to shorten actual_samples so acquisition exits cleanly. Gated to !continuous_mode; streaming uses remain_cnt differently.

What works

  • sigrok-cli recognises the device and runs captures at all stream and buffered modes; auto-pick chooses correctly for both -c continuous=on and -c continuous=off at samplerates from 10 kHz up to the per-mode cap.
  • -c rle=on / -c filter=on / -c external_clock=on propagate to the FPGA's mode bitfield.
  • 16-channel buffered RLE captures terminate at the actual FPGA sample count rather than hanging on the original limit_samples budget.
  • Repeated open/close cycles (e.g. PulseView reconnects) no longer fail on LIBUSB_ERROR_BUSY after a transient claim_interface error.
  • Build clean (no new warnings on the dreamsourcelab-dslogic translation units).

Test plan

  • make builds clean
  • sigrok-cli --scan recognises a connected 2a0e:0034 device
  • sigrok-cli -d dreamsourcelab-dslogic -C 0,1,2,3 -c samplerate=100000000 -c rle=on --samples 100000 -O bits completes within a couple of seconds
  • sigrok-cli -d dreamsourcelab-dslogic -C 0,1,2 -c samplerate=100000000 -c continuous=on --samples 5000000 -O bits runs at full 100 MHz without underrun (auto-pick lands on DSL_STREAM100x3)
  • Open/close cycles do not leak the USB interface
  • A V1-hardware user runs a small capture on an unmodified DSLogic / DSLogic Pro / DSLogic Plus (Spartan) to confirm no regression in the V1 path

huehuehuehueing and others added 8 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.
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