Compare commits

...

246 Commits

Author SHA1 Message Date
Marian Koop
d1be63490e doc: Update CHANGELOG for commit info since v4.11.0.0 tag 2026-10-13 12:45:06 +02:00
Martin Braun
30aab2a4c7 lib: Add missing include
Adds missing <string> to ltc5594.hpp.
2026-10-13 12:41:50 +02:00
Martin Anderseck
dd1dbad6e7 test: Add unit test for LTC5594
This test checks if the correct values are returned for a frequency
within the range of the lookup table and for frequencies above and below
the range.
2026-10-13 12:41:50 +02:00
Martin Anderseck
eab0f7fe89 hbx: Fix LTC freq lookup logic
Previously the logic for the LTC5594 configuration was wrong. We
sometimes fell back into the default case which then didn't choose the
default but the first row in our lookup table. The default value was now
moved into an own constant and the lookup logic is now inverted to be
correct.
2026-10-13 12:41:50 +02:00
Grant Meyerhoff
c4ae50a9cb docs: add subsection on rfnoc_nullsource_ce_tx example 2026-10-09 14:51:32 +02:00
Virendra Kakade
ca8d6c9fab examples: Add new rfnoc_nullsource_ce_tx example
- This example will let you tx data from the host to the NullSRcSink
  block on the FPGa design. This is complementary to the
  rfnoc_nullsource_ce_rx example.

Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-10-09 14:51:32 +02:00
Rui Chen
621fda4633 lib: use two-level namespace on macOS
Signed-off-by: Rui Chen <rui@chenrui.dev>
2026-10-08 14:43:08 +02:00
Abhishek Verma
b1e77b24e4 mpm: Add rtnetlink-driven xport_info cache invalidation
-Add gevent rtnetlink listener for interface and IP changes.
-Refresh cached transport info immediately after network changes.
-Extend x4xx cache TTL to 300 seconds with epoch-based invalidation.
2026-10-08 09:40:00 +02:00
Abhishek Verma
5745c8a640 mpm: x4xx: cache xport_info to speed up GetDeviceInfo
get_xport_info() enumerates network interfaces via netlink/pyudev on every
GetDeviceInfo call (~350ms, non-yielding). Since GetDeviceInfo is
unclaimed and gets flooded during discovery, this could starve claimed
RPCs past their short deadlines like 2 seconds.

Cache the result for 10s (X400_XPORT_INFO_CACHE_TTL); it's stable per
session, and init() drops the cache since IPs may change. This turns most
GetDeviceInfo calls much faster.

Add a dedicated lock for the xport_info_cache so concurrent
GetDeviceInfo calls don't race on refresh/invalidations
2026-10-08 09:40:00 +02:00
Wade Fife
0b7f642d4a fpga: tools: Add support for DCP files as source 2026-10-08 09:32:01 +02:00
Max Köhler
f9ab8f3aae host: x4xx: update default wait time for NCO reset 2026-10-08 09:31:17 +02:00
Max Köhler
020608b971 fpga: x4xx: fixed sequence for RFDC NCO reset 2026-10-08 09:31:17 +02:00
Abhishek Verma
00c16e56e3 mpm: add unit tests around grpc/protobuf helpers
pb_utils.py contains several helpers to translate between
Protobuf and Python datatypes/messages including other utility
functions. This change adds a bunch of unit tests to cover
various aspects of its usage by rpc_server.py.
2026-10-06 14:19:08 +02:00
Max Köhler
dad6f906f9 fpga: remove X420 variants without DDC/DUC 2026-10-06 08:54:58 +02:00
Ahmed Salman
54f185ecdd ci: x420: add weekly hardware test coverage 2026-10-06 08:51:20 +02:00
Frank Dietze
e4672044f9 ci: Windows installer: add retry to checkout 2026-10-05 16:43:49 +02:00
Martin Braun
0cf40c3276 rfnoc_image_builder: Fix .grc builds for empty params
Some blocks (e.g., the fosphor block) don't have parameters in their
respective YAMLs, but the GRC file importer makes the assumption that
there are always parameters, and will crash on a faulty dictionary
lookup when that's not the case. This conditionally checks parameters,
making the lack of parameters non-fatal.
2026-10-05 15:04:22 +02:00
Max Köhler
8ad3b4961d ci: fpga: refactor IP cache 2026-10-05 09:05:39 +02:00
Javier Valenzuela
5709e8b546 fpga: lib: adjust ctrlport_to_wb_uart signal width 2026-10-01 13:02:57 -05:00
Martin Anderseck
1907032f7b firmware: Add descriptors for B2xx mini variants
The current firmware missed descriptors for B200-mini and
B205-mini. Added the missing descriptors.

Also this commit ammends the firmware howto. It specifies
the SDK version we are using as well as some additional
command applied to the SDK files to make the patch successful.

Co-authored-by: Lars Amsel <lars.amsel@emerson.com>
2026-09-30 11:54:00 +02:00
Martin Anderseck
155dfbf352 fx3: Upgrade B2xx build
This is an upgrade to the latest FX3 SDK (1.3.5) and to the ARM gcc in
version 15.3.rel1 for the build of the B2xx firmware and bootloader. All
required steps are explained in the updated readme file.
Additionally, some bounds checks were added both in the firmware and the
bootloader, as well as some more validations and checks to prevent over-
and underflows. Newly introduced checks were documented in the code.
2026-09-30 11:54:00 +02:00
Max Becker
416988b3b6 docs: Document X4xx GPIO SPI support
Document the updated X4xx GPIO SPI interface and transaction-width
behavior.
2026-09-29 13:12:32 +02:00
Max Becker
3ed25c39c7 host: Complete X4xx GPIO SPI support
Update the X4xx SPI core, radio-control integration, examples, and
tests for transactions up to 64 bits and the revised register map.
2026-09-29 13:12:32 +02:00
Max Becker
b1e4016c2b mpm: Update X4xx GPIO SPI support
Update MPM GPIO handling for the updated X4xx GPIO SPI register
interface.

Require the FPGA compatibility number bump introduced with the new
SPI support so MPM does not operate against an incompatible FPGA image.

Bump MPM compat number.
2026-09-29 13:12:32 +02:00
Max Becker
148c861163 host: Extend the SPI interface
Introduce 64-bit SPI transaction methods into the common spi_iface API,
default implementation throws an uhd::unsupported_operation exception.
Add explicit 32-bit aliases for the old/existing SPI transactions
methods.

Expose the width-specific operations through the RFNoC Python bindings.
2026-09-29 13:12:32 +02:00
Max Becker
e6470cf8ce fpga: x4xx: Extend GPIO SPI support
Extend GPIO SPI transactions up to 64 bits and update the X4xx FPGA
register map and RTL to support the new access width.
Migrate the GPIO SPI register definitions to SystemVerilog.

Add testbench for X4xx GPIO SPI module covering both exisiting
32bit functionality and added 64bit functionality.
Separate TB Makefiles for X4xx and X410 implementation variant.

Bump the FPGA compatibility number so host and MPM software can reject
images that do not provide the required GPIO SPI register interface.
2026-09-29 13:12:32 +02:00
Max Becker
772e682503 fpga: Update SPI simulation testbenches
Extend the SPI slave model to propagate simulation failures to the test
execution framework.

Extract the timing monitor from the control-port SPI testbench so it
can be shared by the GPIO SPI simulations while keeping timing checks
centralized and consistent.
2026-09-29 13:12:32 +02:00
Max Becker
440ed5ae76 fpga: tools: Fix --CHECK image build flag
Fix CHECK_VIVADO_DESIGN make target.
This fixes the --CHECK target/argument for available in the rfnoc_image_builder
2026-09-29 10:52:44 +02:00
Roman Pope
ec86cd7b2c lib: Fix lost wakeup in msg_task startup
msg_task_impl's constructor waits on _start_cv until _running is set,
checking the predicate under _mutex. task_loop() set _running and
called notify_one() without taking _mutex, so a notification sent
after the constructor had checked the predicate but before it started
to wait was lost, and the constructor then blocked forever.

The condition-variable handshake replaced a boost::barrier, which
cannot lose a wakeup, in 3855516f6f ("lib: Remove boost::thread usage
from USB transports"), so UHD 4.10.0.0 and 4.11.0.0 are affected. On
a B210 the hang leaves b200_impl's constructor blocked in
msg_task::make() right after "Operating over USB 3.". It shows up
most when the device is opened at a real-time priority, as SDR
applications that raise their priority before opening the device do.

Set _running while holding _mutex, then notify.
2026-09-29 10:49:42 +02:00
Ryan Volz
7a44637627 cmake: Add missing log_add_impl.hpp to distributed headers
This is a new header for v4.11, referenced by the existing log.hpp and
log_add.hpp public headers.

Signed-off-by: Ryan Volz <ryan.volz@gmail.com>
2026-09-29 10:47:51 +02:00
Max Köhler
65b9c0270e fpga: Upgrade to Vivado 2025.2: Repair all testbenches 2026-09-29 09:09:08 +02:00
Max Köhler
81c53bed91 fpga: ci: Replace modelsim with questasim 2026-09-29 09:09:08 +02:00
Max Köhler
0df07dcde8 fpga: Upgrade to Vivado 2025.2: Repair all FPGA compiles 2026-09-29 09:09:08 +02:00
Max Köhler
f3d6cd6c01 fpga: b300: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
4e8fa298dd fpga: n3xx: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
2a036118de fpga: e320: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
f8240b6871 fpga: e31x: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
91ae70080e fpga: x300: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
31ff7ce148 fpga: x400: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
1e80ef2fe9 fpga: lib: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
edac52a8d9 fpga: tools: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Max Köhler
626bcb32ea ci: Upgrade to Vivado 2025.2 2026-09-29 09:09:08 +02:00
Javier Valenzuela
34a43948aa fpga: b310: add synchronizers on async inputs 2026-09-28 23:35:16 -05:00
Javier Valenzuela
ef0a814a3d fpga: lib: fixup uart non-blocking assignments 2026-09-24 08:56:44 +02:00
Javier Valenzuela
7351887ab5 fpga: lib: fixup i2c non-blocking assignments 2026-09-24 08:56:44 +02:00
Javier Valenzuela
04a7f8c6bf fpga: lib: fixup simple_uart instantiation 2026-09-24 08:56:44 +02:00
Grant Meyerhoff
c6e4c50945 tests: replace .format() usage with f-strings 2026-09-22 15:19:41 +02:00
mhallada
9f8defc537 ci: Enable EVM test for x410-x4-400 pipeline job 2026-09-22 15:18:50 +02:00
Grant Meyerhoff
66c66a4d2b devtest: use tx eob ack timeout for benchmark_rate 2026-09-22 15:13:03 +02:00
Grant Meyerhoff
4eeee62aad examples: optionally wait for Tx EOB on benchmark_rate 2026-09-22 15:13:03 +02:00
Grant Meyerhoff
8720fe87ec ci: download b3xx images rather than hardcoding 2026-09-22 10:00:45 +02:00
Abhishek Verma
633782b405 rpc_server: return UNAVAILABLE for RPCs during device reset
Treat a RuntimeError raised while the periph manager is resetting as
transient. Log once at WARNING and abort with gRPC status UNAVAILABLE
so the client retries, instead of logging a scary ERROR.
Real runtime errors still log ERROR and abort when periph manager
object is live.
2026-09-18 16:18:00 +01:00
Max Köhler
3fe3610eb8 fpga: ci: add b310 option to FPGA pipeline 2026-09-18 16:16:53 +01:00
Max Köhler
3f46b68c6d fpga: lib: disable clocks at startup for ddc_ms_tb 2026-09-18 16:15:40 +01:00
Grant Meyerhoff
50a47f75de b300: fix GPS timepulse polarity 2026-09-18 16:14:09 +01:00
Abhishek Verma
6e11078807 mpm: net.py: optimize netlink ops to fix RPC starvation
usrp_hwd runs all gRPC greenlets on a single gevent hub, so any
non-yielding C call blocks every other RPC. Under a uhd_find_devices
discovery flood, the GetDeviceInfo interface enumeration run long enough
And may starve most claimed RPC past its default 2s client deadline,
causing "Deadline Exceeded" failures.

Three places that this commit optimizes, all in net.py:

- get_local_ip_addrs() used pyroute2 IPDB, which spawns helper threads
  and mirrors the full link/addr/route/neighbour tables on every call
  (~100s of ms, non-yielding) --> Replace it with a single stateless
  IPRoute get_addr() dump. Keep IFA_LOCAL (falling back to IFA_ADDRESS)
  so the returned set matches IPDB's behaviour, including point-to-point
  links.

- get_link_speed() used to create a new pyudev.Context() per interface,
  which forks on creation, tripping gRPC's fork handlers. gRPC then
  freezes all workder threads and restarts after fork is done. Fix is to
  reuse one PID-guarded pyudev context per process.

- get_valid_interfaces() did an O(N) netlink round-trip per interface.
  This stalls gRPC server. --> Dump all links and IPv4 addresses once
   and filter in Python.

IPRoute stays per-call: it opens its own netlink socket and does not
fork, so there is no shared state to lock.
2026-09-18 16:12:41 +01:00
Max Köhler
cf14224aa6 fpga: tools: skip phy_opt step for partially routed designs 2026-09-18 16:08:29 +01:00
Max Köhler
ec32eb04ec ci: do not use publish permission for usrp4-hw 2026-09-18 16:07:14 +01:00
Max Köhler
a76ab6ffc9 Revert "ci: usrp4 sim: only run this stage if source is not master"
This reverts commit 879ed08bb1.
2026-09-18 16:07:14 +01:00
Max Köhler
99fe17071f image builder: find FPGA path from Git repository root 2026-09-18 16:05:51 +01:00
TalFel
2feedf3338 multi_usrp: set_master_clock_rate function now sets duc/ddc rates
RFNoC's `multi_usrp` did not fully propagate `set_master_clock_rate()`
to the DUC/DDC chain.

Before this change, `set_master_clock_rate()` updated only the radio
rate. If a channel had a DUC or DDC, the adjacent converter rate was
left stale:
- TX: the DUC output rate was not updated to the new radio rate
- RX: the DDC input rate was not updated to the new radio rate

That caused follow-up `set_tx_rate()` / `set_rx_rate()` calls on the
same `multi_usrp` object to operate on outdated DUC/DDC state. In
practice, after changing the master clock, setting the channel sample
rate could be coerced unexpectedly because interpolation/decimation was
derived from the old converter-side rate.

This change updates `multi_usrp_rfnoc` so that `set_master_clock_rate()`:
- propagates the new radio rate to the attached DUC/DDC
- reapplies the previously requested per-channel TX/RX rate so the
  DUC/DDC continues to follow the user-requested sample rate

Note that property propagation by itself is insufficient here, because
multi_usrp_rfnoc caches RX and TX rates, and these caches require
manual updating.

Co-authored-by: Martin Braun <martin.braun@ettus.com>
2026-09-18 15:51:09 +01:00
Frank Dietze
a48ccdb0a0 ci: mutex_hardware: remove vivado_dir parameter 2026-09-17 18:40:18 +02:00
Frank Dietze
2fa91b605a ci: replace static obx rftest parameters by variables 2026-09-17 18:40:18 +02:00
Wade Fife
5ff8ca11f3 fpga: b310: Use a resources file for RFNOC_OOT_SRCS
Use a resources file to pass RFNoC OOT source files to Vivado TCL
scripts, avoiding ARG_MAX argument list limits.

This makes B310 consistent with other USRPs that were previously
updated in commit fa7c3604.
2026-09-16 09:59:04 -05:00
Wade Fife
ddb7e6866b fpga: tools: Make each BUILD_SEED random
Use --seed only to initialize a local deterministic random number
generator. Generate a new signed 32-bit BUILD_SEED for each FPGA build
attempt instead of passing --seed directly to the first build. This
avoids accidentally using sequential pipeline attempt numbers as
BUILD_SEED values.

Using the same --seed produces the same BUILD_SEED sequence, preserving
reproducibility.
2026-09-16 09:53:50 -05:00
Max Köhler
15283f106b fpga: tools: add target matrix generator script 2026-09-15 23:55:20 +01:00
Marian Koop
5e9888c204 cmake: Bump master to 4.11.1 2026-09-15 23:47:53 +01:00
Niels Garibaldi
aaaca5bff9 fpga: dsp: add FSM for HB FIR temporal decimation
Added a state machine to fix a bug in the temporal decimation code
of the decimating halfband filter module.

Additionally added a testcase to the testbench for the issue this
change is fixing.
2026-09-15 23:47:05 +01:00
Niels Garibaldi
58ab7a88cc dsp: cic: Add FIFO in CIC decimation to allow tready bypass
Added a Fifo to bypass handshaking inside of the cic filter decim
data pipeline.
2026-09-15 23:47:05 +01:00
Max Köhler
5b1bcabcf8 fpga: dsp: register halfband cascade routing selects 2026-09-15 23:47:05 +01:00
Max Köhler
e5c05cfde1 fpga: dsp: flush stale data across reset boundaries 2026-09-15 23:47:05 +01:00
Max Köhler
c1540c92f1 fpga: dsp: simplify interpolation and decimation configuration 2026-09-15 23:47:05 +01:00
Max Köhler
b7a92cd9d9 fpga: dsp: block input during reset and reconfiguration 2026-09-15 23:47:05 +01:00
Max Köhler
efa47018e4 fpga: dsp: distribute reset and clear through DSP paths 2026-09-15 23:47:05 +01:00
Max Köhler
d394ae626a dsp: cic: Add FIFO in CIC interpolation to allow tready bypass 2026-09-15 23:47:05 +01:00
Max Köhler
5ea031a946 fpga: lib: dsp: add multicycle path for CIC barrel shift 2026-09-15 23:47:05 +01:00
Niels Garibaldi
9365ae9b6a fpga: dsp: reverse order of cic barrel shifter
Reversing the order of the barrel shifter module used by the CIC
filter implementations, in order to reduce the possible bits
selectable from the last stage going into the rounding module.
This should make routing easier.
2026-09-15 23:47:05 +01:00
Niels Garibaldi
4904d06da2 fpga: dsp: Reduce CIC comb bus width for cic_filter_interp
We can reduce the bus width of the comb filter stage in the CIC
interpolation filter, as it is the first module in the CIC filter and
only adds one bit per order stage. Therefore, we do not need to
immediately go to the full accumulation bit width needed for the later
integrator stages of the CIC interpolation filter, and can save some
FPGA resources by only extending to the required width for each CIC
stage.

The same logic also applies to the interpolation stage, which only adds
zeros into the bitstream and therefore does not change the bus width.
2026-09-15 23:47:05 +01:00
Niels Garibaldi
57df0ca685 fpga: dsp: Add FIFO_SIZE parameter to CIC comb filter
Adding a FIFO size module parameter to the comb filter module, in
order to selectively set only the last of N-order filter stages to
use a 2-stage FIFO.
2026-09-15 23:47:05 +01:00
Virendra Kakade
285cce69a2 ci: Build UHD RPMs with B310 support
Install nib310rio-dev before configuring the Fedora RPM installer build.
The RPM path runs CMake directly in the build container, so installing the
development package there makes nib310rio/nib310rio.h available when UHD
decides whether to enable B300 support.
2026-09-15 23:44:29 +01:00
Virendra Kakade
eb785ff23b ci: Build UHD DEBs with B310 support
Add nib310rio-dev to the Debian build dependencies so the UHD package
build fails if the B310 userspace header is unavailable instead of
silently disabling B300 support.

The Ubuntu installer pipeline downloads the nib310rio Debian package
artifact and exposes it to pbuilder as a local apt repository. This lets
pbuilder satisfy the new dependency inside the clean build root where
CMake checks for nib310rio/nib310rio.h.
2026-09-15 23:44:29 +01:00
Marian Koop
0d7ed3b1a4 images: Update to final 4.11.0.0 release candidate 2026-09-08 10:24:37 +01:00
Marian Koop
6fbadafde6 cmake: Update stable branch property 2026-09-08 10:24:37 +01:00
Marian Koop
c57e8cb30d doc: Extend x4xx loading the fpga image section. 2026-09-08 10:06:38 +01:00
Martin Anderseck
72d896bd25 CHANGELOG: Correct and update UHD 4.11 features
This adds the Linux kernel update as highlight and corrects the
statement about the X420/X440 repeatable phase offsets.
2026-09-08 09:28:31 +01:00
Jörg Hofrichter
d687c4ebbe host: mpmd based uhd_image_loader: prefer .bin files
Prefer .bin over .bit files when loading FPGA without full
FPGA path name. This changes the order which was introduced
with commit 2618b51cb8.
2026-09-08 09:27:55 +01:00
Michael Dickens
6cb889675f python: add range property tree access 2026-09-08 00:02:36 +01:00
Lars Amsel
9e8b5b9553 examples: Fix l-band capture setup
Because X440 images now can also contain DDC/DUC we cannot direct
connect the radio with the replay block anymore. Instead we have
to rely on the uhd.rfnoc util connect_through_blocks function
which will find a route through the statically connected DDC/DUC
if they are present or connect the SEPs directly if they are not.
2026-09-07 17:06:06 +01:00
Grant Meyerhoff
4caf56aa0d docs: b300: add more complete multi-device sync docs 2026-09-07 17:03:43 +01:00
Virendra Kakade
94fd6241b8 examples: Fix replay capture at coerced sample rates
rfnoc_rx_replay_samples_to_file.py could hang and then abort with
"Timeout while loading replay buffer!" whenever the requested sample
rate was not an integer divisor of the master clock rate. For example,
requesting 200 Msps at an MCR of 368.64 MHz would leave the record
buffer stuck at exactly 50% fullness.

Two problems combined to cause this:

First, the rate was read back from the DDC before the graph was
committed. At that point the DDC's input sample rate property is not
yet valid, so set_output_rate() falls back to setting the output edge
property and returns the uncoerced, requested rate. The example then
reported a rate that the DDC never actually applied.

Second, run_capture() derived the number of samples to request from the
radio with int(radio_rate / rate), truncating instead of rounding. With
a radio rate of 368.64 Msps and a nominal rate of 200 Msps this yielded
a ratio of 1 rather than the decimation the DDC had actually settled on,
so the radio was told to produce only half the samples needed to fill
the record buffer.

The rate is now read back with get_output_rate() after graph.commit(),
when property propagation has completed, and the user is warned when
the requested rate had to be coerced. The rate ratio is computed with
rounding, so the radio is asked for the full number of samples the
record buffer needs.

While here, fix two related issues in the readback path. The DDC port
is now taken from the destination port of the discovered edge rather
than assuming it matches the radio channel index, and the rate is read
per channel (from the DDC where one is present, otherwise from the
radio) so that chains without a DDC are not ignored. Rates are compared
against a tolerance rather than for exact float equality.

Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-09-07 17:03:01 +01:00
Marian Koop
323d066e64 images: Update manifest for v4.11.0.0-rc3 2026-09-02 20:35:42 +01:00
Jörg Hofrichter
9a3a47bb69 CHANGELOG: Update UHD 4.11 new features descriptions
- Updated the description of the fabric resampler addition.
- Corrected the documentation for the default state of the MPM serialization setting.
2026-09-02 18:40:29 +01:00
Frank Dietze
7bd5f8047a debian: update upload_debs.sh 2026-09-02 18:39:53 +01:00
Frank Dietze
635e73e7e0 debian: fix arch strings 2026-09-02 18:39:53 +01:00
Marian Koop
856a3e7dd2 docs: Update statement in x4xx user manual section. 2026-09-02 18:34:54 +01:00
Jörg Hofrichter
7d26845c08 docs: x4xx: Added information on locked FPGA variants 2026-09-02 16:45:32 +01:00
Jörg Hofrichter
218975dd22 CHANGELOG: Added information on X4XX Locked FPGA Variants 2026-09-02 16:45:32 +01:00
Jörg Hofrichter
b5718dc22b mpm: usrp_update_fs: adopt EEPROM path for E31X 2026-09-02 15:55:00 +01:00
Grant Meyerhoff
394768bcff docs: b300: adjust wording with default change
- Tx QEC tracking cals were enabled by default in a recent change.
2026-09-02 10:12:37 +02:00
Javier Valenzuela
31ca7a5497 host: docs: add b300 resource utilization 2026-09-02 10:11:07 +02:00
Virendra Kakade
b8114103c7 docs: b300 streaming docs
Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-09-02 10:09:32 +02:00
Wade Fife
081429caa9 fpga: lib: Disable XPM FIFO for Spartan-6 FPGAs 2026-09-02 09:58:09 +02:00
Wade Fife
85e163a0d9 fpga: lib: Add DEVICE parameter to gpif2 and radio_legacy 2026-09-02 09:58:09 +02:00
Wade Fife
6a8a56afb2 fpga: b2xx: Add DEVICE parameter
Adds a DEVICE parameter set to SPARTAN6 to properly indicate the FPGA
device family.
2026-09-02 09:58:09 +02:00
Wade Fife
d110c060f2 fpga: Fix check_timing.py break indent
This fixes an issue introduced by commit 699cff5941, where the break
statement was incorrectly indented outside the for loop.
2026-09-02 09:40:20 +02:00
Grant Meyerhoff
881f19db7c examples: handle sigint in rx_ascii_art_dft 2026-08-28 16:11:32 +01:00
Max Köhler
4e3912efd8 fpga: fix typo in yml files 2026-08-28 16:09:35 +01:00
Max Köhler
9da28f06f7 fpga: x420: Use standard ce clock for DDC and DUC blocks for 400 MHz 2026-08-28 16:09:35 +01:00
Max Köhler
be791eb065 ci: apply target name for X420 FPGA compiles 2026-08-28 16:06:17 +01:00
Max Köhler
0e680f9cb6 image builder: add x420 support 2026-08-28 16:06:17 +01:00
Grant Meyerhoff
e866ff81f9 adrv9032: make tx qec tracking cals default 2026-08-28 15:03:39 +01:00
Jörg Hofrichter
3cfb163340 fpga: e320/n3xx: dts: Fixed PHY mode for 1G connections
Without this change, kernel 6.12 generates the following warning:

nixge 40000000.ethernet (unnamed net_device) (uninitialized): fixed link full duplex 1000Mbps not recognised

With the change, the link is correctly detected as 1G:

nixge 40000000.ethernet sfp0: configuring for fixed/gmii link mode
2026-08-28 15:02:53 +01:00
Niels Garibaldi
17c0114771 fpga: dsp: fix race condition in axi_tag_time_ms
Fixed an issue when multiple timed DDS commands were present, where
the first one was popped and then the second command immediately
got popped as well to the stale a lookahead signal.
The fix is to clear the lookahead signal and threshold signal on
every fifo pop command.
2026-08-28 14:59:35 +01:00
Niels Garibaldi
31be5b875a fpga: dsp: add rounding to CIC filter barrel shifter
Add an additional rounding step to the cic barrel shifter module
to avoid any DC bias occuring, caused by bit truncation without
rounding.
2026-08-28 14:59:35 +01:00
Wade Fife
02f11f970c b310: Flush stale RX data before enabling DMA
Signed-off-by: Grant Meyerhoff <grant.meyerhoff@emerson.com>
2026-08-28 14:58:07 +01:00
Wade Fife
53a1ef762e rfnoc: fir filter: Use burst op to write coefficients 2026-08-27 16:13:24 +01:00
Wade Fife
000cd8e3cb fpga: b310: Fix synthesis-only builds 2026-08-27 16:10:53 +01:00
mschill
0e3b241021 fpga: x440: add X1_200 bifile variant 2026-08-27 13:31:33 +01:00
Marian Koop
84d6af328b ci: Update fpga packaging configs 2026-08-27 08:41:16 +01:00
Max Köhler
44ecdb1f39 uhd: remove rj45 argument from X420 IQ correction 2026-08-27 08:40:35 +01:00
Max Köhler
5a8d5b3a6e fpga: x420: adding endpoint 0 for ctrl only 2026-08-27 08:40:35 +01:00
Marian Koop
874c196fec images: Update manifest for v4.11.0.0-rc2 2026-08-25 22:48:25 +01:00
Jörg Hofrichter
dffdfef5f5 mpm: make discovery response dependent on mpm_device
Display the "locked_fpga" information only for x4xx.
2026-08-25 22:13:45 +01:00
Jörg Hofrichter
d539960867 mpm: use flag locked_fpga=True for L variant devices 2026-08-25 22:03:46 +01:00
Max Becker
f0b4bf59f7 examples: rfnoc-gain: Update OOT example
- Fix target link libraries (UHD::uhd instead of uhd)
- Updated README
  - Better describe the different ways to build the FPGA bitfiles
  - Mention pybind11 requirement
- Add x410 image core to CMakeList to enable using make for this image
2026-08-25 17:40:59 +02:00
Abhishek Verma
0dda23bb52 rpc_server: remove reference leakage warning
We see this warning about lingering references during UHD operations
like image load even if they are not. We did some testing to verify.
It seems the garbage collector could take some time to remove these
reference. But the warning here could be misleading. Hence, removed.
2026-08-25 14:26:48 +02:00
Abhishek Verma
1da439c020 host/mpm: change MPM_RPC_PORT to avoid UHD<4.10 discovery crash
UHD hosts <4.10 speak mprpc and unconditionally connect to the RPC port
(49601) of any device they discover. When they reach a gRPC-based MPM
device, the protocol mismatch crashes uhd_find_device.

UHd host 4.10, prevents this by using rpc_version key from discovery
response and skips RPC attempt if mprpc device found during
uhd_find_devices.

This change moves the RPC port to 49602. Now, old UHD <4.10 hosts can
still discover both mprpc and grpc devices. But their mprpc connection
attempt on 49601 during uhd_find_devices is refused by grpc device
(running on new rpc port 49602) and is marked as not reachable instead
of crashing.
2026-08-25 12:11:37 +02:00
Martin Anderseck
ec7e94bd4b doc: Add Discoverable Feature Python doc
This adds the documentation of how to work with discoverable features in
the UHD Python API.
2026-08-24 23:12:45 +02:00
Martin Anderseck
167c179816 test: Add DiscoverableFeature Python API test
This adds a test that verifies that the DiscoverableFeature Python API
works properly.
2026-08-24 23:12:45 +02:00
Martin Anderseck
44791931c8 host: examples: Add Python SPI example for X4xx
Since with the availability of discoverable features in Python it is
possible to use the front panel SPI of the X4xx series from there, this
example shows how to do this. It is pretty much on par with the C++
version.
2026-08-24 23:12:45 +02:00
Martin Anderseck
538a2e3fb3 python: Make discoverable features available
This makes all discoverable features available and creates a pattern for
any future discoverable features.
2026-08-24 23:12:45 +02:00
Abhishek Verma
05443d301d host/mpm: make gRPC robust to idle teardowns
The main RPC channel can sit idle for a long time (e.g. an interactive
Python session) while only the claim-loop channel sees traffic. Without
keepalive, gRPC self-closes the idle transport (~30 min) and the next
RPC fails re-establishing within its per-call deadline.

With this change the client now pings every 30 s to keep the channel
warm, and the server is configured to accept that cadence instead of
replying GOAWAY "too_many_pings".
2026-08-23 02:23:02 +02:00
Abhishek Verma
a076ee6dd7 rpc_server: disable BDP pings on server
The MPM gRPC server runs on a single-threaded gevent hub. gRPC's BDP
(bandwidth-estimation) probe fires repeatedly while the reclaim loop
keeps the link busy, and its ACK must return within grpcio's short,
RTT-derived internal BDP deadline. Any blocking hardware register read
(e.g. a ClkMgr PLL-lock poll) stalls all socket I/O on the hub until it
returns; , but these polls also run periodically in steady state.
When a stall outlasts the BDP deadline the server sends GOAWAY
"ping_timeout" and drops the transport. The fix is to disable BDP on
the server. The default flow-control window is fine on the LAN link,
so this costs nothing here. BDP is left enabled on the client, whose
native-threaded stack never freezes.
2026-08-23 02:23:02 +02:00
Jörg Hofrichter
dfc1ad17c3 mpm: e320: fix reading of FAN speed
The fan is named fan0 in the hwmon based implementation.
2026-08-23 02:19:52 +02:00
Lars Amsel
29c459f1b3 fpga: enable clock watch circuit on X440 images
Enable clock watch circuit for images with 200 and 400MHz bandwidth
as well. With this we also move from watch the rfdc_clk to watch the
radio_clk. This makes is easier to calculate the max clock rate for
all flavours of images.
2026-08-23 02:17:12 +02:00
Marian Koop
f9e8c5896f python: Fix error in python import warning on Windows 2026-08-23 02:11:55 +02:00
Jörg Hofrichter
e23454654f mpm: xportmgr_udp: create nft tables only if missing 2026-08-23 02:11:21 +02:00
Abhishek Verma
c4cf03925a mpm: replace mprpc with grpc pkg requirements 2026-08-23 02:08:45 +02:00
Max Köhler
f0b5d454cc doc: add phase coherency information 2026-08-23 02:03:43 +02:00
Niels Garibaldi
344c6793ac doc: fpga: host: Updated Wideband DDC DUC related documentation
Updated the references to DDC and DUC RFNoC blocks to reflect the
new split between legacy(singlesample) and wideband(multisample)
implementations.
Updated the related manual references as well as the C++ block
controller API comments that are pulled in by doxygen.
Also fixed a minor outdated comment in the FPGA source code.
2026-08-23 02:03:43 +02:00
Marian Koop
8d9c53c83e ci: fpga: Added pipeline switch to optionally build alternate X420 designs
Added pipeline switch to select between building X420_CG targets
with (default) or w/o DDC/DUC (alternate). In the pipeline the default
is to build the alternative design because of the low compile success
rates for these fpga designs when DDC/DUC is included.
2026-08-23 02:02:27 +02:00
Niels Garibaldi
6ab55e0cf4 fpga: add wideband ddc and duc blocks to default fpga images(Part2)
Adding the RFNoC DDC and DUC blocks to our shipping bitfiles.

The new wideband DxC implementation allow processing input/output
rates up to a maximum of 2000MSps.
This commit adds the DDC/DUC blocks to some of our shipping
bitfiles for all channels.

Co-authored-by: Niels Garibaldi <niels.steffen.garibaldi@emerson.com>
Co-authored-by: André Zoch <andre.zoch@emerson.com>
Co-authored-by: Meghana Halladamath <meghana.halladamath@emerson.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Max Köhler <max.koehler@emerson.com>
2026-08-23 02:02:27 +02:00
Virendra Kakade
c0be7431c0 fixup! docs: add b310 kernel driver docs 2026-08-23 00:22:47 +02:00
Virendra Kakade
cc29eb4496 docs: add b310 transport notes
Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-08-23 00:22:47 +02:00
Virendra Kakade
add2d9efa6 docs: add b310 kernel driver docs
Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-08-23 00:22:47 +02:00
Grant Meyerhoff
0e9a62834f docs: Add B310 documentation 2026-08-23 00:22:47 +02:00
Grant Meyerhoff
e98e866709 b300: fix external ref assignment 2026-08-23 00:22:03 +02:00
Grant Meyerhoff
bc64ff0ad6 b300: disallow changing clock source mid-session 2026-08-23 00:22:03 +02:00
Grant Meyerhoff
9291d7e18b adrv9032: store nco as per channel 2026-08-23 00:18:25 +02:00
Grant Meyerhoff
e8079eca45 adrv9032: fix nco source check 2026-08-23 00:18:25 +02:00
Grant Meyerhoff
b8d34e36be adrv9032: correct channel assignment for agc 2026-08-23 00:18:25 +02:00
Virendra Kakade
3716dd0884 b300: depend on installed nib310rio.h for Linux PCIe support
Remove the vendored copy of nib310rio.h from the inchworm tree and
replace it with a build-time dependency on the header installed by
the nib310rio-dev package:

    /usr/include/nib310rio/nib310rio.h

CMake now searches for this header before registering the B300
component. On Linux, if the header is absent, B300 support is
silently skipped with a status message pointing to the missing
package. If the user explicitly passes -DENABLE_B300=ON without
the header present, CMake fails with a clear error.

Update the two Linux-only include sites to use the system path:

    #include <nib310rio/nib310rio.h>

This makes the build-time dependency on the kernel driver explicit
and removes the risk of the vendored copy drifting out of sync with
the installed driver.

Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-08-23 00:17:02 +02:00
Max Köhler
a9ddd67fb9 fpga: lib: rfnoc: Merge payload and async FIFO
Merge the payload and async FIFOs in the axis_data_to_chdr and
chdr_to_axis_data conversion conversion modules.
2026-08-20 17:31:01 +02:00
Max Köhler
2ff1a639eb fpga: lib: Make axis_width_conv FIFO size configurable 2026-08-20 17:31:01 +02:00
Niels Garibaldi
2f0fb8a1fa ci: add ddc/duc ats template tests for x410_x4_400 2026-08-20 17:08:19 +02:00
Frank Dietze
879ed08bb1 ci: usrp4 sim: only run this stage if source is not master 2026-08-20 16:07:33 +02:00
Max Köhler
27e8768431 ci: remove unused hwtools references 2026-08-20 13:27:17 +02:00
Max Köhler
5cb4e4f57f fpga: ci: use latest version of hwtools 2026-08-20 13:27:17 +02:00
Marian Koop
f88231c7c1 ci: Add Python 3.13 support 2026-08-20 02:03:09 +02:00
Marian Koop
6efbd7986b ci: docker: Re-add python 3.13 targets 2026-08-20 02:03:09 +02:00
Marian Koop
414e99f509 ci: docker: Re-add support for using vcpkg pipeline cache
Re-add steps to use and create pipeline cache for vcpkg binary caching.
Enable optionally disabling consuming and creating cache via
pipeline variable.
2026-08-20 02:03:09 +02:00
Marian Koop
b9f346664a ci: docker: Improve build success rate for windows containers
Resolve common transitive docker build failures on Windows,
which forced commit 4d0c9c8dfd
   Author: Marian Koop <marian.koop@emerson.com>
   Date:   Tue Aug 11 17:01:53 2026 +0200

       ci: docker: Revert docker build improvements

- Abstract vcpkg triplet port creation in ps script to
  implement error trapping and inspect logs prior to failing
- Move vs_buildtools install to front, to fail fast
- Abstract vs_buildtools install in ps script to
  - implement error trapping and inspect logs prior to failing
  - reduce docker layers
  - eliminate incl. temporary installer data into docker layers
- Switch to using powershell script to trap choco install errors
  and expose error logs during docker build. Script also
  - combines all choco installs into single container layers
  - attempt (by default) 2 retry attempts per package install
    (workaround for temporary URL download/access failues)
- Combine some python commands into single RUN statement
  to reduce docker layers
- Implement docker build step retry attempts (3) to address
  other temporary failures like ability to create layer.
2026-08-20 02:03:09 +02:00
Grant Meyerhoff
9015e01d3c b300: bump expected minor compat number for FPGA 2026-08-20 02:00:29 +02:00
Grant Meyerhoff
e7ea67c2c7 ci: add FPGA image loading before testing 2026-08-20 01:59:34 +02:00
Grant Meyerhoff
27cd1c5a6f ci: update B310 ATS to a new host machine 2026-08-20 01:59:34 +02:00
Grant Meyerhoff
b035928f19 b300: reset Thunderbolt PD after image load 2026-08-20 01:59:34 +02:00
Virendra Kakade
cbcf8a46c6 ci: add nib310rio-dev install step to UHD builds
Add steps-install-nib310rio-dev.yml template that downloads
the deb or rpm artifact based on buildOSName and installs
nib310rio-dev before the build. This satisfies the nib310rio.h
build-time dependency required by UHD.

Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2026-08-20 01:51:42 +02:00
Jörg Hofrichter
af4058bd64 fpga: n3xx: fix UART initialization for WX image
In kernel 6.12, the "current-speed" parameter is required,
otherwise the UART is not initialized.
2026-08-20 01:48:27 +02:00
Max Köhler
fc6e355f24 fpga: refactor modules still using 2clk IP cores 2026-08-20 01:46:19 +02:00
Max Köhler
7537a2a909 fpga: removing deprecated FIFO IP cores 2026-08-20 01:46:19 +02:00
Max Köhler
04bb66663e fpga: lib: enable XPM fifo for all Xilinx FPGAs 2026-08-20 01:46:19 +02:00
Markus Schill
0b80e7f011 fpga: x410: Add rfdc_clk_1_4 to MMCM output
Adds a direct MMCM output for the quarter rfdc_clk.
Additionally, all other consumers of the rfdc_clk and rfdc_clk_2x
receive their clock from the MMCM output directly instead of
the clock buffers. Thus, making the clock buffers obsolete and
removing them from the design.
2026-08-20 01:40:05 +02:00
Javier Valenzuela
9c3959cdb0 fpga: b310: add synchronizers on async inputs 2026-08-19 11:42:54 -05:00
Max Köhler
87ee25d152 fpga: tools: exclude xsim kernel panic testbenches 2026-08-19 10:38:38 -05:00
Max Köhler
e503aaff88 fpga: tools: capture xsim fatal error in testbench execution 2026-08-19 10:38:38 -05:00
Javier Valenzuela
1c0dfa23d8 fpga: b310: update LMK_SYNC constraint 2026-08-18 12:39:10 -05:00
Javier Valenzuela
ed156ae6cf fpga: b310: removing stale comment 2026-08-18 12:39:10 -05:00
Abhishek Verma
99a05593d8 mpm: import no_claim in e31x
no_claim decorator was recently added in method
get_device_info_dyn() but it was missed to get imported from
usrp_mpm.rpc_utils which causes device initialize failure.
2026-08-17 11:01:17 +02:00
Javier Valenzuela
8676b3e257 fpga: b310: remove vhook comments 2026-08-14 16:33:50 -05:00
Javier Valenzuela
a9aed26306 fpga: lib: move ctrlport_to_jtag to lib sirectory 2026-08-14 16:32:09 -05:00
Javier Valenzuela
c17c6a4c3a fpga: b310: update jtag prescalar 2026-08-14 16:31:15 -05:00
Javier Valenzuela
8745af720e fpga: b310: add CPLD jtag constraints 2026-08-14 16:31:15 -05:00
Marian Koop
556b378912 Prepare for 4.11.0.0 RC1 2026-08-12 18:25:49 +02:00
Marian Koop
fdb3a497a5 debian: Add licenses for b310 sources 2026-08-12 18:25:49 +02:00
Marian Koop
4d0c9c8dfd ci: docker: Revert docker build improvements 2026-08-12 18:24:38 +02:00
Niels Garibaldi
c20e60a594 fpga: duc: only include TS in first packet of burst
For the multisample version of the DUC block, in order
to align with the singlesample behavior this changes the packet
generation in axi_rate_change_ms to only include the timestamp in
the first packet of each burst.
2026-08-11 10:47:57 +02:00
Niels Garibaldi
d91c2293d1 fpga: rfnoc: add TS-on-first-only option to axi_rate_change_ms
Add a parameter to the axi_rate_change_ms module called
EN_TIME_ALL_PKTS.
Set to 1, the module will calculate timestamps
for all packets of the output burst.
Set to 0, the module will only insert a timestamp in the first
pkt of each generated output burst.
2026-08-11 10:47:57 +02:00
Grant Meyerhoff
45ea2365b1 cores: restrict spi_core_adrv build for b300 2026-08-11 01:16:12 +02:00
Javier Valenzuela
9849f85918 fpga: b310: removed unused revision reg 2026-08-10 17:06:59 -05:00
Javier Valenzuela
984bc86793 fpga: b300: update jesd signal naming 2026-08-10 17:06:01 -05:00
Wade Fife
4a9b264b86 fpga: lib: Fix double driver in clocking_watchdog_tb 2026-08-10 17:03:45 -05:00
mhallada
e90b0b4643 fpga: Ensure constant initial phase of dds phase accumulator
between bursts

Reload the phase accumulator (with registered phase increment)
in dds_ms at EoB to maintain constant initial phase between bursts
2026-08-10 23:12:25 +02:00
Wade Fife
76ddd382f9 docs: fpga: Update simulator install location 2026-08-10 16:05:30 -05:00
Wade Fife
1bafc03e08 fpga: tools: Add simulator install locations 2026-08-10 16:05:30 -05:00
Grant Meyerhoff
00826ca054 adrv9032: fix licensing notices 2026-08-10 22:56:24 +02:00
Abhishek Verma
3ec3ea3ef6 host: add missing rpc cmds in proto file
- add missing x4xx rpc cmds in proto file
- add missing n3xx rpc cmds in proto file
2026-08-10 22:52:19 +02:00
Lars Amsel
dd7607fc17 ci: add 800 MHz X440 images to pipeline builds 2026-08-10 17:21:47 +02:00
Lars Amsel
30a4b7fc2b fpga: x440: add 800 MHz images 2026-08-10 17:21:47 +02:00
Lars Amsel
33a0dc16bd fpga: add clock watch circuit 2026-08-10 17:21:47 +02:00
Jörg Hofrichter
2618b51cb8 host: mpmd based uhd_image_loader: fallback to .bin file
If the expected FPGA image with .bit file ending is not available,
try with .bin file ending.

Co-authored-by: Lars Amsel <lars.amsel@emerson.com>
2026-08-10 16:40:53 +02:00
Jörg Hofrichter
aa2665d6f4 mpm: print error if loading FPGA bitstream failed 2026-08-10 16:40:53 +02:00
Jörg Hofrichter
752b878a08 mpm: tlv_eeprom: added eeprom IDs for X4XX L variants 2026-08-10 16:40:53 +02:00
Jörg Hofrichter
d46c011757 mpm: tlv_eeprom: print addtl. info in eeprom-id 2026-08-10 16:40:53 +02:00
Jörg Hofrichter
6284863e14 mpm: handle bootgen generated .bin files
- Verify signature in case the device does not support customizable
  FPGA (just as sanity check before actual loading the bitstream).
- Extract the bitstream from the bootgen generated .bin file
  otherwise.
2026-08-10 16:40:53 +02:00
Jörg Hofrichter
d4828aa208 mpm: add bootgen_wrapper script
Provide the following two functions by wrapping the "bootgen"
command line tool:

verify: verify the validity of the signature of the .bin file.

extract_bitstream: extract the FPGA bitstream from the .bin file.
This works if the .bin file includes the FPGA bitstream as a
single partition.
2026-08-10 16:40:53 +02:00
Jörg Hofrichter
eb21a19deb mpm: expose customizable_fpga flag 2026-08-10 16:40:53 +02:00
Jörg Hofrichter
4bf18365d8 mpm: support new L variants in x4xx_update_cpld 2026-08-10 16:40:53 +02:00
Jörg Hofrichter
294ced36f6 mpm: x4xx: l device only supports "bin" file extension for FPGA 2026-08-10 16:40:53 +02:00
Jörg Hofrichter
a58a06f50c mpm: x4xx: add l variant PIDs 2026-08-10 16:40:53 +02:00
Max Köhler
09b0346838 ci: fpga: change demand to super high CPU per thread 2026-08-10 14:35:48 +02:00
Grant Meyerhoff
7a0a5f8085 b300: search for FPGA images if not provided 2026-08-10 14:11:49 +02:00
Marian Koop
0b47148652 ci: docker: Improve windows docker build speed and stability
- Limit vcpkg port creation to release targets
- Re-use vcpkg cache for PR and during week in CI builds
- Merge vc2022 dockerfiles into single source
2026-08-10 09:51:18 +02:00
Marian Koop
82fb06bfe9 ci: docker: Enable support for partial docker pipelines
- add optional --empty-on-error-non-ci to derive_docker_image_matrix.py
  to downgrade derivation errors to warnings in non-strict builds
- handle partial DockerImageNames inputs by tolerating missing/empty
  source matrices and returning {} when fallback mode is active
- refactor matrix unit tests to be artifact-driven and non-strict
  outside CI (warn/skip instead of fail)
- keep strict failure behavior for CI reasons, while allowing fallback
  in manual and PR runs
2026-08-10 09:51:18 +02:00
Marian Koop
a3d8778a84 ci: Improve pipeline unit test test_derive_docker_image_matrix 2026-08-10 09:51:18 +02:00
Marian Koop
943692b9c7 ci: hardware dev test: Add sbom upload stage 2026-08-10 08:56:35 +02:00
Marian Koop
2788ac9e6d ci: Integrate SBOM upload stage into mono pipeline stage list 2026-08-10 08:56:35 +02:00
Marian Koop
0fd2f32cb8 doc: Mention B310 in release highlights 2026-08-10 08:46:44 +02:00
Frank Dietze
72bac0df60 ci: streaming: b310: cleanup implementation 2026-08-10 08:44:34 +02:00
mhallada
6883bb4d39 host: Fix NIPC calculation in ddc/duc
Computed NIPC values now resolve to 1 for
RF_BW < 200 MHz and the workaround in X410_X4_100
bitfile to ensure correct NIPC has been reverted.
2026-08-10 08:38:01 +02:00
Niels Garibaldi
f60ae0e17d doc: add Wideband DDC/DUC information to changelog 2026-08-05 08:43:42 +02:00
Niels Garibaldi
a1f558f16a fpga: add wideband ddc and duc blocks to default fpga images
Adding the RFNoC DDC and DUC blocks to our shipping bitfiles.

The new wideband DxC implementation allow processing input/output
rates up to a maximum of 2000MSps.
This commit adds the DDC/DUC blocks to some of our shipping
bitfiles for all channels.

Co-authored-by: Niels Garibaldi <niels.steffen.garibaldi@emerson.com>
Co-authored-by: André Zoch <andre.zoch@emerson.com>
Co-authored-by: Meghana Halladamath <meghana.halladamath@emerson.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Max Köhler <max.koehler@emerson.com>
2026-08-05 08:43:42 +02:00
Niels Garibaldi
66769905bb fpga: x400 add faster ce_clk variant
Add a new faster ce clock called fast_ce to x4xx that runs at
333.33MHz.

Co-authored-by: Max Köhler <max.koehler@emerson.com>
2026-08-05 08:43:42 +02:00
Niels Garibaldi
e466f427fd host: dsp: add wideband ddc and duc rfnoc blocks
Updated the host related files to support wideband DxC block
variants.
The singlesample and multisample variants both have the same API,
but internally they are using different register address spaces to
interact with the FPGA implementation.
The host implementation checks which implementation a RFNoC DxC
block is using by checking the block version number and will then
automatically choose the correct register address scheme.
This is all transparent to the user, the Block API stays the same.

Co-authored-by: Niels Garibaldi <niels.steffen.garibaldi@emerson.com>
Co-authored-by: André Zoch <andre.zoch@emerson.com>
Co-authored-by: Meghana Halladamath <meghana.halladamath@emerson.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Max Köhler <max.koehler@emerson.com>
2026-08-05 08:43:42 +02:00
Niels Garibaldi
b4aeb78438 fpga: dsp: add wideband ddc and duc rfnoc blocks
Added variants for the DDC/DUC blocks that can process multiple
samples per cycle and can be used for bandwidths higher than
200Mhz.
This new feature is integrated into the existing RFNoC DDC/DUC
blocks. Depending on the Radio bandwidth specified for the USRP
variant, the DxC blocks will instantiate the existing singlesample
implementation(for FPGA image variants <= 200MHz) or instantiate
the multisample implementation(for FPGA image variants > 200MHz).

Co-authored-by: Niels Garibaldi <niels.steffen.garibaldi@emerson.com>
Co-authored-by: André Zoch <andre.zoch@emerson.com>
Co-authored-by: Meghana Halladamath <meghana.halladamath@emerson.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Max Köhler <max.koehler@emerson.com>
2026-08-05 08:43:42 +02:00
Frank Dietze
4ef8c18981 ci: pipeline: separate embedded filesystem builds 2026-08-04 13:53:05 +02:00
Grant Meyerhoff
a18579e3e5 ci: Add support for USRP B310
Co-authored-by: Virendra Kakade <virendra.kakade@ni.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Javier Valenzuela <javier.j.valenzuela@emerson.com>
2026-08-04 12:00:49 +02:00
Grant Meyerhoff
e0f685a4aa usrp: Add support for USRP B310
Co-authored-by: Virendra Kakade <virendra.kakade@ni.com>
Co-authored-by: Javier Valenzuela <javier.j.valenzuela@emerson.com>
Co-authored-by: Aki Tomita <aki.tomita@emerson.com>
Co-authored-by: Tony Liecthy <tony.liechty@ni.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
2026-08-04 12:00:48 +02:00
Javier Valenzuela
b6286bab13 fpga: Add support for USRP B310
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Tony Liecthy <tony.liechty@ni.com>
Co-authored-by: Grant Meyerhoff <grant.meyerhoff@emerson.com>
Co-authored-by: Christopher Oeltjen <christopher.oeltjen@emerson.com>
2026-08-04 10:40:54 +02:00
Javier Valenzuela
2cde73ab6f fpga: lib: add scalability to pcie host datapath components 2026-08-04 10:40:40 +02:00
Javier Valenzuela
d4b827abea fpga: lib: add SV wrapper for ctrlport_timer 2026-08-04 10:40:34 +02:00
Javier Valenzuela
dd708a46f4 fpga: lib: add ctrlport endpoints for I2C, UART and high-throughput SPI
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Tony Liecthy <tony.liechty@ni.com>
2026-08-04 10:40:31 +02:00
Aki Tomita
e98bf35840 platform: Move get_page_size so it's shareable 2026-08-04 09:45:33 +02:00
Grant Meyerhoff
83553e2dd5 common: Add ADRV903x code from API version 2.12.1.4
- Includes all needed files from the ADI-provided software drop, intentionally only including the files needed for the ADRV9032.
- Adds a LICENSE.pdf file that points to ADIs Software License Agreement
- Includes CMakeLists.txt files for building and a .clang-format file to ignore changing format that were not provided by ADI.
2026-08-04 09:45:33 +02:00
Grant Meyerhoff
5bfe6a47ce ci: don't require inputs for non-mandatory args 2026-08-04 09:45:33 +02:00
Martin Anderseck
de040602ee ci: Include *.cmake and *.cmake.in detection in changeset analyzer.
This makes the pipeline trigger tests when *.cmake and *.cmake.in files
have been changed.
2026-08-03 14:43:10 +02:00
Martin Anderseck
c8a8a3d931 ci/tools: Enable Ubuntu 26.04 build
This enables building UHD in Ubuntu 26.04 in the test pipeline.
2026-08-03 12:55:00 +02:00
Martin Anderseck
1af4625e84 cmake: Enable Ubuntu 26.04
These changes are required to build on Ubuntu 26.04 cleanly while still
maintaining backwards compatibility until Ubuntu 22.04.

- Set CMP0167 to NEW in UHD CMake paths that resolve Boost
- Scope policy changes with guard checks so older CMake versions remain
  compatible
- Ensure dependency resolution order applies policy before Boost
  discovery in package config
- Reduce CMake 3.30+ FindBoost policy warnings without breaking Ubuntu
  22.04-era toolchains
2026-08-03 12:55:00 +02:00
Doron Behar
26c0033d1e cmake: Remove Boost::system from UHDConfig.cmake component list
Boost.System has been header-only since Boost 1.69, and as of Boost 1.89
its CMake package config was removed entirely.[1]

This makes the `find_dependency(Boost ... COMPONENTS ... system ...)`
call in the installed `UHDConfig.cmake` fail on any downstream project
that does `find_package(UHD)` while linked against `Boost >= 1.89`.

Timeline of how this happened, for reference:

- Up to and including v4.9.0.1, UHDConfig.cmake.in had no Boost
  dependency check at all.
- ab724a30ec introduced the
  `find_dependency(Boost ... COMPONENTS ... system ...)` call here, to
  correctly declare Boost as a transitive dependency of the newly added
  `UHD::uhd` imported target.
- c45c296d8f fixed UHD's own build to stop
  requiring `Boost::system`, in response to it being dropped as of Boost
  1.89. That commit did not touch `UHDConfig.cmake.in`, which had only
  just gained its own separate `Boost::system` reference ten days
  earlier via ab724a30ec, and so was
  missed.
- `v4.10.0.0` ships with both commits, making it the first release where
  `UHDConfig.cmake.in`'s Boost dependency check exists but still lists
  `system`, breaking `find_package(UHD)` for any consumer linked against
  Boost >= 1.89.

[1]: 7a495bb46d
2026-08-03 12:52:47 +02:00
Frank Dietze
12d9916fc3 ci: streaming: adapt weekly pipeline 2026-08-03 10:08:50 +02:00
Martin Anderseck
c1b344b732 extension: Fix set_*x_antenna() and update C++
This fixes the segfault when calling into set_*x_antenna() within the
example extension and updates the C++ version to UHD default (20 at the
moment) to be able to build again.
The segfault was caused by the extension example never initializing the
pointers to _*x_antenna. It still doesn't do that but refers to the
radio's setter and getter methods now.
2026-08-03 09:55:38 +02:00
Martin Anderseck
2951e32013 test: Add spi_core_4000 test
This adds a unit test for the spi_core_4000 which checks for the
validation of configurations and arguments and verifies the correct
registers are written during a SPI transaction.
2026-08-03 09:51:20 +02:00
Marian Koop
62aec9a0a0 ci: Replace obsolete pipeline resource 2026-07-30 11:15:15 +02:00
Frank Dietze
74b818000f ci: upgrade pip before installing python dependencies 2026-07-30 08:29:07 +02:00
Martin Braun
63ea9328aa ci: Add Ubuntu 26.04 (resolute) build container
Note: clang-format-14 is no longer available for Ubuntu 26.04, so it is
not available in the build containers.
2026-07-29 14:42:21 +02:00
Martin Braun
2a2ce50abe lib: Fix compiler warning in db_dbsrx.cpp
On modern compilers, this file could cause a warning such as:

warning: ‘void operator delete(void*, std::size_t)’ called on pointer
‘<unknown>’ with nonzero offset [8, 9223372036854775800]
[-Wfree-nonheap-object]

A minor refactoring avoids the warning, even though it was unlikely
a real issue in the first place.
2026-07-29 14:39:22 +02:00
Frank Dietze
e416bffa95 ci: streaming: generic agent selection by capabilities 2026-07-29 11:26:04 +02:00
1022 changed files with 309484 additions and 64251 deletions

View File

@ -0,0 +1,107 @@
param(
[string]$PackageName,
[string]$Version = "",
[string]$InstallArgs = "",
[ValidateRange(0, 20)]
[int]$RetryCount = 2,
[ValidateRange(1, 3600)]
[int]$RetryDelaySeconds = 30,
[ValidateRange(0, 100000)]
[int]$LogTailLines = 300,
[Alias('h')]
[switch]$Help,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$ExtraArgs
)
function Show-Usage {
Write-Host "Usage:"
Write-Host " powershell -NoProfile -ExecutionPolicy Bypass -File install-choco-package.ps1 -PackageName <name> [-Version <version>] [-InstallArgs <args>] [-RetryCount <n>] [-RetryDelaySeconds <sec>]"
Write-Host ""
Write-Host "Options:"
Write-Host " -PackageName Chocolatey package ID (required)."
Write-Host " -Version Optional package version passed as --version."
Write-Host " -InstallArgs Optional installer args passed as --installargs."
Write-Host " -RetryCount Number of retries after the first failed attempt (default: 2)."
Write-Host " -RetryDelaySeconds Wait time between retries in seconds (default: 30)."
Write-Host " -LogTailLines Number of log lines to show on final failure (default: 300, use 0 to disable)."
Write-Host " -Help, --help Show this help text."
Write-Host ""
Write-Host "Examples:"
Write-Host " powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 -PackageName doxygen.install -Version 1.9.8"
Write-Host " powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 -PackageName cmake.install -Version 3.22.3 -InstallArgs \"ADD_CMAKE_TO_PATH=System\""
Write-Host " powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 -PackageName git"
Write-Host " powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 -PackageName doxygen.install -Version 1.9.8 -RetryCount 3 -RetryDelaySeconds 30"
Write-Host " powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 -PackageName doxygen.install -Version 1.9.8 -LogTailLines 1000"
}
if ($Help -or ($ExtraArgs -contains '--help')) {
Show-Usage
exit 0
}
if (-not $PackageName) {
Write-Host "Error: -PackageName is required."
Write-Host "Run with --help to see usage."
exit 2
}
$ErrorActionPreference = 'Stop'
$chocoRoot = if ($env:ChocolateyInstall) { $env:ChocolateyInstall } else { 'C:\ProgramData\chocolatey' }
$logPath = Join-Path $chocoRoot 'logs\chocolatey.log'
$packageLibPath = Join-Path (Join-Path $chocoRoot 'lib') $PackageName
$maxAttempts = $RetryCount + 1
$chocoArgs = @('install', '-y', $PackageName)
if ($Version) {
$chocoArgs += @('--version', $Version)
}
if ($InstallArgs) {
$chocoArgs += @('--installargs', $InstallArgs)
}
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
Write-Host "Installing Chocolatey package: $PackageName (attempt $attempt of $maxAttempts)"
& choco @chocoArgs
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
Write-Host "Successfully installed Chocolatey package: $PackageName"
exit 0
}
Write-Host "Chocolatey install failed for package '$PackageName' (exit code $exitCode)."
if ($attempt -lt $maxAttempts) {
Write-Host "Retrying in $RetryDelaySeconds seconds..."
Start-Sleep -Seconds $RetryDelaySeconds
}
}
if ($LogTailLines -gt 0) {
if (Test-Path $logPath) {
Write-Host "Showing last $LogTailLines lines from $logPath"
Get-Content $logPath -Tail $LogTailLines
}
else {
Write-Host "Chocolatey log not found at $logPath"
}
}
else {
Write-Host "Skipping Chocolatey log output because -LogTailLines is set to 0"
}
if (Test-Path $packageLibPath) {
Write-Host "Contents of $packageLibPath"
Get-ChildItem -Path $packageLibPath -Force
}
exit $exitCode

View File

@ -0,0 +1,176 @@
param(
[Parameter(Mandatory = $true)]
[string]$ManifestDir,
[Parameter(Mandatory = $true)]
[string]$Triplet,
[string]$VcpkgRoot = 'C:\vcpkg'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$vcpkgExe = Join-Path $VcpkgRoot 'vcpkg.exe'
if (-not (Test-Path -LiteralPath $vcpkgExe)) {
Write-Error "vcpkg executable not found at: $vcpkgExe"
exit 2
}
if (-not (Test-Path -LiteralPath $ManifestDir)) {
Write-Error "Manifest directory not found: $ManifestDir"
exit 2
}
Write-Host "Running vcpkg install in: $ManifestDir"
Write-Host "Triplet: $Triplet"
Push-Location $ManifestDir
try {
& $vcpkgExe install --triplet $Triplet --clean-after-build
$vcpkgExitCode = $LASTEXITCODE
}
finally {
Pop-Location
}
if ($vcpkgExitCode -eq 0) {
exit 0
}
Write-Host "##[error]vcpkg install failed with exit code: $vcpkgExitCode"
$buildtreesRoot = Join-Path $VcpkgRoot 'buildtrees'
if (-not (Test-Path -LiteralPath $buildtreesRoot)) {
Write-Host "No buildtrees directory found at $buildtreesRoot"
exit $vcpkgExitCode
}
function Write-LogIfExists {
param([string]$Path)
if (Test-Path -LiteralPath $Path) {
$logInfo = Get-Item -LiteralPath $Path
Write-Host "===== BEGIN LOG: $Path ($([int64]$logInfo.Length) bytes) ====="
Get-Content -LiteralPath $Path -Raw
Write-Host "===== END LOG: $Path ====="
return $true
}
return $false
}
function Write-LogFiles {
param(
[Parameter(Mandatory = $true)]
[string]$Directory,
[Parameter(Mandatory = $true)]
[string]$Triplet
)
if (-not (Test-Path -LiteralPath $Directory)) {
return
}
function Get-LogPriority {
param([System.IO.FileInfo]$File)
if ($File.Name -match '^config-') {
return 0
}
if ($File.Name -match '^stdout-') {
return 1
}
if ($File.Name -match '^stderr-') {
return 2
}
return 3
}
$priorityLogs = Get-ChildItem -LiteralPath $Directory -File -Filter "*${Triplet}*.log" -ErrorAction SilentlyContinue |
Sort-Object @{ Expression = { Get-LogPriority -File $_ } },
@{ Expression = { $_.LastWriteTimeUtc }; Descending = $true },
Name
if (-not $priorityLogs) {
$priorityLogs = Get-ChildItem -LiteralPath $Directory -File -Filter '*.log' -ErrorAction SilentlyContinue |
Sort-Object @{ Expression = { Get-LogPriority -File $_ } },
@{ Expression = { $_.LastWriteTimeUtc }; Descending = $true },
Name
}
if (-not $priorityLogs) {
Write-Host "No log files found in $Directory"
return
}
Write-Host "Dumping log files from $Directory"
foreach ($log in $priorityLogs) {
if ($log.Length -eq 0) {
Write-Host "===== SKIP EMPTY LOG: $($log.FullName) ====="
continue
}
Write-Host "===== BEGIN LOG: $($log.FullName) ($([int64]$log.Length) bytes) ====="
Get-Content -LiteralPath $log.FullName -Raw
Write-Host "===== END LOG: $($log.FullName) ====="
}
}
# Use the most recently modified log of any type to identify the failing port.
# This catches failures that occur before configure (fetch/extract/patch stage)
# where no config-*-out.log is ever created.
$candidate = Get-ChildItem -LiteralPath $buildtreesRoot -Recurse -File -Filter '*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
# If the most recent log is not a config-*-out.log, also try to find the newest
# config-*-out.log in the same port directory to seed the related-log dump,
# since config logs contain the most actionable CMake failure details.
if ($candidate) {
$portDirFromRecent = $candidate.DirectoryName
$configInSamePort = Get-ChildItem -LiteralPath $portDirFromRecent -File -Filter "config-$Triplet-*-out.log" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
if ($configInSamePort) {
$candidate = $configInSamePort
}
}
if (-not $candidate) {
Write-Host "No log files found under $buildtreesRoot"
exit $vcpkgExitCode
}
$portDir = $candidate.DirectoryName
$portName = Split-Path -Leaf $portDir
Write-Host "Most recent failing dependency candidate: $portName"
Write-Host "Selected log seed: $($candidate.FullName)"
$seedName = [System.IO.Path]::GetFileNameWithoutExtension($candidate.Name)
if ($seedName.EndsWith('-out', [System.StringComparison]::OrdinalIgnoreCase)) {
$seedPrefix = $seedName.Substring(0, $seedName.Length - 4)
$relatedLogs = @(
(Join-Path $portDir ($seedPrefix + '-CMakeCache.txt.log')),
(Join-Path $portDir ($seedPrefix + '-CMakeConfigureLog.yaml.log')),
(Join-Path $portDir ($seedPrefix + '-out.log'))
)
foreach ($log in $relatedLogs) {
[void](Write-LogIfExists -Path $log)
}
} else {
[void](Write-LogIfExists -Path $candidate.FullName)
}
Write-LogFiles -Directory $portDir -Triplet $Triplet
Write-Host 'Recent buildtrees logs (top 20 by timestamp):'
Get-ChildItem -LiteralPath $buildtreesRoot -Recurse -File -Filter '*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 20 FullName, LastWriteTimeUtc |
Format-Table -AutoSize |
Out-String |
Write-Host
exit $vcpkgExitCode

View File

@ -0,0 +1,101 @@
param(
[Parameter(Mandatory = $true)]
[string]$Url,
# Optional base temp directory for download and installer extraction.
# A random subfolder is always created under this base path and cleaned up unless -PreserveTemp is set.
[string]$TempDir = '',
# Preserve the generated temp working directory for debugging.
[switch]$PreserveTemp,
# All remaining arguments are passed directly to vs_buildtools.exe.
# Example: -InstallerArgs '--quiet','--wait','--norestart','--installPath','C:\VS','--add','Microsoft.VisualStudio.Workload.VCTools','--includeRecommended'
[string[]]$InstallerArgs = @('--quiet', '--wait', '--norestart', '--add', 'Microsoft.VisualStudio.Workload.VCTools', '--includeRecommended')
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# --- URL validation (mirrors check-url.ps1 logic) ---
if ([string]::IsNullOrWhiteSpace($Url)) {
throw "VS_BUILD_TOOLS_URL is empty. Provide a valid download URL."
}
Write-Host "Validating VS Build Tools URL: $Url"
$resp = Invoke-WebRequest -Uri $Url -UseBasicParsing -Method Head
if ($resp.StatusCode -ne 200) {
throw "URL check failed: StatusCode=$($resp.StatusCode)"
}
$ct = $resp.Headers['Content-Type']
if ($ct -and $ct -match 'text/html') {
throw "URL check failed: Content-Type=$ct (expected binary)"
}
$cl = [int64]($resp.Headers['Content-Length'] | Select-Object -First 1)
if ($cl -and $cl -lt 1048576) {
throw "URL check failed: Content-Length=$cl (suspiciously small)"
}
Write-Host "URL validation passed (Content-Type: $ct, Content-Length: $cl bytes)."
# --- Setup temp directory ---
if (-not [string]::IsNullOrWhiteSpace($TempDir)) {
$temp = Join-Path $TempDir ([System.IO.Path]::GetRandomFileName())
} else {
$temp = Join-Path $env:TEMP ([System.IO.Path]::GetRandomFileName())
}
Write-Host "Using temp directory: $temp"
New-Item -ItemType Directory -Path $temp -Force | Out-Null
$env:TEMP = $temp
$env:TMP = $temp
# --- Download ---
$exe = Join-Path $temp 'vs_buildtools.exe'
Write-Host "Downloading VS Build Tools to $exe ..."
Invoke-WebRequest $Url -OutFile $exe -UseBasicParsing
Write-Host "Download complete: $exe"
# --- Install ---
Push-Location $temp
try {
$fullCmd = ".\vs_buildtools.exe " + ($InstallerArgs -join ' ')
Write-Host "Install command: $fullCmd"
$p = Start-Process -FilePath '.\vs_buildtools.exe' -ArgumentList $InstallerArgs -Wait -PassThru
$code = $p.ExitCode
Write-Host "VS installer exit code: $code"
if ($code -ne 0 -and $code -ne 3010) {
# Dump installer logs to stdout before cleanup
Get-ChildItem -Path $env:TEMP -Recurse -File -Include 'dd_*', '*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 20 -ExpandProperty FullName |
ForEach-Object { Write-Host ('==== ' + $_ + ' ===='); Get-Content $_ -Tail 250 }
$b1 = Join-Path $env:ProgramData 'Microsoft\VisualStudio\Packages\_bootstrapper'
if (Test-Path $b1) {
Get-ChildItem -Path $b1 -Recurse -File -Filter '*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 20 -ExpandProperty FullName |
ForEach-Object { Write-Host ('==== ' + $_ + ' ===='); Get-Content $_ -Tail 250 }
}
throw "Installer failed with exit code: $code"
}
}
finally {
Pop-Location
if ($PreserveTemp) {
Write-Host "Preserving temp directory: $temp"
} else {
Remove-Item -LiteralPath $temp -Recurse -Force -ErrorAction SilentlyContinue
}
# Give Windows time to flush pending filesystem writes (e.g. msiexec deferred
# operations) before Docker captures the layer. Without this, hcsshim::ImportLayer
# can fail with ERROR_PATH_NOT_FOUND (0x3) due to a race condition between
# NTFS lazy-write completion and the layer snapshot.
# Runs unconditionally: even a failed install may have partial writes pending.
Write-Host "Waiting 10 seconds for filesystem writes to flush..."
Start-Sleep -Seconds 10
Write-Host "Filesystem flush wait complete."
}
Write-Host "VS Build Tools installed successfully."

View File

@ -0,0 +1,87 @@
# please follow docker best practices
# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
FROM ubuntu:26.04
LABEL maintainer="Ettus Research"
ARG PIP_INDEX_HOST
ARG PIP_INDEX_URL
# This will make apt-get install without question
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get -y upgrade && \
apt-get -y install -q \
build-essential \
ccache \
clang \
curl \
git \
sudo \
# Install UHD dependencies
abi-dumper \
cmake \
doxygen \
dpdk \
libboost-all-dev \
libdpdk-dev \
libgps-dev \
libudev-dev \
libusb-1.0-0-dev \
ncompress \
ninja-build \
python3-dev \
python3-docutils \
python3-mako \
python3-numpy \
python3-pip \
python3-requests \
python3-setuptools \
pybind11-dev \
# gRPC baseline
libgrpc++-dev \
libprotobuf-dev \
# gRPC protobuf compiler
protobuf-compiler-grpc \
# gRPC Python runtime
python3-grpcio \
# Install deb dependencies
debootstrap \
devscripts \
pbuilder \
debhelper \
libncurses5-dev \
python3-ruamel.yaml \
# Install GNURadio dependencies
python3-sphinx \
python3-lxml \
libsdl1.2-dev \
libgsl-dev \
libqwt-qt5-dev \
libqt5opengl5-dev \
libgmp3-dev \
libfftw3-dev \
gir1.2-gtk-3.0 \
libpango1.0-dev \
python3-pyqt5 \
liblog4cpp5-dev \
libzmq3-dev \
python3-click \
python3-click-plugins \
python3-zmq \
python3-scipy \
python3-gi-cairo \
python3-pygccxml \
python3-jsonschema \
libspdlog-dev \
libsndfile1-dev \
&& \
rm -rf /var/lib/apt/lists/*
# Optionally use cached index.
RUN if [ -n "$PIP_INDEX_URL" ]; then \
python3 -m pip config --global set global.index-url $PIP_INDEX_URL && \
python3 -m pip config --global set global.trusted-host $PIP_INDEX_HOST; \
fi
RUN python3 -m pip config list

View File

@ -1,53 +0,0 @@
# Docker build context must be uhd/uhddev root
# please follow docker best practices
# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
FROM mcr.microsoft.com/windows/servercore:ltsc2025
LABEL maintainer="Ettus Research"
ARG PIP_INDEX_HOST=""
ARG PIP_INDEX_URL=""
ENV VCPKG_DISABLE_METRICS=1
RUN setx chocolateyVersion 1.4.0 /m
RUN @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" \
-NoProfile -InputFormat None -ExecutionPolicy Bypass \
-Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; \
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && \
SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
RUN choco install -y cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' --version=3.22.3
RUN choco install -y doxygen.install --version=1.9.8
RUN choco install -y git
RUN choco install -y NSIS --version=3.06.1
RUN choco install -y vim
RUN choco install -y python3 --version=3.10.11
# Optionally use cached index.
RUN if defined PIP_INDEX_URL ( \
pip config --global set global.index-url %PIP_INDEX_URL% && \
pip config --global set global.trusted-host %PIP_INDEX_HOST% \
)
RUN pip config list
RUN python -m pip install --upgrade pip
RUN pip install mako requests numpy ruamel.yaml grpcio
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
Invoke-WebRequest "https://aka.ms/vs/17/release/vs_buildtools.exe" \
-OutFile "%TEMP%\vs_buildtools.exe" -UseBasicParsing
RUN "%TEMP%\vs_buildtools.exe" --quiet --wait --norestart --noUpdateInstaller \
--add Microsoft.VisualStudio.Workload.VCTools \
--includeRecommended
RUN setx VCPKG_INSTALL_DIR "c:\\vcpkg" /m
RUN git clone https://github.com/microsoft/vcpkg %VCPKG_INSTALL_DIR% && \
cd %VCPKG_INSTALL_DIR% && \
bootstrap-vcpkg.bat
# Add custom UHD vcpkg triplet
COPY host/cmake/vcpkg/* c:/vcpkg/triplets/
RUN mkdir c:\\uhd-vcpkg
COPY .ci\\docker\\vcpkg\\vcpkg.json c:/uhd-vcpkg/vcpkg.json
RUN cd c:\\uhd-vcpkg && %VCPKG_INSTALL_DIR%\vcpkg.exe install \
--triplet uhd-x64-windows-static-md \
--clean-after-build

View File

@ -1,53 +0,0 @@
# Docker build context must be uhd/uhddev root
# please follow docker best practices
# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
FROM mcr.microsoft.com/windows/servercore:ltsc2025
LABEL maintainer="Ettus Research"
ARG PIP_INDEX_HOST=""
ARG PIP_INDEX_URL=""
ENV VCPKG_DISABLE_METRICS=1
RUN setx chocolateyVersion 1.4.0 /m
RUN @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" \
-NoProfile -InputFormat None -ExecutionPolicy Bypass \
-Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; \
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && \
SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
RUN choco install -y cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' --version=3.22.3
RUN choco install -y doxygen.install --version=1.9.8
RUN choco install -y git
RUN choco install -y NSIS --version=3.06.1
RUN choco install -y vim
RUN choco install -y python3 --version=3.12.6
# Optionally use cached index.
RUN if defined PIP_INDEX_URL ( \
pip config --global set global.index-url %PIP_INDEX_URL% && \
pip config --global set global.trusted-host %PIP_INDEX_HOST% \
)
RUN pip config list
RUN python -m pip install --upgrade pip
RUN pip install mako requests numpy ruamel.yaml setuptools grpcio
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
Invoke-WebRequest "https://aka.ms/vs/17/release/vs_buildtools.exe" \
-OutFile "%TEMP%\vs_buildtools.exe" -UseBasicParsing
RUN "%TEMP%\vs_buildtools.exe" --quiet --wait --norestart --noUpdateInstaller \
--add Microsoft.VisualStudio.Workload.VCTools \
--includeRecommended
RUN setx VCPKG_INSTALL_DIR "c:\\vcpkg" /m
RUN git clone https://github.com/microsoft/vcpkg %VCPKG_INSTALL_DIR% && \
cd %VCPKG_INSTALL_DIR% && \
bootstrap-vcpkg.bat
# Add custom UHD vcpkg triplet
COPY host/cmake/vcpkg/* c:/vcpkg/triplets/
RUN mkdir c:\\uhd-vcpkg
COPY .ci\\docker\\vcpkg\\vcpkg.json c:/uhd-vcpkg/vcpkg.json
RUN cd c:\\uhd-vcpkg && %VCPKG_INSTALL_DIR%\vcpkg.exe install \
--triplet uhd-x64-windows-static-md \
--clean-after-build

View File

@ -0,0 +1,96 @@
# Docker build context must be uhd/uhddev root
# please follow docker best practices
# https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
# Defaults for variables used in FROM statement
ARG WIN_BASE_TAG=ltsc2025
FROM mcr.microsoft.com/windows/servercore:${WIN_BASE_TAG}
LABEL maintainer="Ettus Research"
# Minimum required argument
ARG VS_BUILD_TOOLS_URL
# Optional arguments for customization
ARG PYTHON_VERSION=3.10.11
ARG VCPKG_MANIFEST_FILE=vcpkg.json
ARG PIP_INDEX_HOST=""
ARG PIP_INDEX_URL=""
ENV VCPKG_DISABLE_METRICS=1
# Optional argument for vcpkg binary cache directory
ARG VCPKG_BINARY_CACHE_DIR="C:\vcpkg-cache"
ENV VCPKG_BINARY_SOURCES=clear;files,${VCPKG_BINARY_CACHE_DIR},readwrite
# Enable long file paths (>260 characters)
RUN reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f
# Install vs build tools
COPY .ci/docker/scripts/install-vs-buildtools.ps1 C:/Temp/install-vs-buildtools.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
"& 'C:\Temp\install-vs-buildtools.ps1' \
-Url '%VS_BUILD_TOOLS_URL%' \
-InstallerArgs '--quiet','--wait','--norestart','--nocache', \
'--add','Microsoft.VisualStudio.Workload.VCTools', \
'--includeRecommended'"
RUN setx chocolateyVersion 1.4.0 /m
RUN @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" \
-NoProfile -InputFormat None -ExecutionPolicy Bypass \
-Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; \
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && \
SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" && \
choco config set webRequestTimeoutSeconds 600
COPY .ci/docker/scripts/install-choco-package.ps1 C:/Temp/install-choco-package.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName doxygen.install -Version 1.9.8 && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName cmake.install -Version 3.22.3 -InstallArgs ADD_CMAKE_TO_PATH=System && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName git && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName NSIS -Version 3.06.1 && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName vim && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName python3 -Version %PYTHON_VERSION%
# Optionally use cached index.
RUN if defined PIP_INDEX_URL ( \
pip config --global set global.index-url %PIP_INDEX_URL% && \
pip config --global set global.trusted-host %PIP_INDEX_HOST% \
)
COPY host/python/requirements.txt C:/Temp/requirements.txt
COPY host/python/requirements_np2.txt C:/Temp/requirements_np2.txt
RUN pip config list && \
python -m pip install --upgrade pip && \
powershell -NoProfile -ExecutionPolicy Bypass -Command \
"if ([version]('%PYTHON_VERSION%') -ge [version]('3.13')) { \
pip install -r C:/Temp/requirements_np2.txt \
} else { \
pip install -r C:/Temp/requirements.txt \
}; \
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }"
RUN setx VCPKG_INSTALL_DIR "c:\\vcpkg" /m
RUN git clone https://github.com/microsoft/vcpkg %VCPKG_INSTALL_DIR% && \
cd %VCPKG_INSTALL_DIR% && \
bootstrap-vcpkg.bat
# Add custom UHD vcpkg triplet
COPY host/cmake/vcpkg/* c:/vcpkg/triplets/
# Define triplet to be used by this container
RUN setx VCPKG_TARGET_TRIPLET "uhd-x64-windows-static-md" /m
RUN echo set(VCPKG_BUILD_TYPE release)>>"%VCPKG_INSTALL_DIR%\triplets\%VCPKG_TARGET_TRIPLET%.cmake"
RUN type "%VCPKG_INSTALL_DIR%\triplets\%VCPKG_TARGET_TRIPLET%.cmake"
# Copy vcpkg cache from mapped folder to
RUN echo Current path %cd%
COPY .cache/vcpkg ${VCPKG_BINARY_CACHE_DIR}
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
"New-Item -ItemType Directory -Path 'c:\uhd-vcpkg' -Force | Out-Null"
COPY .ci/docker/vcpkg/${VCPKG_MANIFEST_FILE} c:/uhd-vcpkg/vcpkg.json
COPY .ci/docker/scripts/install-vcpkg-triplet.ps1 C:/Temp/install-vcpkg-triplet.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-vcpkg-triplet.ps1 \
-ManifestDir C:\uhd-vcpkg \
-Triplet %VCPKG_TARGET_TRIPLET% \
-VcpkgRoot %VCPKG_INSTALL_DIR%

View File

@ -17,32 +17,42 @@ ARG VCPKG_MANIFEST_FILE=vcpkg-vs2026.json
ARG PIP_INDEX_HOST=""
ARG PIP_INDEX_URL=""
ENV VCPKG_DISABLE_METRICS=1
# Optional argument for vcpkg binary cache directory
ARG VCPKG_BINARY_CACHE_DIR="C:\vcpkg-cache"
ENV VCPKG_BINARY_SOURCES=clear;files,${VCPKG_BINARY_CACHE_DIR},readwrite
# Enable long file paths (>260 characters)
RUN reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f
# Install vs build tools
COPY .ci/docker/scripts/install-vs-buildtools.ps1 C:/Temp/install-vs-buildtools.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
"& 'C:\Temp\install-vs-buildtools.ps1' \
-Url '%VS_BUILD_TOOLS_URL%' \
-InstallerArgs '--quiet','--wait','--norestart','--nocache', \
'--add','Microsoft.VisualStudio.Workload.VCTools', \
'--includeRecommended'"
RUN setx chocolateyVersion 2.5.1 /m
RUN @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" \
-NoProfile -InputFormat None -ExecutionPolicy Bypass \
-Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; \
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && \
SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
RUN choco install -y cmake.install --installargs 'ADD_CMAKE_TO_PATH=System' --version=4.2.1
RUN choco install -y doxygen.install --version=1.9.8
RUN choco install -y git
RUN choco install -y NSIS --version=3.11.0
RUN choco install -y vim
RUN choco install -y python3 --version=%PYTHON_VERSION%
COPY .ci/docker/scripts/check-url.ps1 C:/Temp/check-url.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\check-url.ps1 \
-Url "%VS_BUILD_TOOLS_URL%"
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
Invoke-WebRequest "%VS_BUILD_TOOLS_URL%" \
-OutFile "%TEMP%\vs_buildtools.exe" -UseBasicParsing
RUN "%TEMP%\vs_buildtools.exe" --quiet --wait --norestart --noUpdateInstaller \
--add Microsoft.VisualStudio.Workload.VCTools \
--includeRecommended
SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" && \
choco config set webRequestTimeoutSeconds 600
COPY .ci/docker/scripts/install-choco-package.ps1 C:/Temp/install-choco-package.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName doxygen.install -Version 1.9.8 && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName cmake.install -Version 4.2.1 -InstallArgs ADD_CMAKE_TO_PATH=System && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName git && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName NSIS -Version 3.11.0 && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName vim && \
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-choco-package.ps1 \
-PackageName python3 -Version %PYTHON_VERSION%
# Optionally use cached index.
RUN if defined PIP_INDEX_URL ( \
@ -51,18 +61,36 @@ RUN if defined PIP_INDEX_URL ( \
)
COPY host/python/requirements.txt C:/Temp/requirements.txt
RUN pip config list
RUN python -m pip install --upgrade pip
RUN pip install -r C:/Temp/requirements.txt
COPY host/python/requirements_np2.txt C:/Temp/requirements_np2.txt
RUN pip config list && \
python -m pip install --upgrade pip && \
powershell -NoProfile -ExecutionPolicy Bypass -Command \
"if ([version]('%PYTHON_VERSION%') -ge [version]('3.13')) { \
pip install -r C:/Temp/requirements_np2.txt \
} else { \
pip install -r C:/Temp/requirements.txt \
}; \
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }"
RUN setx VCPKG_INSTALL_DIR "c:\\vcpkg" /m
RUN git clone https://github.com/microsoft/vcpkg %VCPKG_INSTALL_DIR% && \
cd %VCPKG_INSTALL_DIR% && \
bootstrap-vcpkg.bat
# Add custom UHD vcpkg triplet
# Add custom UHD vcpkg triplet
COPY host/cmake/vcpkg/* c:/vcpkg/triplets/
RUN mkdir c:\\uhd-vcpkg
# Define triplet to be used by this container
RUN setx VCPKG_TARGET_TRIPLET "uhd-x64-windows-static-md" /m
RUN echo set(VCPKG_BUILD_TYPE release)>>"%VCPKG_INSTALL_DIR%\triplets\%VCPKG_TARGET_TRIPLET%.cmake"
RUN type "%VCPKG_INSTALL_DIR%\triplets\%VCPKG_TARGET_TRIPLET%.cmake"
# Copy vcpkg cache from mapped folder to
RUN echo Current path %cd%
COPY .cache/vcpkg ${VCPKG_BINARY_CACHE_DIR}
RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \
"New-Item -ItemType Directory -Path 'c:\uhd-vcpkg' -Force | Out-Null"
COPY .ci/docker/vcpkg/${VCPKG_MANIFEST_FILE} c:/uhd-vcpkg/vcpkg.json
RUN cd c:\\uhd-vcpkg && %VCPKG_INSTALL_DIR%\vcpkg.exe install \
--triplet uhd-x64-windows-static-md \
--clean-after-build
COPY .ci/docker/scripts/install-vcpkg-triplet.ps1 C:/Temp/install-vcpkg-triplet.ps1
RUN powershell -NoProfile -ExecutionPolicy Bypass -File C:\Temp\install-vcpkg-triplet.ps1 \
-ManifestDir C:\uhd-vcpkg \
-Triplet %VCPKG_TARGET_TRIPLET% \
-VcpkgRoot %VCPKG_INSTALL_DIR%

View File

@ -0,0 +1,108 @@
#
# Copyright 2025 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
# Description:
#
# This template defines a stages-based pipeline for building B310 FPGA
# targets.
#
# See https://aka.ms/yaml for pipeline YAML documentation.
#
parameters:
## Optional parameters
# Option to ignore cached artifacts (if available) and perform
# a clean IP build.
- name: clean_ip_build
type: boolean
default: false
# Number of parallel jobs to use for IP build
- name: num_ip_jobs
type: number
default: 5
# Option to publish intermediate files
- name: publish_int_files
type: boolean
default: false
# Package and publish images
- name: package_and_publish_images
type: boolean
default: true
# Package access in the SDR server ('Internal' or 'Public')
- name: package_access
type: string
default: 'Internal'
# From where to pull FPGA bitfiles if we're not building them
- name: fpga_imgs_source
type: string
default: 'Mono Pipeline'
# B310 FPGA targets to build
- name: b310_full_targets_matrix
type: object
default:
B310_REPLAY:
image_core: b310_replay_rfnoc_image_core.yml
target_name: B310
image_core_name: usrp_b310_fpga_replay
artifact_name: B310_REPLAY
timeout: 720
max_attempts: 2
B310_FIFO:
image_core: b310_fifo_rfnoc_image_core.yml
target_name: B310
image_core_name: usrp_b310_fpga_fifo
artifact_name: B310_FIFO
timeout: 720
max_attempts: 2
- name: b310_reduced_targets_matrix
type: object
default:
B310_REPLAY:
image_core: b310_replay_rfnoc_image_core.yml
target_name: B310
image_core_name: usrp_b310_fpga_replay
artifact_name: B310_REPLAY
timeout: 720
max_attempts: 2
B310_FIFO:
image_core: b310_fifo_rfnoc_image_core.yml
target_name: B310
image_core_name: usrp_b310_fpga_fifo
artifact_name: B310_FIFO
timeout: 720
max_attempts: 2
stages:
### START: B310 stages
- stage: provide_b310_fpga_images_stage
displayName: Provide B310 FPGA Images
dependsOn: analyze_changeset
variables:
UhdTestList: $[stageDependencies.analyze_changeset.analyze.outputs['gen_testlist.UhdTestList']]
BuildRequired: $[or(
contains(stageDependencies.analyze_changeset.analyze.outputs['gen_testlist.UhdTestList'], 'fpga.usrp3.b310'),
contains(stageDependencies.analyze_changeset.analyze.outputs['gen_testlist.UhdTestList'], 'fpga.usrp3.all')
)]
jobs:
- template: ../templates/jobs-provide-fpga-artifacts.yml
parameters:
id: b310
device_name: B310
ip_target: B310_IP
package_name: b3xx_b310_fpga_default
clean_ip_build: ${{ parameters.clean_ip_build }}
num_ip_jobs: ${{ parameters.num_ip_jobs }}
publish_int_files: ${{ parameters.publish_int_files }}
package_and_publish_images: ${{ parameters.package_and_publish_images }}
package_access: ${{ parameters.package_access }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
top_dir: uhddev/fpga/usrp3/top/b300
target_full_matrix: ${{ parameters.b310_full_targets_matrix }}
target_reduced_matrix: ${{ parameters.b310_reduced_targets_matrix }}
### END: B310 stages

View File

@ -33,7 +33,7 @@ echo "---- Run hwsetup ----"
# variables based on the agent's configuration so we can find the EDA
# tools.
pushd $BUILD_SOURCESDIRECTORY/uhddev/.ci/hwtools
source hwsetup.sh --hwtools=1.5.2
source hwsetup.sh
popd
echo "---- Fetch the FPGA simulation libraries ----"
@ -52,36 +52,12 @@ python $(dirname $BASH_SOURCE)/merge_modelsim_ini.py \
# Removing the temporary files
rm -f modelsim.mpf compileFpgaLib.log
echo "---- Fetch Vivado patches ----"
export PATCHES_DIR=$BUILD_SOURCESDIRECTORY/patches
mkdir -p $PATCHES_DIR
pushd $PATCHES_DIR
# Fetch the AR76780 patch for Vivado
cp /opt/synced/Xilinx/patches/AR76780_Vivado_2021_1_preliminary_rev1.zip ./AR76780_Vivado_2021_1_preliminary_rev1.zip
unzip -q -o ./AR76780_Vivado_2021_1_preliminary_rev1.zip -d ./AR76780_Vivado_2021/
rm -f AR76780_Vivado_2021_1_preliminary_rev1.zip
# Add the AR76780 patch for the simulation library
cp /opt/synced/modelsim/patches/fir_compiler_v7_2_76780.zip ./fir_compiler_v7_2_76780.zip
unzip -q -o ./fir_compiler_v7_2_76780.zip -d ./fir_compiler_v7_2_76780/
rm -f ./fir_compiler_v7_2_76780.zip
popd
echo "---- Apply the Vivado patches ----"
pushd $BUILD_SOURCESDIRECTORY
# Apply the patch to Vivado
export XILINX_PATH=$PATCHES_DIR/AR76780_Vivado_2021/vivado
# Add the FIR compiler simulation model to local modelsim.ini
# Delete all lines containing "fir_compiler_v7_2_76780" to avoid duplicates
sed -i -e '/fir_compiler_v7_2_76780/d' ./modelsim.ini
# Duplicate line containing "fir_compiler_v7_2_16"
sed -i -e '/fir_compiler_v7_2_16/p' ./modelsim.ini
# Replace the first "fir_compiler_v7_2_16" line with our new library
PATCHES_DIR_ESC=$(echo $PATCHES_DIR | sed 's_/_\\/_g')
sed -i -e "0,/fir_compiler_v7_2_16/s/^fir_compiler_v7_2_16.*\$/fir_compiler_v7_2_76780 = $PATCHES_DIR_ESC\/fir_compiler_v7_2_76780/" ./modelsim.ini
echo "---- Configure modelsim paths ----"
# Tell our build tools to use this modelsim.ini for this session
export MODELSIM_INI=$(pwd)/modelsim.ini
export MSIM_VIV_COMPLIBDIR=$(pwd)
popd
export MODELSIM_INI=$BUILD_SOURCESDIRECTORY/modelsim.ini
export MSIM_VIV_COMPLIBDIR=$BUILD_SOURCESDIRECTORY
echo "---- Run setupenv ----"
source $1/setupenv.sh --vivado-path $(dirname $XILINX_VIVADO) --modelsim-path $(dirname $MODELSIM)
# update XILINX_VIVADO to the parent directory
echo "Sourcing $1/setupenv.sh with Vivado path: $XILINX_VIVADO and Modelsim path: $(dirname $MODELSIM)"
source $1/setupenv.sh --vivado-path $XILINX_VIVADO --modelsim-path $(dirname $MODELSIM)

View File

@ -127,3 +127,15 @@ stages:
rfnoc_include_dir: ${{ parameters.rfnoc_include_dir }}
### END: E320 stages
### START: B310 stages
- template: ../devices/job-build-fpga-b310.yml
parameters:
clean_ip_build: ${{ parameters.clean_ip_build }}
publish_int_files: ${{ parameters.publish_int_files }}
package_and_publish_images: ${{ parameters.package_and_publish_images }}
package_access: ${{ parameters.package_access }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
### END: B310 stages

View File

@ -59,7 +59,7 @@ jobs:
name: Drivers-NIBuildFarm-RFMIBUILD
demands:
- Agent.OS -equals Linux
- HIGH_CPU_PER_THREAD
- SUPER_HIGH_CPU_PER_THREAD
variables:
- group: sdr-pipeline-vars
strategy:
@ -91,6 +91,7 @@ jobs:
# twice.
echo "##[group]Setting up build environment..."
source $(Build.SourcesDirectory)/uhddev/.ci/fpga/scripts/run_setup.sh ./
python3 -m pip install --upgrade pip
pip3 install -U numpy mako "ruamel.yaml<0.18.0" jsonschema
set -o xtrace
# hwsetup.sh injects garbage cmake versions into PATH, so avoid that by
@ -131,7 +132,7 @@ jobs:
python3 ../../tools/utils/repeat_fpga_build.py \
--image-core $(image_core) $TARGET_ARG $BITFILE_NAME_ARG $INCLUDE_DIR_ARG \
--image-core-name $IMAGE_CORE_NAME \
--vivado-path $(dirname $XILINX_VIVADO) \
--vivado-path $XILINX_VIVADO \
--num $(max_attempts) --seed $BUILD_SEED --persistent \
$TIMEOUT_ARG
pip3 uninstall -y uhd

View File

@ -55,12 +55,10 @@ jobs:
# Pipeline caching is leveraged in this job to minimize the time it takes
# to build all the IP.
# At a high level, the pipeline checks for an available and valid cache
# artifact from its scope (target branch in PR or its own branch) and
# downloads it to the workspace (Cache hit).
# If no valid cache artifact exists (Cache miss), then the build will run
# and the pipeline will automatically save the result as a cache artifact
# for subsequent runs.
# At a high level, the pipeline checks for the newest available cache
# artifact for this IP target in its visible scopes and downloads it.
# The per-run key forces a miss so restoreKeys selects the newest cache;
# successful writable runs then save a new cache entry.
# Further details on "Pipeline caching" available online:
# https://docs.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops
# We always allow the Cache task to run, even in clean builds. This way
@ -68,7 +66,10 @@ jobs:
# artifact for subsequent CI and PR runs to fetch.
- task: Cache@2
inputs:
key: "uhddev-fpga-${{ parameters.ip_target }}"
key: |
"uhddev-fpga" | "${{ parameters.ip_target }}" | "$(Build.BuildId)"
restoreKeys: |
"uhddev-fpga" | "${{ parameters.ip_target }}"
path: ${{ parameters.directory }}/build-ip
cacheHitVar: CACHE_RESTORED
displayName: Cache IP
@ -80,9 +81,8 @@ jobs:
bash $(Build.SourcesDirectory)/uhddev/.ci/fpga/scripts/refresh_ip.sh \
`realpath build-ip/` $BUILD_IP_CACHE_HASH
workingDirectory: ${{ parameters.directory }}
# Refresh IP when caching is allowed (clean_build = false) and a cache
# artifact was fetched.
condition: and(eq('${{ parameters.clean_build }}', 'false'), eq(variables.CACHE_RESTORED, 'true'))
# Refresh IP when caching is allowed and an exact or fallback cache was fetched.
condition: and(eq('${{ parameters.clean_build }}', 'false'), or(eq(variables.CACHE_RESTORED, 'true'), eq(variables.CACHE_RESTORED, 'inexact')))
displayName: "Refresh IP"
- bash: |

View File

@ -36,6 +36,7 @@ jobs:
echo "---- Run testbenches ----"
pushd ./tools/utils
python3 -m pip install --upgrade pip
pip3 install -r requirements.txt
python3 ./run_testbenches.py --logged --simulator ${{ parameters.simulator }} --excludes=${{ variables.excludes_file }}.excludes -j4 --report $(Build.ArtifactStagingDirectory)/junit.xml run
popd

View File

@ -7,11 +7,13 @@
from hwtools.api import vapi
def do_options():
vapi.set_project_name(name="X410_FPGA", pretty_name="USRP X410 FPGA")
vapi.set_top_sim_entity("x4xx")
vapi.set_top_synth_entity("x4xx")
def do_files():
# x400 related files
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400", tree=False)
@ -23,17 +25,28 @@ def do_files():
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/rf/100m")
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/rf/200m")
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/rf/400m")
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/ip/x4xx_ps_rfdc_bd/common/regmap")
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/ip/x4xx_ps_rfdc_bd/x410_ps_rfdc_bd/regmap")
vapi.find_files_and_add_to_fileset(
base="repo", path="fpga/usrp3/top/x400/ip/x4xx_ps_rfdc_bd/common/regmap"
)
vapi.find_files_and_add_to_fileset(
base="repo", path="fpga/usrp3/top/x400/ip/x4xx_ps_rfdc_bd/x410_ps_rfdc_bd/regmap"
)
# library
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/lib/rfnoc")
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/lib/control")
# regmap files
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/regmap", tree=False)
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/regmap/x410", tree=False)
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/cpld/regmap", tree=False)
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/cpld/regmap/x410", tree=False)
vapi.find_files_and_add_to_fileset(
base="repo", path="fpga/usrp3/top/x400/regmap/x410", tree=False
)
vapi.find_files_and_add_to_fileset(
base="repo", path="fpga/usrp3/top/x400/cpld/regmap", tree=False
)
vapi.find_files_and_add_to_fileset(
base="repo", path="fpga/usrp3/top/x400/cpld/regmap/x410", tree=False
)
# simulation files
vapi.find_files_and_add_to_fileset(base="repo", path="fpga/usrp3/top/x400/rf/sim")
@ -44,17 +57,22 @@ def do_files():
vapi.remove_from_fileset(".*lib/rfnoc/.*_tb")
vapi.remove_from_fileset("pll-v")
def do_preparse():
pass
def do_precompile():
pass
def do_presynth():
pass
def do_finish():
vapi.run_user_function(function="update_xmlparse_files", base="self", path="../common.py")
def vhook_warn_filter(msgstr):
return False

View File

@ -21,10 +21,10 @@ def do_setup():
# Note: The hwtools version for CI purposes is fixed in run_setup.sh
rootapi.add_tool(name="hwtools", version="head")
rootapi.add_tool(name="Vivado", version="2021.1", allowlater=False)
rootapi.add_tool(name="modelsim", version="2020.4", allowlater=True)
rootapi.add_tool(name="Diamond", version="3.12", allowlater=True)
rootapi.add_tool(name="Quartus", version="20.1", allowlater=True)
rootapi.add_tool(name="Vivado", version="2025.2")
rootapi.add_tool(name="Questasim", version="2025.1")
rootapi.add_tool(name="Diamond", version="3.12")
rootapi.add_tool(name="Quartus", version="20.1")
def do_finish():

View File

@ -41,12 +41,16 @@ jobs:
docker_image_matrix_windows_python_build=$(sed '9q;d' "$artifact_file")
elif [ "$line_count" -eq 6 ]; then
macos_line=5
docker_image_matrix_lin=$(python3 "$derive_script" --input "$artifact_file" --type build --os linux)
docker_image_matrix_win=$(python3 "$derive_script" --input "$artifact_file" --type build --os windows)
docker_image_matrix_ubuntu_deb=$(python3 "$derive_script" --input "$artifact_file" --type installer --os linux --package deb --filter-out "focal,questing")
docker_image_matrix_source_package=$(python3 "$derive_script" --input "$artifact_file" --type source --os linux --select noble)
docker_image_matrix_fedora_rpm=$(python3 "$derive_script" --input "$artifact_file" --type installer --os linux --package rpm)
docker_image_matrix_windows_python_build=$(python3 "$derive_script" --input "$artifact_file" --section windows-python-build-matrix)
# Keep derive failures blocking for strict CI reasons (CI/scheduled),
# but allow non-CI/manual debug runs to continue with '{}' fallback.
# PR runs also use fallback mode.
# The behavior is implemented by derive_docker_image_matrix.py.
docker_image_matrix_lin=$(python3 "$derive_script" --empty-on-error-non-ci --input "$artifact_file" --type build --os linux)
docker_image_matrix_win=$(python3 "$derive_script" --empty-on-error-non-ci --input "$artifact_file" --type build --os windows)
docker_image_matrix_ubuntu_deb=$(python3 "$derive_script" --empty-on-error-non-ci --input "$artifact_file" --type installer --os linux --package deb --filter-out "focal,questing")
docker_image_matrix_source_package=$(python3 "$derive_script" --empty-on-error-non-ci --input "$artifact_file" --type source --os linux --select noble)
docker_image_matrix_fedora_rpm=$(python3 "$derive_script" --empty-on-error-non-ci --input "$artifact_file" --type installer --os linux --package rpm)
docker_image_matrix_windows_python_build=$(python3 "$derive_script" --empty-on-error-non-ci --input "$artifact_file" --section windows-python-build-matrix)
else
echo "Error: Unexpected DockerImageNames format with $line_count lines; expected 6 or 9"
exit 1

View File

@ -44,6 +44,7 @@ jobs:
# Init toolchain
source $(Build.SourcesDirectory)/uhddev/.ci/fpga/scripts/run_setup.sh $(Build.SourcesDirectory)/uhddev/fpga/usrp3/top/x300
pushd $(Build.SourcesDirectory)/uhddev/fpga/usrp3/tools/utils
python3 -m pip install --upgrade pip
pip3 install -r requirements.txt
popd
# Now run the test.

View File

@ -42,7 +42,7 @@ jobs:
timeoutInMinutes: 120
variables:
# Need to use list format to support inclusion of variable groups.
# Variable group used by .ci/templates/steps-windows-sbom.yml@ettus-rts
# Variable group used by .ci/templates/steps-windows-sbom.yml@ettus-rts
- group: meta-ettus-vars
# Variable group needed for code signing (signtool-p4-path)
- ${{ if eq(parameters.toolset, 'msbuild') }}:
@ -102,14 +102,16 @@ jobs:
# Job uses template which causes self checkout to result in different values
# for $(Build.SourcesDirectory) due to single vs multiple repositories use.
# Switching to use manual path definition $(Pipeline.Workspace)/s/<RepoName>
# for consistent behavior to avoid
# for consistent behavior to avoid
# $(Pipeline.Workspace)/s vs $(Pipeline.Workspace)/s/<RepoName>).
# Custom path now includes <RepoName>, which is now hardcoded, rather than
# Custom path now includes <RepoName>, which is now hardcoded, rather than
# use name from git repo (avoids problem when building from public uhd repo).
# Checkout path needs to be defined relative to $(Pipeline.Workspace).
- checkout: self
clean: true
path: 's/uhddev'
retryCountOnTaskFailure: 3
timeoutInMinutes: 10
# Need to use OS specific steps because predefined variable
# (i.e. $(Pipeline.Workspace) on Linux is treated as runtime shell expansion,
# but actually handled by Azure and the quotes ensure correct, delayed variable
@ -128,6 +130,7 @@ jobs:
- download: uhd_build_docker_container
- template: .ci/templates/steps-setup.yml@ettus-rts
- ${{ if eq(parameters.toolset, 'fedora_rpm') }}:
- template: steps-install-nib310rio-dev.yml
- template: steps-build-uhd-installer-fedora-rpm.yml
parameters:
uhdSrcDir: $(uhdSrcDir)
@ -139,7 +142,7 @@ jobs:
- ${{ if parameters.create_sbom }}:
- template: .ci/templates/steps-linux-sbom.yml@ettus-rts
parameters:
toolset: ${{ parameters.toolset }}
toolset: ${{ parameters.toolset }}
installer: ${{ parameters.installer }}
sbomComponentList: 'uhd libuhd libboost libusb'
buildOSName: $(buildOSName)
@ -148,16 +151,18 @@ jobs:
uhdInstallerDir: $(Build.BinariesDirectory)/uhddev-installer
releaseBinaries: ${{ parameters.releaseBinaries }}
- ${{ if eq(parameters.toolset, 'ubuntu_deb') }}:
- template: steps-install-nib310rio-dev.yml
- template: steps-build-uhd-installer-ubuntu-deb.yml
parameters:
uhdSrcDir: $(uhdSrcDir)
uhdInstallerDir: $(Build.BinariesDirectory)/uhddev-installer
ubuntuReleaseName: $(ubuntuReleaseName)
nib310rioDebPackageDir: $(Pipeline.Workspace)/nib310rio_kernel_packages/nib310rio-deb-packages
cmake_args: ${{ parameters.cmake_args }}
- ${{ if parameters.create_sbom }}:
- template: .ci/templates/steps-linux-sbom.yml@ettus-rts
parameters:
toolset: ${{ parameters.toolset }}
toolset: ${{ parameters.toolset }}
installer: ${{ parameters.installer }}
sbomComponentList: 'uhd libuhd'
buildOSName: $(buildOSName)

View File

@ -87,6 +87,9 @@ jobs:
- download: uhd_build_docker_container
- ${{ if not(or(contains(parameters.toolset, 'msbuild'), eq(parameters.toolset, 'make_homebrew_macos'))) }}:
- template: steps-install-nib310rio-dev.yml
- ${{ if eq(parameters.toolset, 'make') }}:
- template: steps-build-uhd-make.yml
parameters:

View File

@ -0,0 +1,30 @@
parameters:
- name: uhdSrcDir
type: string
- name: uhdArtifactSource
type: string
default: current
- name: uhdFpgaArtifactSource
type: string
default: current
- name: fpga_imgs_source
type: string
jobs:
- template: job-uhd-devtest.yml
parameters:
suiteName: 'b310_test'
knownHost: '|1|jkP5jWiUwlZ78hX40IN2iqrBVqk=|0qCDGjJHYWE0Z4pi4ZecrczzKt8= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg='
toolset: 'make'
uhdSrcDir: '${{ parameters.uhdSrcDir }}'
uhdArtifactSource: '${{ parameters.uhdArtifactSource }}'
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
dutMatrix:
b310:
devType: 'b3xx'
devModel: 'b310'
devName: 'b310-ATS'
devSerial: '3665C03'
devBus: 'pcie'
devtestPattern: 'b3xx'

View File

@ -24,7 +24,6 @@ jobs:
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
redisHost: 'sdr-rhombus'
vivado_dir: '/opt/Xilinx/Vivado_Lab/2020.1/'
dutMatrix:
${{ if contains(parameters.testDevices, 'x3xx') }}:
rhombus-x300-UBX-0:

View File

@ -24,7 +24,6 @@ jobs:
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
redisHost: 'dre-s175'
vivado_dir: $(xilinx_location)
dutMatrix:
${{ if contains(parameters.testDevices, 'b2xx') }}:
sdr-s175-dev-tests-b206-0:
@ -54,7 +53,6 @@ jobs:
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
redisHost: 'dre-s175'
vivado_dir: $(xilinx_location)
dutMatrix:
${{ if contains(parameters.testDevices, 'x3xx') }}:
sdr-s175-dev-tests-x310-4:

View File

@ -20,8 +20,7 @@ jobs:
uhdArtifactSource: '${{ parameters.uhdArtifactSource }}'
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
redisHost: 'sdr-osmium-test'
vivado_dir: '/opt/Xilinx/Vivado_Lab/2023.2/'
redisHost: $(host_redisHost)
dutMatrix:
x310-OBX:
devAgent: osmium
@ -34,4 +33,4 @@ jobs:
devFpga: 'HG,XG'
devtestPattern: 'x3x0'
jtagSerial: $(x310_OBX_jtagSerial)
jtagServer: 'nitest@sdr-osmium-test'
jtagServer: $(host_jtagServer)

View File

@ -20,8 +20,6 @@ parameters:
default: ''
- name: dutMatrix
type: object
- name: vivado_dir
type: string
jobs:
- job: uhd_devtest_${{ parameters.suiteName }}
@ -142,6 +140,7 @@ jobs:
cd $(Agent.TempDirectory)
python3 -m venv devtest_venv
source devtest_venv/bin/activate
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/uhddev/host/tests/devtest/requirements.txt
pip3 install -r $(Build.SourcesDirectory)/uhddev/.ci/utils/requirements.txt
mkdir -p $(Common.TestResultsDirectory)/devtest
@ -168,6 +167,7 @@ jobs:
cd $(Agent.TempDirectory)
python3 -m venv devtest_venv
source devtest_venv/bin/activate
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/uhddev/host/tests/devtest/requirements.txt
pip3 install -r $(Build.SourcesDirectory)/uhddev/.ci/utils/requirements.txt
mkdir -p $(Common.TestResultsDirectory)/devtest
@ -194,6 +194,7 @@ jobs:
cd $(Agent.TempDirectory)
python3 -m venv devtest_venv
source devtest_venv/bin/activate
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/uhddev/host/tests/devtest/requirements.txt
pip3 install -r $(Build.SourcesDirectory)/uhddev/.ci/utils/requirements.txt
mkdir -p $(Common.TestResultsDirectory)/devtest
@ -215,9 +216,11 @@ jobs:
displayName: Run b2xx devtest on $(devName)
- script: |
echo PATH: $PATH
cd $(Agent.TempDirectory)
python3 -m venv devtest_venv
source devtest_venv/bin/activate
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/uhddev/host/tests/devtest/requirements.txt
pip3 install -r $(Build.SourcesDirectory)/uhddev/.ci/utils/requirements.txt
mkdir -p $(Common.TestResultsDirectory)/devtest
@ -225,7 +228,7 @@ jobs:
export PATH=$(Build.BinariesDirectory)/uhddev/build/utils:$(Build.BinariesDirectory)/uhddev/build/examples:$PATH
export LD_LIBRARY_PATH=$(Build.BinariesDirectory)/uhddev/build/lib:$LD_LIBRARY_PATH
python3 ${{ parameters.uhdSrcDir }}/uhddev/.ci/utils/mutex_hardware.py \
--jtag_x3xx $(devType),$(devModel),$(jtagServer),$(jtagSerial),$(Build.BinariesDirectory)/uhddev/build/fpga_images/,${{ parameters.vivado_dir }} \
--jtag_x3xx $(devType),$(devModel),$(jtagServer),$(jtagSerial),$(Build.BinariesDirectory)/uhddev/build/fpga_images/ \
--fpgas $(devFpga) \
"${{ parameters.redisHost }}" "$(devName)" \
"$(Build.BinariesDirectory)/uhddev/build/utils/uhd_usrp_probe --args addr=$(devAddr)" \
@ -238,6 +241,30 @@ jobs:
condition: and(succeeded(), eq(variables.devType, 'x300'), eq(variables.devBus, 'ip'))
displayName: Run x3xx devtest on $(devName) $(devFpga)
- script: |
cd $(Agent.TempDirectory)
python3 -m venv devtest_venv
source devtest_venv/bin/activate
pip3 install -r $(Build.SourcesDirectory)/uhddev/host/tests/devtest/requirements.txt
pip3 install -r $(Build.SourcesDirectory)/uhddev/.ci/utils/requirements.txt
mkdir -p $(Common.TestResultsDirectory)/devtest
cd $(Common.TestResultsDirectory)/devtest
export PATH=$(Build.BinariesDirectory)/uhddev/build/utils:$(Build.BinariesDirectory)/uhddev/build/examples:$PATH
export LD_LIBRARY_PATH=$(Build.BinariesDirectory)/uhddev/build/lib:$LD_LIBRARY_PATH
export UHD_IMAGES_DIR=$(Build.BinariesDirectory)/uhddev/build/fpga_images
uhd_image_loader --args type=$(devType),serial=$(devSerial)
sleep 10
$(Build.BinariesDirectory)/uhddev/build-installed/bin/uhd_images_downloader -t b3xx -t fw
uhd_usrp_probe --args type=$(devType),serial=$(devSerial)
python3 ${{ parameters.uhdSrcDir }}/uhddev/host/tests/devtest/run_testsuite.py \
--src-dir ${{ parameters.uhdSrcDir }}/uhddev/host/tests/devtest \
--devtest-pattern $(devtestPattern) --args type=$(devType),serial=$(devSerial) \
--build-type Release --build-dir $(Build.BinariesDirectory)/uhddev/build \
--python-interp python3 --xml
continueOnError: true
condition: and(succeeded(), eq(variables.devType, 'b3xx'))
displayName: Run b3xx devtest on $(devName)
- script: |
cat $(Common.TestResultsDirectory)/devtest/log*.log
echo ""
@ -251,7 +278,7 @@ jobs:
$(Common.TestResultsDirectory)/devtest/devtestresults.xml
continueOnError: true
displayName: Format devtest xml
condition: and(always(), eq(variables.devType, 'b200'))
condition: and(always(), or(eq(variables.devType, 'b200'), eq(variables.devType, 'b3xx')))
- script: |
cd $(Common.TestResultsDirectory)/devtest
@ -261,7 +288,7 @@ jobs:
$(Common.TestResultsDirectory)/devtest/devtestresults.xml
continueOnError: true
displayName: Format devtest xml
condition: and(always(), ne(variables.devType, 'b200'))
condition: and(always(), and(ne(variables.devType, 'b200'), ne(variables.devType, 'b3xx')))
- publish: $(Common.TestResultsDirectory)
artifact: test-logs-devtest-$(devName)-$(devModel)-$(devFpga)-run$(System.JobAttempt)

View File

@ -0,0 +1,39 @@
parameters:
- name: uhdSrcDir
type: string
- name: uhdArtifactSource
type: string
default: current
- name: uhdFpgaArtifactSource
type: string
default: current
- name: fpga_imgs_source
type: string
- name: testDevices
type: string
default: 'b3xx'
- name: extra_rf_test_args
type: string
default: ''
jobs:
- template: job-uhd-rf-tests.yml
parameters:
toolset: 'make'
uhdSrcDir: '${{ parameters.uhdSrcDir }}'
uhdArtifactSource: '${{ parameters.uhdArtifactSource }}'
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
extra_rf_test_args: '${{ parameters.extra_rf_test_args }}'
dutMatrix:
b310:
devType: 'b3xx'
devModel: 'b310'
devName: 'b310-ATS'
devSerial: '3665C03'
devBus: 'pcie'
devFpga: 'defaultFPGA'
devtestPattern: 'b3xx'
pytestDUT: 'b310'
pytestAtsConfig: 'b300_ats'
pipelineAgent: 'b310-ats'

View File

@ -24,8 +24,7 @@ jobs:
uhdArtifactSource: '${{ parameters.uhdArtifactSource }}'
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: '${{ parameters.fpga_imgs_source }}'
redisHost: 'sdr-osmium-test'
vivado_dir: '/opt/Xilinx/Vivado_Lab/2023.2/'
redisHost: $(host_redisHost)
extra_rf_test_args: '${{ parameters.extra_rf_test_args }}'
dutMatrix:
x310-obx-0:
@ -37,12 +36,12 @@ jobs:
db1Serial: '34A3BA0'
supportsCorrections: true
devBus: 'ip'
devAddr: '192.168.40.2'
sfpAddrs: '192.168.40.2'
devAddr: $(x310_OBX_devAddr)
sfpAddrs: $(x310_OBX_devAddr)
devFpga: 'XG'
devtestPattern: 'x300'
pytestDUT: 'x310'
pytestAtsConfig: 'osmium_ats'
pipelineAgent: 'obx-ats-agent'
jtagSerial: $(x310_OBX_jtagSerial)
jtagServer: 'nitest@sdr-osmium-test'
jtagServer: $(host_jtagServer)

View File

@ -16,9 +16,6 @@ parameters:
default: ''
- name: dutMatrix
type: object
- name: vivado_dir
type: string
default: ''
- name: extra_rf_test_args
type: string
default: ''
@ -225,6 +222,7 @@ jobs:
fi
source $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/setup_venv.sh
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/uhddev/.ci/utils/requirements_312.txt
cd "$(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests"
@ -240,10 +238,11 @@ jobs:
"python3 ../automated_main.py --ats_config $(pytestAtsConfig) --dut $(pytestDUT) \
--test_selector $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/pytests \
--results_path $(Common.TestResultsDirectory)/pytest/rf/$(devFpga) \
--run_vars_overrides "mgmt_addr=$(devAddr)" --run_vars_overrides "addr=$(devAddr)" \
$(parameters.extra_rf_test_args)" || rf_test_status=$?
elif [ "$(devType)" = "x300" ]; then
python3 ${{ parameters.uhdSrcDir }}/.ci/utils/mutex_hardware.py \
--jtag_x3xx $(devType),$(devModel),$(jtagServer),$(jtagSerial),$(Build.BinariesDirectory)/uhddev/build/fpga_images/,${{ parameters.vivado_dir }} \
--jtag_x3xx $(devType),$(devModel),$(jtagServer),$(jtagSerial),$(Build.BinariesDirectory)/uhddev/build/fpga_images/ \
--fpgas $(devFpga) \
--sfp_addrs $(sfpAddrs) \
"${{ parameters.redisHost }}" "$(devName)" \
@ -251,12 +250,32 @@ jobs:
"python3 ../automated_main.py --ats_config $(pytestAtsConfig) --dut $(pytestDUT) \
--test_selector $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/pytests \
--results_path $(Common.TestResultsDirectory)/pytest/rf/$(devFpga) \
--run_vars_overrides "mgmt_addr=$(devAddr)" --run_vars_overrides "addr=$(devAddr)" \
$(parameters.extra_rf_test_args)" || rf_test_status=$?
elif [ "$(devType)" = "b3xx" ]; then
cd $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/pytests
uhd_image_loader --args type=$(devType),serial=$(devSerial)
sleep 10
uhd_images_downloader -t b3xx -t fw
uhd_usrp_probe --args type=$(devType),serial=$(devSerial)
python3 ../automated_main.py --ats_config $(pytestAtsConfig) --dut $(pytestDUT) \
--results_path $(Common.TestResultsDirectory)/pytest/rf/$(devFpga) ${{ parameters.extra_rf_test_args }} || rf_test_status=$?
cd $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests
fi
# only if devBus is "ip" then we need to run the non-rf tests with the same mgmt_addr and addr overrides as the rf tests
if [ "$(devBus)" = "ip" ]; then
python3 automated_main.py --ats_config $(pytestAtsConfig) --dut $(pytestDUT) \
--test_selector $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/pytests_non_rf \
--results_path $(Common.TestResultsDirectory)/pytest/non_rf/$(devFpga) \
--run_vars_overrides "mgmt_addr=$(devAddr)" --run_vars_overrides "addr=$(devAddr)" \
${{ parameters.extra_rf_test_args }} || non_rf_test_status=$?
else
python3 automated_main.py --ats_config $(pytestAtsConfig) --dut $(pytestDUT) \
--test_selector $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/pytests_non_rf \
--results_path $(Common.TestResultsDirectory)/pytest/non_rf/$(devFpga) \
${{ parameters.extra_rf_test_args }} || non_rf_test_status=$?
fi
python3 automated_main.py --ats_config $(pytestAtsConfig) --dut $(pytestDUT) \
--test_selector $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/pytests_non_rf \
--results_path $(Common.TestResultsDirectory)/pytest/non_rf/$(devFpga) \
${{ parameters.extra_rf_test_args }} || non_rf_test_status=$?
if [ $rf_test_status -ne 0 ] || [ $non_rf_test_status -ne 0 ]; then
echo "At least one pytest invocation failed (rf=$rf_test_status, non_rf=$non_rf_test_status)."

View File

@ -1,133 +0,0 @@
parameters:
- name: uhdSrcDir
type: string
- name: uhdArtifactSource
type: string
default: current
- name: uhdFpgaArtifactSource
type: string
default: current
- name: testLength
type: string
- name: dpdkDirs
type: object
default:
- '/opt/dpdk-19.11'
- '/opt/dpdk-23.11'
jobs:
- template: job-uhd-streaming-tests.yml
parameters:
suiteName: 'beauty'
testLength: '${{ parameters.testLength }}'
toolset: 'make'
uhdSrcDir: '${{ parameters.uhdSrcDir }}'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
dpdkDirs: '${{ parameters.dpdkDirs }}'
dutMatrix:
# beauty-N320-0 XG:
# dutName: 'beauty-N320-0'
# dutType: 'N320'
# dutAddr: '192.168.10.4'
# dutSecondAddr: '192.168.20.4'
# dutFPGA: 'XG'
# dutNameId: ''
# dutNumRecvFrames: ''
# dutNumSendFrames: ''
beauty-X310-0:
dutName: 'beauty-X310-0'
dutType: 'x310'
dutAddr: '$(x310_dutAddr)'
dutSecondAddr: '$(x310_dutSecondAddr)'
dutFPGA: 'XG'
dutNameId: ''
dutNumRecvFrames: ''
dutNumSendFrames: ''
jtagSerial: '$(x310_jtagSerial)'
sfpInt0: '$(x310_sfpInt0)'
sfpInt1: '$(x310_sfpInt1)'
# beauty-X410-0 X4_200:
# dutName: 'beauty-X410-0'
# dutFamily: 'x4xx'
# dutType: 'x410'
# dutAddr: '$(x410_dutAddr)'
# dutSecondAddr: '$(x410_dutSecondAddr)'
# dutFPGA:'X4_200'
# dutNameId: ''
# dutEmbeddedImagesArtifact: 'x4xx-images'
# uartSerial: '$(x410_uartSerial)'
# dutNumRecvFrames: ''
# dutNumSendFrames: ''
# sfpInt0: '$(x410_sfpInt0)'
# sfpInt1: '$(x410_sfpInt1)'
beauty-X410-0 CG_400:
dutName: 'beauty-X410-0'
dutFamily: 'x4xx'
dutType: 'x410'
dutAddr: '$(x410_dutAddr)'
dutSecondAddr: '$(x410_dutSecondAddr)'
dutFPGA: 'CG_400'
dutNameId: ''
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x410_uartSerial)'
dutNumRecvFrames: ''
dutNumSendFrames: ''
sfpInt0: '$(x410_sfpInt0)'
sfpInt1: '$(x410_sfpInt1)'
beauty-X410-0 UC_200:
dutName: 'beauty-X410-0'
dutFamily: 'x4xx'
dutType: 'x410'
# UC image: device is reachable only via dutSecondAddr
dutAddr: '$(x410_dutSecondAddr)'
dutSecondAddr: '$(x410_dutSecondAddr)'
dutFPGA: 'UC_200'
dutNameId: ''
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x410_uartSerial)'
dutNumRecvFrames: ''
dutNumSendFrames: ''
sfpInt0: '$(x410_sfpInt0)'
sfpInt1: '$(x410_sfpInt1)'
# beauty-E320-0:
# dutName: 'beauty-E320-0'
# dutType: 'E320'
# dutAddr: '192.168.10.5'
# dutSecondAddr: ''
# dutFPGA: 'XG'
# dutNameId: ''
# dutNumRecvFrames: ''
# dutNumSendFrames: ''
# beauty-N310-0:
# dutName: 'beauty-N310-0'
# dutType: 'N310'
# dutAddr: '192.168.10.6'
# dutSecondAddr: '192.168.20.6'
# dutFPGA: 'XG'
# dutNameId: ''
# dutNumRecvFrames: ''
# dutNumSendFrames: ''
# beauty-X310_TwinRx-0:
# dutName: 'beauty-X310_TwinRx-0'
# dutType: 'X310_TwinRx'
# dutAddr: '192.168.10.7'
# dutSecondAddr: '192.168.20.7'
# dutFPGA: 'XG'
# dutNameId: ''
# dutNumRecvFrames: ''
# dutNumSendFrames: ''
# sfpInt0: 'ens4f0'
# sfpInt1: 'ens4f1'
# beauty-B210-0:
# dutName: 'beauty-B210-0'
# dutFamily: 'b200'
# dutType: 'B210'
# dutAddr: ''
# dutSecondAddr: ''
# dutFPGA: ''
# dutNameId: 'MyB210'
# dutNumRecvFrames: '256'
# dutNumSendFrames: '256'
# dutGetFpgaImagesMethod: 'uhd_images_downloader'

View File

@ -1,50 +0,0 @@
parameters:
- name: uhdSrcDir
type: string
- name: uhdArtifactSource
type: string
default: current
- name: uhdFpgaArtifactSource
type: string
default: current
- name: testLength
type: string
- name: dpdkDirs
type: object
default:
- '/opt/dpdk-19.11'
- '/opt/dpdk-23.11'
jobs:
- template: job-uhd-streaming-tests.yml
parameters:
suiteName: 'sdr_s175_tests_b2xx'
testLength: '${{ parameters.testLength }}'
toolset: 'make'
uhdSrcDir: '${{ parameters.uhdSrcDir }}'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
dpdkDirs: '${{ parameters.dpdkDirs }}'
dutMatrix:
sdr-s175-streaming-tests-B206-0:
dutName: 'sdr_s175_tests-b206-0'
dutFamily: 'b200'
dutType: 'B206'
dutAddr: ''
dutSecondAddr: ''
dutFPGA: ''
dutNameId: 'B206i'
dutNumRecvFrames: '256'
dutNumSendFrames: '256'
dutGetFpgaImagesMethod: 'uhd_images_downloader'
sdr-s175-streaming-tests-B210-0:
dutName: 'sdr_s175_tests-b210-0'
dutFamily: 'b200'
dutType: 'B210'
dutAddr: ''
dutSecondAddr: ''
dutFPGA: ''
dutNameId: 'MyB210'
dutNumRecvFrames: '256'
dutNumSendFrames: '256'
dutGetFpgaImagesMethod: 'uhd_images_downloader'

View File

@ -1,57 +0,0 @@
parameters:
- name: uhdSrcDir
type: string
- name: uhdArtifactSource
default: current
type: string
- name: uhdFpgaArtifactSource
type: string
default: current
- name: testLength
type: string
- name: dpdkDirs
type: object
default:
- '/opt/dpdk-19.11'
- '/opt/dpdk-23.11'
jobs:
- template: job-uhd-streaming-tests.yml
parameters:
suiteName: 'streamingx440'
testLength: '${{ parameters.testLength }}'
toolset: 'make'
nicType: '100gbe'
uhdSrcDir: '${{ parameters.uhdSrcDir }}'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: '${{ parameters.uhdFpgaArtifactSource }}'
dpdkDirs: '${{ parameters.dpdkDirs }}'
dutMatrix:
X440-0 CG_400:
dutName: 'streaming-X440-0'
dutFamily: 'x4xx'
dutType: 'x440'
dutAddr: '$(x440_dutAddr)'
dutSecondAddr: '$(x440_dutSecondAddr)'
dutFPGA: 'CG_400'
dutNameId: ''
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x440_uartSerial)'
dutNumRecvFrames: ''
dutNumSendFrames: ''
sfpInt0: '$(x440_sfpInt0)'
sfpInt1: '$(x440_sfpInt1)'
X440-0 CG_1600:
dutName: 'streaming-X440-0'
dutFamily: 'x4xx'
dutType: 'x440'
dutAddr: '$(x440_dutAddr)'
dutSecondAddr: '$(x440_dutSecondAddr)'
dutFPGA: 'CG_1600'
dutNameId: ''
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x440_uartSerial)'
dutNumRecvFrames: ''
dutNumSendFrames: ''
sfpInt0: '$(x440_sfpInt0)'
sfpInt1: '$(x440_sfpInt1)'

File diff suppressed because it is too large Load Diff

View File

@ -183,6 +183,7 @@ jobs:
py -3.10 -m venv lvtest_venv
call lvtest_venv\Scripts\activate.bat
echo "Installing required Python packages into the virtual environment"
python -m pip install --upgrade pip
pip install lvtest
displayName: Prepare venv (Python 3.10)

View File

@ -0,0 +1,100 @@
#
# Copyright 2021 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
parameters:
- name: stage_name
type: string
- name: display_name
type: string
- name: machine_name
type: string
- name: stage_condition
type: string
- name: enabled
type: boolean
default: true
- name: create_sbom
type: boolean
- name: build_sdk
type: boolean
- name: cache_sstate
type: boolean
- name: ext_fpga
type: boolean
- name: fpga_imgs_source
type: string
stages:
- stage: ${{ parameters.stage_name }}
displayName: ${{ parameters.display_name }}
dependsOn: analyze_changeset
${{ if parameters.enabled }}:
condition: >
and(
succeeded(),
${{ parameters.stage_condition }}
)
${{ else }}:
condition: false
variables:
AUTO_CONF: $(Agent.BuildDirectory)/auto.conf
jobs:
- template: .ci/templates/job_build_filesystem.yml@meta-ettus
parameters:
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
cache_downloads: False
machines:
- ${{ parameters.machine_name }}
auto_conf: $AUTO_CONF
run_from_external_repo: true
prebuild_steps:
- script: |
rm $(AUTO_CONF)
touch $(AUTO_CONF)
displayName: create auto.conf
- template: .ci/templates/oe_localconf_externalsrc.yml@meta-ettus
parameters:
conf_file: $AUTO_CONF
repository: self
package_paths:
- package: uhd
path: host
- package: mpmd
path: mpm
- ${{ if parameters.ext_fpga }}:
- template: .ci/templates/oe_localconf_externalsrc_pipeline.yml@meta-ettus
parameters:
conf_file: $AUTO_CONF
pipeline: uhd_mono_pipeline
patterns: |
usrp_e310_*_fpga.*
usrp_e320_fpga_*.*
usrp_n3??_fpga_*.*
usrp_x4??_fpga_*.*
packages:
- uhd-fpga-images
flatten: true
- script: |
ls -l $(Agent.BuildDirectory)/uhd_mono_pipeline
displayName: list fpga images
- ${{ if contains(parameters.fpga_imgs_source, 'Internal') }}:
- bash: |
cat <<EOF >>$AUTO_CONF
UHD_BASE_URL = "$FILE_SERVER_URL"
EOF
env:
FILE_SERVER_URL: $(InternalFileServerUrl)
- bash: |
cat <<EOF >>$AUTO_CONF
PREFERRED_UHD_VERSION = "4.11%"
EOF
postbuild_steps:
- script: |
rm $(AUTO_CONF)
displayName: delete auto.conf
condition: always()

View File

@ -26,6 +26,10 @@ parameters:
- name: testLength
type: string
default: 'smoke'
- name: selected_streaming_tests
type: string
displayName: Streaming Tests Multi-Select (comma-separated)
default: 'B206,B210,B310,X310_XG,X410_CG_400,X410_UC_200,X440_CG_400,X440_CG_1600'
- name: create_sbom
type: boolean
displayName: Create SBOMs
@ -161,30 +165,35 @@ parameters:
X420_CG_1000:
image_core: x420_CG_1000_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_CG_1000
target_name: X420
artifact_name: X420_CG_1000
timeout: 720
max_attempts: 2
X420_X4_1000:
image_core: x420_X4_1000_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_X4_1000
target_name: X420
artifact_name: X420_X4_1000
timeout: 720
max_attempts: 2
X420_CG_400:
image_core: x420_CG_400_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_CG_400
target_name: X420
artifact_name: X420_CG_400
timeout: 720
max_attempts: 2
X420_X4_400:
image_core: x420_X4_400_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_X4_400
target_name: X420
artifact_name: X420_X4_400
timeout: 720
max_attempts: 2
X420_X4_200:
image_core: x420_X4_200_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_X4_200
target_name: X420
artifact_name: X420_X4_200
timeout: 720
max_attempts: 2
@ -200,6 +209,14 @@ parameters:
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_CG_800:
image_core: x440_CG_800_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_CG_800
target_name: X440
artifact_name: X440_CG_800
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_CG_400:
image_core: x440_CG_400_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_CG_400
@ -216,6 +233,14 @@ parameters:
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_X4_800:
image_core: x440_X4_800_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_X4_800
target_name: X440
artifact_name: X440_X4_800
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_X4_400:
image_core: x440_X4_400_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_X4_400
@ -232,6 +257,72 @@ parameters:
timeout: 720
attempt_timeout: 360
max_attempts: 2
# Next parameters are used to control conditional execution of embedded system image build stages.
# They are not parameters that can be set by the user,
# but rather they are computed based on the output of the analyze_changeset stage.
- name: embedded_stage_condition_e3xx
type: string
default: >
or(
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.linux'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'mpm.build'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.e3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.e310'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.e320'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.e3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.e310'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.e320'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.e3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.e310'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.e320')
)
- name: embedded_stage_condition_n3xx
type: string
default: >
or(
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.linux'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'mpm.build'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.n3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.n310'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.n320'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.n3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.n310'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.n320'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.n3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.n310'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.n320')
)
- name: embedded_stage_condition_x4xx
type: string
default: >
or(
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.linux'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'mpm.build'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.x4xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.x410'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.x420'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.x440'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.x4xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.x410'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.x420'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.x440'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.x4xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.x410'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.x420'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.x440')
)
variables:
- template: ../uhd-pipeline-vars.yml
@ -241,6 +332,9 @@ resources:
- pipeline: uhd_build_docker_container
source: 'uhddev Build Docker Containers'
branch: master
- pipeline: nib310rio_kernel_packages
source: 'usrp-pcie-driver pipeline'
branch: main
- pipeline: usrp-kas-pipeline
source: 'usrp-kas'
branch: scarthgap
@ -284,7 +378,7 @@ resources:
ref: master
- repository: hwtools
type: git
ref: refs/tags/pip/hwsetup/0.1/0.1.3
ref: main
name: DevCentral/hwtools
- repository: labview-usrp-examples
type: github
@ -407,105 +501,70 @@ stages:
custom_boost_version_url: ${{ parameters.custom_boost_version_url }}
grpc_version: ${{ parameters.grpc_version }}
- stage: build_uhd_embedded_system_images
displayName: Build UHD Embedded System Images
dependsOn: analyze_changeset
# This can be improved:
# Right now, if any embedded condition is true, then all embedded targets
# with a checkbox will be built.
condition: >
and(
succeeded(),
or(
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'uhd.build.linux'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'mpm.build'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.e3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.n3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming.x4'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.e3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.n3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.x4'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.e3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.n3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.x4')
)
)
variables:
AUTO_CONF: $(Agent.BuildDirectory)/auto.conf
jobs:
- template: .ci/templates/job_build_filesystem.yml@meta-ettus
parameters:
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
cache_downloads: False
machines:
- ${{ if parameters.build_e310_sg1 }}:
- e310_sg1
- ${{ if parameters.build_e310_sg3 }}:
- e310_sg3
- ${{ if parameters.build_e320 }}:
- e320
- ${{ if parameters.build_n3xx }}:
- n3xx
- ${{ if parameters.build_x4xx }}:
- x4xx
auto_conf: $AUTO_CONF
run_from_external_repo: true
prebuild_steps:
- script: |
rm $(AUTO_CONF)
touch $(AUTO_CONF)
displayName: create auto.conf
- template: .ci/templates/oe_localconf_externalsrc.yml@meta-ettus
parameters:
conf_file: $AUTO_CONF
repository: self
package_paths:
- package: uhd
path: host
- package: mpmd
path: mpm
- ${{ if parameters.ext_fpga }}:
- template: .ci/templates/oe_localconf_externalsrc_pipeline.yml@meta-ettus
parameters:
conf_file: $AUTO_CONF
pipeline: uhd_mono_pipeline
patterns: |
usrp_e310_*_fpga.*
usrp_e320_fpga_*.*
usrp_n3??_fpga_*.*
usrp_x4??_fpga_*.*
packages:
- uhd-fpga-images
flatten: true
- script: |
ls -l $(Agent.BuildDirectory)/uhd_mono_pipeline
displayName: list fpga images
# If requested, we set a variable "UHD_BASE_URL", which is consumed by
# the uhd-fpga-images recipe to an internal file server which is closer
# to the build machines. It is simply appended to auto.conf, where
# bitbake will pick it up and apply it to the recipe.
- ${{ if contains(parameters.fpga_imgs_source, 'Internal') }}:
- bash: |
cat <<EOF >>$AUTO_CONF
UHD_BASE_URL = "$FILE_SERVER_URL"
EOF
env:
FILE_SERVER_URL: $(InternalFileServerUrl)
- bash: |
cat <<EOF >>$AUTO_CONF
PREFERRED_UHD_VERSION = "4.11%"
EOF
postbuild_steps:
- script: |
rm $(AUTO_CONF)
displayName: delete auto.conf
condition: always()
- template: stage-build-uhd-embedded-system-image.yml
parameters:
stage_name: build_uhd_embedded_system_images_e310_sg1
display_name: "[e310_sg1] Build UHD Embedded System Images"
machine_name: e310_sg1
stage_condition: ${{ parameters.embedded_stage_condition_e3xx }}
enabled: ${{ parameters.build_e310_sg1 }}
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
ext_fpga: ${{ parameters.ext_fpga }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- template: stage-build-uhd-embedded-system-image.yml
parameters:
stage_name: build_uhd_embedded_system_images_e310_sg3
display_name: "[e310_sg3] Build UHD Embedded System Images"
machine_name: e310_sg3
stage_condition: ${{ parameters.embedded_stage_condition_e3xx }}
enabled: ${{ parameters.build_e310_sg3 }}
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
ext_fpga: ${{ parameters.ext_fpga }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- template: stage-build-uhd-embedded-system-image.yml
parameters:
stage_name: build_uhd_embedded_system_images_e320
display_name: "[e320] Build UHD Embedded System Images"
machine_name: e320
stage_condition: ${{ parameters.embedded_stage_condition_e3xx }}
enabled: ${{ parameters.build_e320 }}
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
ext_fpga: ${{ parameters.ext_fpga }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- template: stage-build-uhd-embedded-system-image.yml
parameters:
stage_name: build_uhd_embedded_system_images_n3xx
display_name: "[n3xx] Build UHD Embedded System Images"
machine_name: n3xx
stage_condition: ${{ parameters.embedded_stage_condition_n3xx }}
enabled: ${{ parameters.build_n3xx }}
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
ext_fpga: ${{ parameters.ext_fpga }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- template: stage-build-uhd-embedded-system-image.yml
parameters:
stage_name: build_uhd_embedded_system_images_x4xx
display_name: "[x4xx] Build UHD Embedded System Images"
machine_name: x4xx
stage_condition: ${{ parameters.embedded_stage_condition_x4xx }}
enabled: ${{ parameters.build_x4xx }}
create_sbom: ${{ parameters.create_sbom }}
build_sdk: ${{ parameters.build_sdk }}
cache_sstate: ${{ parameters.cache_sstate }}
ext_fpga: ${{ parameters.ext_fpga }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- stage: package_uhd_src
displayName: Package UHD Source
@ -610,6 +669,25 @@ stages:
fpga_imgs_source: 'Internal'
create_sbom: ${{ parameters.create_sbom }}
- template: .ci/templates/stages-nisbom-uploads.yml@ettus-rts
parameters:
artifactSourceMode: current
uhdArtifactSource: uhd_mono_pipeline
releaseBinaries: false
dependsOnGroups:
fedoraInstaller:
- build_uhd_installer_stage_fedora
ubuntuInstaller:
- build_uhd_installer_stage_ubuntu
winInstaller:
- build_uhd_installer_stage_win
embeddedImages:
- build_uhd_embedded_system_images_e310_sg1
- build_uhd_embedded_system_images_e310_sg3
- build_uhd_embedded_system_images_e320
- build_uhd_embedded_system_images_n3xx
- build_uhd_embedded_system_images_x4xx
###############################################################################
# FPGA Stages. Here, we build bitfiles (or at least download the right artifacts)
###############################################################################
@ -626,7 +704,7 @@ stages:
jobs:
- template: ../fpga/templates/job-run-testbenches.yml
parameters:
simulator: modelsim
simulator: qsim
- template: ../fpga/templates/job-run-testbenches.yml
parameters:
simulator: xsim
@ -727,6 +805,7 @@ stages:
parameters:
runSimulation: true
publish: "false"
requestCiPublishPermission: false
###############################################################################
# Hardware Test Stages. These require having valid software and FPGA images.
@ -764,7 +843,8 @@ stages:
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_n3xx
- build_uhd_embedded_system_images_e320
- provide_n300_fpga_images_stage
- provide_n310_fpga_images_stage
- provide_n320_fpga_images_stage
@ -774,7 +854,7 @@ stages:
succeeded(),
or(
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.n3'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.n3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.e320')
)
)
@ -790,7 +870,7 @@ stages:
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_x4xx
- build_gnuradio_stage_linux
- provide_x410_fpga_images_stage
- provide_x440_fpga_images_stage
@ -829,7 +909,7 @@ stages:
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_x4xx
- build_gnuradio_stage_linux
- provide_x410_fpga_images_stage
- provide_x440_fpga_images_stage
@ -853,6 +933,7 @@ stages:
fpga_imgs_source: 'Pipeline'
${{ else }}:
fpga_imgs_source: 'Filesystem'
runX410X4400RfSubset: true
testLength: ${{ parameters.testLength }}
- template: tests/job-uhd-x440-hardware-tests-pebbles.yml
parameters:
@ -867,7 +948,7 @@ stages:
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_x4xx
- build_gnuradio_stage_linux
- provide_x420_fpga_images_stage
# This will make $(UhdTestList) available to jobs/steps/tasks, but not for the
@ -892,12 +973,16 @@ stages:
${{ else }}:
fpga_imgs_source: 'Filesystem'
testLength: ${{ parameters.testLength }}
runPyTest: true
runPyTestNonRF: true
runDevTest: true
runSystemImageTest: true
- stage: test_uhd_x4xx_saison_stage
displayName: RF Test UHD x4xx saison
dependsOn:
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_x4xx
- build_gnuradio_stage_linux
- analyze_changeset
- provide_x440_fpga_images_stage
@ -929,7 +1014,7 @@ stages:
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_n3xx
- build_gnuradio_stage_linux
- provide_n300_fpga_images_stage
- provide_n310_fpga_images_stage
@ -979,36 +1064,48 @@ stages:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- stage: test_streaming_beauty_stage
displayName: Test UHD Streaming beauty
- stage: test_uhd_b310_stage
displayName: Test UHD B310
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- provide_x300_fpga_images_stage
- provide_x310_fpga_images_stage
- provide_x410_fpga_images_stage
- build_gnuradio_stage_linux
- provide_b310_fpga_images_stage
condition: >
and(
succeeded(),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming')
or(
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.rf.b3xx'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.all'),
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'devtest.b3xx')
)
)
jobs:
- template: job-uhd-streaming-tests-beauty.yml
- template: job-uhd-devtest-b310.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- template: job-uhd-rf-tests-b310.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
testLength: ${{ parameters.testLength }}
- template: job-uhd-streaming-tests-s175.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
testLength: ${{ parameters.testLength }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- stage: test_streaming_x440_stage
displayName: Test UHD Streaming x440
- stage: uhd_streaming_tests_stage
displayName: UHD Streaming Tests
dependsOn:
- analyze_changeset
- build_uhd_stage_linux
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_e320
- build_uhd_embedded_system_images_n3xx
- build_uhd_embedded_system_images_x4xx
- provide_e320_fpga_images_stage
- provide_n300_fpga_images_stage
- provide_n310_fpga_images_stage
- provide_n320_fpga_images_stage
- provide_x300_fpga_images_stage
- provide_x310_fpga_images_stage
- provide_x410_fpga_images_stage
- provide_x440_fpga_images_stage
condition: >
and(
@ -1016,16 +1113,17 @@ stages:
contains(dependencies.analyze_changeset.outputs['analyze.gen_testlist.UhdTestList'], 'hw.streaming')
)
jobs:
- template: job-uhd-streaming-tests-x440.yml
- template: job-uhd-streaming-tests.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
testLength: ${{ parameters.testLength }}
selectedTests: ${{ split(replace(parameters.selected_streaming_tests, ' ', ''), ',') }}
- stage: test_labview_usrp_examples
displayName: Test LabVIEW USRP Examples
dependsOn:
- analyze_changeset
- build_uhd_embedded_system_images
- build_uhd_embedded_system_images_x4xx
- build_uhd_installer_stage_win
- provide_x410_fpga_images_stage
jobs:

View File

@ -35,6 +35,21 @@ parameters:
steps:
- script: |
setlocal enabledelayedexpansion
echo pythonVersion=$(pythonVersion)
if "$(pythonVersion)"=="python310" (
set pyVersionType=1
) else if "$(pythonVersion)"=="python311" (
set pyVersionType=1
) else if "$(pythonVersion)"=="python312" (
set pyVersionType=1
) else (
set pyVersionType=2
)
echo pyVersionType=!pyVersionType!
echo ##vso[task.setvariable variable=PYVERSIONTYPE]!pyVersionType!
echo environment var VCPKG_TARGET_TRIPLET=%VCPKG_TARGET_TRIPLET%
call ${{ parameters.uhdSrcDir }}\.ci\utils\extract-vs-version.bat "$(cmakeCompiler)"
copy /y ${{ parameters.uhdSrcDir }}\.ci\docker\vcpkg\%vcpkgManifestFile% vcpkg.json
%VCPKG_INSTALL_DIR%\vcpkg.exe install --triplet uhd-x64-windows-static-md --clean-after-build
@ -43,7 +58,14 @@ steps:
- script: |
pip config list
pip install -r ${{ parameters.uhdSrcDir }}\host\python\requirements.txt
python -m pip install --upgrade pip
if "%PYVERSIONTYPE%"=="1" (
pip install -r ${{ parameters.uhdSrcDir }}\host\python\requirements.txt
) else (
echo Using numpy 2.x requirements for python313 and above
pip install -r ${{ parameters.uhdSrcDir }}\host\python\requirements_np2.txt
pip install "pybind11>=2.12"
)
displayName: refresh python dependencies
- script: |
@ -53,7 +75,8 @@ steps:
-DVCPKG_TARGET_TRIPLET=uhd-$(vsArch)-windows-static-md ^
-DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALL_DIR%/scripts/buildsystems/vcpkg.cmake ^
-DVCPKG_INSTALLED_DIR=${{ parameters.uhdVcpkgManifestDir }}\vcpkg_installed ^
-DVCPKG_MANIFEST_DIR=${{ parameters.uhdVcpkgManifestDir }}
-DVCPKG_MANIFEST_DIR=${{ parameters.uhdVcpkgManifestDir }} ^
-DVCPKG_MANIFEST_INSTALL=OFF
mkdir ${{ parameters.uhdImageDir }}
python ${{ parameters.uhdBuildDir }}/utils/uhd_images_downloader.py -i ${{ parameters.uhdImageDir }} ${{ parameters.uhdImagesDownloaderExtraArgs }}
displayName: Download uhd-images
@ -84,6 +107,7 @@ steps:
-DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALL_DIR%/scripts/buildsystems/vcpkg.cmake ^
-DVCPKG_INSTALLED_DIR=${{ parameters.uhdVcpkgManifestDir }}\vcpkg_installed ^
-DVCPKG_MANIFEST_DIR=${{ parameters.uhdVcpkgManifestDir }} ^
-DVCPKG_MANIFEST_INSTALL=OFF ^
-DUHD_IMAGES_SRC_DIR="${{ parameters.uhdImageDir }}" ^
-DUHD_IMAGES_DIR="C:\\Program Files (x86)\\UHD\\share\\uhd\\images,C:\\Program Files\\UHD\\share\\uhd\\images" ^
-DENABLE_EXTEND_WIN_PACKAGING=ON ^

View File

@ -8,13 +8,25 @@ parameters:
- name: cmake_args
type: string
default: " "
- name: nib310rioDebPackageDir
type: string
default: ""
steps:
- script: |
set -euo pipefail
sudo pbuilder create --debootstrapopts --variant=buildd --distribution ${{ parameters.ubuntuReleaseName }}
displayName: Setup pbuilder
- script: |
set -euo pipefail
mkdir -p ${{ parameters.uhdInstallerDir }}
cd ${{ parameters.uhdSrcDir }}
python3 tools/debs/uhd_ubuntu_deb.py ${{ parameters.ubuntuReleaseName }} --buildpath ${{ parameters.uhdInstallerDir }}
pbuilder_args=()
if [[ -n "${{ parameters.nib310rioDebPackageDir }}" ]]; then
pushd "${{ parameters.nib310rioDebPackageDir }}"
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
popd
pbuilder_args+=(--pbuilder-extra-apt-repo "${{ parameters.nib310rioDebPackageDir }}")
fi
python3 tools/debs/uhd_ubuntu_deb.py ${{ parameters.ubuntuReleaseName }} --buildpath ${{ parameters.uhdInstallerDir }} "${pbuilder_args[@]}"
displayName: Build dsc and debs

View File

@ -51,6 +51,7 @@ steps:
cd ${{ parameters.uhdBuildDir }}
pyenv exec python3 -m venv ${{ parameters.uhdBuildDir }}/pyenv
source ${{ parameters.uhdBuildDir }}/pyenv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install mako numpy requests ruamel.yaml
displayName: Install python UHD deps

View File

@ -0,0 +1,10 @@
steps:
- script: |
cd $(Agent.TempDirectory)
python3 -m venv pytest_venv
source pytest_venv/bin/activate
echo '##vso[task.setvariable variable=PYTEST_VENV]'$(realpath pytest_venv)
python3 -m pip install -U pip
pip3 install -r $(Build.SourcesDirectory)/ettus-rts/config/remote/streaming/requirements.txt
displayName: Create pytest venv

View File

@ -0,0 +1,24 @@
steps:
- download: nib310rio_kernel_packages
artifact: nib310rio-deb-packages
displayName: Download nib310rio-dev deb
condition: and(succeeded(), contains(variables['buildOSName'], 'ubuntu'))
- download: nib310rio_kernel_packages
artifact: nib310rio-rpm-packages
displayName: Download nib310rio-dev rpm
condition: and(succeeded(), contains(variables['buildOSName'], 'fedora'))
- script: |
set -euo pipefail
if command -v dpkg >/dev/null 2>&1; then
pkg=$(find "$(Pipeline.Workspace)/nib310rio_kernel_packages/nib310rio-deb-packages" \
-type f -name 'nib310rio-dev_*_all.deb' | sort -V | tail -n 1)
sudo dpkg -i "$pkg"
else
# Search only RPMS/ to avoid picking up the source RPM from SRPMS/
pkg=$(find "$(Pipeline.Workspace)/nib310rio_kernel_packages/nib310rio-rpm-packages/RPMS" \
-type f -name 'nib310rio-dev-*.rpm' | sort -V | tail -n 1)
sudo rpm -i "$pkg"
fi
displayName: Install nib310rio-dev

View File

@ -60,6 +60,7 @@ steps:
python3 -m venv rfnoc-block-test-venv
source rfnoc-block-test-venv/bin/activate
TEST_DIR=$(Build.SourcesDirectory)/uhddev/host/tests/rfnoc_block_tests_on_target
python3 -m pip install --upgrade pip
pip install -r $TEST_DIR/requirements.txt
pytest --args ${{ parameters.uhdArgs }} $TEST_DIR/${{ parameters.testSelector }} -vv --junitxml=$(Common.TestResultsDirectory)/${{ parameters.resultFileNameBase }}.xml
displayName: "${{ parameters.resultFileNameBase }}: Run test"

View File

@ -13,6 +13,9 @@ parameters:
- name: extra_rf_test_args
type: string
default: ''
- name: runX410X4400RfSubset
type: boolean
default: false
- name: testLength
type: string
@ -25,6 +28,7 @@ jobs:
runPyTestNonRF: false
runDevTest: false
runSystemImageTest: false
runX410X4400RfSubset: ${{ and(parameters.runX410X4400RfSubset, eq(parameters.fpga_imgs_source, 'Pipeline')) }}
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
@ -40,5 +44,6 @@ jobs:
uartSerial: $(x410_uartSerial)
pipelineAgent: pebbles-agent-1
pytestAtsConfig: uhd_oss_ats
pytestConfigDut: 'x410-400'
pytestDUT: 'x410'
sfp0ip: '192.168.30.62'

View File

@ -12,57 +12,75 @@ parameters:
default: ''
- name: testLength
type: string
- name: runPyTest
type: boolean
default: true
- name: runPyTestNonRF
type: boolean
default: true
- name: runDevTest
type: boolean
default: true
- name: runSystemImageTest
type: boolean
default: true
- name: forceIQCorrections
type: boolean
default: false
jobs:
- template: templates/job-uhd-x4xx-hardware-tests.yml
parameters:
suiteName: 'dre_s175_x420_3'
toolset: 'make'
runPyTest: true
runPyTestNonRF: false
runDevTest: false
runSystemImageTest: false
master_clock_rate: '245.76e6'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
extra_rf_test_args: '${{ parameters.extra_rf_test_args }}'
testLength: '${{ parameters.testLength }}'
dutMatrix:
dre_s175-x420-3:
dutName: 'dre_s175-x420-3'
dutType: 'x4xx'
devtestPattern: 'x420'
dutFPGA: 'CG_1000'
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x420_uartSerial)'
pipelineAgent: dre-s175-x420-3
pytestAtsConfig: ec21_ats
pytestDUT: 'x420'
sfp0ip: '$(x420_dutAddr)'
- template: templates/job-uhd-x4xx-hardware-tests.yml
parameters:
suiteName: 'dre_s175_x420_4'
toolset: 'make'
runPyTest: false
runPyTestNonRF: true
runDevTest: true
runSystemImageTest: true
master_clock_rate: '245.76e6'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
extra_rf_test_args: '${{ parameters.extra_rf_test_args }}'
testLength: '${{ parameters.testLength }}'
dutMatrix:
dre_s175-x420-4:
dutName: 'dre_s175-x420-4'
dutType: 'x4xx'
devtestPattern: 'x420'
dutFPGA: 'CG_1000'
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x420_uartSerial)'
pipelineAgent: dre-s175-x420-4
pytestAtsConfig: dre_s175_ats
pytestDUT: 'x420'
sfp0ip: '$(x420_dutAddr)'
- ${{ if parameters.runPyTest }}:
- template: templates/job-uhd-x4xx-hardware-tests.yml
parameters:
suiteName: 'dre_s175_x420_3'
toolset: 'make'
runPyTest: true
runPyTestNonRF: false
runDevTest: false
runSystemImageTest: false
forceIQCorrections: ${{ parameters.forceIQCorrections }}
master_clock_rate: '245.76e6'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
extra_rf_test_args: '${{ parameters.extra_rf_test_args }}'
testLength: '${{ parameters.testLength }}'
dutMatrix:
dre_s175-x420-3:
dutName: 'dre_s175-x420-3'
dutType: 'x4xx'
devtestPattern: 'x420'
dutFPGA: 'CG_1000'
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x420_uartSerial)'
pipelineAgent: dre-s175-x420-3
pytestAtsConfig: ec21_ats
pytestDUT: 'x420'
sfp0ip: '$(x420_dutAddr)'
- ${{ if or(parameters.runPyTestNonRF, parameters.runDevTest, parameters.runSystemImageTest) }}:
- template: templates/job-uhd-x4xx-hardware-tests.yml
parameters:
suiteName: 'dre_s175_x420_4'
toolset: 'make'
runPyTest: false
runPyTestNonRF: ${{ parameters.runPyTestNonRF }}
runDevTest: ${{ parameters.runDevTest }}
runSystemImageTest: ${{ parameters.runSystemImageTest }}
master_clock_rate: '245.76e6'
uhdArtifactSource: ${{ parameters.uhdArtifactSource }}
uhdFpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
extra_rf_test_args: '${{ parameters.extra_rf_test_args }}'
testLength: '${{ parameters.testLength }}'
dutMatrix:
dre_s175-x420-4:
dutName: 'dre_s175-x420-4'
dutType: 'x4xx'
devtestPattern: 'x420'
dutFPGA: 'CG_1000'
dutEmbeddedImagesArtifact: 'x4xx-images'
uartSerial: '$(x420_uartSerial)'
pipelineAgent: dre-s175-x420-4
pytestAtsConfig: dre_s175_ats
pytestDUT: 'x420'
sfp0ip: '$(x420_dutAddr)'

View File

@ -18,6 +18,9 @@ parameters:
- name: runRfnocBlockTest
type: boolean
default: false
- name: runX410X4400RfSubset
type: boolean
default: false
- name: forceIQCorrections
type: boolean
default: false
@ -249,7 +252,7 @@ jobs:
ssh-keygen -f ~/.ssh/known_hosts -R $USRP_EMB_TARGET_IP
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "rm -rf ~/usrp_sysimg_test && mkdir -p ~/usrp_sysimg_test"
scp -o StrictHostKeyChecking=no -r $(Build.SourcesDirectory)/meta-ettus-dev/contrib/test/tests root@$USRP_EMB_TARGET_IP:~/usrp_sysimg_test
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "pip3 install unittest-xml-reporting && cd ~/usrp_sysimg_test/tests && python3 -m xmlrunner discover . -v"
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "python3 -m pip install -U pip && pip3 install unittest-xml-reporting && cd ~/usrp_sysimg_test/tests && python3 -m xmlrunner discover . -v"
EXITCODE=$?
scp -o StrictHostKeyChecking=no -r root@$USRP_EMB_TARGET_IP:~/usrp_sysimg_test .
exit $EXITCODE
@ -296,6 +299,7 @@ jobs:
cd $(Agent.TempDirectory)
python3 -m venv devtest_venv
source devtest_venv/bin/activate
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/uhddev/host/tests/devtest/requirements.txt
cd $(Common.TestResultsDirectory)/devtest
python3 $(Build.SourcesDirectory)/uhddev/host/tests/devtest/run_testsuite.py \
@ -342,6 +346,7 @@ jobs:
# Activate virtual environment and install dependencies
source $USRP_EMB_VENV/bin/activate
python3 -m pip install --upgrade pip
pip3 install -r $(Build.SourcesDirectory)/ettus-rts/.ci/utils/x420_iq_dc_correction_requirements.txt
# Run the X420 IQ correction script
@ -385,7 +390,7 @@ jobs:
mkdir -p $(Common.TestResultsDirectory)/pytest/embedded
cd $(Common.TestResultsDirectory)/pytest/embedded
ssh-keygen -f ~/.ssh/known_hosts -R $USRP_EMB_TARGET_IP
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "pip3 install --upgrade 'pytest<8.4.0' pytest-helpers-namespace pytest-tempdir pytest-salt"
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "python3 -m pip install -U pip && pip3 install --upgrade 'pytest<8.4.0' pytest-helpers-namespace pytest-tempdir pytest-salt"
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "rm -rf ~/usrp_pytest && mkdir -p ~/usrp_pytest"
scp -o StrictHostKeyChecking=no -r $(Build.SourcesDirectory)/ettus-rts/config/remote/ root@$USRP_EMB_TARGET_IP:~/usrp_pytest
ssh -o StrictHostKeyChecking=no -tt root@$USRP_EMB_TARGET_IP "cd ~/usrp_pytest/remote/python_tests/pytests && \
@ -420,6 +425,37 @@ jobs:
resultFileNameBase: 'complex_gain_block_test'
testSelector: 'test_radio_control_complex_gain.py'
- ${{ if parameters.runX410X4400RfSubset }}:
- template: ../../steps-download-fpga-artifact.yml
parameters:
dutType: $(pytestDUT)
dutFPGA: 'X4_400'
fpgaArtifactSource: ${{ parameters.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ parameters.fpga_imgs_source }}
- script: |
UHD_INSTALL_PATH=$(uhd_artifact_directory)/uhddev-$(testOS)-${{ parameters.toolset }}/uhddev/build-installed
export PATH=$UHD_INSTALL_PATH/bin:$PATH
if [ -d "$UHD_INSTALL_PATH/lib64" ]; then
export LD_LIBRARY_PATH=$UHD_INSTALL_PATH/lib64:$LD_LIBRARY_PATH
else
export LD_LIBRARY_PATH=$UHD_INSTALL_PATH/lib:$LD_LIBRARY_PATH
fi
uhd_image_loader --args type=$(dutType),addr=$(USRP_EMB_TARGET_IP) \
--fpga=$FPGA_ARTIFACT_DIR/$FPGA_BITFILE_NAME.bit
displayName: 'X410/X4_400 RF subset: Load FPGA on USRP'
- template: step-uhd-run-pytest-on-host.yml
parameters:
runPyTest: true
displayString: 'X410/X4_400 RF subset'
testFolder: 'pytests'
pytestConfigDut: $(pytestConfigDut)
testSelector: 'test_ddc_response.py test_rx_offset_tone_sweep.py test_tx_offset_tone_sweep.py test_tx_evm.py test_rx_evm.py'
testLength: ${{ parameters.testLength }}
warmUpAfterBoot: ${{ parameters.warmUpAfterBoot }}
toolset: ${{ parameters.toolset }}
# Run the mender tests last because they
# modify the flashed image on the device
- script: |

View File

@ -10,6 +10,12 @@ parameters:
- name: extra_rf_test_args
type: string
default: ''
- name: pytestConfigDut
type: string
default: '$(pytestDUT)'
- name: testSelector
type: string
default: '.'
- name: testLength
type: string
default: 'smoke'
@ -69,7 +75,7 @@ steps:
source $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/setup_venv.sh
cd $(Build.SourcesDirectory)/ettus-rts/config/remote/python_tests/${{ parameters.testFolder }}
python3 ../automated_main.py --test_selector . --ats_config $(pytestAtsConfig) --dut $(pytestDUT) --run_vars_overrides 'mgmt_addr=$(USRP_EMB_TARGET_IP)' --results_path '$(Common.TestResultsDirectory)/pytest/host' --test_length ${{ parameters.testLength }} ${{ parameters.extra_rf_test_args }}
python3 ../automated_main.py --test_selector ${{ parameters.testSelector }} --ats_config $(pytestAtsConfig) --dut ${{ parameters.pytestConfigDut }} --run_vars_overrides 'mgmt_addr=$(USRP_EMB_TARGET_IP)' --results_path '$(Common.TestResultsDirectory)/pytest/host' --test_length ${{ parameters.testLength }} ${{ parameters.extra_rf_test_args }}
displayName: Run ${{ parameters.displayString }} on host
continueOnError: true
condition: and(succeeded(), ${{ parameters.runPyTest }})

View File

@ -37,6 +37,14 @@ parameters:
type: boolean
displayName: Build Windows images
default: true
- name: prune_docker_images
type: boolean
displayName: Prune dangling Docker images before Windows build
default: false
- name: use_vcpkg_cache
type: boolean
displayName: Use vcpkg binary cache for Windows build
default: true
variables:
- template: uhd-pipeline-vars.yml
@ -49,7 +57,8 @@ variables:
'Ubuntu-2004-builder':{'buildOSName':'ubuntu2004','dockerImageName':'uhd-builder-ubuntu2004','releaseName':'focal'},
'Ubuntu-2204-builder':{'buildOSName':'ubuntu2204','dockerImageName':'uhd-builder-ubuntu2204','releaseName':'jammy'},
'Ubuntu-2404-builder':{'buildOSName':'ubuntu2404','dockerImageName':'uhd-builder-ubuntu2404','releaseName':'noble'},
'Ubuntu-2510-builder':{'buildOSName':'ubuntu2510','dockerImageName':'uhd-builder-ubuntu2510','releaseName':'questing'}}"
'Ubuntu-2510-builder':{'buildOSName':'ubuntu2510','dockerImageName':'uhd-builder-ubuntu2510','releaseName':'questing'},
'Ubuntu-2604-builder':{'buildOSName':'ubuntu2604','dockerImageName':'uhd-builder-ubuntu2604','releaseName':'resolute'}}"
- ${{ else }}:
- name: linuxDockerImages
value:
@ -58,6 +67,7 @@ variables:
value:
"{'VS2022-x64-py310-builder':{'buildOSName':'win-vs22-v143-x64-py310','dockerImageName':'uhd-builder-vs2022-v143-x64-py310','cmakeCompiler':'Visual Studio 17 2022','cmakeArch':'x64','vsArch':'x64','vsYear':'2022','pythonVersion':'python310'},
'VS2022-x64-py312-builder':{'buildOSName':'win-vs22-v143-x64-py312','dockerImageName':'uhd-builder-vs2022-v143-x64-py312','cmakeCompiler':'Visual Studio 17 2022','cmakeArch':'x64','vsArch':'x64','vsYear':'2022','pythonVersion':'python312'},
'VS2022-x64-py313-builder':{'buildOSName':'win-vs22-v143-x64-py313','dockerImageName':'uhd-builder-vs2022-v143-x64-py313','cmakeCompiler':'Visual Studio 17 2022','cmakeArch':'x64','vsArch':'x64','vsYear':'2022','pythonVersion':'python313'},
'VS2026-x64-py310-builder':{'buildOSName':'win-vs26-v145-x64-py310','dockerImageName':'uhd-builder-vs2026-v145-x64-py310','cmakeCompiler':'Visual Studio 18 2026','cmakeArch':'x64','vsArch':'x64','vsYear':'2026','pythonVersion':'python310'},
'VS2026-x64-py312-builder':{'buildOSName':'win-vs26-v145-x64-py312','dockerImageName':'uhd-builder-vs2026-v145-x64-py312','cmakeCompiler':'Visual Studio 18 2026','cmakeArch':'x64','vsArch':'x64','vsYear':'2026','pythonVersion':'python312'}}"
# temporarily exclude py313 Windows builder
@ -67,7 +77,7 @@ variables:
- name: python312_version
value: '3.12.10'
- name: python313_version
value: '3.13.11'
value: '3.13.14'
- ${{ else }}:
- name: winDockerImages
value:
@ -88,6 +98,13 @@ variables:
- name: dockerImagePrefix
value: 'rnd-docker-pre/ni/rfsdr/'
- ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Reason'], 'Manual')) }}:
- name: isPRorManualBuild
value: 'true'
- ${{ else }}:
- name: isPRorManualBuild
value: 'false'
jobs:
- job: set_uhd_docker_image_version
displayName: Set UHD Docker Image Version
@ -208,6 +225,8 @@ jobs:
timeoutInMinutes: 600
variables:
uhdDockerImageVersion: $[ dependencies.set_uhd_docker_image_version.outputs['set_uhd_version.uhdDockerImageVersion'] ]
VCPKG_BINARY_CACHE_DIR: $(Build.SourcesDirectory)\.cache\vcpkg
VCPKG_BINARY_CACHE_CONTAINER_DIR: 'C:\vcpkg-binary-cache'
pool:
name: Drivers-NIBuildFarm-RFMIBUILD
demands:
@ -227,8 +246,11 @@ jobs:
REM Set DOCKER_CONFIG based on vsYear
if "$(vsYear)"=="2026" (
echo ##vso[task.setvariable variable=DOCKER_CONFIG]uhd-builder-vs2026-v145-x64
) else if "$(vsYear)"=="2022" (
echo ##vso[task.setvariable variable=DOCKER_CONFIG]uhd-builder-vs2022-v143-x64
) else (
echo ##vso[task.setvariable variable=DOCKER_CONFIG]$(dockerImageName)
echo ERROR: Unknown vsYear value "$(vsYear)".
exit /b 1
)
REM Set PYTHON_VERSION_ARG based on pythonVersion
@ -239,8 +261,16 @@ jobs:
) else if "$(pythonVersion)"=="python313" (
echo ##vso[task.setvariable variable=PYTHON_VERSION_ARG]--build-arg PYTHON_VERSION=$(python313_version)
) else (
echo WARNING: pythonVersion is not defined or unknown value "$(pythonVersion)".
echo ##vso[task.setvariable variable=PYTHON_VERSION_ARG]
)
REM Create vcpkg cache directory if it does not exist (e.g. on a cache miss).
REM Docker COPY fails if the source path is absent from the build context.
REM mkdir creates the full path including intermediate directories in one call.
if not exist "$(VCPKG_BINARY_CACHE_DIR)" (
mkdir "$(VCPKG_BINARY_CACHE_DIR)"
)
displayName: Set dynamic build variables
- script: |
call $(Build.SourcesDirectory)\.ci\utils\extract-vs-version.bat "$(cmakeCompiler)"
@ -248,8 +278,44 @@ jobs:
- powershell: |
$(Build.SourcesDirectory)\.ci\docker\scripts\check-url.ps1 -Url "$(vsToolsUrl)"
displayName: Verify expected VS Tools Installer URL
- ${{ if parameters.use_vcpkg_cache }}:
- powershell: |
$now = Get-Date
$calendar = [System.Globalization.CultureInfo]::InvariantCulture.Calendar
$week = $calendar.GetWeekOfYear(
$now,
[System.Globalization.CalendarWeekRule]::FirstFourDayWeek,
[System.DayOfWeek]::Monday
)
$cacheTimestamp = "{0}w{1:D2}" -f $now.Year, $week
Write-Host "CacheTimestamp=$cacheTimestamp"
Write-Host "##vso[task.setvariable variable=CacheTimestamp]$cacheTimestamp"
displayName: Set CacheTimestamp
- task: Cache@2
inputs:
key: |
"vcpkg" | "$(vsYear)" | "$(CacheTimestamp)"
${{ if eq(variables['isPRorManualBuild'], 'true') }}:
restoreKeys: |
"vcpkg" | "$(vsYear)"
path: $(VCPKG_BINARY_CACHE_DIR)
displayName: Cache vcpkg binary packages
- script: |
echo Pruning dangling images and stopped containers to reclaim disk space before build.
docker container prune -f
REM Pass 1: remove old uhd-builder images from previous runs (largest space consumers).
for /f "tokens=3" %%i in ('docker images -a --format table ^| findstr "uhd-builder"') do docker rmi %%i >nul 2>&1
REM Pass 2: remove any <none> / <untagged> dangling layers left behind after pass 1.
for /f "tokens=3" %%i in ('docker images -a --format table ^| findstr /c:"<none>" /c:"<untagged>"') do docker rmi %%i >nul 2>&1
docker images --format table
REM Always exit with success (0) so this cleanup step never fails the pipeline.
exit /b 0
displayName: Prune dangling Docker images
condition: and(succeeded(), or(${{ parameters.prune_docker_images }}, ne(variables['isPRorManualBuild'], 'true')))
- task: Docker@2
displayName: Build Image
retryCountOnTaskFailure: 3
inputs:
command: 'build'
Dockerfile: '$(Build.SourcesDirectory)/.ci/docker/$(DOCKER_CONFIG).Dockerfile'
@ -262,11 +328,63 @@ jobs:
buildContext: $(Build.SourcesDirectory)
arguments: >
--no-cache --pull
--build-arg VCPKG_BINARY_CACHE_DIR=$(VCPKG_BINARY_CACHE_CONTAINER_DIR)
--build-arg VS_BUILD_TOOLS_URL=$(vsToolsUrl)
--build-arg PIP_INDEX_HOST=$(pip_index_host)
--build-arg PIP_INDEX_URL=$(pip_index_url)
--build-arg VCPKG_MANIFEST_FILE=$(vcpkgManifestFile)
$(PYTHON_VERSION_ARG)
- script: |
docker images --format table
displayName: List Docker images
- ${{ if parameters.use_vcpkg_cache }}:
- script: |
set IMAGE_ID=
REM Match image by name:version in plain docker images output, independent of registry hostname prefix.
REM docker images --format with Go templates is unreliable on Windows Docker.
REM Standard output columns: REPOSITORY TAG IMAGE-ID CREATED SIZE -> tokens=3 gives IMAGE-ID.
for /f "tokens=3" %%i in ('docker images --format table ^| findstr /i "$(dockerImageName)" ^| findstr /i "$(uhdDockerImageVersion)"') do set IMAGE_ID=%%i
if not defined IMAGE_ID (
echo WARNING: Could not find locally built image for $(dockerImagePrefix)$(dockerImageName):$(uhdDockerImageVersion)
echo WARNING: Skipping vcpkg cache extraction and preventing cache publish for this run.
if exist "$(VCPKG_BINARY_CACHE_DIR)" (
rmdir /s /q "$(VCPKG_BINARY_CACHE_DIR)"
)
exit /b 0
)
echo Using local IMAGE_ID=%IMAGE_ID%
docker rm -f vcpkgcache >nul 2>&1
docker create --name vcpkgcache ^
%IMAGE_ID%
if errorlevel 1 (
echo WARNING: Failed to create temporary container for cache extraction.
echo WARNING: Skipping vcpkg cache extraction and preventing cache publish for this run.
if exist "$(VCPKG_BINARY_CACHE_DIR)" (
rmdir /s /q "$(VCPKG_BINARY_CACHE_DIR)"
)
exit /b 0
)
docker images --format table
if exist "$(VCPKG_BINARY_CACHE_DIR)" (
rmdir /s /q "$(VCPKG_BINARY_CACHE_DIR)"
)
docker cp vcpkgcache:$(VCPKG_BINARY_CACHE_CONTAINER_DIR) "$(VCPKG_BINARY_CACHE_DIR)"
if errorlevel 1 (
echo WARNING: Failed to copy vcpkg cache from built image.
echo WARNING: Skipping vcpkg cache publish for this run.
docker rm -f vcpkgcache >nul 2>&1
if exist "$(VCPKG_BINARY_CACHE_DIR)" (
rmdir /s /q "$(VCPKG_BINARY_CACHE_DIR)"
)
exit /b 0
)
docker rm vcpkgcache
displayName: Extract vcpkg cache
- task: Docker@2
displayName: Push Image
inputs:
@ -277,6 +395,12 @@ jobs:
containerRegistry: rnd-docker-niartifacts-pre-publish
repository: '$(dockerImagePrefix)$(dockerImageName)'
tags: $(uhdDockerImageVersion)
- task: PublishPipelineArtifact@1
inputs:
targetPath: $(VCPKG_BINARY_CACHE_DIR)
artifactName: vcpkg-binary-cache-$(dockerImageName))-run$(Build.BuildId)
displayName: Publish vcpkg binary cache as artifact
condition: eq(variables['System.Debug'], 'true')
- job: test_image_win
displayName: Test Docker Image Windows

View File

@ -7,6 +7,10 @@ parameters:
type: boolean
displayName: Run X410 Embedded Tests
default: false
- name: run_x420_embedded_tests
type: boolean
displayName: Run X420 Embedded Tests
default: false
- name: run_x440_embedded_tests
type: boolean
displayName: Run X440 Embedded Tests
@ -15,10 +19,10 @@ parameters:
type: boolean
displayName: Run Streaming Tests
default: false
- name: run_x440_streaming_tests
type: boolean
displayName: Run X440 Streaming Tests
default: false
- name: selected_streaming_tests
type: string
displayName: Streaming Tests Multi-Select (comma-separated)
default: 'B206,B210,B310,X310_XG,X410_CG_400,X410_UC_200,X440_CG_400,X440_CG_1600'
- name: run_devtests
type: boolean
displayName: Run devtests
@ -55,6 +59,14 @@ parameters:
type: boolean
displayName: Run LabVIEW test
default: false
- name: run_b310_devtests
type: boolean
displayName: Run B310 devtests
default: false
- name: run_b310_rf_tests
type: boolean
displayName: Run B310 RF tests
default: false
- name: fpga_imgs_source
type: string
values:
@ -74,11 +86,12 @@ parameters:
- 'smoke'
- 'full'
- 'stress'
- name: dpdkDirs
- name: dpdkVersions
type: object
default:
- '/opt/dpdk-19.11'
- '/opt/dpdk-23.11'
- '21.11'
- '23.11'
- '25.11'
- name: x300_fpga_matrix
type: object
default:
@ -127,6 +140,14 @@ parameters:
run: true
X440_CG_1600:
run: true
- name: uploadSbomArtifacts
type: boolean
displayName: Publish SBOMs to DepTrack
default: false
- name: sbomExclFromUpload
type: string
displayName: Don't upload listed SBOMs (space-separated list of image names)
default: ' '
variables:
- name: uhdFpgaArtifactSource
@ -147,6 +168,9 @@ resources:
- pipeline: uhd_mono_pipeline
source: 'uhddev mono pipeline'
branch: master
- pipeline: uhd_mono_pipeline_pr
source: 'uhddev mono pipeline PR'
branch: master
- pipeline: usrp-kas-pipeline
source: 'usrp-kas'
branch: scarthgap
@ -155,9 +179,6 @@ resources:
- pipeline: uhd_fpga_pipeline
source: 'uhddev fpga pipeline'
branch: master
- pipeline: uhd_fpga_pr_pipeline
source: 'uhddev fpga pipeline PR'
branch: master
repositories:
- repository: meta-ettus
type: github
@ -174,10 +195,6 @@ resources:
endpoint: EttusResearch
name: EttusResearch/ettus-rts
ref: master
- repository: hwtools
type: git
ref: refs/tags/pip/hwsetup/0.1/0.1.3
name: DevCentral/hwtools
- repository: labview-usrp-examples
type: github
endpoint: EttusResearch
@ -196,6 +213,22 @@ stages:
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
- stage: test_uhd_x420_embedded_stage
displayName: Test X420 Embedded UHD
dependsOn: []
condition: ${{ parameters.run_x420_embedded_tests }}
jobs:
- template: templates/tests/job-uhd-x420-hardware-tests-s175.yml
parameters:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
testLength: ${{ parameters.testLength }}
runPyTest: false
runPyTestNonRF: true
runDevTest: true
runSystemImageTest: true
- stage: test_uhd_x440_embedded_stage
displayName: Test X440 Embedded UHD
dependsOn: []
@ -207,38 +240,19 @@ stages:
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
- stage: test_streaming_stage
displayName: Test UHD Streaming
- stage: stage_uhd_streaming_tests
displayName: UHD Streaming Tests
dependsOn: []
condition: ${{ parameters.run_streaming_tests }}
jobs:
- template: templates/job-uhd-streaming-tests-beauty.yml
- template: templates/job-uhd-streaming-tests.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
testLength: ${{ parameters.testLength }}
dpdkDirs: '${{ parameters.dpdkDirs }}'
- template: templates/job-uhd-streaming-tests-s175.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
testLength: ${{ parameters.testLength }}
dpdkDirs: '${{ parameters.dpdkDirs }}'
- stage: test_x440_streaming_stage
displayName: Test X440 Streaming
dependsOn: []
condition: ${{ parameters.run_x440_streaming_tests }}
jobs:
- template: templates/job-uhd-streaming-tests-x440.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
testLength: ${{ parameters.testLength }}
dpdkDirs: '${{ parameters.dpdkDirs }}'
dpdkVersions: ${{ parameters.dpdkVersions }}
selectedTests: ${{ split(replace(parameters.selected_streaming_tests, ' ', ''), ',') }}
- stage: test_uhd_devtest
displayName: Test UHD Devtest
@ -296,6 +310,31 @@ stages:
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
- stage: test_b310_rf_tests
displayName: Test B310 RF Tests
dependsOn: []
condition: ${{ parameters.run_b310_rf_tests }}
jobs:
- template: templates/job-uhd-rf-tests-b310.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
extra_rf_test_args: ${{ parameters.extra_rf_test_args }}
- stage: test_b310_devtest
displayName: Test B310 Devtest
dependsOn: []
condition: ${{ parameters.run_b310_devtests }}
jobs:
- template: templates/job-uhd-devtest-b310.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
- stage: test_x410_rf_tests
displayName: Run X410 RF tests
dependsOn: []
@ -306,6 +345,7 @@ stages:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
runX410X4400RfSubset: true
extra_rf_test_args: ${{ parameters.extra_rf_test_args }}
testLength: ${{ parameters.testLength }}
@ -321,6 +361,10 @@ stages:
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
extra_rf_test_args: ${{ parameters.extra_rf_test_args }}
testLength: ${{ parameters.testLength }}
runPyTest: true
runPyTestNonRF: false
runDevTest: false
runSystemImageTest: false
- stage: test_x440_rf_tests
displayName: Run X440 RF tests
@ -360,3 +404,10 @@ stages:
parameters:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
- ${{ if eq(parameters.uploadSbomArtifacts, true) }}:
- template: .ci/templates/stages-nisbom-uploads.yml@ettus-rts
parameters:
sbomExclFromUpload: ${{ parameters.sbomExclFromUpload }}
uhdArtifactSource: uhd_mono_pipeline
releaseBinaries: false

View File

@ -12,12 +12,12 @@ pr: none
parameters:
- name: fpga_imgs_source
type: string
displayName: FPGA Images Source
values:
- 'Filesystem/Images Downloader'
- 'Mono Pipeline'
- 'Mono Pipeline PR'
- 'FPGA Pipeline'
displayName: FPGA Images Source
default: 'Mono Pipeline'
- name: testLength
type: string
@ -26,6 +26,10 @@ parameters:
- 'full'
- 'stress'
default: 'smoke'
- name: selected_streaming_tests
type: string
displayName: Streaming Tests Multi-Select (comma-separated)
default: 'B206,B210,X310_XG,X410_CG_400,X410_UC_200,X440_CG_400,X440_CG_1600'
variables:
- name: uhdFpgaArtifactSource
@ -73,11 +77,6 @@ resources:
endpoint: EttusResearch
name: EttusResearch/ettus-rts
ref: master
- repository: hwtools
type: git
ref: refs/tags/pip/hwsetup/0.1/0.1.3
name: DevCentral/hwtools
stages:
- stage: test_uhd_devtest_stage
displayName: Test UHD Devtest
@ -88,6 +87,16 @@ stages:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
- template: templates/tests/job-uhd-x420-hardware-tests-s175.yml
parameters:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
testLength: ${{ parameters.testLength }}
runPyTest: false
runPyTestNonRF: true
runDevTest: true
runSystemImageTest: true
- template: templates/tests/job-uhd-x440-hardware-tests-sdr-test0.yml
parameters:
uhdArtifactSource: uhd_mono_pipeline
@ -104,24 +113,13 @@ stages:
displayName: Test UHD Streaming
dependsOn: []
jobs:
- template: templates/job-uhd-streaming-tests-beauty.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
testLength: ${{ parameters.testLength }}
- template: templates/job-uhd-streaming-tests-s175.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
testLength: ${{ parameters.testLength }}
- template: templates/job-uhd-streaming-tests-x440.yml
- template: templates/job-uhd-streaming-tests.yml
parameters:
uhdSrcDir: $(Build.SourcesDirectory)/uhddev
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
testLength: ${{ parameters.testLength }}
selectedTests: ${{ split(replace(parameters.selected_streaming_tests, ' ', ''), ',') }}
- stage: test_rf_tests_stage
displayName: Test UHD RF Tests
@ -138,7 +136,19 @@ stages:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
runX410X4400RfSubset: true
testLength: ${{ parameters.testLength }}
- template: templates/tests/job-uhd-x420-hardware-tests-s175.yml
parameters:
uhdArtifactSource: uhd_mono_pipeline
uhdFpgaArtifactSource: ${{ variables.uhdFpgaArtifactSource }}
fpga_imgs_source: ${{ variables.fpga_imgs_source }}
testLength: ${{ parameters.testLength }}
runPyTest: true
runPyTestNonRF: false
runDevTest: false
runSystemImageTest: false
forceIQCorrections: true
- template: templates/tests/job-uhd-x440-hardware-tests-pebbles.yml
parameters:
uhdArtifactSource: uhd_mono_pipeline

View File

@ -135,6 +135,10 @@ parameters:
- 'files.ettus.com (Public)'
displayName: Published Package Access
default: 'Internal'
- name: selected_streaming_tests
type: string
displayName: Streaming Tests Multi-Select (comma-separated)
default: 'B206,B210,B310,X310_XG,X410_CG_400,X410_UC_200,X440_CG_400,X440_CG_1600'
trigger: none
@ -181,3 +185,5 @@ extends:
publish_int_files: true
package_and_publish_images: ${{ parameters.package_and_publish_images }}
package_access: ${{ parameters.package_access }}
# Test parameters
selected_streaming_tests: ${{ parameters.selected_streaming_tests }}

View File

@ -118,6 +118,10 @@ parameters:
- 'files.ettus.com (Public)'
displayName: Published Package Access
default: 'Internal'
- name: selected_streaming_tests
type: string
displayName: Streaming Tests Multi-Select (comma-separated)
default: 'B206,B210,B310,X310_XG,X410_CG_400,X410_UC_200,X440_CG_400,X440_CG_1600'
trigger:
batch: true
@ -161,3 +165,5 @@ extends:
publish_int_files: true
package_and_publish_images: ${{ parameters.package_and_publish_images }}
package_access: ${{ parameters.package_access }}
# Test parameters
selected_streaming_tests: ${{ parameters.selected_streaming_tests }}

View File

@ -7,12 +7,24 @@
"""Derive package-specific docker image matrices from DockerImageNames artifact data.
Supports Python 3.8 or later.
Strictness control:
- `UHD_MATRIX_STRICT_CI=true|1|yes|on`: force strict mode (errors are blocking)
- `UHD_MATRIX_STRICT_CI=false|0|no|off`: force non-strict mode
- `UHD_MATRIX_STRICT_CI` unset: auto-detect strictness
- strict for Azure CI reasons `IndividualCI`, `BatchedCI`, `Schedule`
- non-strict for Azure manual/debug runs
- for non-Azure environments, strict if `CI=true`
When `--empty-on-error-non-ci` is provided, non-strict mode converts derivation
errors to `{}` and emits an Azure warning log issue.
"""
from __future__ import annotations
import argparse
import ast
import os
import sys
from pathlib import Path
from typing import Any
@ -43,7 +55,102 @@ SECTION_DATA_MAP = {
}
def is_strict_ci_build() -> bool:
"""Return True when matrix validation/derivation failures are blocking.
Override behavior via `UHD_MATRIX_STRICT_CI`:
- true/1/yes/on: force strict
- false/0/no/off: force non-strict
- auto/unset: use environment-based detection
"""
override = os.getenv("UHD_MATRIX_STRICT_CI", "").strip().lower()
if override in {"1", "true", "yes", "on"}:
return True
if override in {"0", "false", "no", "off"}:
return False
if os.getenv("TF_BUILD", "").strip().lower() == "true":
return os.getenv("BUILD_REASON", "").strip() in {
"IndividualCI",
"BatchedCI",
"Schedule",
}
# Non-Azure fallback used by local and other CI systems.
return os.getenv("CI", "").strip().lower() == "true"
def _is_strict_ci_build() -> bool:
"""Backward-compatible internal alias."""
return is_strict_ci_build()
def _empty_fallback_message(exc: Exception) -> str:
"""Format a consistent warning message for non-strict fallback."""
return (
"derive_docker_image_matrix failed in non-CI build; "
"defaulting to empty object. Error: "
f"{exc}"
)
def _emit_non_strict_fallback(exc: Exception) -> int:
"""Emit warning and fallback output for non-strict mode."""
message = _empty_fallback_message(exc)
print(f"##vso[task.logissue type=warning]{message}", file=sys.stderr)
print("{}")
return 0
def _emit_failure(exc: Exception) -> int:
"""Emit normal error output for strict mode."""
print(str(exc), file=sys.stderr)
return 1
def _error_exit_for_exception(exc: Exception, empty_on_error_non_ci: bool) -> int:
"""Map an exception to exit handling based on strictness and CLI mode."""
if empty_on_error_non_ci and not is_strict_ci_build():
return _emit_non_strict_fallback(exc)
return _emit_failure(exc)
def _format_cli_output(args: argparse.Namespace, matrix_text: str) -> None:
"""Write normal CLI output, either plain text or Azure variable format."""
if args.vso_variable:
print(f"##vso[task.setvariable variable={args.vso_variable};isOutput=true;]{matrix_text}")
else:
print(matrix_text)
def _derive_cli_value(args: argparse.Namespace, artifact_data: dict[str, Any]) -> Any:
"""Resolve the selected CLI mode into an artifact value/matrix."""
if args.section:
return get_artifact_value(artifact_data, args.section)
if args.type:
if not args.os:
raise ValueError("--os is required when --type is specified")
return derive_typed_matrix(
artifact_data,
args.type,
args.os,
package=args.package,
select=args.select,
filter_out=args.filter_out,
)
if not args.package:
raise ValueError("Either --section or --type or --package must be specified")
if not args.os:
raise ValueError("--os is required when --package is specified")
if args.select:
raise ValueError("--select is only supported with --type")
return derive_package_matrix(artifact_data, args.os, args.package, args.filter_out)
def _parse_matrix_string(raw_value: str) -> dict[str, dict[str, Any]]:
if not raw_value:
return {}
parsed = ast.literal_eval(raw_value)
if not isinstance(parsed, dict):
raise ValueError("Expected matrix content to parse as a dict")
@ -331,6 +438,14 @@ def _build_arg_parser() -> argparse.ArgumentParser:
default="",
help="Optional variable name to emit in Azure DevOps setvariable format",
)
parser.add_argument(
"--empty-on-error-non-ci",
action="store_true",
help=(
"On non-CI Azure builds, convert derivation errors to '{}' and continue. "
"CI builds still fail with non-zero exit."
),
)
return parser
@ -340,36 +455,12 @@ def main() -> int:
try:
artifact_data = parse_docker_image_names_file(Path(args.input))
if args.section:
value = get_artifact_value(artifact_data, args.section)
elif args.type:
if not args.os:
raise ValueError("--os is required when --type is specified")
value = derive_typed_matrix(
artifact_data,
args.type,
args.os,
package=args.package,
select=args.select,
filter_out=args.filter_out,
)
else:
if not args.package:
raise ValueError("Either --section or --type or --package must be specified")
if not args.os:
raise ValueError("--os is required when --package is specified")
if args.select:
raise ValueError("--select is only supported with --type")
value = derive_package_matrix(artifact_data, args.os, args.package, args.filter_out)
value = _derive_cli_value(args, artifact_data)
matrix_text = format_value_for_pipeline(value)
except Exception as exc: # pylint: disable=broad-except
print(str(exc), file=sys.stderr)
return 1
return _error_exit_for_exception(exc, args.empty_on_error_non_ci)
if args.vso_variable:
print(f"##vso[task.setvariable variable={args.vso_variable};isOutput=true;]{matrix_text}")
else:
print(matrix_text)
_format_cli_output(args, matrix_text)
return 0

View File

@ -12,6 +12,7 @@ import shlex
import subprocess
import sys
import time
import shutil
import labgrid
from fabric import Connection
@ -52,17 +53,21 @@ class AcquiredPlace:
subprocess.run(["labgrid-client", "-c", self.config, "release"])
def jtag_x3xx(
dev_type, dev_model, jtag_server, jtag_serial, fpga_folder, fpga, redis_server, vivado_dir
):
def jtag_x3xx(dev_type, dev_model, jtag_server, jtag_serial, fpga_folder, fpga, redis_server):
"""Download the bitfile to the FPGA using jtag interface."""
if dev_model not in ["x300", "x310"]:
raise RuntimeError(f"{dev_type} not supported with jtag_x3xx")
remote_working_dir = "pipeline_fpga"
vivado_lab_path = os.path.join(vivado_dir, "bin/vivado_lab")
vivado_program_jtag = "{} -mode batch -source {}/viv_hardware_utils.tcl -nolog -nojournal -tclargs program".format(
vivado_lab_path, remote_working_dir
vivado_lab = shutil.which("vivado_lab")
if vivado_lab is None:
raise RuntimeError("vivado_lab is not available on PATH")
vivado_program_jtag = (
f"{shlex.quote(vivado_lab)} -mode batch "
f"-source {remote_working_dir}/viv_hardware_utils.tcl "
"-nolog -nojournal -tclargs program"
)
print("Waiting on jtag mutex for {}".format(jtag_server), flush=True)
with Redlock(
key="hw_jtag_{}".format(jtag_server), masters=redis_server, auto_release_time=1000 * 60 * 5
@ -329,7 +334,7 @@ def main(args):
os.chdir(os.path.join(working_dir, fpga))
if args.jtag_x3xx:
dev_type, dev_model, jtag_server, jtag_serial, fpga_folder, vivado_dir = (
dev_type, dev_model, jtag_server, jtag_serial, fpga_folder = (
args.jtag_x3xx.split(",")
)
jtag_x3xx(
@ -340,7 +345,6 @@ def main(args):
fpga_folder,
fpga,
get_redis_server(args),
vivado_dir,
)
if dev_type and dev_type in ["n3xx", "e3xx"]:

View File

@ -1,6 +1,6 @@
4.10.0_20260711.1
rnd-docker-ci/ni/rfsdr/
{'Fedora-42-builder':{'buildOSName':'fedora42','dockerImageName':'uhd-builder-fedora42','releaseName':'42'}, 'Fedora-43-builder':{'buildOSName':'fedora43','dockerImageName':'uhd-builder-fedora43','releaseName':'43'}, 'Fedora-44-builder':{'buildOSName':'fedora44','dockerImageName':'uhd-builder-fedora44','releaseName':'44'}, 'Ubuntu-2004-builder':{'buildOSName':'ubuntu2004','dockerImageName':'uhd-builder-ubuntu2004','releaseName':'focal'}, 'Ubuntu-2204-builder':{'buildOSName':'ubuntu2204','dockerImageName':'uhd-builder-ubuntu2204','releaseName':'jammy'}, 'Ubuntu-2404-builder':{'buildOSName':'ubuntu2404','dockerImageName':'uhd-builder-ubuntu2404','releaseName':'noble'}, 'Ubuntu-2510-builder':{'buildOSName':'ubuntu2510','dockerImageName':'uhd-builder-ubuntu2510','releaseName':'questing'}}
4.10.0_20260727.3
rnd-docker-pre/ni/rfsdr/
{'VS2022-x64-py310-builder':{'buildOSName':'win-vs22-v143-x64-py310','dockerImageName':'uhd-builder-vs2022-v143-x64-py310','cmakeCompiler':'Visual Studio 17 2022','cmakeArch':'x64','vsArch':'x64','vsYear':'2022','pythonVersion':'python310'}, 'VS2022-x64-py312-builder':{'buildOSName':'win-vs22-v143-x64-py312','dockerImageName':'uhd-builder-vs2022-v143-x64-py312','cmakeCompiler':'Visual Studio 17 2022','cmakeArch':'x64','vsArch':'x64','vsYear':'2022','pythonVersion':'python312'}, 'VS2026-x64-py310-builder':{'buildOSName':'win-vs26-v145-x64-py310','dockerImageName':'uhd-builder-vs2026-v145-x64-py310','cmakeCompiler':'Visual Studio 18 2026','cmakeArch':'x64','vsArch':'x64','vsYear':'2026','pythonVersion':'python310'}, 'VS2026-x64-py312-builder':{'buildOSName':'win-vs26-v145-x64-py312','dockerImageName':'uhd-builder-vs2026-v145-x64-py312','cmakeCompiler':'Visual Studio 18 2026','cmakeArch':'x64','vsArch':'x64','vsYear':'2026','pythonVersion':'python312'}}
{'macOS-15-x64':{'buildOSName':'macOS-15-x64', 'macOSVersionDemand':'15', 'macOSArchDemand': 'X64'}}
{}

View File

@ -16,9 +16,11 @@ from __future__ import annotations
import argparse
import ast
import os
import subprocess
import sys
import unittest
import warnings
from pathlib import Path
from typing import Optional
@ -33,6 +35,15 @@ DEFAULT_FIXTURE = (
ARTIFACT_FILE_OVERRIDE: Optional[Path] = None
def _is_ci_build() -> bool:
"""Return True for CI contexts where test failures must remain blocking."""
from derive_docker_image_matrix import ( # type: ignore[import-not-found]
is_strict_ci_build,
)
return is_strict_ci_build()
class DeriveDockerImageMatrixTests(unittest.TestCase):
"""Validate docker image matrix derivation behavior for current artifacts."""
@ -59,42 +70,93 @@ class DeriveDockerImageMatrixTests(unittest.TestCase):
self.fixture = self.__class__.fixture
self.using_artifact_override = ARTIFACT_FILE_OVERRIDE is not None
self.artifact_data = self.parse_docker_image_names_file(self.fixture)
self._warn_for_empty_optional_matrices()
def _warn_for_empty_optional_matrices(self) -> None:
"""Warn if expected matrix sections are present but empty."""
line_map = {
"linux_matrix": 3,
"win_matrix": 4,
"macos_builders": 5,
}
for key, line_no in line_map.items():
value = self.artifact_data.get(key)
if isinstance(value, dict) and not value:
warnings.warn(
f"DockerImageNames line {line_no} ({key}) is an empty dict; "
"tests requiring populated matrix entries may be skipped.",
UserWarning,
)
def _skip_if_matrix_empty(self, key: str, line_no: int) -> None:
"""Skip tests that need populated matrix content when section is empty."""
value = self.artifact_data.get(key)
if isinstance(value, dict) and not value:
warnings.warn(
f"Skipping test because DockerImageNames line {line_no} ({key}) is empty.",
UserWarning,
)
self.skipTest(f"Empty matrix at line {line_no}: {key}")
def _ubuntu_linux_entries(self) -> dict[str, dict[str, object]]:
"""Return Ubuntu entries from linux_matrix that include a releaseName."""
entries: dict[str, dict[str, object]] = {}
for key, payload in self.artifact_data["linux_matrix"].items():
if key.startswith("Ubuntu-") and payload.get("releaseName"):
entries[key] = payload
return entries
def _fedora_linux_entries(self) -> dict[str, dict[str, object]]:
"""Return Fedora entries from linux_matrix that include a releaseName."""
entries: dict[str, dict[str, object]] = {}
for key, payload in self.artifact_data["linux_matrix"].items():
if key.startswith("Fedora-") and payload.get("releaseName"):
entries[key] = payload
return entries
def test_parses_fixture_file(self):
"""Parse the fixture and expose expected top-level fields."""
if self.using_artifact_override:
self.assertTrue(self.artifact_data["docker_build_number"])
else:
self.assertEqual(self.artifact_data["docker_build_number"], "4.10.0_20260711.1")
self.assertIn("Ubuntu-2404-builder", self.artifact_data["linux_matrix"])
self.assertTrue(self.artifact_data["docker_build_number"])
self._skip_if_matrix_empty("linux_matrix", 3)
self.assertTrue(
any(key.startswith("Ubuntu-") for key in self.artifact_data["linux_matrix"]),
"Expected at least one Ubuntu entry in linux_matrix",
)
def test_derives_deb_matrix_from_linux_matrix(self):
"""Derive Ubuntu deb matrix using releaseName from linux matrix entries."""
self._skip_if_matrix_empty("linux_matrix", 3)
matrix = self.derive_package_matrix(self.artifact_data, os_name="linux", package="deb")
self.assertIn("Ubuntu-2204-builder", matrix)
self.assertIn("Ubuntu-2404-builder", matrix)
self.assertIn("Ubuntu-2510-builder", matrix)
self.assertEqual(matrix["Ubuntu-2204-builder"]["ubuntuReleaseName"], "jammy")
self.assertEqual(matrix["Ubuntu-2404-builder"]["ubuntuReleaseName"], "noble")
expected = self._ubuntu_linux_entries()
self.assertEqual(set(matrix.keys()), set(expected.keys()))
for key, payload in expected.items():
self.assertEqual(matrix[key]["ubuntuReleaseName"], payload["releaseName"])
def test_derives_rpm_matrix_from_linux_matrix(self):
"""Derive Fedora rpm matrix using releaseName from linux matrix entries."""
self._skip_if_matrix_empty("linux_matrix", 3)
matrix = self.derive_package_matrix(self.artifact_data, os_name="linux", package="rpm")
self.assertIn("Fedora-42-builder", matrix)
self.assertEqual(matrix["Fedora-42-builder"]["fedoraReleaseName"], "42")
self.assertEqual(matrix["Fedora-44-builder"]["fedoraReleaseName"], "44")
expected = self._fedora_linux_entries()
self.assertEqual(set(matrix.keys()), set(expected.keys()))
for key, payload in expected.items():
self.assertEqual(matrix[key]["fedoraReleaseName"], payload["releaseName"])
def test_filter_out_matches_release_name(self):
"""Exclude entries when filter-out matches a derived release field."""
self._skip_if_matrix_empty("linux_matrix", 3)
base = self.derive_package_matrix(self.artifact_data, os_name="linux", package="deb")
if not base:
self.skipTest("No Ubuntu entries available for deb filter-out test")
selected_key, selected_payload = next(iter(base.items()))
selected_release = str(selected_payload["ubuntuReleaseName"])
matrix = self.derive_package_matrix(
self.artifact_data,
os_name="linux",
package="deb",
filter_out="questing,focal",
filter_out=selected_release,
)
self.assertNotIn("Ubuntu-2510-builder", matrix)
self.assertNotIn("Ubuntu-2004-builder", matrix)
self.assertIn("Ubuntu-2404-builder", matrix)
self.assertNotIn(selected_key, matrix)
def test_invalid_os_package_combination_rejected(self):
"""Reject unsupported OS/package combinations."""
@ -103,14 +165,19 @@ class DeriveDockerImageMatrixTests(unittest.TestCase):
def test_build_type_derivation_with_filter_out(self):
"""Build type returns linux matrix and honors filter_out by releaseName."""
self._skip_if_matrix_empty("linux_matrix", 3)
selected_key, selected_payload = next(iter(self.artifact_data["linux_matrix"].items()))
selected_release = str(selected_payload.get("releaseName", "")).strip()
if not selected_release:
self.skipTest("Selected linux matrix entry has no releaseName for filter-out test")
matrix = self.derive_typed_matrix(
self.artifact_data,
target_type="build",
os_name="linux",
filter_out="questing",
filter_out=selected_release,
)
self.assertNotIn("Ubuntu-2510-builder", matrix)
self.assertIn("Ubuntu-2404-builder", matrix)
self.assertNotIn(selected_key, matrix)
def test_build_type_derivation_for_windows_returns_win_matrix(self):
"""Build type windows path should return the windows matrix unchanged."""
@ -123,14 +190,21 @@ class DeriveDockerImageMatrixTests(unittest.TestCase):
def test_source_type_select_returns_single_entry(self):
"""Source type should return a single selected Ubuntu entry."""
self._skip_if_matrix_empty("linux_matrix", 3)
ubuntu_entries = self._ubuntu_linux_entries()
if not ubuntu_entries:
self.skipTest("No Ubuntu entries available for source select test")
selected_key, selected_payload = next(iter(ubuntu_entries.items()))
selected_release = str(selected_payload["releaseName"])
matrix = self.derive_typed_matrix(
self.artifact_data,
target_type="source",
os_name="linux",
select="noble",
select=selected_release,
)
self.assertEqual(set(matrix.keys()), {"Ubuntu-2404-builder"})
self.assertEqual(matrix["Ubuntu-2404-builder"]["ubuntuReleaseName"], "noble")
self.assertEqual(set(matrix.keys()), {selected_key})
self.assertEqual(matrix[selected_key]["ubuntuReleaseName"], selected_release)
def test_source_type_requires_select(self):
"""Source/docs types should require explicit select value."""
@ -148,7 +222,7 @@ class DeriveDockerImageMatrixTests(unittest.TestCase):
self.artifact_data,
target_type="source",
os_name="linux",
select="jammy,noble",
select="__no_such_release__",
)
def test_select_and_filter_out_are_mutually_exclusive(self):
@ -188,6 +262,64 @@ class DeriveDockerImageMatrixTests(unittest.TestCase):
parsed = ast.literal_eval(result.stdout.strip())
self.assertEqual(parsed, self.artifact_data["win_matrix"])
def test_cli_empty_on_error_manual_build_returns_empty_object(self):
"""Manual Azure build should use fallback and return '{}' on derivation errors."""
script_path = Path(__file__).resolve().parents[1] / "derive_docker_image_matrix.py"
env = os.environ.copy()
env["TF_BUILD"] = "true"
env["BUILD_REASON"] = "Manual"
result = subprocess.run(
[
sys.executable,
str(script_path),
"--input",
str(self.fixture),
"--empty-on-error-non-ci",
"--type",
"source",
"--os",
"linux",
],
check=False,
capture_output=True,
text=True,
env=env,
)
self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertEqual(result.stdout.strip(), "{}")
self.assertIn("##vso[task.logissue type=warning]", result.stderr)
self.assertIn("defaulting to empty object", result.stderr)
def test_cli_empty_on_error_non_ci_stays_strict_in_scheduled_ci(self):
"""Strict scheduled CI should still fail even when fallback flag is set."""
script_path = Path(__file__).resolve().parents[1] / "derive_docker_image_matrix.py"
env = os.environ.copy()
env["TF_BUILD"] = "true"
env["BUILD_REASON"] = "Schedule"
result = subprocess.run(
[
sys.executable,
str(script_path),
"--input",
str(self.fixture),
"--empty-on-error-non-ci",
"--type",
"source",
"--os",
"linux",
],
check=False,
capture_output=True,
text=True,
env=env,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("--select is required", result.stderr)
if __name__ == "__main__":
# Run this file directly, or use unittest discovery from the repo root.
@ -202,4 +334,25 @@ if __name__ == "__main__":
if args.artifact_file:
ARTIFACT_FILE_OVERRIDE = Path(args.artifact_file)
unittest.main(argv=[sys.argv[0], *remaining_args])
# Preserve unittest CLI filtering behavior via remaining_args.
loader = unittest.defaultTestLoader
suite = loader.loadTestsFromName(__name__)
if remaining_args:
suite = loader.loadTestsFromNames(remaining_args, module=sys.modules[__name__])
result = unittest.TextTestRunner(verbosity=2).run(suite)
if result.wasSuccessful():
raise SystemExit(0)
if _is_ci_build():
raise SystemExit(1)
# Non-CI runs should report issues as warnings but not fail callers.
message = (
"Matrix validation had failures/errors "
f"(failures={len(result.failures)}, errors={len(result.errors)}). "
"Treating as warnings because this is not a CI build."
)
warnings.warn(message, UserWarning)
print(f"##vso[task.logissue type=warning]{message}")
raise SystemExit(0)

118
CHANGELOG
View File

@ -2,25 +2,62 @@ Change Log for Releases
==============================
## 004.next
* New Features
- X4xx: Extended existing GPIO SPI support beyond 32 bits, to 64-bit transfers.
- FPGA tools: Added support for DCP files as source inputs.
- example: `benchmark_rate` can optionally wait for TX end-of-burst
acknowledgements with a configurable timeout (`--tx_eob_ack_timeout`).
* Bug Fixes
- X4xx: Corrected the RFDC NCO reset sequence and adjusted its default wait time.
- B3xx: Corrected the GPS timepulse polarity.
- B2xx: Added missing firmware descriptors for B200-mini and B205-mini variants.
- MultiUSRP API: RFNoC-backed `set_master_clock_rate()` now updates connected
DDC/DUC rates while preserving the requested TX/RX sample rates.
- FPGA DSP: Fixed halfband temporal decimation.
- FPGA DSP: Improved resource usage, CIC flow-control and reset handling.
- RFNoC: The image builder now handles GRC builds with empty parameters and
correctly processes the `--CHECK` image-build option.
- MPM: Improved X4xx device discovery responsiveness by caching transport
information and refreshing it when network interfaces or addresses change.
- MPM: RPC requests made during a device reset now return a transient
`UNAVAILABLE` status so clients can retry.
* API Changes
- Added explicit 32-bit and 64-bit SPI transaction methods to the C++ API
and RFNoC Python bindings. 64-bit transactions are only available on some
devices.
* FPGA
- Updated FPGA build flow and test benches to Vivado 2025.2.
## 004.011.000.000
* Highlights / Main Changes
- Update underlying OE version on embedded devices to Scarthgap (5.0 LTS).
Note that some of the MPM updates require this update.
- Add support for the new USRP B310, a PCIe/Thunderbolt-based device which
supports 2x2 RF channels at up to 100 MHz bandwidth per channel.
- Add support for X410, X420 and X440 variants with locked FPGA
- Update underlying OE version on embedded devices to Scarthgap (5.0 LTS)
and Linux kernel to version 6.12.
- Migrated the MPM RPC transport layer from mprpc (msgpack-RPC) to gRPC/Protobuf,
impacting all embedded devices running MPM.
* New Features
- X420 and X440:
- The X420_X4_200, X420_CG_400, X420_X4_400 and X440_X4_200 FPGA images now
use fixed fabric resamplers (3x) and RFDC resamplers instead of RFDC
resamplers alone. This ensures repeatable phase and time offsets between
repeatability events on these updated FPGA images. Note that this feature
is currently mutually exclusive with dual rate support on X440, and it
increases the resource utilization. To revert to the previous image with
non-repeatable phase offsets, a custom image must be built.
- X420:
- Added support for the 400 MHz FPGA variants, including the X420_CG_400
and X420_X4_400 images.
- X440:
- The X440_X4_200 image now uses fabric resamplers instead of RFDC
resamplers, which ensures repeatable phase offsets between devices and
reboots. Note that this feature is currently mutually exclusive with dual
rate support, and it increases the resource utilization. To revert to the
previous image with non-repeatable phase offsets, a custom image must be
built. By default, it is set to false (i.e., same as old behavior),
except for the X440_X4_200 image.
- A new BSP parameter (USE_FABRIC_RESAMPLERS) is introduced to toggle this
new feature.
- X4xx: The ref_locked sensor no longer queries the sticky bits; instead, it
only reports the current lock state. The ref_stable sensor is added to
include the sticky bits.
- Introduced a new BSP parameter (USE_FABRIC_RESAMPLERS) in the X4_200 image
to enable easy toggling between using fixed fabric resamplers (the new default)
and the previous behavior. The parameter is set to true (use fixed fabric
resamplers) in this image and is not available in other X440 FPGA images.
- X4xx:
- The ref_locked sensor no longer queries the sticky bits; instead, it
only reports the current lock state. The ref_stable sensor is added to
include the sticky bits.
- Python:
- The ref_clk_calibration feature is now accessible in the Python API.
- Streamers:
@ -40,12 +77,32 @@ Change Log for Releases
CHDR width).
- Allow parallel initialization of RFNoC-based devices for faster session
start when using multiple USRPs in a multi_usrp or rfnoc_graph object.
- Wideband DDC/DUC:
- Updated the RFNoC DDC and DUC blocks to support bandwidths > 200 MHz.
With this update, users can add the DDC and DUC blocks to their custom
FPGA images for bandwidths up to 1600 MHz (2000 MSps).
- Shipping FPGA images:
- Added new shipping FPGA images for x420:
- x420_X4_400
- x420_CG_400
- Added DDC and DUC functionality for all channels to the following
shipping FPGA images:
- x410_CG_400
- x420_X4_400
- x420_CG_400
- x420_X4_1000
- x420_CG_1000
- x440_X4_800
- x440_CG_800
- x440_X4_1600
- x440_CG_1600
For custom designs based on one of these updated rfnoc_image_core.yml
files, please note that the new addition of the DDC/DUC blocks to FPGA
images will have an impact on how much custom logic can fit into these
designs. If needed the DDC/DUC blocks can be removed to revert to the
previous behavior.
- MPM-based devices can now be initialized in parallel within a MultiUSRP
session.
- X420:
- Added support for the 400 MHz FPGA variants, including the X420_CG_400
and X420_X4_400 images.
- Added fabric-resampler support to the X420 FPGA designs.
- GPS:
- Added support for UBLOX communication.
- Improved compatibility with GPS receivers by accepting additional
@ -73,11 +130,16 @@ Change Log for Releases
- Some logging APIs that previously used boost::posix_time are now
declared deprecated, and alternatives that use standard C++ constructs have
been introduced.
- MPM device initialization is parallel by default. The serialize_init=1
device argument restores serial initialization.
- MPM device initialization is serial by default. The `init_threads=N`
device argument enables parallel initialization.
- Added an option to resynchronize X4xx SPLL R-dividers after reference-lock
loss using reset_spll_r_divider=1 in the synchronization arguments. This
is intended for advanced users only.
- Minor DDC/DUC C++ API changes as part of the Wideband DDC/DUC update.
* FPGA
- Many improvements to resource utilization for improved build success rate
and compilation time.
- Enabled usage of VHDL 2008.
* Codebase Management
- Fully removed Boost.Thread, Boost.Date_Time, and Boost.Chrono dependencies
for libuhd. Other Boost usage has been reduced.
@ -91,10 +153,14 @@ Change Log for Releases
or via a package manager such as vcpkg.
- Improved build reproducibility by removing the build-directory path from
embedded build information.
* FPGA
- Many improvements to resource utilization for improved build success rate
and compilation time.
- Enabled usage of VHDL 2008.
* Validated OS Environments
(Versions for build and runtime dependencies can be determined from the
docker container definitions in the UHD repository at .ci/docker/...)
- Linux:
- Ubuntu: jammy (22.04), noble (24.04), resolute (26.04)
- Fedora: 41, 42, 43
- Windows: 10 22H2, 11 24H2
- MacOS: Sequoia (15.7)
## 004.010.000.000
* Highlights / Main Changes
@ -449,8 +515,8 @@ Changes compared to UHD 4.9.0.0:
- Add device DNA feature for X310, X410, X440. This allows reading back the
FPGA's DNA, which is a unique identifier.
- Embedded OS for E3xx, N3xx, and X4xx devices
- With the switch to Yocto 4.0 (kirkstone), the SD card and eMMC images no
longer pre-populate the second system partition. Refer to the device
- With the switch to Yocto 4.0 (kirkstone), the SD card and eMMC images no
longer pre-populate the second system partition. Refer to the device
specific "Updating Filesystems" section in the user manual for how to populate it.
This reduces the size of compressed .sdimg filesystem images.
- X440

View File

@ -16,10 +16,13 @@ managing the transport from the host to the FPGA by configuring IO and DMA.
In order to compile the USRP B200 and B210 bootloader and firmware, you will
need the FX3 SDK distributed by Cypress Semiconductor. You can download the
[FX3 SDK from here](https://www.cypress.com/documentation/software-and-drivers/ez-usb-fx3-software-development-kit)
[FX3 SDK from here](https://www.cypress.com/documentation/software-and-drivers/ez-usb-fx3-software-development-kit).
Version 1.3.5 is known to be working.
From the SDK, extract the ARM cross-compile toolchain tarball `ARM-GCC.tar.gz`
and the FX3 firmware tarball `fx3_firmware_linux.tar.gz`.
Get the latest version of the [GNU Toolchains for ARM from arm.com](https://gitlab.arm.com/tooling/gnu-toolchains-for-arm)
and extract it. Version 15.3.rel1 is known to be working.
Set up the appropriate environment variables for the build:
@ -32,15 +35,39 @@ Set up the appropriate environment variables for the build:
These should look something like this:
```
$ export ARMGCC_INSTALL_PATH=/path/to/my/files/arm-2013.11
$ export ARMGCC_VERSION=4.8.1
$ export ARMGCC_INSTALL_PATH=/path/to/my/files/arm-gnu-toolchain-15.3.rel1-x86_64-arm-none-eabi
$ export ARMGCC_VERSION=15.3.1
$ export FX3FWROOT=/path/to/my/files/cyfx3sdk
```
The SDK makefiles invoke `arm-none-eabi-gcc` unqualified and only use
`ARMGCC_INSTALL_PATH` to locate the libraries, so the toolchain must also be on
your `PATH`:
```
$ export PATH=$ARMGCC_INSTALL_PATH/bin:$PATH
```
Without this, the build either fails with `arm-none-eabi-gcc: No such file or
directory` or, if a different ARM toolchain happens to be installed, silently
compiles with that compiler while linking against the libraries of the toolchain
above. Verify the expected compiler is picked up:
```
$ arm-none-eabi-gcc --version
```
## Applying the Patch to the Toolchain
Now, you'll need to apply a patch to some files in the Cypress SDK. Apply the
patch `b200/fx3_mem_map.patch` as follows:
Now, you'll need to apply a patch to some files in the Cypress SDK. Before doing
so, ensure all files in the SDK have Unix line endings and are writable for the user:
```
$ cd $FX3FWROOT
$ find . -type f -exec dos2unix {} \; -exec chmod u+w {} \;
```
Apply the patch `b200/fx3_mem_map.patch` as follows:
```
$ cd uhd.git/firmware/fx3

File diff suppressed because it is too large Load Diff

View File

@ -9,43 +9,46 @@
#define EEPROM_REV_UNRECOGNIZED -1
#define EEPROM_SIGNATURE_ADDR 0x0000
#define EEPROM_SIGNATURE_ADDR 0x0000
#define EEPROM_SIGNATURE_LENGTH 4
#define EEPROM_REV0_SIGNATURE 0xB2145943
#define EEPROM_REV0_SIGNATURE 0xB2145943
#define EEPROM_REV1_OR_GREATER_SIGNATURE 0xB01A5943
#define EEPROM_REV0_VID_ADDR 0x0006
#define EEPROM_REV1_VID_ADDR 0x7F06
#define EEPROM_VID_LENGTH 2
#define EEPROM_VID_LENGTH 2
#define VID_ETTUS_RESEARCH 0x2500
#define VID_ETTUS_RESEARCH 0x2500
#define VID_NATIONAL_INSTRUMENTS 0x3923
#define VID_CYPRESS 0x04B4
#define VID_CYPRESS 0x04B4
#define EEPROM_REV0_PID_ADDR 0x0004
#define EEPROM_REV1_PID_ADDR 0x7F08
#define EEPROM_PID_LENGTH 2
#define EEPROM_PID_LENGTH 2
#define PID_ETTUS_B200_B210 0x0020
#define PID_NI_USRP_2900 0x7813
#define PID_NI_USRP_2901 0x7814
#define PID_ETTUS_B200_MINI 0x0021
#define PID_ETTUS_B205_MINI 0x0022
#define PID_ETTUS_B206_MINI 0x0023
#define PID_NI_USRP_2900 0x7813
#define PID_NI_USRP_2901 0x7814
#define PID_CYPRESS_DEFAULT 0x00F0
#define EEPROM_REV0_SERIAL_ADDR 0x04F7
#define EEPROM_REV1_SERIAL_ADDR 0x7F25
#define EEPROM_SERIAL_LENGTH 9
#define EEPROM_SERIAL_LENGTH 9
#define EEPROM_REV1_MAGIC_ADDR 0x7F00
#define EEPROM_MAGIC_LENGTH 2
#define EEPROM_EXPECTED_MAGIC 0xB200
#define EEPROM_MAGIC_LENGTH 2
#define EEPROM_EXPECTED_MAGIC 0xB200
#define EEPROM_REV1_REV_ADDR 0x7F02
#define EEPROM_REV_LENGTH 2
#define EEPROM_EXPECTED_REV 1
#define EEPROM_REV_LENGTH 2
#define EEPROM_EXPECTED_REV 1
#define EEPROM_REV1_COMPAT_ADDR 0x7F04
#define EEPROM_COMPAT_LENGTH 2
#define EEPROM_EXPECTED_COMPAT 1
#define EEPROM_COMPAT_LENGTH 2
#define EEPROM_EXPECTED_COMPAT 1
#endif /* _COMMON_CONST_H */
#endif /* _COMMON_CONST_H */

View File

@ -9,96 +9,107 @@
information read from the EEPROM. */
/* Standard Device Descriptor for USB 2.0 */
unsigned char common_usb2_dev_desc[] =
{
0x12, /* Descriptor size */
0x01,/* CY_U3P_USB_DEVICE_DESCR Device descriptor type */
0x10,0x02, /* USB 2.10 */
0xFF, /* Device class */
0x00, /* Device sub-class */
0x00, /* Device protocol */
0x40, /* Maxpacket size for EP0 : 64 bytes */
0xB4,0x04, /* Vendor ID */
0xF0,0x00, /* Product ID */
0x00,0x00, /* Device release number */
0x01, /* Manufacture string index */
0x02, /* Product string index */
0x03, /* Serial number string index */
0x01 /* Number of configurations */
unsigned char common_usb2_dev_desc[] = {
0x12, /* Descriptor size */
0x01, /* CY_U3P_USB_DEVICE_DESCR Device descriptor type */
0x10,
0x02, /* USB 2.10 */
0xFF, /* Device class */
0x00, /* Device sub-class */
0x00, /* Device protocol */
0x40, /* Maxpacket size for EP0 : 64 bytes */
0xB4,
0x04, /* Vendor ID */
0xF0,
0x00, /* Product ID */
0x00,
0x00, /* Device release number */
0x01, /* Manufacture string index */
0x02, /* Product string index */
0x03, /* Serial number string index */
0x01 /* Number of configurations */
};
/* Standard Device Descriptor for USB 3.0 */
unsigned char common_usb3_dev_desc[] =
{
0x12, /* Descriptor size */
0x01,/* CY_U3P_USB_DEVICE_DESCR Device descriptor type */
0x00,0x03, /* USB 3.0 */
0xFF, /* Device class */
0x00, /* Device sub-class */
0x00, /* Device protocol */
0x09, /* Maxpacket size for EP0 : 2^9 */
0xB4,0x04, /* Vendor ID */
0xF0,0x00, /* Product ID */
0x00,0x00, /* Device release number */
0x01, /* Manufacture string index */
0x02, /* Product string index */
0x03, /* Serial number string index */
0x01 /* Number of configurations */
unsigned char common_usb3_dev_desc[] = {
0x12, /* Descriptor size */
0x01, /* CY_U3P_USB_DEVICE_DESCR Device descriptor type */
0x00,
0x03, /* USB 3.0 */
0xFF, /* Device class */
0x00, /* Device sub-class */
0x00, /* Device protocol */
0x09, /* Maxpacket size for EP0 : 2^9 */
0xB4,
0x04, /* Vendor ID */
0xF0,
0x00, /* Product ID */
0x00,
0x00, /* Device release number */
0x01, /* Manufacture string index */
0x02, /* Product string index */
0x03, /* Serial number string index */
0x01 /* Number of configurations */
};
/* Standard Device Qualifier Descriptor */
const unsigned char common_dev_qual_desc[] =
{
0x0A, /* Descriptor size */
0x06,/* CY_U3P_USB_DEVQUAL_DESCR Device qualifier descriptor type */
0x00,0x02, /* USB 2.0 */
0xFF, /* Device class */
0x00, /* Device sub-class */
0x00, /* Device protocol */
0x40, /* Maxpacket size for EP0 : 64 bytes */
0x01, /* Number of configurations */
0x00 /* Reserved */
const unsigned char common_dev_qual_desc[] = {
0x0A, /* Descriptor size */
0x06, /* CY_U3P_USB_DEVQUAL_DESCR Device qualifier descriptor type */
0x00,
0x02, /* USB 2.0 */
0xFF, /* Device class */
0x00, /* Device sub-class */
0x00, /* Device protocol */
0x40, /* Maxpacket size for EP0 : 64 bytes */
0x01, /* Number of configurations */
0x00 /* Reserved */
};
/* Standard Binary Device Object Store Descriptor */
const unsigned char common_usb_bos_desc[] =
{
0x05, /* Descriptor size */
0x0F,/* CY_U3P_BOS_DESCR Device descriptor type */
0x16,0x00, /* Length of this descriptor and all sub descriptors */
0x02, /* Number of device capability descriptors */
const unsigned char common_usb_bos_desc[] = {
0x05, /* Descriptor size */
0x0F, /* CY_U3P_BOS_DESCR Device descriptor type */
0x16,
0x00, /* Length of this descriptor and all sub descriptors */
0x02, /* Number of device capability descriptors */
/* USB 2.0 extension */
0x07, /* Descriptor size */
0x10,/* CY_U3P_DEVICE_CAPB_DESCR Device capability type descriptor */
0x02,/* CY_U3P_USB2_EXTN_CAPB_TYPE USB 2.0 extension capability type */
0x02,0x00,0x00,0x00, /* Supported device level features: LPM support */
0x07, /* Descriptor size */
0x10, /* CY_U3P_DEVICE_CAPB_DESCR Device capability type descriptor */
0x02, /* CY_U3P_USB2_EXTN_CAPB_TYPE USB 2.0 extension capability type */
0x02,
0x00,
0x00,
0x00, /* Supported device level features: LPM support */
/* SuperSpeed device capability */
0x0A, /* Descriptor size */
0x10,/* CY_U3P_DEVICE_CAPB_DESCR Device capability type descriptor */
0x03,/* CY_U3P_SS_USB_CAPB_TYPE SuperSpeed device capability type */
0x00, /* Supported device level features */
0x0E,0x00, /* Speeds supported by the device : SS, HS and FS */
0x03, /* Functionality support */
0x00, /* U1 Device Exit latency */
0x00,0x00 /* U2 Device Exit latency */
0x0A, /* Descriptor size */
0x10, /* CY_U3P_DEVICE_CAPB_DESCR Device capability type descriptor */
0x03, /* CY_U3P_SS_USB_CAPB_TYPE SuperSpeed device capability type */
0x00, /* Supported device level features */
0x0E,
0x00, /* Speeds supported by the device : SS, HS and FS */
0x03, /* Functionality support */
0x00, /* U1 Device Exit latency */
0x00,
0x00 /* U2 Device Exit latency */
};
/* Standard Language ID String Descriptor */
const unsigned char common_string_lang_id_desc[] =
{
0x04, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x09,0x04 /* Language ID supported */
const unsigned char common_string_lang_id_desc[] = {
0x04, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x09,
0x04 /* Language ID supported */
};
// clang-format off
/* Ettus Manufacturer String Descriptor */
const unsigned char common_ettus_manufacturer_desc[] =
const unsigned char common_ettus_manufacturer_desc[] =
{
0x26, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x26, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'E',0x00,
't',0x00,
't',0x00,
@ -120,10 +131,10 @@ const unsigned char common_ettus_manufacturer_desc[] =
};
/* NI Manufacturer String Descriptor */
const unsigned char common_ni_manufacturer_desc[] =
const unsigned char common_ni_manufacturer_desc[] =
{
0x36, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x36, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'N',0x00,
'a',0x00,
't',0x00,
@ -154,10 +165,25 @@ const unsigned char common_ni_manufacturer_desc[] =
/* Ettus Product String Descriptor */
const unsigned char common_b200_product_desc[] =
const unsigned char common_b200_product_desc[] =
{
0x14, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x14, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'U',0x00,
'S',0x00,
'R',0x00,
'P',0x00,
' ',0x00,
'B',0x00,
'2',0x00,
'x',0x00,
'0',0x00
};
const unsigned char common_b200_mini_product_desc[] =
{
0x1E, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'U',0x00,
'S',0x00,
'R',0x00,
@ -166,14 +192,59 @@ const unsigned char common_b200_product_desc[] =
'B',0x00,
'2',0x00,
'0',0x00,
'0',0x00
'0',0x00,
' ',0x00,
'm',0x00,
'i',0x00,
'n',0x00,
'i',0x00
};
const unsigned char common_b205_mini_product_desc[] =
{
0x1E, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'U',0x00,
'S',0x00,
'R',0x00,
'P',0x00,
' ',0x00,
'B',0x00,
'2',0x00,
'0',0x00,
'5',0x00,
' ',0x00,
'm',0x00,
'i',0x00,
'n',0x00,
'i',0x00
};
const unsigned char common_b206_mini_product_desc[] =
{
0x1E, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'U',0x00,
'S',0x00,
'R',0x00,
'P',0x00,
' ',0x00,
'B',0x00,
'2',0x00,
'0',0x00,
'6',0x00,
' ',0x00,
'm',0x00,
'i',0x00,
'n',0x00,
'i',0x00
};
/* NI-USRP 2900 Product String Descriptor */
const unsigned char common_niusrp_2900_product_desc[] =
const unsigned char common_niusrp_2900_product_desc[] =
{
0x1A, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x1A, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'N',0x00,
'I',0x00,
' ',0x00,
@ -189,10 +260,10 @@ const unsigned char common_niusrp_2900_product_desc[] =
};
/* NI-USRP 2901 Product String Descriptor */
const unsigned char common_niusrp_2901_product_desc[] =
const unsigned char common_niusrp_2901_product_desc[] =
{
0x1A, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x1A, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'N',0x00,
'I',0x00,
' ',0x00,
@ -208,10 +279,10 @@ const unsigned char common_niusrp_2901_product_desc[] =
};
/* Unknown Product String Descriptor */
const unsigned char common_unknown_desc[] =
const unsigned char common_unknown_desc[] =
{
0x10, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
0x10, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'U',0x00,
'n',0x00,
'k',0x00,
@ -221,17 +292,18 @@ const unsigned char common_unknown_desc[] =
'n',0x00
};
unsigned char common_dev_serial_desc[] =
unsigned char common_dev_serial_desc[] =
{
0x14, /* Descriptor Size */
0x03,/* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'0', 0x00,
'0', 0x00,
'0', 0x00,
'0', 0x00,
'0', 0x00,
'0', 0x00,
'0', 0x00,
'0', 0x00,
'0', 0x00
0x14, /* Descriptor Size */
0x03, /* CY_U3P_USB_STRING_DESCR Device Descriptor Type */
'0',0x00,
'0',0x00,
'0',0x00,
'0',0x00,
'0',0x00,
'0',0x00,
'0',0x00,
'0',0x00,
'0',0x00
};
//clang-format on

View File

@ -32,9 +32,18 @@ extern const unsigned char common_ettus_manufacturer_desc[];
/* NI Manufacturer String Descriptor */
extern const unsigned char common_ni_manufacturer_desc[];
/* Ettus Product String Descriptor */
/* Ettus B200/B210 Product String Descriptor */
extern const unsigned char common_b200_product_desc[];
/* Ettus B200 mini Product String Descriptor */
extern const unsigned char common_b200_mini_product_desc[];
/* Ettus B205 mini Product String Descriptor */
extern const unsigned char common_b205_mini_product_desc[];
/* Ettus B206 mini Product String Descriptor */
extern const unsigned char common_b206_mini_product_desc[];
/* NI-USRP 2900 Product String Descriptor */
extern const unsigned char common_niusrp_2900_product_desc[];

View File

@ -12,7 +12,8 @@
/* Check the rev, magic value, and eeprom compatibility field to determine if
our eeprom map is up-to-date with what is written on the device */
int eeprom_is_readable(eeprom_read_t read_fn) {
int eeprom_is_readable(eeprom_read_t read_fn)
{
/* Only check this once, then store that info */
static int is_readable = UNREAD;
@ -22,16 +23,14 @@ int eeprom_is_readable(eeprom_read_t read_fn) {
int rev = get_rev(read_fn);
if (rev == EEPROM_REV_UNRECOGNIZED)
{
if (rev == EEPROM_REV_UNRECOGNIZED) {
is_readable = 0;
return is_readable;
}
/* If rev is 0, there's no further checks available */
if (rev == 0)
{
if (rev == 0) {
is_readable = 1;
return is_readable;
}
@ -91,7 +90,6 @@ int get_rev(eeprom_read_t read_fn)
}
/* Read the vendor ID from EEPROM using the function specified*/
uint16_t get_vid(eeprom_read_t read_fn)
{
@ -189,6 +187,12 @@ const uint8_t* get_product_string_descriptor(uint16_t pid)
{
if (pid == PID_ETTUS_B200_B210) {
return common_b200_product_desc;
} else if (pid == PID_ETTUS_B200_MINI) {
return common_b200_mini_product_desc;
} else if (pid == PID_ETTUS_B205_MINI) {
return common_b205_mini_product_desc;
} else if (pid == PID_ETTUS_B206_MINI) {
return common_b206_mini_product_desc;
} else if (pid == PID_NI_USRP_2900) {
return common_niusrp_2900_product_desc;
} else if (pid == PID_NI_USRP_2901) {
@ -196,4 +200,4 @@ const uint8_t* get_product_string_descriptor(uint16_t pid)
} else {
return common_unknown_desc;
}
}
}

View File

@ -6,43 +6,45 @@
#include "cyu3i2c.h"
static uint16_t glI2cPageSize = 0x40; /* I2C Page size to be used for transfers. */
/* I2c initialization for EEPROM programming. */
void CyFxI2cInit (uint16_t pageLen) {
void CyFxI2cInit(uint16_t pageLen)
{
CyU3PI2cConfig_t i2cConfig;
/* Initialize and configure the I2C master module. */
CyU3PI2cInit ();
CyU3PI2cInit();
/* Start the I2C master block. The bit rate is set at 100KHz.
* The data transfer is done via DMA. */
CyU3PMemSet ((uint8_t *)&i2cConfig, 0, sizeof(i2cConfig));
CyU3PMemSet((uint8_t*)&i2cConfig, 0, sizeof(i2cConfig));
i2cConfig.bitRate = CY_FX_USBI2C_I2C_BITRATE;
i2cConfig.busTimeout = 0xFFFFFFFF;
i2cConfig.dmaTimeout = 0xFFFF;
i2cConfig.isDma = CyFalse;
CyU3PI2cSetConfig (&i2cConfig, NULL);
CyU3PI2cSetConfig(&i2cConfig, NULL);
glI2cPageSize = pageLen;
}
/* I2C read / write for programmer application. */
void CyFxUsbI2cTransfer (
uint16_t byteAddress,
uint8_t devAddr,
uint16_t byteCount,
uint8_t *buffer,
CyBool_t isRead)
void CyFxUsbI2cTransfer(uint16_t byteAddress,
uint8_t devAddr,
uint16_t byteCount,
uint8_t* buffer,
CyBool_t isRead)
{
CyU3PI2cPreamble_t preamble;
uint16_t pageCount = (byteCount / glI2cPageSize);
uint16_t resCount = glI2cPageSize;
uint16_t resCount = glI2cPageSize;
if (byteCount == 0) {
return;
}
if ((byteCount % glI2cPageSize) != 0) {
pageCount ++;
pageCount++;
resCount = byteCount % glI2cPageSize;
}
@ -56,7 +58,8 @@ void CyFxUsbI2cTransfer (
preamble.buffer[3] = (devAddr | 0x01);
preamble.ctrlMask = 0x0004;
CyU3PI2cReceiveBytes (&preamble, buffer, (pageCount == 1) ? resCount : glI2cPageSize, 0);
CyU3PI2cReceiveBytes(
&preamble, buffer, (pageCount == 1) ? resCount : glI2cPageSize, 0);
} else {
/* Write. Update the preamble information. */
preamble.length = 3;
@ -65,18 +68,19 @@ void CyFxUsbI2cTransfer (
preamble.buffer[2] = (uint8_t)(byteAddress & 0xFF);
preamble.ctrlMask = 0x0000;
CyU3PI2cTransmitBytes (&preamble, buffer, (pageCount == 1) ? resCount : glI2cPageSize, 0);
CyU3PI2cTransmitBytes(
&preamble, buffer, (pageCount == 1) ? resCount : glI2cPageSize, 0);
/* Wait for the write to complete. */
preamble.length = 1;
CyU3PI2cWaitForAck(&preamble, 200);
}
/* An additional delay seems to be required after receiving an ACK. */
CyU3PThreadSleep (1);
CyU3PThreadSleep(1);
/* Update the parameters */
byteAddress += glI2cPageSize;
byteAddress += glI2cPageSize;
buffer += glI2cPageSize;
pageCount --;
pageCount--;
}
}

View File

@ -7,33 +7,34 @@
#include "cyu3externcstart.h"
#include "cyu3usbconst.h"
#include "cyu3types.h"
#include "cyu3usbconst.h"
/* Following two definitions made in b200_main.h for consistency. */
/* define B200_VREQ_EEPROM_WRITE (uint8_t)(0xBA) */
/* define B200_VREQ_EEPROM_READ (uint8_t)(0xBB) */
static uint16_t glI2cPageSize = 0x40; /* I2C Page size to be used for transfers. */
/* This application uses EEPROM as the slave I2C device. The I2C EEPROM
* part number used is 24LC256. The capacity of the EEPROM is 256K bits */
#define CY_FX_USBI2C_I2C_MAX_CAPACITY (32 * 1024) /* Capacity in bytes */
#define CY_FX_USBI2C_I2C_MAX_CAPACITY (32 * 1024) /* Capacity in bytes */
/* The following constant is defined based on the page size that the I2C
* device support. 24LC256 support 64 byte page write access. */
#define CY_FX_USBI2C_I2C_PAGE_SIZE (64)
#define CY_FX_USBI2C_I2C_PAGE_SIZE (64)
/* I2C Data rate */
#define CY_FX_USBI2C_I2C_BITRATE (100000)
#define CY_FX_USBI2C_I2C_BITRATE (100000)
/* Give a timeout value of 5s for any programming. */
#define CY_FX_USB_I2C_TIMEOUT (5000)
#define CY_FX_USB_I2C_TIMEOUT (5000)
/* Function forward-declerations. */
void CyFxI2cInit (uint16_t pageLen);
void CyFxUsbI2cTransfer (uint16_t byteAddress, uint8_t devAddr,
uint16_t byteCount, uint8_t *buffer, CyBool_t isRead);
void CyFxI2cInit(uint16_t pageLen);
void CyFxUsbI2cTransfer(uint16_t byteAddress,
uint8_t devAddr,
uint16_t byteCount,
uint8_t* buffer,
CyBool_t isRead);
#include "cyu3externcend.h"

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ endif
MODULE = b200_main
SOURCE += $(MODULE).c b200_i2c.c b200_usb_descriptors.c ../common/common_helpers.c ../common/common_descriptors.c cyfxtx.c
SOURCE += $(MODULE).c b200_i2c.c b200_usb_descriptors.c syscalls.c ../common/common_helpers.c ../common/common_descriptors.c cyfxtx.c
INCFLAGS = -I../common

View File

@ -0,0 +1,68 @@
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
int _close(int file)
{
(void)file;
errno = ENOSYS;
return -1;
}
int _fstat(int file, struct stat* st)
{
(void)file;
if (st != 0) {
st->st_mode = S_IFCHR;
}
return 0;
}
int _getpid(void)
{
return 1;
}
int _isatty(int file)
{
(void)file;
return 1;
}
int _kill(int pid, int sig)
{
(void)pid;
(void)sig;
errno = EINVAL;
return -1;
}
int _lseek(int file, int ptr, int dir)
{
(void)file;
(void)ptr;
(void)dir;
return 0;
}
int _read(int file, char* ptr, int len)
{
(void)file;
(void)ptr;
(void)len;
return 0;
}
void _exit(int status)
{
(void)status;
while (1) {
}
}
int _write(int file, const char* ptr, int len)
{
(void)file;
(void)ptr;
return len;
}

View File

@ -1,10 +1,10 @@
diff -r -u cyfx3sdk/fw_build/boot_fw/cyfx3.ld cyfx3sdk-modified/fw_build/boot_fw/cyfx3.ld
--- cyfx3sdk/fw_build/boot_fw/cyfx3.ld 2018-05-23 02:25:01.000000000 -0700
+++ cyfx3sdk-modified/fw_build/boot_fw/cyfx3.ld 2020-08-22 14:54:46.407481500 -0700
@@ -39,12 +39,18 @@
--- cyfx3sdk/fw_build/boot_fw/cyfx3.ld 2026-08-11 15:34:04.645797167 +0200
+++ cyfx3sdk-modified/fw_build/boot_fw/cyfx3.ld 2026-08-11 15:51:14.427111162 +0200
@@ -41,12 +41,18 @@
MEMORY
{
I-TCM : ORIGIN = 0x100 LENGTH = 0x100
+ BLANK : ORIGIN = 0x40070000 LENGTH = 0x0100
SYS_MEM : ORIGIN = 0x40078000 LENGTH = 0x7000
DATA : ORIGIN = 0x4007F000 LENGTH = 0x1000
@ -17,12 +17,12 @@ diff -r -u cyfx3sdk/fw_build/boot_fw/cyfx3.ld cyfx3sdk-modified/fw_build/boot_fw
+ . += 0x100;
+ } > BLANK
+
.text :
.vectors :
{
*(RESET)
*(CYU3P_ITCM_SECTION)
diff -r -u cyfx3sdk/fw_build/fx3_fw/cyfxtx.c cyfx3sdk-modified/fw_build/fx3_fw/cyfxtx.c
--- cyfx3sdk/fw_build/fx3_fw/cyfxtx.c 2018-05-23 02:25:01.000000000 -0700
+++ cyfx3sdk-modified/fw_build/fx3_fw/cyfxtx.c 2020-08-21 12:34:14.344699800 -0700
--- cyfx3sdk/fw_build/fx3_fw/cyfxtx.c 2026-08-11 15:34:04.685797262 +0200
+++ cyfx3sdk-modified/fw_build/fx3_fw/cyfxtx.c 2026-08-11 15:34:17.397827996 +0200
@@ -98,7 +98,7 @@
area which is used by the application code as well as the drivers to allocate thread
stacks and other internal data structures.
@ -33,8 +33,8 @@ diff -r -u cyfx3sdk/fw_build/fx3_fw/cyfxtx.c cyfx3sdk-modified/fw_build/fx3_fw/c
/*
diff -r -u cyfx3sdk/fw_build/fx3_fw/fx3_512k.ld cyfx3sdk-modified/fw_build/fx3_fw/fx3_512k.ld
--- cyfx3sdk/fw_build/fx3_fw/fx3_512k.ld 2018-05-23 02:25:01.000000000 -0700
+++ cyfx3sdk-modified/fw_build/fx3_fw/fx3_512k.ld 2020-08-21 12:34:14.345050200 -0700
--- cyfx3sdk/fw_build/fx3_fw/fx3_512k.ld 2026-08-11 15:34:04.665797215 +0200
+++ cyfx3sdk-modified/fw_build/fx3_fw/fx3_512k.ld 2026-08-11 15:34:17.381827956 +0200
@@ -32,11 +32,11 @@
The default memory map used for FX3 applications is as follows:
@ -52,10 +52,10 @@ diff -r -u cyfx3sdk/fw_build/fx3_fw/fx3_512k.ld cyfx3sdk-modified/fw_build/fx3_f
Interrupt handlers are placed in I-TCM (16KB). The first 256 bytes of ITCM are
reserved for Exception Vectors and will be loaded during firmware initialization.
@@ -58,8 +58,8 @@
@@ -59,8 +59,8 @@
MEMORY
{
I-TCM : ORIGIN = 0x100 LENGTH = 0x3F00
I-TCM : ORIGIN = 0x200 LENGTH = 0x3E00
- SYS_MEM : ORIGIN = 0x40003000 LENGTH = 0x2D000
- DATA : ORIGIN = 0x40030000 LENGTH = 0x8000
+ SYS_MEM : ORIGIN = 0x40003000 LENGTH = 0x35000
@ -63,7 +63,7 @@ diff -r -u cyfx3sdk/fw_build/fx3_fw/fx3_512k.ld cyfx3sdk-modified/fw_build/fx3_f
}
SECTIONS
@@ -114,5 +114,17 @@
@@ -115,5 +115,17 @@
. = ALIGN(4);
} > DATA
__exidx_end = .;

View File

@ -29,6 +29,10 @@ parameters:
type: boolean
displayName: Build N3xx
default: false
- name: build_b310
type: boolean
displayName: Build B310
default: false
- name: build_e31x
type: boolean
displayName: Build E31x
@ -125,30 +129,35 @@ parameters:
X420_CG_1000:
image_core: x420_CG_1000_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_CG_1000
target_name: X420
artifact_name: X420_CG_1000
timeout: 720
max_attempts: 2
X420_X4_1000:
image_core: x420_X4_1000_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_X4_1000
target_name: X420
artifact_name: X420_X4_1000
timeout: 720
max_attempts: 2
X420_CG_400:
image_core: x420_CG_400_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_CG_400
target_name: X420
artifact_name: X420_CG_400
timeout: 720
max_attempts: 2
X420_X4_400:
image_core: x420_X4_400_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_X4_400
target_name: X420
artifact_name: X420_X4_400
timeout: 720
max_attempts: 2
X420_X4_200:
image_core: x420_X4_200_rfnoc_image_core.yml
image_core_name: usrp_x420_fpga_X4_200
target_name: X420
artifact_name: X420_X4_200
timeout: 720
max_attempts: 2
@ -164,6 +173,14 @@ parameters:
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_CG_800:
image_core: x440_CG_800_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_CG_800
target_name: X440
artifact_name: X440_CG_800
timeout: 1080
attempt_timeout: 360
max_attempts: 2
X440_CG_400:
image_core: x440_CG_400_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_CG_400
@ -180,6 +197,14 @@ parameters:
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_X4_800:
image_core: x440_X4_800_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_X4_800
target_name: X440
artifact_name: X440_X4_800
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_X4_400:
image_core: x440_X4_400_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_X4_400
@ -196,6 +221,14 @@ parameters:
timeout: 720
attempt_timeout: 360
max_attempts: 2
X440_X1_200:
image_core: x440_X1_200_rfnoc_image_core.yml
image_core_name: usrp_x440_fpga_X1_200
target_name: X440
artifact_name: X440_X1_200
timeout: 720
attempt_timeout: 360
max_attempts: 2
trigger: none
pr: none
@ -205,10 +238,11 @@ extends:
parameters:
add_tests: ${{
format(
'{0};{1};{2};{3};{4};{5};{6};{7};{8};{9};{10};',
'{0};{1};{2};{3};{4};{5};{6};{7};{8};{9};{10};{11};',
iif(parameters.run_testbenches,'fpga.usrp3.testbenches',''),
iif(parameters.build_x3x0,'fpga.usrp3.x3xx',''),
iif(parameters.build_n3xx,'fpga.usrp3.n3xx',''),
iif(parameters.build_b310,'fpga.usrp3.b310',''),
iif(parameters.build_e31x,'fpga.usrp3.e31x',''),
iif(parameters.build_e320,'fpga.usrp3.e320',''),
iif(parameters.build_x410,'fpga.usrp3.x410',''),

View File

@ -28,7 +28,7 @@ def print_timing_constraint_summary(twr_file):
if 'constraint' in line and 'met' in line: done = True
if not keep and done: keep = True
if keep: output += line
if done: break
if done: break
print("\n\n"+output)
if __name__=='__main__': map(print_timing_constraint_summary, sys.argv[1:])

View File

@ -22,10 +22,8 @@ include $(LIB_DIR)/control/Makefile.srcs
include $(LIB_DIR)/fifo/Makefile.srcs
include $(LIB_DIR)/timing/Makefile.srcs
include $(LIB_DIR)/packet_proc/Makefile.srcs
include $(LIB_DIR)/vita/Makefile.srcs
include $(LIB_DIR)/dsp/Makefile.srcs
include $(LIB_DIR)/axi/Makefile.srcs
include $(LIB_DIR)/radio/Makefile.srcs
include $(BASE_DIR)/x300/coregen_dsp/Makefile.srcs
DESIGN_SRCS += $(abspath \
@ -33,32 +31,13 @@ $(FIFO_SRCS) \
$(CONTROL_LIB_SRCS) \
$(TIMING_SRCS) \
$(PACKET_PROC_SRCS) \
$(VITA_SRCS) \
$(DSP_SRCS) \
$(AXI_SRCS) \
$(RADIO_SRCS) \
$(COREGEN_DSP_SRCS) \
)
TOP_MODULE = radio
#-------------------------------------------------
# IP Specific
#-------------------------------------------------
# If simulation contains IP, define the IP_DIR and point
# it to the base level IP directory
IP_DIR = $(BASE_DIR)/x300/ip
# Include makefiles and sources for all IP components
# *after* defining the IP_DIR
include $(IP_DIR)/fifo_short_2clk/Makefile.inc
include $(IP_DIR)/fifo_4k_2clk/Makefile.inc
DESIGN_SRCS += $(abspath \
$(IP_FIFO_4K_2CLK_SRCS) \
$(IP_FIFO_SHORT_2CLK_SRCS) \
)
# DESIGN_SRCS and VERILOG_DEFS must be defined
.DEFAULT_GOAL := netlist
netlist: .prereqs $(DESIGN_SRCS)

View File

@ -11,7 +11,7 @@
// 2) Never cross a 4KByte address boundary within a single transaction, this is an AXI4 rule.
// 3) 2^SIZE must be greater than 4KB so that the 4KByte page protection also deals with FIFO wrap corner case.
//
module axi_dma_fifo
module axi_dma_fifo
#(
parameter SIMULATION = 0, // Shorten flush counter for simulation
parameter DEFAULT_BASE = 30'h00000000,
@ -23,7 +23,7 @@ module axi_dma_fifo
parameter MAX_PKT_LEN = 12 // Log2 of maximum packet length
) (
input bus_clk,
input bus_reset,
input bus_reset,
input dram_clk,
input dram_reset,
//
@ -32,7 +32,7 @@ module axi_dma_fifo
output [0 : 0] m_axi_awid, // Write address ID. This signal is the identification tag for the write address signals
output [31 : 0] m_axi_awaddr, // Write address. The write address gives the address of the first transfer in a write burst
output [7 : 0] m_axi_awlen, // Burst length. The burst length gives the exact number of transfers in a burst.
output [2 : 0] m_axi_awsize, // Burst size. This signal indicates the size of each transfer in the burst.
output [2 : 0] m_axi_awsize, // Burst size. This signal indicates the size of each transfer in the burst.
output [1 : 0] m_axi_awburst, // Burst type. The burst type and the size information, determine how the address is calculated
output [0 : 0] m_axi_awlock, // Lock type. Provides additional information about the atomic characteristics of the transfer.
output [3 : 0] m_axi_awcache, // Memory type. This signal indicates how transactions are required to progress
@ -49,12 +49,12 @@ module axi_dma_fifo
output [7 : 0] m_axi_wstrb, // Write strobes. This signal indicates which byte lanes hold valid data.
output m_axi_wlast, // Write last. This signal indicates the last transfer in a write burst
output [0 : 0] m_axi_wuser, // User signal. Optional User-defined signal in the write data channel.
output m_axi_wvalid, // Write valid. This signal indicates that valid write data and strobes are available.
output m_axi_wvalid, // Write valid. This signal indicates that valid write data and strobes are available.
input m_axi_wready, // Write ready. This signal indicates that the slave can accept the write data.
//
// AXI Write response channel signals
//
input [0 : 0] m_axi_bid, // Response ID tag. This signal is the ID tag of the write response.
input [0 : 0] m_axi_bid, // Response ID tag. This signal is the ID tag of the write response.
input [1 : 0] m_axi_bresp, // Write response. This signal indicates the status of the write transaction.
input [0 : 0] m_axi_buser, // User signal. Optional User-defined signal in the write response channel.
input m_axi_bvalid, // Write response valid. This signal indicates that the channel is signaling a valid response
@ -68,7 +68,7 @@ module axi_dma_fifo
output [2 : 0] m_axi_arsize, // Burst size. This signal indicates the size of each transfer in the burst.
output [1 : 0] m_axi_arburst, // Burst type. The burst type and the size information determine how the address for each transfer
output [0 : 0] m_axi_arlock, // Lock type. This signal provides additional information about the atomic characteristics
output [3 : 0] m_axi_arcache, // Memory type. This signal indicates how transactions are required to progress
output [3 : 0] m_axi_arcache, // Memory type. This signal indicates how transactions are required to progress
output [2 : 0] m_axi_arprot, // Protection type. This signal indicates the privilege and security level of the transaction
output [3 : 0] m_axi_arqos, // Quality of Service, QoS. QoS identifier sent for each read transaction.
output [3 : 0] m_axi_arregion, // Region identifier. Permits a single physical interface on a slave to be re-used
@ -83,7 +83,7 @@ module axi_dma_fifo
input [1 : 0] m_axi_rresp, // Read response. This signal indicates the status of the read transfer
input m_axi_rlast, // Read last. This signal indicates the last transfer in a read burst.
input [0 : 0] m_axi_ruser, // User signal. Optional User-defined signal in the read data channel.
input m_axi_rvalid, // Read valid. This signal indicates that the channel is signaling the required read data.
input m_axi_rvalid, // Read valid. This signal indicates that the channel is signaling the required read data.
output m_axi_rready, // Read ready. This signal indicates that the master can accept the read data and response
//
// CHDR friendly AXI stream input
@ -212,14 +212,14 @@ module axi_dma_fifo
wire [(72-AWIDTH-29-1):0] set_sync_discard0;
wire [(72-(2*AWIDTH)-1):0] set_sync_discard1;
fifo_short_2clk set_sync_fifo0(
fifo_xpm_2clk #(.WIDTH(72), .DEPTH(32), .TYPE("distributed")) set_sync_fifo0(
.rst(bus_reset),
.wr_clk(bus_clk), .din({{(72-AWIDTH-29){1'b0}}, timeout_bclk, supress_enable_bclk, supress_threshold_bclk, fifo_base_addr_bclk}),
.wr_en(1'b1), .full(), .wr_data_count(),
.rd_clk(dram_clk), .dout({set_sync_discard0, set_timeout, set_suppress_en, set_supress_threshold, set_fifo_base_addr}),
.rd_en(1'b1), .empty(), .rd_data_count()
);
fifo_short_2clk set_sync_fifo1(
fifo_xpm_2clk #(.WIDTH(72), .DEPTH(32), .TYPE("distributed")) set_sync_fifo1(
.rst(bus_reset),
.wr_clk(bus_clk), .din({{(72-(2*AWIDTH)){1'b0}}, ~fifo_addr_mask_bclk, fifo_addr_mask_bclk}),
.wr_en(1'b1), .full(), .wr_data_count(),
@ -233,7 +233,7 @@ module axi_dma_fifo
localparam [2:0] INPUT_IDLE = 0;
localparam [2:0] INPUT1 = 1;
localparam [2:0] INPUT2 = 2;
localparam [2:0] INPUT3 = 3;
localparam [2:0] INPUT3 = 3;
localparam [2:0] INPUT4 = 4;
localparam [2:0] INPUT5 = 5;
localparam [2:0] INPUT6 = 6;
@ -255,7 +255,7 @@ module axi_dma_fifo
localparam [2:0] OUTPUT_IDLE = 0;
localparam [2:0] OUTPUT1 = 1;
localparam [2:0] OUTPUT2 = 2;
localparam [2:0] OUTPUT3 = 3;
localparam [2:0] OUTPUT3 = 3;
localparam [2:0] OUTPUT4 = 4;
localparam [2:0] OUTPUT5 = 5;
localparam [2:0] OUTPUT6 = 6;
@ -267,17 +267,17 @@ module axi_dma_fifo
reg [AWIDTH-1:0] read_addr;
reg read_ctrl_valid;
wire read_ctrl_ready;
reg [7:0] read_count = 8'd0;
reg [7:0] read_count = 8'd0;
reg [8:0] read_count_plus_one = 9'd1; // Maintain a +1 version to break critical timing paths
reg update_read;
// Track main FIFO active size.
reg [AWIDTH-3:0] space, occupied, occupied_minus_one; // Maintain a -1 version to break critical timing paths
reg [AWIDTH-3:0] input_page_boundry, output_page_boundry; // Cache in a register to break critical timing paths
// Assign FIFO status bits
wire [71:0] status_out_bclk;
fifo_short_2clk status_fifo_2clk(
fifo_xpm_2clk #(.WIDTH(72), .DEPTH(32), .TYPE("distributed")) status_fifo_2clk(
.rst(dram_reset),
.wr_clk(dram_clk), .din({{(72-(AWIDTH-2)){1'b0}}, occupied}),
.wr_en(1'b1), .full(), .wr_data_count(),
@ -323,10 +323,10 @@ module axi_dma_fifo
wire bist_running, bist_done;
wire [1:0] bist_error;
axi_chdr_test_pattern #(
.DELAY_MODE(EXT_BIST ? "DYNAMIC" : "STATIC"),
.SID_MODE(EXT_BIST ? "DYNAMIC" : "STATIC"),
.DELAY_MODE(EXT_BIST ? "DYNAMIC" : "STATIC"),
.SID_MODE(EXT_BIST ? "DYNAMIC" : "STATIC"),
.BW_COUNTER(EXT_BIST ? 1 : 0),
.SR_BASE(SR_BASE + 4)
) axi_chdr_test_pattern_i (
@ -381,7 +381,7 @@ module axi_dma_fifo
wire [DWIDTH-1:0] i_tdata_i0;
wire i_tvalid_i0, i_tready_i0, i_tlast_i0;
wire [DWIDTH-1:0] i_tdata_i1;
wire i_tvalid_i1, i_tready_i1, i_tlast_i1;
@ -399,15 +399,15 @@ module axi_dma_fifo
///////////////////////////////////////////////////////////////////////////////
wire write_in, read_in, empty_in, full_in;
assign i_tready_fifo = ~full_in;
assign write_in = i_tvalid_fifo & i_tready_fifo;
assign i_tvalid_i0 = ~empty_in;
assign read_in = i_tvalid_i0 & i_tready_i0;
wire [6:0] discard_i0;
fifo_short_2clk fifo_short_2clk_i0 (
fifo_xpm_2clk #(.WIDTH(72), .DEPTH(32), .TYPE("distributed")) fifo_short_2clk_i0 (
.rst(bus_reset),
.wr_clk(bus_clk),
.din({7'h0,i_tlast_fifo,i_tdata_fifo}), // input [71 : 0] din
@ -424,16 +424,16 @@ module axi_dma_fifo
axi_fifo_flop2 #(.WIDTH(DWIDTH+1)) input_pipe_i0
(
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata({i_tlast_i0, i_tdata_i0}),
.i_tvalid(i_tvalid_i0),
.i_tdata({i_tlast_i0, i_tdata_i0}),
.i_tvalid(i_tvalid_i0),
.i_tready(i_tready_i0),
//
.o_tdata({i_tlast_i1, i_tdata_i1}),
.o_tvalid(i_tvalid_i1),
.o_tdata({i_tlast_i1, i_tdata_i1}),
.o_tvalid(i_tvalid_i1),
.o_tready(i_tready_i1)
);
@ -453,33 +453,33 @@ module axi_dma_fifo
);
axi_fifo_flop2 #(.WIDTH(DWIDTH)) input_pipe_i1 (
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata(i_tdata_i2),
.i_tvalid(i_tvalid_i2),
.i_tdata(i_tdata_i2),
.i_tvalid(i_tvalid_i2),
.i_tready(i_tready_i2),
//
.o_tdata(i_tdata_i3),
.o_tvalid(i_tvalid_i3),
.o_tdata(i_tdata_i3),
.o_tvalid(i_tvalid_i3),
.o_tready(i_tready_i3)
);
axi_fifo #(.WIDTH(DWIDTH),.SIZE(10)) fifo_i1 (
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata(i_tdata_i3),
.i_tvalid(i_tvalid_i3),
.i_tdata(i_tdata_i3),
.i_tvalid(i_tvalid_i3),
.i_tready(i_tready_i3),
//
.o_tdata(i_tdata_input),
.o_tvalid(i_tvalid_input),
.o_tdata(i_tdata_input),
.o_tvalid(i_tvalid_input),
.o_tready(i_tready_input),
//
.space(space_input),
.space(space_input),
.occupied(occupied_input)
);
@ -488,12 +488,12 @@ module axi_dma_fifo
// passing upstream of the DRAM FIFO.
// In this situation supress read requests to the DRAM FIFO so that more bandwidth is available to writes.
//
always @(posedge dram_clk)
always @(posedge dram_clk)
begin
space_input_reg <= space_input;
if ((space_input_reg < set_supress_threshold[15:0]) && set_suppress_en)
supress_reads <= 1'b1;
else
else
supress_reads <= 1'b0;
end
@ -506,16 +506,16 @@ module axi_dma_fifo
wire [DWIDTH-1:0] o_tdata_i0;
wire o_tvalid_i0, o_tready_i0;
wire [DWIDTH-1:0] o_tdata_i1;
wire o_tvalid_i1, o_tready_i1;
wire [DWIDTH-1:0] o_tdata_i2;
wire o_tvalid_i2, o_tready_i2;
wire [DWIDTH-1:0] o_tdata_i3;
wire o_tvalid_i3, o_tready_i3;
wire [DWIDTH-1:0] o_tdata_i4;
wire o_tvalid_i4, o_tready_i4, o_tlast_i4;
@ -525,69 +525,69 @@ module axi_dma_fifo
wire checksum_error;
axi_fifo #(.WIDTH(DWIDTH),.SIZE(10)) fifo_i2 (
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata(o_tdata_output),
.i_tvalid(o_tvalid_output),
.i_tdata(o_tdata_output),
.i_tvalid(o_tvalid_output),
.i_tready(o_tready_output),
//
.o_tdata(o_tdata_i0),
.o_tvalid(o_tvalid_i0),
.o_tdata(o_tdata_i0),
.o_tvalid(o_tvalid_i0),
.o_tready(o_tready_i0),
//
.space(space_output),
.space(space_output),
.occupied(occupied_output)
);
// Place FLops straight after SRAM read access for timing.
axi_fifo_flop2 #(.WIDTH(DWIDTH)) output_pipe_i0
(
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata(o_tdata_i0),
.i_tvalid(o_tvalid_i0),
.i_tdata(o_tdata_i0),
.i_tvalid(o_tvalid_i0),
.i_tready(o_tready_i0),
//
.o_tdata(o_tdata_i1),
.o_tvalid(o_tvalid_i1),
.o_tdata(o_tdata_i1),
.o_tvalid(o_tvalid_i1),
.o_tready(o_tready_i1 && ~supress_reads)
);
// Read suppression logic
// The CL part of this exists between these
// axi_flops
// axi_flops
axi_fifo_flop2 #(.WIDTH(DWIDTH)) output_pipe_i1
(
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata(o_tdata_i1),
.i_tvalid(o_tvalid_i1 && ~supress_reads),
.i_tdata(o_tdata_i1),
.i_tvalid(o_tvalid_i1 && ~supress_reads),
.i_tready(o_tready_i1),
//
.o_tdata(o_tdata_i2),
.o_tvalid(o_tvalid_i2),
.o_tdata(o_tdata_i2),
.o_tvalid(o_tvalid_i2),
.o_tready(o_tready_i2)
);
// Pipeline flop before tlast extraction logic
axi_fifo_flop2 #(.WIDTH(DWIDTH)) output_pipe_i2
(
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata(o_tdata_i2),
.i_tvalid(o_tvalid_i2),
.i_tdata(o_tdata_i2),
.i_tvalid(o_tvalid_i2),
.i_tready(o_tready_i2),
//
.o_tdata(o_tdata_i3),
.o_tvalid(o_tvalid_i3),
.o_tdata(o_tdata_i3),
.o_tvalid(o_tvalid_i3),
.o_tready(o_tready_i3)
);
@ -611,16 +611,16 @@ module axi_dma_fifo
// Pipeline flop after tlast extraction logic
axi_fifo_flop2 #(.WIDTH(DWIDTH+1)) output_pipe_i3
(
.clk(dram_clk),
.reset(dram_reset),
.clk(dram_clk),
.reset(dram_reset),
.clear(clear),
//
.i_tdata({o_tlast_i4,o_tdata_i4}),
.i_tvalid(o_tvalid_i4),
.i_tdata({o_tlast_i4,o_tdata_i4}),
.i_tvalid(o_tvalid_i4),
.i_tready(o_tready_i4),
//
.o_tdata({o_tlast_i5,o_tdata_i5}),
.o_tvalid(o_tvalid_i5),
.o_tdata({o_tlast_i5,o_tdata_i5}),
.o_tvalid(o_tvalid_i5),
.o_tready(o_tready_i5)
);
@ -630,8 +630,8 @@ module axi_dma_fifo
assign o_tvalid_fifo = ~empty_out;
assign read_out = o_tvalid_fifo & o_tready_fifo;
wire [6:0] discard_i1;
fifo_short_2clk fifo_short_2clk_i1 (
fifo_xpm_2clk #(.WIDTH(72), .DEPTH(32), .TYPE("distributed")) fifo_short_2clk_i1 (
.rst(dram_reset),
.wr_clk(dram_clk),
.din({7'h0,o_tlast_i5,o_tdata_i5}), // input [71 : 0] din
@ -683,7 +683,7 @@ module axi_dma_fifo
//
// INPUT_IDLE.
// To start an input transfer to DRAM need:
// 1) Space in the DRAM FIFO
// 1) Space in the DRAM FIFO
// and either
// 2) 256 entrys in the input FIFO
// or
@ -698,13 +698,13 @@ module axi_dma_fifo
input_timeout_reset <= 1'b1;
// Calculate number of entries remaining until next 4KB page boundry is crossed minus 1.
// Note, units of calculation are 64bit wide words. Address is always 64bit alligned.
input_page_boundry <= {write_addr[AWIDTH-1:12],9'h1ff} - write_addr[AWIDTH-1:3];
input_page_boundry <= {write_addr[AWIDTH-1:12],9'h1ff} - write_addr[AWIDTH-1:3];
end else if (input_timeout_triggered) begin // input FIFO timeout waiting for new data.
input_state <= INPUT2;
input_timeout_reset <= 1'b1;
// Calculate number of entries remaining until next 4KB page boundry is crossed minus 1.
// Note, units of calculation are 64bit wide words. Address is always 64bit alligned.
input_page_boundry <= {write_addr[AWIDTH-1:12],9'h1ff} - write_addr[AWIDTH-1:3];
input_page_boundry <= {write_addr[AWIDTH-1:12],9'h1ff} - write_addr[AWIDTH-1:3];
end else begin
input_timeout_reset <= 1'b0;
input_state <= INPUT_IDLE;
@ -771,14 +771,14 @@ module axi_dma_fifo
input_state <= INPUT5; // Move on
else
input_state <= INPUT4; // Wait for deassert
end
end
//
// INPUT5.
// Transaction has been accepted by AXI4 DMA engine. Now we wait for the re-assertion
// of write_ctrl_ready which signals that the AXI4 DMA engine has receieved a response
// for the whole write transaction and we assume that this means it is commited to DRAM.
// We are now free to update write_addr pointer and go back to idle state.
//
//
INPUT5: begin
write_ctrl_valid <= 1'b0;
if (write_ctrl_ready) begin
@ -798,7 +798,7 @@ module axi_dma_fifo
update_write <= 1'b0;
end
default:
default:
input_state <= INPUT_IDLE;
endcase // case(input_state)
@ -840,7 +840,7 @@ module axi_dma_fifo
//
// OUTPUT_IDLE.
// To start an output tranfer from DRAM
// 1) Space in the small output FIFO
// 1) Space in the small output FIFO
// and either
// 2) 256 entrys in the DRAM FIFO
// or
@ -928,14 +928,14 @@ module axi_dma_fifo
output_state <= OUTPUT5; // Move on
else
output_state <= OUTPUT4; // Wait for deassert
end
end
//
// OUTPUT5.
// Transaction has been accepted by AXI4 DMA engine. Now we wait for the re-assertion
// of read_ctrl_ready which signals that the AXI4 DMA engine has receieved a last signal and good response
// for the whole read transaction.
// We are now free to update read_addr pointer and go back to idle state.
//
//
OUTPUT5: begin
read_ctrl_valid <= 1'b0;
if (read_ctrl_ready) begin
@ -955,7 +955,7 @@ module axi_dma_fifo
output_state <= OUTPUT_IDLE;
end
default:
default:
output_state <= OUTPUT_IDLE;
endcase // case(output_state)

View File

@ -18,17 +18,22 @@
//
// Parameters:
//
// DATA_W : The bit width of tdata for all ports.
// NUM_PORTS : The number of output ports on which to duplicate the input.
// INPUT_REG : Set to 1 to add an input register stage to break combinatorial
// paths. Set to 0 to allow combinatorial input paths.
// DATA_W : The bit width of tdata for all ports.
// NUM_PORTS : The number of output ports on which to duplicate the
// input.
// INPUT_REG : Set to 1 to add an input register stage to break
// combinatorial paths. Set to 0 to allow combinatorial input
// paths.
// OUT_FIFO_SIZE : Configures the per-port output FIFO size. Defaults to a
// short (32-deep) FIFO.
//
module axis_split #(
parameter DATA_W = 32,
parameter NUM_PORTS = 4,
parameter INPUT_REG = 0
parameter DATA_W = 32,
parameter NUM_PORTS = 4,
parameter INPUT_REG = 0,
parameter OUT_FIFO_SIZE = 5
) (
input wire clk,
input wire rst,
@ -106,11 +111,20 @@ module axis_split #(
genvar i;
for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_ports
// We use axi_fifo_short specifically because it can tolerate tvalid
// de-asserting at any time. This is normally not allowed by AXI-Stream.
axi_fifo_short #(
.WIDTH (DATA_W)
) axi_fifo_short_i (
// We add a FIFO on each output port because our axi_fifo tolerates tvalid
// de-asserting at any time, which is normally not allowed by AXI-Stream.
// This may occur here because the output streams are independent and we
// block all transfers when any one output stalls. To ensure we have this
// protection built in, we do not allow bypass mode.
if (OUT_FIFO_SIZE < 0) begin : gen_bypass_mode
OUT_FIFO_SIZE_must_be_non_negative();
end
axi_fifo #(
.WIDTH (DATA_W),
.SIZE (OUT_FIFO_SIZE)
) axi_fifo_i (
.clk (clk),
.reset (rst),
.clear (1'b0),

View File

@ -4,7 +4,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_width_conv
// Description:
// Description:
// An AXI-Stream width conversion module that can convert from
// an arbitrary input width to an arbitrary output width. The
// module also supports an optional clock crossing. Data bits
@ -17,7 +17,7 @@
// the AXI standard where the bits are "byte qualifiers". In
// this module, tkeep is a "word qualifier" where the width
// of a word can be arbitrary. If WORD_W = 8, the behavior
// of this module is identical to an AXI width converter.
// of this module is identical to an AXI width converter.
//
// Parameters:
// - WORD_W: Bitwidth of a word
@ -25,6 +25,8 @@
// - OUT_WORDS: Number of words in the output stream
// - SYNC_CLKS: Are s_axis_aclk and m_axis_aclk synchronous to each other?
// - PIPELINE: Which ports to pipeline? {NONE, IN, OUT, INOUT}
// - FIFO_SIZE: The size of the FIFO given as log2 of the number of
// WORD_W-bit words.
//
// Signals:
// - s_axis_* : Input sample stream (AXI-Stream)
@ -35,9 +37,10 @@ module axis_width_conv #(
parameter IN_WORDS = 4,
parameter OUT_WORDS = 6,
parameter SYNC_CLKS = 0,
parameter PIPELINE = "NONE"
parameter PIPELINE = "NONE",
parameter FIFO_SIZE = 1
)(
// Data In (AXI-Stream)
// Data In (AXI-Stream)
input wire s_axis_aclk, // Input stream Clock
input wire s_axis_rst, // Input stream Reset
input wire [(IN_WORDS*WORD_W)-1:0] s_axis_tdata, // Input stream tdata
@ -45,7 +48,7 @@ module axis_width_conv #(
input wire s_axis_tlast, // Input stream tlast
input wire s_axis_tvalid, // Input stream tvalid
output wire s_axis_tready, // Input stream tready
// Data Out (AXI-Stream)
// Data Out (AXI-Stream)
input wire m_axis_aclk, // Output stream Clock
input wire m_axis_rst, // Output stream Reset
output wire [(OUT_WORDS*WORD_W)-1:0] m_axis_tdata, // Output stream tdata
@ -118,13 +121,13 @@ module axis_width_conv #(
y = b;
while (!done) begin
if (x < y) begin
swap = x;
x = y;
y = swap;
swap = x;
x = y;
y = swap;
end else if (y != 0) begin
x = x - y;
end else begin
done = 1'b1;
done = 1'b1;
end
end
// x is the greatest common divisor
@ -148,11 +151,10 @@ module axis_width_conv #(
// - The input and output clocks are the same
// - The upsizer is effectively a passthrough and input registering is requested
// - The downsizer is effectively a passthrough and output registering is requested
localparam [0:0] SKIP_FIFO = (SYNC_CLKS == 1) && (
localparam [0:0] SKIP_FIFO = (SYNC_CLKS == 1) && (FIFO_SIZE <= 1) && (
((PIPELINE == "IN" || PIPELINE == "INOUT") && (UPSIZE_RATIO == 1)) ||
((PIPELINE == "OUT" || PIPELINE == "INOUT") && (DOWNSIZE_RATIO == 1))
);
localparam FIFO_SIZE = 1;
//----------------------------------------------
// In => Upsizer => FIFO => Downsizer => Out
@ -190,8 +192,14 @@ module axis_width_conv #(
assign fifo_o_tvalid = fifo_i_tvalid;
assign fifo_i_tready = fifo_o_tready;
end else begin
// calculate the actual FIFO size as the width might be different than// WORD_W
localparam integer FIFO_ELEMENTS = $ceil((2.0 ** FIFO_SIZE) / INT_KEEP_W);
localparam integer FIFO_ELEMENTS_MIN = FIFO_ELEMENTS > 0 ? FIFO_ELEMENTS : 1;
localparam integer REQUESTED_FIFO_SIZE = $clog2(FIFO_ELEMENTS_MIN);
localparam integer ADJUSTED_FIFO_SIZE = REQUESTED_FIFO_SIZE > 0 ? REQUESTED_FIFO_SIZE : 1;
if (SYNC_CLKS) begin
axi_fifo #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(FIFO_SIZE)) fifo_i (
axi_fifo #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(ADJUSTED_FIFO_SIZE)) fifo_i (
.clk(s_axis_aclk), .reset(s_axis_rst), .clear(1'b0),
.i_tdata({fifo_i_tlast, fifo_i_tkeep, fifo_i_tdata}),
.i_tvalid(fifo_i_tvalid), .i_tready(fifo_i_tready),
@ -200,7 +208,7 @@ module axis_width_conv #(
.space(), .occupied()
);
end else begin
axi_fifo_2clk #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(FIFO_SIZE)) fifo_i (
axi_fifo_2clk #(.WIDTH(INT_DATA_W+INT_KEEP_W+1), .SIZE(ADJUSTED_FIFO_SIZE)) fifo_i (
.reset(s_axis_rst),
.i_aclk(s_axis_aclk),
.i_tdata({fifo_i_tlast, fifo_i_tkeep, fifo_i_tdata}),

View File

@ -18,9 +18,15 @@ axi_forwarding_cam.v \
axi_setting_reg.v \
axi_slave_mux.v \
axi_test_vfifo.v \
axis_to_ctrlport.sv \
ctrlport_to_axis.sv \
axis_spi.sv \
bin2gray.v \
binary_encoder.v \
clock_div.v \
../rfnoc/core/ctrlport_pkg.sv \
ctrlport_spi_adrv_pkg.sv \
ctrlport_spi_adrv.sv \
db_control.v \
fe_control.v \
filter_bad_sid.v \
@ -64,7 +70,12 @@ map/axis_muxed_kv_map.v \
axil_ctrlport_master.v\
handshake.v\
handshake_latch.sv\
clocking_watchdog.sv\
ctrlport_to_regport.v \
ctrlport_to_wb_i2c.v \
ctrlport_to_wb_uart.v \
ctrlport_to_simple_spi.sv \
ctrlport_to_jtag.v \
glitch_free_mux.v \
regport_if.sv \
))

View File

@ -0,0 +1,380 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_spi
//
// Description:
//
// SPI master (CPOL=0, CPHA=0) with AXI-Stream TX and RX interfaces. Each
// byte presented on the input stream is shifted out MSB-first on MOSI while
// MISO bits are simultaneously assembled into bytes and presented on the
// output stream (full-duplex).
//
// CS_N is asserted (driven low) and held low for the duration of the
// AXI-Stream packet. Between words, CS_N stays asserted and SCLK simply
// pauses if the next byte in the packet is not yet available (s_axis_tvalid
// == 0) or if there's back-pressure on the output (m_axis_tready == 0).
//
// After the last SCLK falling edge, CS_N deassertion timing is controlled by
// CS_HOLD and CS_GUARD:
//
// CS_HOLD=0 : CS_N deasserts on the same clock edge as the last SCLK
// falling edge.
// CS_HOLD=1 : CS_N stays low for one additional half-period before being
// deasserted.
//
// After CS_N deasserts, CS_GUARD determines the number of half-period
// cycles before the next CS_N assertion for the next packet:
//
// CS_GUARD=1 : One half SCLK period before next CS_N assertion.
// CS_GUARD=2 : One full SCLK period before next CS_N assertion.
//
// SPI Clock Rate:
//
// The SPI clock rate is controlled by setting the desired number of clock
// cycles per half SPI clock period, minus 1. In other words, the actual
// SPI clock frequency for a given clock frequency will be:
//
// f_sclk = f_clk / (2 * (half_per + 1))
//
// Or, the required half period setting to approximate given SPI clock
// frequency can be calculated as:
//
// half_per = round((f_clk / (2 * f_sclk)) - 1)
//
// Parameters:
//
// HALF_PER_W : Width of the half-period input port in bits. This determines
// the slowest SPI clock rate that can be achieved.
// CS_HOLD : Number of half SCLK periods to hold CS_N low after last
// falling edge of SCLK. Can be 0 or 1.
// CS_GUARD : Number of half SCLK periods to hold CS_N between
// transactions. Can be 1 or 2.
//
// Signals:
//
// clk / rst : Clock and active-high synchronous reset.
// half_per : Half-period value; compared against an internal counter
// each cycle. SCLK toggles every (half_per + 1) clock cycles.
// Minimum value is 0, which yields the fastest SCLK rate of
// clk/2. This port must remain stable for the duration of any
// active transaction; it is expected to be driven by a static
// constant or an external register that is only updated while
// the core is idle.
// s_axis_* : AXI-Stream slave (TX data in). Each byte is serialized
// MSB-first.
// m_axis_* : AXI-Stream master (RX data out). Each byte is serialized
// MSB-first.
// sclk : SPI clock output
// cs_n : Active-low chip select
// mosi : Master-out / slave-in
// miso : Master-in / slave-out input
//
`default_nettype none
module axis_spi #(
int HALF_PER_W = 8,
bit CS_HOLD = 1,
int CS_GUARD = 2,
localparam int DATA_W = 8
) (
input wire clk,
input wire rst,
// Half-period control
input wire [HALF_PER_W-1:0] half_per,
// AXI-Stream Slave (TX data in)
input wire [DATA_W-1:0] s_axis_tdata,
input wire s_axis_tvalid,
output logic s_axis_tready,
input wire s_axis_tlast,
// AXI-Stream Master (RX data out)
output logic [DATA_W-1:0] m_axis_tdata,
output logic m_axis_tvalid,
input wire m_axis_tready,
output logic m_axis_tlast,
// SPI Interface
output logic sclk = 1'b0,
output logic cs_n = 1'b1,
output logic mosi = 1'b0,
input wire miso
);
//---------------------------------------------------------------------------
// Parameter Checks
//---------------------------------------------------------------------------
if (CS_HOLD < 0 || CS_HOLD > 1) begin : gen_hold_check
$error("axis_spi: CS_HOLD must be 0 or 1 (setting is %0d)", CS_HOLD);
end
if (CS_GUARD < 1 || CS_GUARD > 2) begin : gen_guard_check
$error("axis_spi: CS_GUARD must be 1 or 2 (setting is %0d)", CS_GUARD);
end
//---------------------------------------------------------------------------
// Local Parameters
//---------------------------------------------------------------------------
// Enough bits to count 0 .. DATA_W-1
localparam int BIT_CNT_W = $clog2(DATA_W);
//---------------------------------------------------------------------------
// State Machine Type
//---------------------------------------------------------------------------
typedef enum logic [2:0] {
ST_IDLE,
ST_SHIFT_A,
ST_SHIFT_B,
ST_HOLD,
ST_GUARD1,
ST_GUARD2
} state_t;
state_t state;
//---------------------------------------------------------------------------
// Internal Signals
//---------------------------------------------------------------------------
// Half-period counter. Done asserts when it reaches half_per.
logic [HALF_PER_W-1:0] half_per_cnt;
logic half_per_done;
// Bit position counter (0 to DATA_W-1)
logic [BIT_CNT_W-1:0] bit_cnt;
// TX / RX shift registers
logic [DATA_W-1:0] mosi_sr;
logic [DATA_W-1:0] miso_sr;
// tlast latched to indicate the end of the SPI transaction
logic tlast_reg;
// True when CS_N is currently asserted (mid-packet)
logic in_packet;
// One-entry registered output buffer
logic [DATA_W-1:0] out_data;
logic out_valid;
logic out_last;
// True when the output buffer is free this cycle (empty or being drained)
logic out_buf_free;
//---------------------------------------------------------------------------
// Combinational Assignments
//---------------------------------------------------------------------------
assign half_per_done = (half_per_cnt == half_per);
assign out_buf_free = !out_valid || m_axis_tready;
// AXI-Stream output driven from the registered output buffer
assign m_axis_tdata = out_data;
assign m_axis_tvalid = out_valid;
assign m_axis_tlast = out_last;
// Accept input when idle and the output buffer has room. In ST_IDLE the SPI
// bus is paused (SCLK stopped), so CS_N stays asserted while we wait for
// the next word when in_packet=1.
assign s_axis_tready = (state == ST_IDLE) && out_buf_free;
//---------------------------------------------------------------------------
// Half-Period Counter
//---------------------------------------------------------------------------
//
// Counts up every cycle while a SPI transaction is occurring. Resets to zero
// the same cycle half_per_done fires so each half-period is exactly
// (half_per + 1) clock cycles.
//
//---------------------------------------------------------------------------
always_ff @(posedge clk) begin
if (rst) begin
half_per_cnt <= '0;
end else begin
if (state == ST_IDLE) begin
half_per_cnt <= '0;
end else begin
if (half_per_done) begin
half_per_cnt <= '0;
end else begin
half_per_cnt <= half_per_cnt + 1'b1;
end
end
end
end
//---------------------------------------------------------------------------
// State Machine
//---------------------------------------------------------------------------
always_ff @(posedge clk) begin
if (rst) begin
state <= ST_IDLE;
sclk <= 1'b0;
cs_n <= 1'b1;
mosi <= 1'b0;
mosi_sr <= 'X;
miso_sr <= 'X;
bit_cnt <= 'X;
tlast_reg <= 1'b0;
in_packet <= 1'b0;
out_data <= 'X;
out_valid <= 1'b0;
out_last <= 'X;
end else begin
//-----------------------------------------------------------------------
// Output buffer drain
//-----------------------------------------------------------------------
if (m_axis_tready && out_valid) begin
out_valid <= 1'b0;
end
unique case (state)
//---------------------------------------------------------------------
// IDLE: SCLK stopped. CS_N follows in_packet (stays asserted between
// bytes of a packet, deasserted before/after).
//
// On input handshake: latch word and tlast, pre-drive MOSI with the
// MSB, assert CS_N, and enter the shift loop.
//---------------------------------------------------------------------
ST_IDLE: begin
sclk <= 1'b0;
cs_n <= !in_packet;
bit_cnt <= '0;
if (s_axis_tvalid && out_buf_free) begin
mosi_sr <= s_axis_tdata;
tlast_reg <= s_axis_tlast;
// Assert CS_N and pre-drive MSB onto MOSI before the first
// SCLK rising edge (CPHA=0 requirement).
cs_n <= 1'b0;
mosi <= s_axis_tdata[DATA_W-1];
in_packet <= 1'b1;
state <= ST_SHIFT_A;
end
end
//---------------------------------------------------------------------
// SHIFT_A: First (low) half of the current SCLK bit period.
//
// SCLK rises at the end of this half, MISO is sampled on the rising
// edge.
//---------------------------------------------------------------------
ST_SHIFT_A: begin
if (half_per_done) begin
sclk <= 1'b1;
// Sample MISO into the LSB of the receive shift register
miso_sr <= {miso_sr[DATA_W-2:0], miso};
state <= ST_SHIFT_B;
end
end
//---------------------------------------------------------------------
// SHIFT_B: Second (high) half of the current SCLK bit period.
//
// SCLK falls at the end of this half, MOSI advances to the next bit on
// the falling edge.
//
// On the last bit, push the assembled received byte to the output
// buffer and choose the next state.
//---------------------------------------------------------------------
ST_SHIFT_B: begin
if (half_per_done) begin
sclk <= 1'b0;
bit_cnt <= bit_cnt + 1;
// Advance MOSI shift register and drive the next bit
mosi_sr <= {mosi_sr[DATA_W-2:0], 1'b0};
mosi <= mosi_sr[DATA_W-2];
if (bit_cnt == DATA_W-1) begin
// Last bit don. Push received word to output buffer. out_valid
// guaranteed 0 here (see block header).
out_data <= miso_sr;
out_valid <= 1'b1;
out_last <= tlast_reg;
bit_cnt <= '0;
if (tlast_reg) begin
// End of packet. Update CS_N based on CS_HOLD.
in_packet <= 1'b0;
if (CS_HOLD) begin
state <= ST_HOLD;
end else begin
cs_n <= 1'b1;
state <= ST_GUARD1;
end
end else begin
// More words to come. Return to IDLE to accept the next word.
// CS_N stays asserted while we wait.
state <= ST_IDLE;
end
end else begin
state <= ST_SHIFT_A;
end
end
end
//---------------------------------------------------------------------
// HOLD: Hold CS_N asserted for one extra half-period after the last
// SCLK falling edge (only entered if CS_HOLD=1), then deassert CS_N
// and proceed to the guard entry state.
//---------------------------------------------------------------------
ST_HOLD: begin
if (half_per_done) begin
cs_n <= 1'b1;
state <= ST_GUARD1;
end
end
//---------------------------------------------------------------------
// GUARD1/GUARD2: Hold CS_N deasserted for CS_GUARD half-periods after
// the end of the transaction.
//---------------------------------------------------------------------
ST_GUARD1: begin
if (half_per_done) begin
state <= (CS_GUARD > 1) ? ST_GUARD2 : ST_IDLE;
end
end
ST_GUARD2: begin
if (half_per_done) begin
state <= ST_IDLE;
end
end
default: begin
state <= ST_IDLE;
end
endcase
end
end
endmodule : axis_spi
`default_nettype wire

View File

@ -0,0 +1,135 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_to_ctrlport
//
// Description:
//
// Converts an AXI-Stream bus into a readable CtrlPort interface. Read
// requests get responded to as soon as data becomes available on the
// AXI-Stream input.
//
// Parameters:
//
// WIDTH : Width of the data path in bits
// FIFO_SIZE_LOG2 : Log base 2 of the depth of the input AXI-Stream FIFO.
// This FIFO is optional. Depth can be set to -1 to remove
// it entirely.
//
// Signals:
//
// clk : Clock
// rst : Active-high synchronous reset
// s_axis_tdata : AXI-Stream data input
// s_axis_tvalid : AXI-Stream valid
// s_axis_tready : AXI-Stream ready
// ctrl_rd : CtrlPort read request strobe (single-cycle pulse)
// ctrl_data : CtrlPort read data output
// ctrl_ack : CtrlPort acknowledgement strobe (single-cycle pulse)
//
`default_nettype none
module axis_to_ctrlport #(
parameter int WIDTH = 32,
parameter int FIFO_SIZE_LOG2 = 0
) (
input wire clk,
input wire rst,
// AXI-Stream slave
input wire [WIDTH-1:0] s_axis_tdata,
input wire s_axis_tvalid,
output wire s_axis_tready,
// CtrlPort read interface
input wire ctrl_rd,
output logic [WIDTH-1:0] ctrl_data,
output logic ctrl_ack
);
//---------------------------------------------------------------------------
// Declarations
//---------------------------------------------------------------------------
typedef enum logic { ST_IDLE, ST_WAIT } state_t;
state_t state;
// Internal stream between the input FIFO and the FSM.
logic [WIDTH-1:0] fifo_o_tdata;
logic fifo_o_tvalid;
logic fifo_o_tready;
//---------------------------------------------------------------------------
// Input FIFO
//---------------------------------------------------------------------------
axi_fifo #(
.WIDTH (WIDTH),
.SIZE (FIFO_SIZE_LOG2)
) input_fifo_i (
.clk (clk),
.reset (rst),
.clear (1'b0),
.i_tdata (s_axis_tdata),
.i_tvalid (s_axis_tvalid),
.i_tready (s_axis_tready),
.o_tdata (fifo_o_tdata),
.o_tvalid (fifo_o_tvalid),
.o_tready (fifo_o_tready),
.space (),
.occupied ()
);
//---------------------------------------------------------------------------
// State Machine
//---------------------------------------------------------------------------
assign ctrl_data = fifo_o_tdata;
assign fifo_o_tready = ctrl_ack;
always_ff @(posedge clk) begin
if (rst) begin
state <= ST_IDLE;
ctrl_ack <= 1'b0;
end else begin
// Default: deassert ACK every cycle.
ctrl_ack <= 1'b0;
case (state)
ST_IDLE: begin
if (ctrl_rd) begin
if (fifo_o_tvalid) begin
// If we have data available, then ACK on next clock cycle.
ctrl_ack <= 1'b1;
end else begin
// If we don't have data yet, then wait until it arrives.
state <= ST_WAIT;
end
end
end
ST_WAIT: begin
// ACK after we get a new data word
if (fifo_o_tvalid) begin
ctrl_ack <= 1'b1;
state <= ST_IDLE;
end
end
default: begin
state <= ST_IDLE;
end
endcase
end
end
endmodule : axis_to_ctrlport
`default_nettype wire

View File

@ -0,0 +1,186 @@
//
// Copyright 2026 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: clocking_watchdog
//
// Description:
// Monitors clocking-health indicators and asserts clock_fault if any
// indicator is out of spec. Two indicators are checked:
//
// 1. Lock status (clock_locked)
// clock_fault is asserted in the monitor_clk domain whenever clock_locked
// is not asserted (after synchronization into monitor_clk). This covers
// PLL unlock events regardless of the actual frequency of watched_clk.
//
// 2. Frequency of watched_clk
// The rate of watched_clk is measured by dividing it down to ~1 kHz
// with clock_div (N = CLK_DIV_1KHZ, chosen to be even), synchronising
// the divided clock into the monitor_clk domain, and counting how many
// monitor_clk cycles pass between two consecutive rising edges of the
// divided clock. A count below (MS_CYCLES_MON_CLK - 1) means the
// watched_clk edge arrived sooner than expected, i.e. watched_clk is
// running faster than MAX_CLOCK_RATE, and clock_fault_mon is asserted.
// The counter saturates at MS_CYCLES_MON_CLK to prevent rollover when
// watched_clk is slower than the maximum rate.
//
// clock_fault_mon (monitor_clk domain) is synchronised back into the
// watched_clk domain before being driven out as clock_fault so that
// downstream consumers can safely sample it on either clock.
//
// Clock domains:
// monitor_clk : measurement and fault evaluation domain
// watched_clk : clock being monitored; only clock_div runs here
//
// Reset:
// rst is synchronous to monitor_clk. For logic in the watched_clk domain
// (clock_div and the clock_fault synchronizer), rst is synchronized into the
// watched_clk domain before use.
//
module clocking_watchdog #(
// Maximum allowed frequency of watched_clk in Hz. A watched_clk rate
// strictly above this value causes clock_fault to be asserted.
parameter int MAX_CLOCK_RATE = 125_000_000,
// Frequency of monitor_clk in Hz. Used to derive the 1 ms measurement
// window (MS_CYCLES_MON_CLK = MONITOR_CLK_FREQ / 1000).
parameter int MONITOR_CLK_FREQ = 100_000_000
) (
// Free-running reference clock used for all measurement logic.
input logic monitor_clk,
// Synchronous reset (monitor_clk domain). Also drives clk_in_rst of
// clock_div so the watched_clk-domain divider is reset as well.
input logic rst,
// PLL/MMCM lock indicator for watched_clk. Active high; when low,
// clock_fault is asserted immediately regardless of the measured rate.
input logic clock_locked,
// Clock whose frequency is being monitored.
input logic watched_clk,
// Asserted (1) when a clocking fault is detected, deasserted (0) when
// clock_locked is high and watched_clk is within [0, MAX_CLOCK_RATE].
// Registered in the watched_clk domain.
output logic clock_fault
);
// Number of monitor_clk cycles in 1 ms; defines the measurement window.
localparam int unsigned MS_CYCLES_MON_CLK = MONITOR_CLK_FREQ / 1000;
// Number of watched_clk cycles in 1 ms at MAX_CLOCK_RATE.
localparam int unsigned MAX_COUNT_PER_MS = MAX_CLOCK_RATE / 1000;
// Worst-case tolerance of each clock source in ppm. The frequency margin
// assumes two independent clocks, so their relative error is 2 * CLOCK_PPM.
localparam int unsigned CLOCK_PPM = 100;
// Divider ratio for clock_div. clock_div requires an even divisor, so
// MAX_COUNT_PER_MS is rounded up to the nearest even integer when needed.
localparam int unsigned CLK_DIV_1KHZ = ((MAX_COUNT_PER_MS % 2) != 0) ?
(MAX_COUNT_PER_MS + 1) : MAX_COUNT_PER_MS;
// Worst-case relative error from two independent CLOCK_PPM clocks over a
// 1 ms measurement window. Ceiling rounds any fractional cycle up into the
// safety margin.
localparam int unsigned FREQ_MARGIN_CYCLES =
int'($ceil(real'(MS_CYCLES_MON_CLK * (2 * CLOCK_PPM)) / 1_000_000.0));
// One extra monitor_clk cycle for synchronizer/edge-detection quantization
// when the divided watched clock is sampled into the monitor_clk domain.
localparam int unsigned SYNC_MARGIN_CYCLES = 1;
// Counts monitor_clk cycles since the last rising edge of the divided
// clock. One extra bit to hold MS_CYCLES_MON_CLK without wrap-around.
logic unsigned [$clog2(MS_CYCLES_MON_CLK+1)-1:0] ms_tick_count;
logic clock_locked_mon; // clock_locked synchronised into monitor_clk domain
logic watched_clk_div1k; // watched_clk divided by CLK_DIV_1KHZ (~1 kHz)
logic watched_clk_div1k_mon; // divided clock synchronised into monitor_clk domain
logic watched_clk_div1k_mon_prev; // one-cycle delay for rising-edge detection
logic clock_fault_mon = 1'b1; // fault flag evaluated in monitor_clk domain
logic rst_watched_clk; // rst synchronized into watched_clk domain
synchronizer #(
.WIDTH(1),
.STAGES(2),
.INITIAL_VAL(0),
.FALSE_PATH_TO_IN(1)
) sync_data_clock_locked_i (
.clk(monitor_clk),
.rst(rst),
.in(clock_locked),
.out(clock_locked_mon)
);
synchronizer #(
.WIDTH(1),
.STAGES(2),
.INITIAL_VAL(1),
.FALSE_PATH_TO_IN(1)
) sync_rst_watched_clk_i (
.clk(watched_clk),
.rst(1'b0),
.in(rst),
.out(rst_watched_clk)
);
clock_div #(
.N(CLK_DIV_1KHZ)
) clock_div_1khz_i (
.clk_in(watched_clk),
.clk_in_rst(rst_watched_clk),
.clk_out(watched_clk_div1k)
);
synchronizer #(
.WIDTH(1),
.STAGES(2),
.INITIAL_VAL(0),
.FALSE_PATH_TO_IN(1)
) sync_watched_clk_1khz_i (
.clk(monitor_clk),
.rst(rst),
.in(watched_clk_div1k),
.out(watched_clk_div1k_mon)
);
// Measurement and fault evaluation (monitor_clk domain).
//
// On each monitor_clk cycle ms_tick_count increments, saturating at
// MS_CYCLES_MON_CLK to prevent wrap-around for slow watched_clk rates.
//
// On each rising edge of the synchronised divided clock:
// - clock_fault_mon is set if ms_tick_count is below the nominal 1 ms
// count minus both the frequency-tolerance margin and the
// synchronizer quantization margin, meaning the interval completed
// too early and watched_clk is running above MAX_CLOCK_RATE.
// - ms_tick_count is reset to start the next measurement window.
//
// During reset or when clock_locked is not asserted, clock_fault_mon is
// held high (fault asserted) and the counter is held at zero.
always_ff @(posedge monitor_clk) begin
if (rst || !clock_locked_mon) begin
ms_tick_count <= '0;
watched_clk_div1k_mon_prev <= '0;
clock_fault_mon <= 1'b1;
end else begin
watched_clk_div1k_mon_prev <= watched_clk_div1k_mon;
// Saturate to avoid rollover for slower-than-max watched clocks.
if (ms_tick_count < MS_CYCLES_MON_CLK) begin
ms_tick_count <= ms_tick_count + 1'b1;
end
if (watched_clk_div1k_mon && !watched_clk_div1k_mon_prev) begin
clock_fault_mon <= ms_tick_count
< MS_CYCLES_MON_CLK - FREQ_MARGIN_CYCLES - SYNC_MARGIN_CYCLES;
ms_tick_count <= '0;
end
end
end
synchronizer #(
.WIDTH(1),
.STAGES(2),
.INITIAL_VAL(1),
.FALSE_PATH_TO_IN(1)
) sync_clock_fault_i (
.clk(watched_clk),
.rst(rst_watched_clk),
.in(clock_fault_mon),
.out(clock_fault)
);
endmodule

View File

@ -0,0 +1,991 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_spi_adrv
//
// Description:
//
// SPI master for the ADI ADRV transceiver family. It is controlled via a
// CtrlPort slave interface and it drives a standard 4-wire SPI bus (CPOL=0,
// CPHA=0). It is designed to make efficient use of the CtrlPort bus by
// eliminating the need to write redundant address bytes (e.g., repeating the
// address for writes to the same address, or writes to sequential
// addresses), the need to write dummy bytes in order to flush out a read
// result, or the need to read back echo bytes in responses. Only the
// relevant bytes need to be communicated over CtrlPort.
//
// Furthermore, the SPI transactions are blocking in the sense that the
// CtrlPort interface will not ACK until the request is accepted and
// guaranteed to complete. This allows multiple SPI transactions to be
// enqueued in the underlying CtrlPort endpoint FIFO without the need for
// polling the core for status.
//
// Two methods are available for issuing SPI transactions: REG_CONTROl and
// REG_SI.
//
// REG_CONTROL Interface
//
// With this interface, the REG_CONTROL register controls the mode,
// direction, and the number of data bytes in the transaction. Four
// transaction modes are supported via the REG_CONTROL register.
//
// RAW : The exact byte stream in the data register is shifted out.
// The caller is responsible for embedding any SPI address.
//
// SI_SEQ : Single instruction, sequential. Each data byte is preceded
// by the 16-bit SPI address written in REG_CONTROL, which
// auto-increments by one for every byte. Three SPI bytes are
// sent per data byte.
//
// SI_REP : Single instruction, repeated. Same as SI_SEQ but the
// address does not increment. The same address is sent
// before every data byte.
//
// STREAMING : The 16-bit SPI address is sent once, followed by all data
// bytes back-to-back. Two address bytes plus num_bytes data
// bytes are sent total.
//
//
// For write operations in all the above modes, the RX state machine
// ignores the incoming bytes from the SPI interface without storing them.
// So, it is not necessary to read anything when doing a write.
//
// For read operations in RAW mode, all MISO bytes are returned such that
// you must read the same number of bytes that were written.
//
// For read operations in SI_SEQ, SI_REP, and STREAMING mode, the state
// machine automatically drives MOSI with zeros during the data phase, so
// that it is not necessary to write any data bytes to execute a read. And
// it automatically captures only the data bytes from MISO; the address
// echo bytes are discarded. So, you only need to read the number of data
// bytes that were requested to be written (excluding the address bytes).
//
// REG_SI Interface
//
// For a single-byte read or write (referred to as "single instruction" in
// the ADI code), there is also a REG_SI register to which a single 3-byte
// instruction can be written (addr_hi, addr_lo, and data byte). This is
// equivalent to using RAW mode with a length of 3 bytes. If the MSB of the
// address (bit 15) is set, then it is treated as a read and the 3 RX data
// bytes are inserted into the RX FIFO and can be read by reading the
// REG_DATA register.
//
// The benefit of using REG_SI is that you don't need to also write to the
// REG_CONTROL register. So, fewer CtrlPort writes are needed for three
// instructions or less compared to RAW mode. On the downside, each
// instruction is its own SPI transaction (i.e., CS_n deasserts between
// each instruction). So, for 2 or more instructions, additional time may
// be used on the SPI bus.
//
// RX Data FIFO
//
// Received bytes are packed into 32-bit words (first byte in bits [7:0],
// second byte in bits [15:8], etc.) and stored in the RX word FIFO. Each
// transaction ends on a word boundary; if the number of received bytes is
// not a multiple of 4, the remaining bytes in the last word are undefined.
// This means that the first byte of a transaction always starts with bits
// [7:0] of a new FIFO word, regardless of how many bytes the previous
// transaction produced.
//
// Each REG_SI read request always produces exactly 3 received bytes, which
// are padded to a full 32-bit word (one FIFO entry). So each REG_SI read
// fills one word of the FIFO, but only bytes [23:0] are valid.
//
// The host software must not overfill the FIFO. The default depth holds
// one complete maximum-size transaction.
//
// Data Word Byte Ordering
//
// Words are little-endian packed. That is, byte 0 (the first byte to be
// sent over SPI) occupies data[7:0], byte 1 occupies data[15:8], etc. Each
// byte is serialized MSB-first. That is, bit 7 leaves the MOSI/MISO pin
// first, bit 0 last.
//
// CS_N Behavior
//
// Asserts when REG_CONTROL is written and deasserts automatically after
// all num_bytes have been shifted and captured. CS_N remains asserted until
// the entire transaction has completed; the SPI clock simply pauses
// between data bytes when the TX buffer is empty.
//
// For REG_SI, CS_N asserts/deasserts for each single instruction.
//
// CtrlPort Flow Control
//
// REG_CONTROL write : ACKs after the control word has been accepted.
// REG_DATA write : ACKs after the write has been accepted and is
// guaranteed to complete.
// REG_DATA read : ACK'd when the data to be read becomes available.
//
// SPI Clock Rate
//
// The SPI clock rate is controlled by setting the desired number of clock
// cycles per half SPI clock period, minus 1. In other words, the actual
// SPI clock frequency for a given clock frequency will be:
//
// f_sclk = f_clk / (2 * (half_per + 1))
//
// Or, the required half period setting for a given SPI clock frequency can
// be calculated as:
//
// half_per = (f_clk / (2 * f_sclk)) - 1
//
// Parameters:
//
// BASE_ADDR : CtrlPort base address.
// NUM_BYTES_W : Log base 2 of maximum number data bytes supported per
// transaction. Must not exceed SPI_NUM_BYTES_W.
// HALF_PER : Default sclk half-period length in clk cycles
// HALF_PER_W : Width of the half-period register in bits.
// HALF_PER_EN : When 1, the half-period register (REG_HALF_PER) exists and
// is software-readable and writable. When 0, the register
// does not exist; the SCLK rate is fixed at the HALF_PER
// parameter value.
// CS_HOLD : 1 = hold CS_N low for one extra half-period after the last
// SCLK falling edge. 0 = deassert CS_N immediately on the
// last SCLK falling edge.
// CS_GUARD : Number of CS_N-high half-periods between transactions. Must
// be 1 or 2.
// RX_FIFO_SIZE : Log base 2 of the depth of the 32-bit wide RX word FIFO. By
// default, it holds one complete maximum-size transaction
// (2**NUM_BYTES_W bytes / 4 bytes per word). The host
// software must NOT overfill the FIFO.
//
// Signals:
//
// clk / rst : Clock and active-high synchronous reset.
// s_ctrlport_* : CtrlPort slave request/response signals.
// sclk : SPI clock output (registered).
// cs_n : Active-low chip select (registered).
// mosi : Master-out / slave-in data (registered).
// miso : Master-in / slave-out data input.
//
`default_nettype none
module ctrlport_spi_adrv
import ctrlport_pkg::*;
import ctrlport_spi_adrv_pkg::*;
#(
int BASE_ADDR = 0,
int NUM_BYTES_W = 8,
int HALF_PER = 9,
int HALF_PER_W = 8,
bit HALF_PER_EN = 1,
bit CS_HOLD = 1,
int CS_GUARD = 2,
int RX_FIFO_SIZE = NUM_BYTES_W-2
) (
input wire clk,
input wire rst,
// CtrlPort Slave Request
input wire s_ctrlport_req_wr,
input wire s_ctrlport_req_rd,
input wire [CTRLPORT_ADDR_W-1:0] s_ctrlport_req_addr,
input wire [CTRLPORT_DATA_W-1:0] s_ctrlport_req_data,
// CtrlPort Slave Response
output logic s_ctrlport_resp_ack,
output ctrlport_status_t s_ctrlport_resp_status,
output logic [CTRLPORT_DATA_W-1:0] s_ctrlport_resp_data,
// SPI Interface
output logic sclk,
output logic cs_n,
output logic mosi,
input wire miso
);
//---------------------------------------------------------------------------
// Parameter Checks
//---------------------------------------------------------------------------
if (NUM_BYTES_W > SPI_NUM_BYTES_W) begin : gen_error
$error("NUM_BYTES_W (%0d) must not exceed SPI_NUM_BYTES_W (%0d).",
NUM_BYTES_W, SPI_NUM_BYTES_W);
end : gen_error
//---------------------------------------------------------------------------
// Declarations
//---------------------------------------------------------------------------
// SPI clock half-period. Driven by a software-writable register when
// HALF_PER_EN=1; wired to the parameter value when HALF_PER_EN=0.
logic [HALF_PER_W-1:0] half_per;
// TX 32-bit AXI-Stream (from register block into TX width converter)
logic [31:0] tx_word_tdata;
logic [ 3:0] tx_word_tkeep;
logic tx_word_tvalid;
logic tx_word_tready;
logic tx_word_tlast;
// TX 8-bit AXI-Stream (from TX width converter into axis_spi)
logic [7:0] tx_byte_tdata;
logic tx_byte_tvalid;
logic tx_byte_tready;
logic tx_byte_tlast;
// RX 8-bit AXI-Stream (from axis_spi into RX FSM)
logic [7:0] spi_rx_tdata;
logic spi_rx_tvalid;
logic spi_rx_tready;
logic spi_rx_tlast;
// RX 8-bit AXI-Stream (from RX FSM into RX width converter)
logic [7:0] rx_byte_tdata;
logic rx_byte_tvalid;
logic rx_byte_tready;
logic rx_byte_tlast;
// RX 32-bit AXI-Stream (from RX width converter into RX FIFO)
logic [31:0] rx_word_tdata;
logic rx_word_tvalid;
logic rx_word_tready;
logic rx_word_tlast;
// RX FIFO output
logic [31:0] rx_fifo_tdata;
logic rx_fifo_tvalid;
logic rx_fifo_tready;
//---------------------------------------------------------------------------
// Address Decode
//---------------------------------------------------------------------------
logic address_in_range;
logic is_si_reg;
logic is_ctrl_reg;
logic is_data_win;
assign address_in_range =
(s_ctrlport_req_addr[CTRLPORT_ADDR_W-1:REG_ADDR_W] ==
BASE_ADDR[CTRLPORT_ADDR_W-1:REG_ADDR_W]);
assign is_si_reg = address_in_range &&
(s_ctrlport_req_addr[0+:REG_ADDR_W] == REG_SI);
assign is_ctrl_reg = address_in_range &&
(s_ctrlport_req_addr[0+:REG_ADDR_W] == REG_CONTROL);
assign is_data_win = address_in_range &&
(s_ctrlport_req_addr[0+:REG_ADDR_W] >= REG_DATA) &&
(s_ctrlport_req_addr[0+:REG_ADDR_W] < REG_DATA + 64);
//---------------------------------------------------------------------------
// Half-Period Register
//---------------------------------------------------------------------------
//
// When HALF_PER_EN=1 the register exists and is software-readable and
// writable. When HALF_PER_EN=0 none of this logic exists and half_per is a
// constant tied to the HALF_PER parameter.
//---------------------------------------------------------------------------
logic half_per_ack;
if (HALF_PER_EN) begin : gen_half_per_reg
logic is_half_per_reg;
assign is_half_per_reg = address_in_range &&
(s_ctrlport_req_addr[0+:REG_ADDR_W] == REG_HALF_PER);
always_ff @(posedge clk) begin
if (rst) begin
half_per <= HALF_PER;
half_per_ack <= 1'b0;
end else begin
half_per_ack <= 1'b0;
if (is_half_per_reg) begin
if (s_ctrlport_req_wr) begin
half_per <= s_ctrlport_req_data[HALF_PER_W-1:0];
half_per_ack <= 1'b1;
end else if (s_ctrlport_req_rd) begin
half_per_ack <= 1'b1;
end
end
end
end
end else begin : gen_half_per_fixed
assign half_per = HALF_PER;
assign half_per_ack = 1'b0;
end : gen_half_per_fixed
//---------------------------------------------------------------------------
// Control Register Write Handling
//---------------------------------------------------------------------------
// For writes to REG_SI, we must also write a control word equivalent to a
// 3-byte RAW control word, with read or write set based on the MSB of the
// address.
spi_adrv_ctrl_t si_ctrl_word;
assign si_ctrl_word = '{
dir: s_ctrlport_req_data[7] ? SPI_READ : SPI_WRITE,
rsvd1: 'X,
mode: MODE_RAW,
num_bytes: 3,
addr: 'X
};
logic [CTRLPORT_DATA_W-1:0] ctrl_data;
logic ctrl_wr;
logic ctrl_ack;
logic [CTRLPORT_DATA_W-1:0] ctrl_tdata;
logic ctrl_tvalid;
logic ctrl_tready;
// Control word split to TX and RX FSMs.
spi_adrv_ctrl_t ctrl_tx_tdata;
logic ctrl_tx_tvalid;
logic ctrl_tx_tready;
spi_adrv_ctrl_t ctrl_rx_tdata;
logic ctrl_rx_tvalid;
logic ctrl_rx_tready;
// REG_CONTROL writes pass the data word through. REG_SI writes use the
// synthesized control word above.
assign ctrl_data = is_si_reg ? si_ctrl_word : s_ctrlport_req_data;
assign ctrl_wr = s_ctrlport_req_wr && (is_ctrl_reg || is_si_reg);
ctrlport_to_axis #(
.WIDTH (CTRLPORT_DATA_W),
.FIFO_SIZE_LOG2 (1)
) ctrlport_to_axis_ctrl (
.clk (clk),
.rst (rst),
.ctrl_data (ctrl_data),
.ctrl_wr (ctrl_wr),
.ctrl_ack (ctrl_ack),
.m_axis_tdata (ctrl_tdata),
.m_axis_tvalid (ctrl_tvalid),
.m_axis_tready (ctrl_tready)
);
// Duplicate the control word to the TX FSM (port 0) and RX FSM (port 1).
axis_split #(
.DATA_W (CTRLPORT_DATA_W),
.NUM_PORTS (2),
.INPUT_REG (0),
.OUT_FIFO_SIZE (1)
) ctrl_split_i (
.clk (clk),
.rst (rst),
.s_axis_tdata (ctrl_tdata),
.s_axis_tvalid (ctrl_tvalid),
.s_axis_tready (ctrl_tready),
.m_axis_tdata ({ctrl_rx_tdata, ctrl_tx_tdata}),
.m_axis_tvalid ({ctrl_rx_tvalid, ctrl_tx_tvalid}),
.m_axis_tready ({ctrl_rx_tready, ctrl_tx_tready})
);
//---------------------------------------------------------------------------
// Data Register Write Handling
//---------------------------------------------------------------------------
logic data_wr;
logic data_ack;
assign data_wr = s_ctrlport_req_wr && (is_data_win || is_si_reg);
ctrlport_to_axis #(
.WIDTH (CTRLPORT_DATA_W),
.FIFO_SIZE_LOG2 (0)
) ctrlport_to_axis_data (
.clk (clk),
.rst (rst),
.ctrl_data (s_ctrlport_req_data),
.ctrl_wr (data_wr),
.ctrl_ack (data_ack),
.m_axis_tdata (tx_word_tdata),
.m_axis_tvalid (tx_word_tvalid),
.m_axis_tready (tx_word_tready)
);
//---------------------------------------------------------------------------
// Data Register Read Handling
//---------------------------------------------------------------------------
logic [CTRLPORT_DATA_W-1:0] data_a2c_data;
logic data_a2c_ack;
logic data_a2c_rd;
assign data_a2c_rd = s_ctrlport_req_rd && is_data_win;
axis_to_ctrlport #(
.WIDTH (CTRLPORT_DATA_W),
.FIFO_SIZE_LOG2 (0)
) axis_to_ctrlport_rx (
.clk (clk),
.rst (rst),
.s_axis_tdata (rx_fifo_tdata),
.s_axis_tvalid (rx_fifo_tvalid),
.s_axis_tready (rx_fifo_tready),
.ctrl_rd (data_a2c_rd),
.ctrl_data (data_a2c_data),
.ctrl_ack (data_a2c_ack)
);
//---------------------------------------------------------------------------
// CtrlPort Response Acknowledgement
//---------------------------------------------------------------------------
//
// A REG_SI write triggers both ctrl_wr and data_wr simultaneously, so
// ctrl_ack and data_ack fire independently at different times. This state
// machine ensures exactly one s_ctrlport_resp_ack pulse is issued per
// request, once all expected ACKs have arrived.
//
// ST_ACK_IDLE : Waiting for a write request.
// ST_ACK_WAIT_ONE : Expecting one more ACK (REG_CONTROL or REG_DATA write,
// or REG_SI write after one of the two ACKs has arrived).
// ST_ACK_WAIT_TWO : Expecting two ACKs (REG_SI write, neither arrived yet);
// if both arrive on the same cycle, coalesce immediately.
//
// data_a2c_ack (axis-to-ctrlport for REG_DATA reads) is always single-source
// and bypasses the state machine, ORing directly into s_ctrlport_resp_ack.
//
//---------------------------------------------------------------------------
typedef enum logic [1:0] {
ST_ACK_IDLE = 0,
ST_ACK_WAIT_ONE = 1,
ST_ACK_WAIT_TWO = 2
} ack_state_t;
ack_state_t ack_state;
logic ack_out;
always_ff @(posedge clk) begin
if (rst) begin
ack_state <= ST_ACK_IDLE;
ack_out <= 1'b0;
end else begin
ack_out <= 1'b0;
case (ack_state)
ST_ACK_IDLE : begin
if (s_ctrlport_req_wr) begin
if (is_si_reg)
ack_state <= ST_ACK_WAIT_TWO;
else
ack_state <= ST_ACK_WAIT_ONE;
end
end
ST_ACK_WAIT_ONE : begin
if (ctrl_ack || data_ack) begin
ack_out <= 1'b1;
ack_state <= ST_ACK_IDLE;
end
end
ST_ACK_WAIT_TWO : begin
if (ctrl_ack && data_ack) begin
// Both arrived on the same cycle.
ack_out <= 1'b1;
ack_state <= ST_ACK_IDLE;
end else if (ctrl_ack || data_ack) begin
// One arrived; wait for the other.
ack_state <= ST_ACK_WAIT_ONE;
end
end
default : ack_state <= ST_ACK_IDLE;
endcase
end
end
always_ff @(posedge clk) begin
if (rst) begin
s_ctrlport_resp_ack <= 1'b0;
s_ctrlport_resp_data <= 'X;
end else begin
s_ctrlport_resp_ack <= ack_out | data_a2c_ack | half_per_ack;
s_ctrlport_resp_data <= half_per_ack ? half_per : data_a2c_data;
end
end
assign s_ctrlport_resp_status = STS_OKAY;
//---------------------------------------------------------------------------
// TX Width Converter (32-bit -> 8-bit)
//---------------------------------------------------------------------------
axis_width_conv #(
.WORD_W (8),
.IN_WORDS (4),
.OUT_WORDS (1),
.SYNC_CLKS (1),
.PIPELINE ("OUT")
) axis_width_conv_tx (
.s_axis_aclk (clk),
.s_axis_rst (rst),
.s_axis_tdata (tx_word_tdata),
.s_axis_tkeep ('1),
.s_axis_tlast ('0),
.s_axis_tvalid (tx_word_tvalid),
.s_axis_tready (tx_word_tready),
.m_axis_aclk (clk),
.m_axis_rst (rst),
.m_axis_tdata (tx_byte_tdata),
.m_axis_tkeep (),
.m_axis_tlast (tx_byte_tlast),
.m_axis_tvalid (tx_byte_tvalid),
.m_axis_tready (tx_byte_tready)
);
//---------------------------------------------------------------------------
// TX State Machine
//---------------------------------------------------------------------------
// AXI-Stream from TX FSM into axis_spi.
logic [7:0] spi_tx_tdata;
logic spi_tx_tvalid;
logic spi_tx_tready;
logic spi_tx_tlast;
typedef enum logic [3:0] {
ST_TX_IDLE = 0,
ST_TX_RAW = 1,
ST_TX_SI_ADDR_HI = 2,
ST_TX_SI_ADDR_LO = 3,
ST_TX_SI_DATA = 4,
ST_TX_STREAM_ADDR_HI = 5,
ST_TX_STREAM_ADDR_LO = 6,
ST_TX_STREAM_DATA = 7,
ST_TX_DRAIN = 8
} tx_state_t;
tx_state_t tx_state;
logic [SPI_NUM_BYTES_W-1:0] tx_bytes_rem; // Data bytes remaining
logic [ 15:0] tx_cur_addr; // Running SPI address
logic [ 1:0] tx_drain_cnt; // Padding bytes to drain after transaction
always_ff @(posedge clk) begin
if (rst) begin
tx_state <= ST_TX_IDLE;
tx_bytes_rem <= '0;
tx_cur_addr <= '0;
tx_drain_cnt <= 2'b0;
end else begin
case (tx_state)
ST_TX_IDLE : begin
if (ctrl_tx_tvalid) begin
tx_bytes_rem <= ctrl_tx_tdata.num_bytes;
tx_cur_addr <= ctrl_tx_tdata.addr;
tx_drain_cnt <= 2'b0 - ctrl_tx_tdata.num_bytes[1:0];
case (ctrl_tx_tdata.mode)
MODE_RAW : tx_state <= ST_TX_RAW;
MODE_SI_SEQ,
MODE_SI_REP : tx_state <= ST_TX_SI_ADDR_HI;
MODE_STREAMING : tx_state <= ST_TX_STREAM_ADDR_HI;
default : tx_state <= ST_TX_RAW;
endcase
end
end
ST_TX_RAW : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_bytes_rem <= tx_bytes_rem - 1;
if (tx_bytes_rem == 1) begin
tx_state <= (tx_drain_cnt != 0) ? ST_TX_DRAIN : ST_TX_IDLE;
end
end
end
ST_TX_SI_ADDR_HI : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_state <= ST_TX_SI_ADDR_LO;
end
end
ST_TX_SI_ADDR_LO : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_state <= ST_TX_SI_DATA;
end
end
ST_TX_SI_DATA : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_bytes_rem <= tx_bytes_rem - 1;
if (tx_bytes_rem == 1) begin
if (ctrl_tx_tdata.dir == SPI_WRITE && tx_drain_cnt != 0) begin
tx_state <= ST_TX_DRAIN;
end else begin
tx_state <= ST_TX_IDLE;
end
end else begin
if (ctrl_tx_tdata.mode == MODE_SI_SEQ) begin
tx_cur_addr <= tx_cur_addr + 1;
end
tx_state <= ST_TX_SI_ADDR_HI;
end
end
end
ST_TX_STREAM_ADDR_HI : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_state <= ST_TX_STREAM_ADDR_LO;
end
end
ST_TX_STREAM_ADDR_LO : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_state <= ST_TX_STREAM_DATA;
end
end
ST_TX_STREAM_DATA : begin
if (spi_tx_tvalid && spi_tx_tready) begin
tx_bytes_rem <= tx_bytes_rem - 1;
if (tx_bytes_rem == 1) begin
if (ctrl_tx_tdata.dir == SPI_WRITE && tx_drain_cnt != 0) begin
tx_state <= ST_TX_DRAIN;
end else begin
tx_state <= ST_TX_IDLE;
end
end
end
end
ST_TX_DRAIN : begin
if (tx_byte_tvalid) begin
tx_drain_cnt <= tx_drain_cnt - 1;
if (tx_drain_cnt == 1) begin
tx_state <= ST_TX_IDLE;
end
end
end
default : begin
tx_state <= ST_TX_IDLE;
end
endcase
end
end
// ctrl_tx_tready is driven combinationally so that the control FIFO word is
// consumed on the exact same cycle that the last SPI byte is accepted by
// axis_spi. This ensures the FIFO advances before the FSM re-enters
// ST_TX_IDLE, so it sees the correct next control word.
always_comb begin
spi_tx_tdata = 8'h00;
spi_tx_tvalid = 1'b0;
spi_tx_tlast = 1'b0;
tx_byte_tready = 1'b0;
ctrl_tx_tready = 1'b0;
case (tx_state)
ST_TX_IDLE : begin
// Waiting; nothing to send.
end
ST_TX_RAW : begin
// RAW: pass tx_byte bytes straight through regardless of direction.
spi_tx_tdata = tx_byte_tdata;
spi_tx_tvalid = tx_byte_tvalid;
tx_byte_tready = spi_tx_tready;
spi_tx_tlast = (tx_bytes_rem == 1);
ctrl_tx_tready = spi_tx_tvalid && spi_tx_tready && (tx_bytes_rem == 1);
end
ST_TX_SI_ADDR_HI : begin
// addr[15:8] = addr_hi; first byte on the wire.
spi_tx_tdata = tx_cur_addr[15:8];
spi_tx_tvalid = 1'b1;
end
ST_TX_SI_ADDR_LO : begin
// addr[7:0] = addr_lo; second byte on the wire.
spi_tx_tdata = tx_cur_addr[7:0];
spi_tx_tvalid = 1'b1;
end
ST_TX_SI_DATA : begin
if (ctrl_tx_tdata.dir == SPI_READ) begin
// Drive MOSI=0 for reads; do not consume TX data FIFO.
spi_tx_tdata = 8'h00;
spi_tx_tvalid = 1'b1;
end else begin
spi_tx_tdata = tx_byte_tdata;
spi_tx_tvalid = tx_byte_tvalid;
tx_byte_tready = spi_tx_tready;
end
spi_tx_tlast = (tx_bytes_rem == 1);
ctrl_tx_tready = spi_tx_tvalid && spi_tx_tready && (tx_bytes_rem == 1);
end
ST_TX_STREAM_ADDR_HI : begin
spi_tx_tdata = tx_cur_addr[15:8];
spi_tx_tvalid = 1'b1;
end
ST_TX_STREAM_ADDR_LO : begin
spi_tx_tdata = tx_cur_addr[7:0];
spi_tx_tvalid = 1'b1;
end
ST_TX_STREAM_DATA : begin
if (ctrl_tx_tdata.dir == SPI_READ) begin
spi_tx_tdata = 8'h00;
spi_tx_tvalid = 1'b1;
end else begin
spi_tx_tdata = tx_byte_tdata;
spi_tx_tvalid = tx_byte_tvalid;
tx_byte_tready = spi_tx_tready;
end
spi_tx_tlast = (tx_bytes_rem == 1);
ctrl_tx_tready = spi_tx_tvalid && spi_tx_tready && (tx_bytes_rem == 1);
end
ST_TX_DRAIN : begin
// Consume width-converter padding bytes without sending them on MOSI.
tx_byte_tready = 1'b1;
end
default : begin
// Nothing
end
endcase
end
//---------------------------------------------------------------------------
// SPI Interface
//---------------------------------------------------------------------------
axis_spi #(
.HALF_PER_W (HALF_PER_W),
.CS_HOLD (CS_HOLD),
.CS_GUARD (CS_GUARD)
) axis_spi_i (
.clk (clk),
.rst (rst),
.half_per (half_per),
.s_axis_tdata (spi_tx_tdata),
.s_axis_tvalid (spi_tx_tvalid),
.s_axis_tready (spi_tx_tready),
.s_axis_tlast (spi_tx_tlast),
.m_axis_tdata (spi_rx_tdata),
.m_axis_tvalid (spi_rx_tvalid),
.m_axis_tready (spi_rx_tready),
.m_axis_tlast (spi_rx_tlast),
.sclk (sclk),
.cs_n (cs_n),
.mosi (mosi),
.miso (miso)
);
//---------------------------------------------------------------------------
// RX State Machine
//---------------------------------------------------------------------------
//
// Filters bytes from spi_rx_*, forwarding data-phase bytes to rx_byte_*
// (axis_width_conv_rx) per the mode table below:
//
// RAW write : Discard all bytes until tlast.
// RAW read : Forward all bytes until tlast.
// SI_SEQ/REP : For each group of 3, discard addr_hi, discard addr_lo,
// forward data byte; repeat until tlast.
// STREAMING : Discard addr_hi, discard addr_lo, then forward all bytes
// until tlast.
//
//---------------------------------------------------------------------------
typedef enum logic [3:0] {
ST_RX_IDLE = 0,
ST_RX_RAW_DROP = 1,
ST_RX_SI_DISCARD_HI = 2,
ST_RX_SI_DISCARD_LO = 3,
ST_RX_SI_DATA = 4,
ST_RX_STREAM_DISCARD_HI = 5,
ST_RX_STREAM_DISCARD_LO = 6,
ST_RX_PASS_DATA = 7
} rx_state_t;
rx_state_t rx_state;
always_ff @(posedge clk) begin
if (rst) begin
rx_state <= ST_RX_IDLE;
end else begin
case (rx_state)
ST_RX_IDLE : begin
if (ctrl_rx_tvalid) begin
case (ctrl_rx_tdata.mode)
MODE_RAW : begin
rx_state <= ctrl_rx_tdata.dir == SPI_READ ?
ST_RX_PASS_DATA : ST_RX_RAW_DROP;
end
MODE_SI_SEQ,
MODE_SI_REP : begin
rx_state <= ctrl_rx_tdata.dir == SPI_READ ?
ST_RX_SI_DISCARD_HI : ST_RX_RAW_DROP;
end
MODE_STREAMING : begin
rx_state <= ctrl_rx_tdata.dir == SPI_READ ?
ST_RX_STREAM_DISCARD_HI : ST_RX_RAW_DROP;
end
default : rx_state <= ST_RX_RAW_DROP;
endcase
end
end
ST_RX_PASS_DATA : begin
if (spi_rx_tvalid && spi_rx_tready && spi_rx_tlast) begin
rx_state <= ST_RX_IDLE;
end
end
ST_RX_RAW_DROP : begin
if (spi_rx_tvalid && spi_rx_tready && spi_rx_tlast) begin
rx_state <= ST_RX_IDLE;
end
end
ST_RX_SI_DISCARD_HI : begin
if (spi_rx_tvalid && spi_rx_tready) begin
rx_state <= ST_RX_SI_DISCARD_LO;
end
end
ST_RX_SI_DISCARD_LO : begin
if (spi_rx_tvalid && spi_rx_tready) begin
rx_state <= ST_RX_SI_DATA;
end
end
ST_RX_SI_DATA : begin
if (spi_rx_tvalid && spi_rx_tready) begin
if (spi_rx_tlast) begin
rx_state <= ST_RX_IDLE;
end else begin
rx_state <= ST_RX_SI_DISCARD_HI;
end
end
end
ST_RX_STREAM_DISCARD_HI : begin
if (spi_rx_tvalid && spi_rx_tready) begin
rx_state <= ST_RX_STREAM_DISCARD_LO;
end
end
ST_RX_STREAM_DISCARD_LO : begin
if (spi_rx_tvalid && spi_rx_tready) begin
rx_state <= ST_RX_PASS_DATA;
end
end
default : begin
rx_state <= ST_RX_IDLE;
end
endcase
end
end
always_comb begin
// Default: consume from spi_rx but don't forward (discard).
spi_rx_tready = 1'b0;
rx_byte_tdata = spi_rx_tdata;
rx_byte_tvalid = 1'b0;
rx_byte_tlast = spi_rx_tlast;
ctrl_rx_tready = 1'b0;
case (rx_state)
ST_RX_IDLE : begin
// Waiting; hold spi_rx backpressured.
end
ST_RX_PASS_DATA,
ST_RX_SI_DATA : begin
// Forward bytes to the width converter.
spi_rx_tready = rx_byte_tready;
rx_byte_tvalid = spi_rx_tvalid;
ctrl_rx_tready = spi_rx_tvalid && spi_rx_tready && spi_rx_tlast;
end
ST_RX_RAW_DROP : begin
// Consume and discard; do not forward.
spi_rx_tready = 1'b1;
ctrl_rx_tready = spi_rx_tvalid && spi_rx_tready && spi_rx_tlast;
end
ST_RX_SI_DISCARD_HI,
ST_RX_SI_DISCARD_LO,
ST_RX_STREAM_DISCARD_HI,
ST_RX_STREAM_DISCARD_LO : begin
// Consume and discard; do not forward.
spi_rx_tready = 1'b1;
end
default : begin
// Discard.
spi_rx_tready = 1'b1;
end
endcase
end
//---------------------------------------------------------------------------
// RX Width Converter (8-bit -> 32-bit)
//---------------------------------------------------------------------------
axis_width_conv #(
.WORD_W (8),
.IN_WORDS (1),
.OUT_WORDS (4),
.SYNC_CLKS (1),
.PIPELINE ("NONE")
) axis_width_conv_rx (
.s_axis_aclk (clk),
.s_axis_rst (rst),
.s_axis_tdata (rx_byte_tdata),
.s_axis_tkeep (1'b1),
.s_axis_tlast (rx_byte_tlast),
.s_axis_tvalid (rx_byte_tvalid),
.s_axis_tready (rx_byte_tready),
.m_axis_aclk (clk),
.m_axis_rst (rst),
.m_axis_tdata (rx_word_tdata),
.m_axis_tkeep (),
.m_axis_tlast (rx_word_tlast),
.m_axis_tvalid (rx_word_tvalid),
.m_axis_tready (rx_word_tready)
);
//---------------------------------------------------------------------------
// RX FIFO
//---------------------------------------------------------------------------
axi_fifo #(
.WIDTH (32),
.SIZE (RX_FIFO_SIZE)
) axi_fifo_i (
.clk (clk),
.reset (rst),
.clear (1'b0),
.i_tdata (rx_word_tdata),
.i_tvalid (rx_word_tvalid),
.i_tready (rx_word_tready),
.o_tdata (rx_fifo_tdata),
.o_tvalid (rx_fifo_tvalid),
.o_tready (rx_fifo_tready),
.space (),
.occupied ()
);
endmodule : ctrlport_spi_adrv
`default_nettype wire

View File

@ -0,0 +1,72 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Package: ctrlport_spi_adrv_pkg
//
// Description:
//
// Shared constants and types for ctrlport_spi_adrv and its testbench.
//
//
package ctrlport_spi_adrv_pkg;
//---------------------------------------------------------------------------
// Register offsets (byte addresses relative to BASE_ADDRESS)
//---------------------------------------------------------------------------
localparam int REG_HALF_PER = 'h00;
localparam int REG_SI = 'h04;
localparam int REG_CONTROL = 'h3C;
localparam int REG_DATA = 'h40;
// Actual address space required for this block.
localparam int REG_ADDR_W = 7;
//---------------------------------------------------------------------------
// CONTROL register field widths
//---------------------------------------------------------------------------
localparam int SPI_MODE_W = 2;
localparam int SPI_NUM_BYTES_W = 12;
localparam int SPI_ADDR_W = 16;
//---------------------------------------------------------------------------
// SPI direction encoding
//---------------------------------------------------------------------------
typedef enum logic {
SPI_WRITE = 1'b0,
SPI_READ = 1'b1
} spi_adrv_dir_t;
//---------------------------------------------------------------------------
// SPI mode encoding
//---------------------------------------------------------------------------
typedef enum logic [SPI_MODE_W-1:0] {
MODE_RAW = 0, // Raw byte stream
MODE_SI_SEQ = 1, // Single Instruction, sequential addresses
MODE_SI_REP = 2, // Single Instruction, same address repeated
MODE_STREAMING = 3 // Send address once, followed by all data bytes back-to-back
} spi_adrv_mode_t;
//---------------------------------------------------------------------------
// CONTROL register
//---------------------------------------------------------------------------
typedef struct packed {
spi_adrv_dir_t dir; // [31]
logic rsvd1; // [30]
spi_adrv_mode_t mode; // [29:28]
logic [SPI_NUM_BYTES_W-1:0] num_bytes; // [27:16]
logic [ SPI_ADDR_W-1:0] addr; // [15:0]
} spi_adrv_ctrl_t;
endpackage : ctrlport_spi_adrv_pkg

View File

@ -0,0 +1,150 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_to_axis
//
// Description:
//
// Converts a CtrlPort write interface into an AXI-Stream bus. The module
// contains an internal FIFO and will not acknowledge the write until the
// FIFO has emptied enough to guarantee that another write can be accepted.
//
// Parameters:
//
// WIDTH : Width of the data path in bits.
// FIFO_SIZE_LOG2 : Log base 2 of the internal FIFO size.
//
// Signals:
//
// clk : Clock
// rst : Active-high synchronous reset
// ctrl_data : CtrlPort data word
// ctrl_wr : CtrlPort write request strobe (single-cycle pulse)
// ctrl_ack : CtrlPort acknowledgement strobe (single-cycle pulse)
// m_axis_tdata : AXI-Stream data output
// m_axis_tvalid : AXI-Stream valid
// m_axis_tready : AXI-Stream ready (from downstream)
//
`default_nettype none
module ctrlport_to_axis #(
parameter int WIDTH = 32,
parameter int FIFO_SIZE_LOG2 = 1
) (
input wire clk,
input wire rst,
// CtrlPort write interface
input wire [WIDTH-1:0] ctrl_data,
input wire ctrl_wr,
output logic ctrl_ack,
// AXI-Stream master
output wire [WIDTH-1:0] m_axis_tdata,
output wire m_axis_tvalid,
input wire m_axis_tready
);
//---------------------------------------------------------------------------
// Declarations
//---------------------------------------------------------------------------
typedef enum logic { ST_IDLE, ST_FULL } state_t;
state_t state;
logic fifo_i_tready;
logic [15:0] fifo_space;
//---------------------------------------------------------------------------
// State Machine
//---------------------------------------------------------------------------
always_ff @(posedge clk) begin
if (rst) begin
state <= ST_IDLE;
ctrl_ack <= 1'b0;
end else begin
// Default assignments
ctrl_ack <= 1'b0;
unique case (state)
ST_IDLE: begin
if (ctrl_wr) begin
if (fifo_space > 1 && FIFO_SIZE_LOG2 > 0) begin
// If there's 2 or more spaces in the FIFO, acknowledge
// immediately. If FIFO_SIZE_LOG2 is 0 (depth of 1), then the
// space can never be greater than 1.
ctrl_ack <= 1'b1;
end else begin
// If there's only one space, this write fills the FIFO, and we
// can't acknowledge until we have enough room to accept the next
// write.
state <= ST_FULL;
end
end
end
ST_FULL: begin
// Acknowledge the write as soon as space frees up in the FIFO.
if (fifo_space > 0) begin
ctrl_ack <= 1'b1;
state <= ST_IDLE;
end
end
default: begin
state <= ST_IDLE;
end
endcase
end
end
//---------------------------------------------------------------------------
// Output FIFO
//---------------------------------------------------------------------------
axi_fifo #(
.WIDTH (WIDTH),
.SIZE (FIFO_SIZE_LOG2)
) data_fifo_i (
.clk (clk),
.reset (rst),
.clear (1'b0),
.i_tdata (ctrl_data),
.i_tvalid (ctrl_wr),
.i_tready (fifo_i_tready),
.o_tdata (m_axis_tdata),
.o_tvalid (m_axis_tvalid),
.o_tready (m_axis_tready),
.space (fifo_space),
.occupied ()
);
//---------------------------------------------------------------------------
// Assertions
//---------------------------------------------------------------------------
if (FIFO_SIZE_LOG2 < 0) begin : gen_fifo_size_check
$error("FIFO_SIZE_LOG2 must be >= 0");
end
//synthesis translate_off
always_ff @(posedge clk) begin
if (ctrl_wr && !fifo_i_tready) begin
$error("ctrlport_to_axis: Write strobe occurred when the FIFO was not ready!");
end
end
//synthesis translate_on
endmodule : ctrlport_to_axis
`default_nettype wire

View File

@ -49,8 +49,8 @@ module ctrlport_to_jtag #(
output wire tms
);
`include "../../../../lib/rfnoc/core/ctrlport.vh"
`include "../regmap/jtag_regmap_utils.vh"
`include "../rfnoc/core/ctrlport.vh"
`include "jtag_regmap_utils.vh"
//---------------------------------------------------------------------------
// Local Registers

View File

@ -0,0 +1,371 @@
//
// Copyright 2025 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_to_simple_spi
//
// Description:
// Implements a simple SPI core controlled via CtrlPort. This design
// is an adaptation of the simple_spi_core module.
//
// Settings reg map:
//
// BASE_ADDRESS+0 divider setting
// bits [15:0] spi clock divider
//
// BASE_ADDRESS+4 configuration input
// bits [23:0] slave select, bit0 = slave0 enabled
// bits [29:24] num bits (1 through 32)
// bit [30] data input edge = in data bit latched on rising edge of clock
// bit [31] data output edge = out data bit latched on rising edge of clock
//
// BASE_ADDRESS+8 output data
// Writing this register begins a spi transaction.
// Bits are latched out starting from bit 31.
// Therefore, data has to be MSB aligned for transmission
//
// BASE_ADDRESS+12 Status
// bit [0] ready signal, high when spi core can begin another transaction
//
// BASE_ADDRESS+16 Readback
// Readback data from the last SPI transaction
//
module ctrlport_to_simple_spi # (
//settings register base address
int BASE_ADDRESS = 0,
//width of serial enables (up to 24 is possible)
int SEN_WIDTH = 8,
//idle state of the spi clock
bit CLK_IDLE = 0,
//idle state of the serial enables
logic [23:0] SEN_IDLE = '1
)
(
input wire ctrlport_clk,
input wire ctrlport_rst,
// Request
input wire s_ctrlport_req_wr,
input wire s_ctrlport_req_rd,
input wire [19:0] s_ctrlport_req_addr,
input wire [31:0] s_ctrlport_req_data,
// Response
output logic s_ctrlport_resp_ack = 1'b0,
output logic [ 1:0] s_ctrlport_resp_status = 2'b0,
output logic [31:0] s_ctrlport_resp_data = 32'b0,
//spi interface, slave selects, clock, data in, data out
output logic [SEN_WIDTH-1:0] sen,
output logic sclk,
output logic mosi,
input wire miso,
//optional debug output
output logic [31:0] debug
);
`include "../rfnoc/core/ctrlport.vh"
`include "ctrlport_to_simple_spi_regmap_utils.vh"
wire address_in_range =
(s_ctrlport_req_addr >= BASE_ADDRESS) &&
(s_ctrlport_req_addr < BASE_ADDRESS + 'h14); // 5 registers (20 bytes)
logic [15:0] sclk_divider;
logic [23:0] slave_select;
logic [5:0] num_bits;
logic datain_edge, dataout_edge;
logic [31:0] mosi_data;
logic trigger_spi;
logic [31:0] datain_reg;
logic [23:0] readback_reg;
logic readback_stb;
logic ready;
logic force_cs;
always_ff @(posedge ctrlport_clk) begin
if (ctrlport_rst) begin
s_ctrlport_resp_ack <= '0;
s_ctrlport_resp_data <= 'x;
s_ctrlport_resp_status <= '0;
trigger_spi <= 1'b0;
end else begin
// Reset response
s_ctrlport_resp_ack <= '0;
s_ctrlport_resp_data <= 'x;
s_ctrlport_resp_status <= '0;
trigger_spi <= 1'b0;
// Write request
if (s_ctrlport_req_wr) begin
s_ctrlport_resp_ack <= '1;
case (s_ctrlport_req_addr)
BASE_ADDRESS + SCLK_DIVIDER: begin
sclk_divider <= s_ctrlport_req_data[SPI_CLK_DIV_MSB : SPI_CLK_DIV];
force_cs <= s_ctrlport_req_data[FORCE_SS];
end
BASE_ADDRESS + SPI_PERSONALITY: begin
dataout_edge <= s_ctrlport_req_data[SPI_MOSI_EDGE];
datain_edge <= s_ctrlport_req_data[SPI_MISO_EDGE];
num_bits <= s_ctrlport_req_data[SPI_NUM_BITS_MSB : SPI_NUM_BITS];
slave_select <= s_ctrlport_req_data[SPI_SLAVE_SELECT_MSB : SPI_SLAVE_SELECT];
end
BASE_ADDRESS + SPI_MOSI_DATA: begin
mosi_data <= s_ctrlport_req_data[SPI_DATA_OUT_MSB : SPI_DATA_OUT];
trigger_spi <= 1'b1; // trigger the SPI transaction
end
// No register implementation for provided address
default: begin
// Acknowledge and provide error status if address is in range
if (address_in_range) begin
s_ctrlport_resp_status <= CTRL_STS_CMDERR;
// No response if out of range
end else begin
s_ctrlport_resp_ack <= 1'b0;
end
end
endcase
end else if (s_ctrlport_req_rd) begin
// read request handling
case (s_ctrlport_req_addr)
BASE_ADDRESS + SCLK_DIVIDER: begin
// Divider setting readback
s_ctrlport_resp_data[SPI_CLK_DIV_MSB : SPI_CLK_DIV] <= sclk_divider[SPI_CLK_DIV_SIZE-1 : 0];
s_ctrlport_resp_data[FORCE_SS] <= force_cs;
s_ctrlport_resp_ack <= 1'b1;
end
BASE_ADDRESS + SPI_PERSONALITY: begin
// Control register readback
s_ctrlport_resp_data[SPI_MOSI_EDGE] <= dataout_edge;
s_ctrlport_resp_data[SPI_MISO_EDGE] <= datain_edge;
s_ctrlport_resp_data[SPI_NUM_BITS_MSB : SPI_NUM_BITS] <= num_bits;
s_ctrlport_resp_data[SPI_SLAVE_SELECT_MSB : SPI_SLAVE_SELECT] <= slave_select;
s_ctrlport_resp_ack <= 1'b1;
end
BASE_ADDRESS + SPI_STATUS: begin
// Data register readback
s_ctrlport_resp_data[SPI_READY] <= ready;
s_ctrlport_resp_ack <= 1'b1;
end
BASE_ADDRESS + SPI_READBACK: begin
// Readback data from the last SPI transaction
s_ctrlport_resp_data[SPI_READBACK_DATA_MSB : SPI_READBACK_DATA] <= readback_reg;
s_ctrlport_resp_ack <= 1'b1;
end
// No register implementation for provided address
default: begin
// Acknowledge and provide error status if address is in range
if (address_in_range) begin
s_ctrlport_resp_status <= CTRL_STS_CMDERR;
// No response if out of range
end else begin
s_ctrlport_resp_ack <= 1'b0;
end
end
endcase
end
end
end
// Latch readback signal
always_ff @(posedge ctrlport_clk) begin
if (ctrlport_rst) begin
readback_reg <= '0;
end else if(readback_stb) begin
readback_reg <= datain_reg[23:0]; // readback data is latched from the last SPI transaction
end
end
// SPI Engine Implementation
// The implementation below is leveraged from the simple_spi_core module.
typedef enum logic [2:0] {
WAIT_TRIG,
PRE_IDLE,
CLK_REG,
CLK_INV,
POST_IDLE,
IDLE_SEN
} case_t;
case_t spi_state;
logic ready_reg;
// De-assert ready when a new transaction is triggered. ready_reg will be deasserted
// on the clock cycle after the trigger, and keep the ready signal low until the
// transaction is done.
assign ready = ready_reg && ~trigger_spi;
// Serial clock either idles or is in one of two clock states
logic sclk_reg;
assign sclk = sclk_reg;
// Serial enables either idle or enabled (based on spi_state or forced
// via FORCE_SS register field).
// One pipeline stage to break critical path from register in I/O pads.
wire sen_is_idle = ((spi_state == WAIT_TRIG) ||
(spi_state == IDLE_SEN)) &&
~force_cs;
wire [23:0] sen24 = (sen_is_idle)? SEN_IDLE : (SEN_IDLE ^ slave_select);
logic [SEN_WIDTH-1:0] sen_reg = SEN_IDLE;
always_ff @(posedge ctrlport_clk) begin
if (ctrlport_rst) begin
sen_reg <= SEN_IDLE;
end else begin
sen_reg <= sen24[SEN_WIDTH-1:0];
end
end
assign sen = sen_reg;
// Data output shift register
// One pipeline stage to break critical path from register in I/O pads.
logic [31:0] dataout_reg;
wire [31:0] dataout_next = {dataout_reg[30:0], 1'b0};
always_ff @(posedge ctrlport_clk) begin
mosi <= dataout_reg[31];
end
// Data input shift register
// Two pipeline stages to break critical path from register in I/O pads.
logic miso_pipe, miso_pipe2;
always_ff @(posedge ctrlport_clk) begin
miso_pipe2 <= miso;
miso_pipe <= miso_pipe2;
end
wire [31:0] datain_next = {datain_reg[30:0], miso_pipe};
// Counter for spi clock
logic [15:0] sclk_counter;
wire sclk_counter_done = (sclk_counter == sclk_divider);
wire [15:0] sclk_counter_next = (sclk_counter_done)? 0 : sclk_counter + 1;
// Counter for latching bits miso/mosi
logic [6:0] bit_counter;
wire [6:0] bit_counter_next = bit_counter + 1;
wire bit_counter_done = (bit_counter_next == num_bits);
always_ff @(posedge ctrlport_clk) begin
if (ctrlport_rst) begin
spi_state <= WAIT_TRIG;
sclk_reg <= CLK_IDLE;
ready_reg <= 0;
readback_stb <= 1'b0;
end
else begin
case (spi_state)
WAIT_TRIG: begin
if (trigger_spi) begin
spi_state <= PRE_IDLE;
end
readback_stb <= 1'b0;
ready_reg <= ~trigger_spi;
dataout_reg <= mosi_data;
sclk_counter <= 0;
bit_counter <= 0;
sclk_reg <= CLK_IDLE;
end
// Drive clock to idle state. This also drives the serial enables
// to their active state.
PRE_IDLE: begin
if (sclk_counter_done) begin
spi_state <= CLK_REG;
end
sclk_counter <= sclk_counter_next;
sclk_reg <= CLK_IDLE;
end
// Clock generation states
CLK_REG: begin
if (sclk_counter_done) begin
spi_state <= CLK_INV;
if (datain_edge != CLK_IDLE) begin
datain_reg <= datain_next;
end
if (dataout_edge != CLK_IDLE && bit_counter != 0) begin
dataout_reg <= dataout_next;
end
sclk_reg <= ~CLK_IDLE; //transition to rising when CLK_IDLE == 0
end
sclk_counter <= sclk_counter_next;
end
CLK_INV: begin
if (sclk_counter_done) begin
// Have all bits been shifted out?
spi_state <= (bit_counter_done)? POST_IDLE : CLK_REG;
bit_counter <= bit_counter_next;
if (datain_edge == CLK_IDLE) begin
datain_reg <= datain_next;
end
if (dataout_edge == CLK_IDLE && ~bit_counter_done) begin
dataout_reg <= dataout_next;
end
sclk_reg <= CLK_IDLE; //transition to falling when CLK_IDLE == 0
end
sclk_counter <= sclk_counter_next;
end
// Drive clock to idle state
POST_IDLE: begin
if (sclk_counter_done) begin
spi_state <= IDLE_SEN;
end
sclk_counter <= sclk_counter_next;
sclk_reg <= CLK_IDLE;
end
// Drive serial enables to idle state
IDLE_SEN: begin
if (sclk_counter_done) begin
ready_reg <= 1'b1;
readback_stb <= 1'b1;
spi_state <= WAIT_TRIG;
end
sclk_counter <= sclk_counter_next;
sclk_reg <= CLK_IDLE;
end
default: spi_state <= WAIT_TRIG;
endcase //spi_state
end
end
assign debug = {
8'h00, //8
trigger_spi, spi_state, //4
sclk, mosi, miso, ready, //4
1'b0, bit_counter[6:0], //8
sclk_counter_done, bit_counter_done, //2
sclk_counter[5:0] //6
};
endmodule : ctrlport_to_simple_spi

View File

@ -0,0 +1,86 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_to_simple_spi_regmap_utils.vh
//
// Description:
//
// The constants in this file are autogenerated by XmlParse.
//
//===============================================================================
// A numerically ordered list of registers and their HDL source files
//===============================================================================
// SCLK_DIVIDER : 0x0 (b310_core.sv)
// SPI_PERSONALITY : 0x4 (b310_core.sv)
// SPI_MOSI_DATA : 0x8 (b310_core.sv)
// SPI_STATUS : 0xC (b310_core.sv)
// SPI_READBACK : 0x10 (b310_core.sv)
//===============================================================================
// RegTypes
//===============================================================================
//===============================================================================
// Register Group CTRLPORT_TO_SIMPLE_SPI_REGMAP
//===============================================================================
// SCLK_DIVIDER Register (from b310_core.sv)
localparam SCLK_DIVIDER = 'h0; // Register Offset
localparam SCLK_DIVIDER_SIZE = 32; // register width in bits
localparam SCLK_DIVIDER_MASK = 32'h1FFFF;
localparam SPI_CLK_DIV_SIZE = 16; //SCLK_DIVIDER:SPI_CLK_DIV
localparam SPI_CLK_DIV_MSB = 15; //SCLK_DIVIDER:SPI_CLK_DIV
localparam SPI_CLK_DIV = 0; //SCLK_DIVIDER:SPI_CLK_DIV
localparam FORCE_SS_SIZE = 1; //SCLK_DIVIDER:FORCE_SS
localparam FORCE_SS_MSB = 16; //SCLK_DIVIDER:FORCE_SS
localparam FORCE_SS = 16; //SCLK_DIVIDER:FORCE_SS
// SPI_PERSONALITY Register (from b310_core.sv)
localparam SPI_PERSONALITY = 'h4; // Register Offset
localparam SPI_PERSONALITY_SIZE = 32; // register width in bits
localparam SPI_PERSONALITY_MASK = 32'hFFFFFFFF;
localparam SPI_SLAVE_SELECT_SIZE = 24; //SPI_PERSONALITY:SPI_SLAVE_SELECT
localparam SPI_SLAVE_SELECT_MSB = 23; //SPI_PERSONALITY:SPI_SLAVE_SELECT
localparam SPI_SLAVE_SELECT = 0; //SPI_PERSONALITY:SPI_SLAVE_SELECT
localparam SPI_NUM_BITS_SIZE = 6; //SPI_PERSONALITY:SPI_NUM_BITS
localparam SPI_NUM_BITS_MSB = 29; //SPI_PERSONALITY:SPI_NUM_BITS
localparam SPI_NUM_BITS = 24; //SPI_PERSONALITY:SPI_NUM_BITS
localparam SPI_MISO_EDGE_SIZE = 1; //SPI_PERSONALITY:SPI_MISO_EDGE
localparam SPI_MISO_EDGE_MSB = 30; //SPI_PERSONALITY:SPI_MISO_EDGE
localparam SPI_MISO_EDGE = 30; //SPI_PERSONALITY:SPI_MISO_EDGE
localparam SPI_MOSI_EDGE_SIZE = 1; //SPI_PERSONALITY:SPI_MOSI_EDGE
localparam SPI_MOSI_EDGE_MSB = 31; //SPI_PERSONALITY:SPI_MOSI_EDGE
localparam SPI_MOSI_EDGE = 31; //SPI_PERSONALITY:SPI_MOSI_EDGE
// SPI_MOSI_DATA Register (from b310_core.sv)
localparam SPI_MOSI_DATA = 'h8; // Register Offset
localparam SPI_MOSI_DATA_SIZE = 32; // register width in bits
localparam SPI_MOSI_DATA_MASK = 32'hFFFFFFFF;
localparam SPI_DATA_OUT_SIZE = 32; //SPI_MOSI_DATA:SPI_DATA_OUT
localparam SPI_DATA_OUT_MSB = 31; //SPI_MOSI_DATA:SPI_DATA_OUT
localparam SPI_DATA_OUT = 0; //SPI_MOSI_DATA:SPI_DATA_OUT
// SPI_STATUS Register (from b310_core.sv)
localparam SPI_STATUS = 'hC; // Register Offset
localparam SPI_STATUS_SIZE = 32; // register width in bits
localparam SPI_STATUS_MASK = 32'h1;
localparam SPI_READY_SIZE = 1; //SPI_STATUS:SPI_READY
localparam SPI_READY_MSB = 0; //SPI_STATUS:SPI_READY
localparam SPI_READY = 0; //SPI_STATUS:SPI_READY
// SPI_READBACK Register (from b310_core.sv)
localparam SPI_READBACK = 'h10; // Register Offset
localparam SPI_READBACK_SIZE = 32; // register width in bits
localparam SPI_READBACK_MASK = 32'hFFFFFFFF;
localparam SPI_READBACK_DATA_SIZE = 32; //SPI_READBACK:SPI_READBACK_DATA
localparam SPI_READBACK_DATA_MSB = 31; //SPI_READBACK:SPI_READBACK_DATA
localparam SPI_READBACK_DATA = 0; //SPI_READBACK:SPI_READBACK_DATA
// function SCLK_DIVIDERRec not implemented for Verilog
// function SPI_PERSONALITYRec not implemented for Verilog
// function SPI_MOSI_DATARec not implemented for Verilog
// function SPI_STATUSRec not implemented for Verilog
// function SPI_READBACKRec not implemented for Verilog

View File

@ -11,14 +11,16 @@
//
// Parameters:
//
// BASE_ADDRESS : Base address for CtrlPort registers.
// BASE_ADDRESS : Base address for CtrlPort registers.
// REG_STRIDE_SIZE : Address separation between registers.
//
`default_nettype wire
module ctrlport_to_wb_i2c #(
parameter BASE_ADDRESS = 0
parameter BASE_ADDRESS = 0,
parameter REG_STRIDE_SIZE = 1
) (
//---------------------------------------------------------------
// ControlPort Slave
@ -64,10 +66,11 @@ module ctrlport_to_wb_i2c #(
wire wb_ack_o;
wire [31:0] wb_dat_o;
// Check for address to be in range [base_addr..base_addr+8)
localparam NUM_ADDRESSES = 8;
// Check for address to be in range [base_addr..base_addr+8*stride)
localparam REG_WINDOW_SIZE = 8*REG_STRIDE_SIZE;
wire address_in_range = (s_ctrlport_req_addr >= BASE_ADDRESS) &&
(s_ctrlport_req_addr < BASE_ADDRESS + NUM_ADDRESSES);
(s_ctrlport_req_addr < BASE_ADDRESS + REG_WINDOW_SIZE);
// Following chapter 3.2.3 (classic standard SINGLE WRITE cycle) of
// https://cdn.opencores.org/downloads/wbspec_b4.pdf
@ -100,59 +103,62 @@ module ctrlport_to_wb_i2c #(
end else if (s_ctrlport_req_wr) begin
// Assume there is a valid address
//1-1 translation of wb to ctrl port interface
if (s_ctrlport_req_addr < 8) begin
if (address_in_range) begin
wb_cyc_i <= 1'b1;
wb_we_i <= 1'b1;
wb_dat_i <= s_ctrlport_req_data;
end
case (s_ctrlport_req_addr)
BASE_ADDRESS + WB_PRER_LO: begin
wb_adr_i = WB_PRER_LO;
BASE_ADDRESS + WB_PRER_LO * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_PRER_LO;
end
BASE_ADDRESS + WB_PRER_HI: begin
wb_adr_i = WB_PRER_HI;
BASE_ADDRESS + WB_PRER_HI * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_PRER_HI;
end
BASE_ADDRESS + WB_CTR: begin
wb_adr_i = WB_CTR;
BASE_ADDRESS + WB_CTR * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_CTR;
end
BASE_ADDRESS + WB_TXR: begin
wb_adr_i = WB_TXR;
BASE_ADDRESS + WB_TXR * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_TXR;
end
BASE_ADDRESS + WB_CR: begin
wb_adr_i = WB_CR;
BASE_ADDRESS + WB_CR * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_CR;
end
endcase
// Read requests
end else if (s_ctrlport_req_rd) begin
// Assume there is a valid address
wb_cyc_i <= 1'b1;
wb_we_i <= 1'b0;
case (s_ctrlport_req_addr)
BASE_ADDRESS + WB_PRER_LO: begin
wb_adr_i <= WB_PRER_LO;
end
BASE_ADDRESS + WB_PRER_HI: begin
wb_adr_i <= WB_PRER_HI;
end
BASE_ADDRESS + WB_CTR: begin
wb_adr_i <= WB_CTR;
end
BASE_ADDRESS + WB_RXR: begin
wb_adr_i = WB_RXR;
end
BASE_ADDRESS + WB_SR: begin
wb_adr_i = WB_SR;
end
// Respond with 0
default: begin
s_ctrlport_resp_ack <= 1'b1;
s_ctrlport_resp_data <= 'b0;
end
if(address_in_range) begin
// Assume there is a valid address
wb_cyc_i <= 1'b1;
wb_we_i <= 1'b0;
endcase
case (s_ctrlport_req_addr)
BASE_ADDRESS + WB_PRER_LO * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_PRER_LO;
end
BASE_ADDRESS + WB_PRER_HI * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_PRER_HI;
end
BASE_ADDRESS + WB_CTR * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_CTR;
end
BASE_ADDRESS + WB_RXR * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_RXR;
end
BASE_ADDRESS + WB_SR * REG_STRIDE_SIZE: begin
wb_adr_i <= WB_SR;
end
// Respond with 0
default: begin
s_ctrlport_resp_ack <= 1'b1;
s_ctrlport_resp_data <= 'b0;
end
endcase
end
end
end
end

View File

@ -0,0 +1,191 @@
//
// Copyright 2025 Ettus Research, A National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_to_wb_uart
//
// Description:
//
// This module wraps a simple UART and provides a ControlPort interface.
//
// Parameters:
//
// BASE_ADDRESS : Base address for CtrlPort registers.
// REG_STRIDE_SIZE : Address separation between registers.
//
`default_nettype wire
module ctrlport_to_wb_uart #(
parameter BASE_ADDRESS = 0,
parameter REG_STRIDE_SIZE = 1,
parameter CLKDIV_DEFAULT = 0,
parameter RX_SIZE = 8
) (
//---------------------------------------------------------------
// ControlPort Slave
//---------------------------------------------------------------
input wire ctrlport_clk,
input wire ctrlport_rst,
input wire s_ctrlport_req_wr,
input wire s_ctrlport_req_rd,
input wire [19:0] s_ctrlport_req_addr,
input wire [31:0] s_ctrlport_req_data,
output reg s_ctrlport_resp_ack,
output reg [ 1:0] s_ctrlport_resp_status = 0,
output reg [31:0] s_ctrlport_resp_data = 0,
//---------------------------------------------------------------
// UART signals
//---------------------------------------------------------------
input wire uart_rx,
output wire uart_tx
);
`include "../rfnoc/core/ctrlport.vh"
//---------------------------------------------------------------
// Translating CtrlPort <-> Wishbone UART core
//---------------------------------------------------------------
reg wb_cyc_i; // Active bus cycle
reg wb_we_i = 1'b0; // Write access
reg [ 2:0] wb_adr_i = 3'b0;
reg [31:0] wb_dat_i = 32'b0;
wire wb_ack_o;
wire [31:0] wb_dat_o;
localparam SUART_CLKDIV = 0;
localparam SUART_TXLEVEL = 1;
localparam SUART_RXLEVEL = 2;
localparam SUART_TXCHAR = 3;
localparam SUART_RXCHAR = 4;
// Check for address to be in range [base_addr..base_addr+5*stride)
localparam REG_WINDOW_SIZE = 5*REG_STRIDE_SIZE;
wire address_in_range = (s_ctrlport_req_addr >= BASE_ADDRESS) &&
(s_ctrlport_req_addr < BASE_ADDRESS + REG_WINDOW_SIZE);
// Following chapter 3.2.3 (classic standard SINGLE WRITE cycle) of
// https://cdn.opencores.org/downloads/wbspec_b4.pdf
always @(posedge ctrlport_clk) begin
// Reset internal registers and responses
if (ctrlport_rst) begin
s_ctrlport_resp_ack <= 0;
s_ctrlport_resp_data <= 0;
s_ctrlport_resp_status <= CTRL_STS_OKAY;
wb_cyc_i <= 0;
wb_we_i <= 0;
wb_dat_i <= 0;
wb_adr_i <= 0;
end else begin
// Request independent default assignments
s_ctrlport_resp_ack <= 1'b0;
// Wait for ack on active bus transactions
if (wb_cyc_i) begin
if (wb_ack_o) begin
// End bus cycle and generate response
wb_cyc_i <= 1'b0;
s_ctrlport_resp_ack <= 1'b1;
s_ctrlport_resp_data <= wb_dat_o;
//removed error condition since there is no error signal in UART
//core
s_ctrlport_resp_status <= CTRL_STS_OKAY;
end
// Write requests
end else if (s_ctrlport_req_wr) begin
// Assume there is a valid address
//1-1 translation of wb to ctrl port interface
if (address_in_range) begin
wb_cyc_i <= 1'b1;
wb_we_i <= 1'b1;
wb_dat_i <= s_ctrlport_req_data;
end
case (s_ctrlport_req_addr)
BASE_ADDRESS + SUART_CLKDIV * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_CLKDIV;
end
BASE_ADDRESS + SUART_TXLEVEL * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_TXLEVEL;
end
BASE_ADDRESS + SUART_RXLEVEL * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_RXLEVEL;
end
BASE_ADDRESS + SUART_TXCHAR * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_TXCHAR;
end
BASE_ADDRESS + SUART_RXCHAR * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_RXCHAR;
end
endcase
// Read requests
end else if (s_ctrlport_req_rd) begin
if(address_in_range) begin
// Assume there is a valid address
wb_cyc_i <= 1'b1;
wb_we_i <= 1'b0;
case (s_ctrlport_req_addr)
BASE_ADDRESS + SUART_CLKDIV * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_CLKDIV;
end
BASE_ADDRESS + SUART_TXLEVEL * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_TXLEVEL;
end
BASE_ADDRESS + SUART_RXLEVEL * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_RXLEVEL;
end
BASE_ADDRESS + SUART_TXCHAR * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_TXCHAR;
end
BASE_ADDRESS + SUART_RXCHAR * REG_STRIDE_SIZE: begin
wb_adr_i <= SUART_RXCHAR;
end
// Respond with 0
default: begin
s_ctrlport_resp_ack <= 1'b1;
s_ctrlport_resp_data <= 'b0;
end
endcase
end
end
end
end
//wishbone-based UART core
// Makin default baud
simple_uart #(
.CLKDIV_DEFAULT(CLKDIV_DEFAULT),
.RX_SIZE(RX_SIZE)
) simple_uart_i (
.clk_i(ctrlport_clk),
.rst_i(ctrlport_rst),
.adr_i(wb_adr_i),
.dat_i(wb_dat_i),
.dat_o(wb_dat_o),
.we_i(wb_we_i),
.stb_i(wb_cyc_i),
.cyc_i(wb_cyc_i),
.ack_o(wb_ack_o),
.rx_int_o(),
.tx_int_o(),
.tx_o(uart_tx),
.rx_i(uart_rx),
.baud_o()
);
endmodule

View File

@ -5,6 +5,11 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
#
##################################################
# HB Filter Includes
##################################################
include $(BASE_DIR)/../lib/dsp/hb_filter/Makefile.srcs
##################################################
# DSP Sources
##################################################
@ -44,9 +49,26 @@ srl.v \
tx_frontend.v \
variable_delay_line.v \
axi_fir_multisample_filter.sv \
axis_fir_ms_preload.sv \
complex_multiply.sv \
complex_multiply_iq.sv \
complex_multiply_stb.sv \
timed_complex_gain_regs.svh \
timed_complex_gain.sv \
utils/cic_utils_pkg.sv \
cic_integrator.sv \
cic_integrator_core.sv \
cic_staircase_pipeline.sv \
cic_destaircase_pipeline.sv \
dsp48_add3.sv \
dsp48_accum.sv \
dsp48_prefix_accum.sv \
cic_decimator.sv \
cic_interpolator.sv \
cic_comb_filter.sv \
cic_barrel_shift.sv \
cic_filter_decim.sv \
cic_filter_interp.sv \
))
DSP_SRCS += $(HB_FILTER_SRCS)

View File

@ -27,13 +27,19 @@
// ACCUM_WIDTH - Accumulator width
// COEFFS_VEC - Vector of NUM_COEFFS values, each of width COEFF_WIDTH to
// initialize coeffs. Defaults to an impulse.
// RELOADABLE_COEFFS - Enable (1) or disable (0) reloading coefficients at runtime (via reload bus)
// BLANK_OUTPUT - Disable (1) or enable (0) output when initially filling internal pipeline
// USE_EMBEDDED_REGS_COEFFS - Reduce register usage by only using embedded registers in DSP slices.
// Updating taps while streaming will cause temporary output corruption!
//
// RELOADABLE_COEFFS - Enable (1) or disable (0) reloading coefficients
// at runtime (via reload bus)
// BLANK_OUTPUT - Disable (1) or enable (0) output when initially filling internal
// pipeline
// USE_EMBEDDED_REGS_COEFFS - Reduce register usage by only using embedded registers in DSP
// slices. Updating taps while streaming will cause temporary output
// corruption!
// RESET_DATA_PATH - Reset FIR arithmetic pipeline registers when
// reset or clear assert.
//
// Notes:
// - If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at least once as COEFFS_VEC is ignored!
// - If using USE_EMBEDDED_REGS_COEFFS, coefficients must be written at least once as COEFFS_VEC is
// ignored!
// - If using RELOADABLE_COEFFS, coefficients must be written in reverse order!
// - Optimizations: For static coefficients (RELOADABLE_COEFFS = 0), zero and symmetric coefficient
// optimizations are automatically enabled to reduce DSP slice utilization.
@ -52,7 +58,8 @@ module axi_fir_multisample_filter #(
bit [COEFF_WIDTH-1:0] COEFFS_VEC [NUM_COEFFS] = '{0: (1 << (COEFF_WIDTH-1)) - 1, default: 0},
bit RELOADABLE_COEFFS = 1,
bit BLANK_OUTPUT = 1,
bit USE_EMBEDDED_REGS_COEFFS = 1
bit USE_EMBEDDED_REGS_COEFFS = 1,
bit RESET_DATA_PATH = 1
)(
// clocks and control signals
input logic clk,
@ -112,7 +119,8 @@ module axi_fir_multisample_filter #(
? check_coeffs_symmetric() : 0;
localparam int NUM_SLICES = OPTIMIZE_FOR_SYMMETRIC_COEFFS
? NUM_COEFFS/2 + NUM_COEFFS[0] : NUM_COEFFS;
localparam int PIPELINE_DELAY = NUM_SLICES + 5 // +4 pipeline depth in fir_filter_slice.v, +1 of shift register
// +4 pipeline depth in fir_filter_slice.v, +1 of shift register
localparam int PIPELINE_DELAY = NUM_SLICES + 5
- (OPTIMIZE_FOR_ZERO_COEFFS ? count_zeros_up_to_index(NUM_SLICES) : 0);
localparam int SHIFT_REG_WIDTH = (NUM_SPC + 1) * NUM_COEFFS; // length of shift register
@ -262,7 +270,8 @@ module axi_fir_multisample_filter #(
end else begin
localparam int GAP = OPTIMIZE_FOR_ZERO_COEFFS ? count_zeros_up_to_index(j) : 0;
localparam bit APPLY_SYMMETRY = (OPTIMIZE_FOR_SYMMETRIC_COEFFS == 1) // enabled?
&& !((NUM_COEFFS[0] == 1) && (j == NUM_SLICES-1)); // not the center tap for odd NUM_COEFFS
&& !((NUM_COEFFS[0] == 1) && (j == NUM_SLICES-1));
// not the center tap for odd NUM_COEFFS
fir_filter_slice #(
.IN_WIDTH(IN_WIDTH),
@ -271,8 +280,8 @@ module axi_fir_multisample_filter #(
.OUT_WIDTH(ACCUM_WIDTH))
fir_filter_slice (
.clk(clk),
.reset(reset),
.clear(clear),
.reset(RESET_DATA_PATH ? (reset | clear) : 1'b0),
.clear(1'b0),
.sample_in_stb(s_axis_data_tvalid & s_axis_data_tready),
// NUM_SPC is added j times due to the pipeline delay.
// +1 for selecting the next index for the FIR result calculation
@ -287,10 +296,13 @@ module axi_fir_multisample_filter #(
// Mirror index is NUM_COEFFS-1-j, since sample_in_b inside fir_filter_slice
// has one cycle delay less that sample_in_a, we need to take it one cycle
// later such that both values are in-sync. Thus we add another NUM_SPC to the index.
.sample_in_b(APPLY_SYMMETRY ? data_shift_reg[ (NUM_COEFFS-1-j) + (j-GAP+1)*NUM_SPC + K_FLIPPED ] : '0),
.sample_in_b(APPLY_SYMMETRY ?
data_shift_reg[ (NUM_COEFFS-1-j) + (j-GAP+1)*NUM_SPC + K_FLIPPED ] : '0),
.sample_forward(),
// For proper coeffient loading, coeff_forward must be shifted in backwards. coeffs[] is already backwards
.coeff_in(((USE_EMBEDDED_REGS_COEFFS == 1) && (RELOADABLE_COEFFS == 1)) ? coeff_forward[j] : coeffs[j]),
// For proper coeffient loading, coeff_forward must be shifted in backwards.
// coeffs[] is already backwards
.coeff_in(((USE_EMBEDDED_REGS_COEFFS == 1) && (RELOADABLE_COEFFS == 1)) ?
coeff_forward[j] : coeffs[j]),
.coeff_forward(coeff_forward[j+1]),
.coeff_load_stb(coeff_load_stb),
.sample_accum(sample_accum[j]),

View File

@ -0,0 +1,187 @@
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: axis_fir_ms_preload
//
// Description:
// Thin wrapper around axi_fir_multisample_filter that optionally pre-fills
// the filter pipeline with zeros on reset/clear before allowing real data
// through such that the filter behaves like after a cold start.
//
// When PRELOAD_ZEROES=1: on reset or clear, feeds zeroes
// into the FIR while blocking upstream (s_axis_data_tready=0) and
// suppressing downstream output (m_axis_data_tvalid=0).
// After preloading, the wrapper becomes transparent.
//
// When PRELOAD_ZEROES=0: transparent pass-through; clear is still wired to
// the FIR.
//
// Parameters:
// PRELOAD_ZEROES_NUM_TAPS:
// The number of serial pipeline stages (taps) that axi_fir_multisample_filter
// actually instantiates after applying its coefficient optimizations.
// This determines the number of cycles required to flush the FIR pipeline
// by feeding zeros, and thus how long the input/output should be gated during
// preload.
//
// Standard FIR (no optimizations, or RELOADABLE_COEFFS=1, default behavior):
// PRELOAD_ZEROES_NUM_TAPS = NUM_COEFFS
// Example: 41-tap FIR -> 41 stages, PIPELINE_DELAY = 46
//
// Halfband FIR (RELOADABLE_COEFFS=0, symmetric + alternating-zero taps):
// PRELOAD_ZEROES_NUM_TAPS = (NUM_COEFFS + 5) / 4
// Example: HBF47 -> 13 stages, PIPELINE_DELAY = 18
// HBF63 -> 17 stages, PIPELINE_DELAY = 22
//
`default_nettype none
module axis_fir_ms_preload #(
int IN_WIDTH = 16,
int NUM_SPC = 4,
int COEFF_WIDTH = 16,
int OUT_WIDTH = 16,
int NUM_COEFFS = 41,
int CLIP_BITS = $clog2(NUM_COEFFS),
int ACCUM_WIDTH = IN_WIDTH + COEFF_WIDTH + $clog2(NUM_COEFFS) - 1,
bit [COEFF_WIDTH-1:0] COEFFS_VEC [NUM_COEFFS] = '{0: (1 << (COEFF_WIDTH-1)) - 1, default: 0},
bit RELOADABLE_COEFFS = 1,
bit BLANK_OUTPUT = 1,
bit USE_EMBEDDED_REGS_COEFFS = 1,
bit PRELOAD_ZEROES = 0,
int PRELOAD_ZEROES_NUM_TAPS = NUM_COEFFS // See module description above
) (
input wire clk,
input wire reset,
input wire clear,
input wire [NUM_SPC*IN_WIDTH-1:0] s_axis_data_tdata,
input wire s_axis_data_tlast,
input wire s_axis_data_tvalid,
output logic s_axis_data_tready,
output logic [NUM_SPC*OUT_WIDTH-1:0] m_axis_data_tdata,
output logic m_axis_data_tlast,
output logic m_axis_data_tvalid,
input wire m_axis_data_tready,
input wire [COEFF_WIDTH-1:0] s_axis_reload_tdata,
input wire s_axis_reload_tvalid,
input wire s_axis_reload_tlast,
output logic s_axis_reload_tready
);
//----------------------------------------------------------------------------
// Preload logic
//----------------------------------------------------------------------------
// Goal: after preloading, the filter behaves identically to a cold-start.
// The only observable difference is the PIPELINE_DELAY backpressure cycles on reset/clear.
//
// PRELOAD_ZEROES_NUM_TAPS:
// number of serial DSP stages in the wrapped FIR
// (after potential optimization, see module description).
// The FIR's internal PIPELINE_DELAY equals
// PRELOAD_ZEROES_NUM_TAPS + 5
// PIPELINE_FILL_CYCLES:
// number of cycles needed to fill FIR pipeline with zeroes.
// ROUND_CLIP_DELAY:
// axi_round_and_clip is not reset by clear and holds 2 in-flight zero beats
// after PIPELINE_FILL_CYCLES.
// Drain beats drive fir_s_tvalid=0 so they exit through fir_m_tready=1
// (suppressed) before the output gate opens, ensuring fir_m_tvalid=0.
localparam int FIR_DSP_PIPELINE_DELAY = PRELOAD_ZEROES_NUM_TAPS + 5;
localparam int PIPELINE_FILL_CYCLES = NUM_COEFFS + FIR_DSP_PIPELINE_DELAY;
localparam int ROUND_CLIP_DELAY = 2; // axi_round (1 cycle) + axi_clip (1 cycle)
localparam int PIPELINE_DELAY = PIPELINE_FILL_CYCLES + ROUND_CLIP_DELAY;
localparam int CNT_W = $clog2(PIPELINE_DELAY + 1);
logic [CNT_W-1:0] preload_cnt;
logic filling; // fill phase: drive zeros into FIR with tvalid=1
logic preloading; // entire preload window (fill + drain), gate input/output
always_ff @(posedge clk) begin
if (reset | clear) begin
preload_cnt <= '0;
filling <= PRELOAD_ZEROES;
preloading <= PRELOAD_ZEROES;
end else if (preloading) begin
preload_cnt <= preload_cnt + 1'b1;
filling <= PRELOAD_ZEROES && (preload_cnt < PIPELINE_FILL_CYCLES - 1);
preloading <= PRELOAD_ZEROES && (preload_cnt < PIPELINE_DELAY - 1);
end
end
//----------------------------------------------------------------------------
// AXI-Stream muxing
//----------------------------------------------------------------------------
// Internal signals to/from the wrapped FIR
logic [NUM_SPC*IN_WIDTH-1:0] fir_s_tdata;
logic fir_s_tvalid;
logic fir_s_tlast;
logic fir_s_tready;
logic [NUM_SPC*OUT_WIDTH-1:0] fir_m_tdata;
logic fir_m_tvalid;
logic fir_m_tlast;
logic fir_m_tready;
// Fill phase (filling=1): feed zeros with tvalid=1 to fill the FIR shift register.
// Drain phase (filling=0, preloading=1): drive tvalid=0 so round+clip empties its
// pipeline, ensuring fir_m_tvalid=0 when the output gate opens.
// Normal phase (preloading=0): fully transparent pass-through.
// Upstream (s_axis_data_tready) is held low for the entire preload window so no real
// data is consumed until the filter is clean.
assign fir_s_tdata = filling ? '0 : s_axis_data_tdata;
assign fir_s_tvalid = filling ? 1'b1 : (preloading ? 1'b0 : s_axis_data_tvalid);
assign fir_s_tlast = filling ? 1'b0 : s_axis_data_tlast;
assign s_axis_data_tready = preloading ? 1'b0 : fir_s_tready;
// Output gate: hold output suppressed for the entire preload window.
// fir_m_tready=1 drains round+clip silently during both fill and drain phases.
// When the gate opens (preloading=0), fir_m_tvalid is guaranteed to be 0.
assign fir_m_tready = preloading ? 1'b1 : m_axis_data_tready;
assign m_axis_data_tdata = fir_m_tdata;
assign m_axis_data_tlast = fir_m_tlast;
assign m_axis_data_tvalid = preloading ? 1'b0 : fir_m_tvalid;
//----------------------------------------------------------------------------
// Wrapped FIR instance
//----------------------------------------------------------------------------
localparam bit RESET_DATA_PATH = !PRELOAD_ZEROES;
axi_fir_multisample_filter #(
.IN_WIDTH (IN_WIDTH),
.NUM_SPC (NUM_SPC),
.COEFF_WIDTH (COEFF_WIDTH),
.OUT_WIDTH (OUT_WIDTH),
.NUM_COEFFS (NUM_COEFFS),
.CLIP_BITS (CLIP_BITS),
.ACCUM_WIDTH (ACCUM_WIDTH),
.COEFFS_VEC (COEFFS_VEC),
.RELOADABLE_COEFFS (RELOADABLE_COEFFS),
.BLANK_OUTPUT (BLANK_OUTPUT),
.USE_EMBEDDED_REGS_COEFFS(USE_EMBEDDED_REGS_COEFFS),
.RESET_DATA_PATH (RESET_DATA_PATH)
) fir_inst (
.clk (clk),
.reset (reset),
.clear (clear),
.s_axis_data_tdata (fir_s_tdata),
.s_axis_data_tlast (fir_s_tlast),
.s_axis_data_tvalid (fir_s_tvalid),
.s_axis_data_tready (fir_s_tready),
.m_axis_data_tdata (fir_m_tdata),
.m_axis_data_tlast (fir_m_tlast),
.m_axis_data_tvalid (fir_m_tvalid),
.m_axis_data_tready (fir_m_tready),
.s_axis_reload_tdata (s_axis_reload_tdata),
.s_axis_reload_tvalid(s_axis_reload_tvalid),
.s_axis_reload_tlast (s_axis_reload_tlast),
.s_axis_reload_tready(s_axis_reload_tready)
);
endmodule : axis_fir_ms_preload
`default_nettype wire

View File

@ -0,0 +1,224 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_barrel_shift
//
// Description:
//
// Rate-dependent output scaling stage shared by the CIC decimation and
// interpolation filters. Accepts SPC I/Q samples at the full accumulator
// width (IN_WIDTH bits per sample) and right-shifts each I and Q component
// to normalize the CIC gain, then rounds the result to OUT_WIDTH/2 bits per
// component.
//
// Each component enters the shifter scaled up by one bit, so the most
// significant bit that the shift would otherwise discard survives as the LSB
// of the shifted value. The final quantization is then an ordinary
// axi_round_and_clip stage that removes that single bit with
// round-to-nearest. Rounding this way keeps the quantization error zero-mean
// instead of biasing every sample towards negative infinity as plain
// truncation does.
//
// The required shift for rate factor R is ceil(log2((R*M)^N)), where M=1
// (differential delay) and N=ORDER. A compile-time lookup table
// (shift_lut[1:MAX_RATE]) pre-computes this value for every supported rate.
//
// The shift itself is a SHIFT_W-stage binary barrel shifter: stage k
// conditionally shifts right by 2^k positions when shift_amount[k] is set.
// Pipeline depth is SHIFT_W = ceil(log2(IN_WIDTH/2 - OUT_WIDTH/2 + 1)) cycles
// plus the two register stages of the round and clip.
//
// Parameters:
//
// SPC : Number of samples per clock cycle (power of 2, >= 1).
// MAX_RATE : Maximum rate factor (decimation or interpolation ratio).
// Sizes the compile-time shift lookup table.
// ORDER : CIC filter order (number of integrator/comb stages).
// Used to compute the per-rate shift value.
//
// Ports:
//
// rate_factor : Active rate factor (1..MAX_RATE). Must be stable while
// data is in flight. Sampled through a 2-stage pipeline to
// align the shift with data latency through upstream stages.
// data_in : AXI-Stream input; tdata is SPC * IN_WIDTH bits wide,
// packed as {I[IN_WIDTH/2-1:0], Q[IN_WIDTH/2-1:0]} per sample.
// data_out : AXI-Stream output; tdata is SPC * OUT_WIDTH bits wide,
// packed as {I[OUT_WIDTH/2-1:0], Q[OUT_WIDTH/2-1:0]} per sample.
//
`default_nettype none
module cic_barrel_shift #(
parameter int SPC = 4,
parameter int MAX_RATE = 255,
parameter int ORDER = 4
)(
input wire clk,
input wire rst,
input wire clear,
// Active rate factor (decimation or interpolation ratio, 1..MAX_RATE).
input wire [$clog2(MAX_RATE+1)-1:0] rate_factor,
// Data in: SPC samples, each IN_WIDTH bits wide ({I, Q} each IN_WIDTH/2 bits)
AxiStreamIf.slave data_in,
// Data out: SPC samples, each OUT_WIDTH bits wide ({I, Q} each OUT_WIDTH/2 bits)
AxiStreamIf.master data_out
);
import cic_utils_pkg::*;
localparam int IN_WIDTH = data_in.DATA_WIDTH / SPC;
localparam int OUT_WIDTH = data_out.DATA_WIDTH / SPC;
localparam int SHIFT_W = barrel_shift_width(IN_WIDTH, OUT_WIDTH);
localparam int IN_COMP_W = IN_WIDTH/2;
localparam int OUT_COMP_W = OUT_WIDTH/2;
// One extra LSB so the bit dropped by the shift is available for rounding.
localparam int SHIFT_COMP_W = IN_COMP_W + 1;
// The normalized result fits in the output range; retain the round bit.
localparam int ROUND_COMP_W = OUT_COMP_W + 1;
//---------------------------------------------------------------------------
// Compile-time shift lookup table
//
// shift_lut[r] = ceil(log2((r * 1)^ORDER)) = ceil(log2(r**ORDER))
// (differential delay M=1 is absorbed into the formula).
//---------------------------------------------------------------------------
logic [SHIFT_W-1:0] shift_lut [1:MAX_RATE];
for (genvar r = 1; r <= MAX_RATE; r++) begin : gen_shift_lut
localparam int SHIFT_R = $clog2((longint'(r)) ** ORDER);
assign shift_lut[r] = SHIFT_W'(SHIFT_R);
end : gen_shift_lut
//---------------------------------------------------------------------------
// Reading the shift amount from the LUT.
//
// The shift_lut is implemented in a BRAM. In order to improve timing a 2
// stage pipeline without any reset is used to allow the address register and
// the output register of the BRAM to be activated.
// The rate_factor is assumed to be stable while data is in flight, so the shift amount is
// guaranteed to be stable when the data arrives at the barrel shifter.
//---------------------------------------------------------------------------
logic [SHIFT_W-1:0] shift_amount [2];
always_ff @(posedge clk) begin
shift_amount[0] <= shift_lut[rate_factor];
shift_amount[1] <= shift_amount[0];
end
// Lane signals aggregated from the per-sample generate block below.
logic [SPC-1:0] lane_tvalid;
logic [SPC-1:0] lane_tlast;
logic [SPC-1:0] lane_tready;
logic [SPC-1:0][OUT_WIDTH-1:0] lane_tdata;
// All lanes share the same tready (aligned by construction).
assign data_in.tready = lane_tready[0];
for (genvar samp_idx = 0; samp_idx < SPC; samp_idx++) begin : gen_shift_out
// Arithmetic right-shift pipeline: stages shift right in descending bit
// order, from the most significant set bit to the least significant one.
logic signed [SHIFT_COMP_W-1:0] q_shifted[SHIFT_W+1];
logic signed [SHIFT_COMP_W-1:0] i_shifted[SHIFT_W+1];
logic [SHIFT_W:0] shifter_tvalid;
logic shifter_tlast[SHIFT_W+1];
logic stage_tready [SHIFT_W+1];
// A rate change leading to a new shift amount is only legal while the
// barrel-shifter pipeline is empty. The shift amount is shared by all
// pipeline stages, so changing it with valid data in flight would apply the
// new amount to part of an old word. This assumption is also used by the
// barrel-shifter multicycle path in
// fpga/usrp3/top/x400/constraints/timing/common.xdc.
// The assertion is done even when reset or clear is present to ensure the
// multicycle path is used correctly at all times.
// synthesis translate_off
assert property (
@(posedge clk) $changed(shift_amount[1]) |-> (shifter_tvalid == 0)[*2]
) else
$error("cic_barrel_shift: data processed before shift_amount was settled");
// synthesis translate_on
// Input stage: scale up by one bit so the shift keeps the round bit.
assign q_shifted[SHIFT_W] = {data_in.tdata[IN_WIDTH*samp_idx +: IN_COMP_W], 1'b0};
assign i_shifted[SHIFT_W] = {data_in.tdata[IN_WIDTH*samp_idx + IN_COMP_W +: IN_COMP_W], 1'b0};
assign shifter_tvalid[SHIFT_W] = data_in.tvalid;
assign shifter_tlast[SHIFT_W] = data_in.tlast;
for (genvar shift_bit = SHIFT_W - 1; shift_bit >= 0; shift_bit--) begin : gen_shift_bits
// Combinational next-stage values for this shift bit.
logic signed [SHIFT_COMP_W-1:0] q_next;
logic signed [SHIFT_COMP_W-1:0] i_next;
always_comb begin
if (shift_amount[1][shift_bit]) begin
q_next = q_shifted[shift_bit + 1] >>> (2 ** shift_bit);
i_next = i_shifted[shift_bit + 1] >>> (2 ** shift_bit);
end else begin
q_next = q_shifted[shift_bit + 1];
i_next = i_shifted[shift_bit + 1];
end
end
// Register every second stage and pass through the others. This places
// a 4:1 mux before each registered stage, which fits LUT6 best for
// Xilinx architectures.
axi_fifo #(
.WIDTH (2*SHIFT_COMP_W + 1), // {tlast, i, q}
.SIZE ((shift_bit % 2 == 1) ? 0 : -1)
) pipe_ff (
.clk (clk),
.reset (rst),
.clear (clear),
.i_tdata ({shifter_tlast[shift_bit + 1], i_next, q_next}),
.i_tvalid(shifter_tvalid[shift_bit + 1]),
.i_tready(stage_tready[shift_bit + 1]),
.o_tdata ({shifter_tlast[shift_bit], i_shifted[shift_bit], q_shifted[shift_bit]}),
.o_tvalid(shifter_tvalid[shift_bit]),
.o_tready(stage_tready[shift_bit]),
.occupied(),
.space()
);
end : gen_shift_bits
// The upper shifted bits are redundant sign extension. Keep the output
// component and the extra LSB needed for round-to-nearest.
logic [OUT_WIDTH-1:0] rounded;
logic rounded_tvalid;
logic rounded_tlast;
axi_round_and_clip_complex #(
.WIDTH_IN (ROUND_COMP_W),
.WIDTH_OUT(OUT_COMP_W),
.CLIP_BITS(0),
.FIFOSIZE (1)
) round_clip_complex (
.clk (clk),
// axi_round_and_clip has no clear input, so flush it through reset.
.reset (rst | clear),
.i_tdata ({i_shifted[0][ROUND_COMP_W-1:0], q_shifted[0][ROUND_COMP_W-1:0]}),
.i_tlast (shifter_tlast[0]),
.i_tvalid(shifter_tvalid[0]),
.i_tready(stage_tready[0]),
.o_tdata (rounded),
.o_tlast (rounded_tlast),
.o_tvalid(rounded_tvalid),
.o_tready(data_out.tready)
);
assign lane_tready[samp_idx] = stage_tready[SHIFT_W];
assign lane_tvalid[samp_idx] = rounded_tvalid;
assign lane_tlast[samp_idx] = rounded_tlast;
assign lane_tdata[samp_idx] = rounded;
end : gen_shift_out
assign data_out.tdata = lane_tdata;
// All lanes are aligned; use lane 0 for the aggregate valid/last.
assign data_out.tvalid = lane_tvalid[0];
assign data_out.tlast = lane_tlast[0];
endmodule : cic_barrel_shift
`default_nettype wire

View File

@ -0,0 +1,213 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_comb_filter
//
// Description:
//
// A CIC comb filter stage that processes multiple samples per clock cycle.
// Implements the difference equation y[n] = x[n] - x[n-1] with a fixed
// delay of 1 sample.
//
// With D=1 the delayed sample x[n-1] is always either:
// - lane (lane_idx-1) of the current input word (for lanes 1..SPC-1), or
// - lane (SPC-1) of the previous input word (for lane 0).
//
// Only a single register (prev_last_sample) is needed to hold the last
// sample of the previous word.
//
// I and Q components within each sample (each COMP_W bits wide) are
// subtracted independently to prevent borrow propagation across the
// half-word boundary.
//
// Pipeline depth is 1 clock cycle (subtraction registered on input
// transfer). The output is buffered through a single-stage axi_fifo to
// break the combinational backpressure path.
//
// data_in ------+-------------------------+
// (AXI-S) | |
// v v
// +-------------------+ +----------------------------+
// | prev_last_sample | | Subtraction Stage |
// | (registered) +->| Lane 0 : x[0] - prev |
// +-------------------+ | Lane idx: x[idx] - x[idx-1]|
// | (I/Q subtracted sep.) |
// +-------------+--------------+
// |
// v
// +-------------+-------------+
// | output_buffer (flop) |
// +-------------+-------------+
// |
// v
// data_out
// (AXI-S)
//
//
// Parameters:
// ACCUM_W : Bit width of each input/output sample, accounting for
// any register growth of the CIC filter this module is
// used in. Must have at least one bit of headroom
// compared to the actual input Sample width to
// prevent overflow in the subtraction within this module.
//
// SPC : Number of samples processed per clock cycle. Must be a
// power of 2 and at least 1.
//
// FIFO_SIZE : Log2 of the depth of the output buffer FIFO. Set to 0 for
// a single-stage flop.
//
`default_nettype none
module cic_comb_filter #(
parameter int ACCUM_W = 96,
parameter int SPC = 4,
parameter int FIFO_SIZE = 0
) (
input wire clk,
input wire rst,
// Clear comb filter (resets previous-sample register)
input wire clr,
// Data in
AxiStreamIf.slave data_in,
// Data out
AxiStreamIf.master data_out
);
import cic_utils_pkg::*;
//---------------------------------------------------------------------------
// Type definitions
//---------------------------------------------------------------------------
typedef cic_utils#(
.SPC (SPC),
.SAMP_W(ACCUM_W)
) util_c;
typedef util_c::sample_t sample_t; // Single sample (ACCUM_W bits).
//---------------------------------------------------------------------------
// Local parameters
//---------------------------------------------------------------------------
// Width of each I/Q component within a accumulator-width sample.
localparam int COMP_W = ACCUM_W / 2;
//---------------------------------------------------------------------------
// Synthesis-time checks
//---------------------------------------------------------------------------
// SPC must be a power of 2 and at least 1.
if (SPC < 1 || (SPC & (SPC - 1)) != 0) begin : gen_check_nspc
$error("cic_comb_filter: SPC must be a power of 2 and at least 1, got SPC=%0d", SPC);
end
// AXI stream interface width checks.
if (data_in.DATA_WIDTH != ACCUM_W*SPC) begin : gen_check_data_in_width
$error("cic_comb_filter: data_in DATA_WIDTH (%0d) must equal ACCUM_W*SPC (%0d)",
data_in.DATA_WIDTH, ACCUM_W*SPC);
end
if (data_out.DATA_WIDTH != ACCUM_W*SPC) begin : gen_check_data_out_width
$error("cic_comb_filter: data_out DATA_WIDTH (%0d) must equal ACCUM_W*SPC (%0d)",
data_out.DATA_WIDTH, ACCUM_W*SPC);
end
// ACCUM_W must be even to allow separate I and Q components.
if (ACCUM_W % 2 != 0) begin : gen_check_samp_w
$error({"cic_comb_filter: ACCUM_W must be even to allow separate I and Q components,",
" got ACCUM_W=%0d"}, ACCUM_W);
end
//---------------------------------------------------------------------------
// Internal signal declarations
//---------------------------------------------------------------------------
// Input handshake
logic input_transfer;
assign input_transfer = data_in.tvalid & data_in.tready;
// Previous word's last sample (lane SPC-1). For lane 0 of the current
// word, this provides x[n-1]. Cleared to zero on reset/clear, which
// naturally provides the zero initial condition for the first word.
sample_t prev_last_sample;
// Subtraction stage output (combinational, before output FIFO).
logic [ACCUM_W*SPC-1:0] sub_tdata;
//---------------------------------------------------------------------------
// Previous-sample register
//---------------------------------------------------------------------------
always_ff @(posedge clk) begin
if (rst || clr) begin
prev_last_sample <= '0;
end else if (input_transfer) begin
prev_last_sample <= data_in.tdata[ACCUM_W*(SPC-1) +: ACCUM_W];
end
end
//---------------------------------------------------------------------------
// Per-component subtraction (combinational)
//
// Concatenate {data_in.tdata, prev_last_sample} to form (SPC+1) samples
// worth of COMP_W-wide component slots. Then for each output slot k:
// sub_tdata[k] = shifted_data[k+2] - shifted_data[k]
// where +2 skips one full sample (I and Q components).
//
// This implements y[n] = x[n] - x[n-1] with I and Q subtracted
// independently to prevent borrow propagation across the half-word boundary.
//---------------------------------------------------------------------------
logic [ACCUM_W*(SPC+1)-1:0] shifted_data;
always_comb begin
shifted_data = {data_in.tdata, prev_last_sample};
end
// Iterate over all I and Q components of the input word, starting at the first
// sample of the current input word (index 2 to skip prev_last_sample).
for (genvar comp_idx = 0; comp_idx < SPC*2; comp_idx++) begin : gen_output_lanes
always_comb begin
sub_tdata[COMP_W*comp_idx +: COMP_W] =
data_in.tdata[COMP_W*comp_idx +: COMP_W] -
shifted_data[COMP_W*comp_idx +: COMP_W];
end
end : gen_output_lanes
//---------------------------------------------------------------------------
// Output FIFO (single stage)
//
// Optionally breaks the combinational path from data_out.tready to
// data_in.tready.
//---------------------------------------------------------------------------
logic [SPC*ACCUM_W:0] fifo_out_tdata;
axi_fifo #(
.WIDTH(SPC * ACCUM_W + 1),
.SIZE (FIFO_SIZE)
) output_buffer (
.clk (clk),
.reset (rst),
.clear (clr),
.i_tdata ({data_in.tlast, sub_tdata}),
.i_tvalid(data_in.tvalid),
.i_tready(data_in.tready),
.o_tdata (fifo_out_tdata),
.o_tvalid(data_out.tvalid),
.o_tready(data_out.tready),
.space (),
.occupied()
);
assign data_out.tdata = fifo_out_tdata[SPC*ACCUM_W-1:0];
assign data_out.tlast = fifo_out_tdata[SPC*ACCUM_W];
endmodule : cic_comb_filter
`default_nettype wire

View File

@ -0,0 +1,273 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_decimator
//
// Description:
//
// A N-to-1 CIC decimator stage that produces one output per N input samples.
// This module is intended for usage in the multisample CIC filter. This
// module supports changing the decimation factor at runtime, but changing
// the decimation factor while the module is actively processing data may
// cause unexpected results and corrupted output data.
//
// The decimation process is as follows:
// 1. On each clock cycle, we receive SPC input samples (one word). A word
// counter tracks the position within the current decimation group
// (0 to decimation_factor-1).
// 2. Each output slot independently knows which word position and sample
// within that word contains its needed sample. When the matching word
// arrives, the sample is grabbed directly into the output register.
// 3. Once all decimation_factor words have been received (the output word
// is fully assembled), compute_output fires and the output FIFO
// captures the completed output word on the next clock cycle.
//
// Parameters:
// SAMP_W : Bit width of each input/output samples.
// SPC : Number of samples processed per clock cycle.
// R_MAX : Maximum decimation factor supported. Used to size internal
// registers.
//
// NOTE:
// - This module always expects to receive enough input words to produce an
// output word. This greatly reduces the complexity of this module as we
// do not have to worry about not having enough input words to produce an
// output when the end of a burst is reached. The reason we can make this
// assumption is because this module is intended to be used in the
// multisample CIC filter, which includes the axi_rate_change module at
// the beginning of the chain to ensure that groups of words (SPC
// samples) are only passed to the chain in increments of SPC. The
// axi_rate_change module also ensures that the end of a burst is aligned
// with the end of a group, discarding words of a partial group at the
// end of a burst if necessary. This allows this downstream module to
// operate without needing to worry about partial groups of input samples.
// - The decimation factor can be changed at runtime, but will result in
// incorrect output data if it changes in the middle of processing a group
// of input words. It is the responsibility of the user to only change the
// decimation factor when the module is idle (not processing input words).
`default_nettype none
module cic_decimator #(
parameter int SAMP_W = 32,
parameter int SPC = 4,
parameter int R_MAX = 255
) (
input wire clk,
input wire rst,
// Clear decimator
input wire clr,
// Data in
AxiStreamIf.slave data_in,
// Data out
AxiStreamIf.master data_out,
// Configuration interface
input wire [($clog2(R_MAX+1) - 1):0] decim_factor
);
import cic_utils_pkg::*;
//---------------------------------------------------------------------------
// Parameter declarations and Type definitions
//---------------------------------------------------------------------------
// Module constants
localparam int NSPC_LOG2 = (SPC == 1) ? 1 : $clog2(SPC);
localparam int R_MAX_LOG2 = (R_MAX == 1) ? 1 : $clog2(R_MAX);
// flat_idx needs to hold (SPC-1) * (R_MAX-1), so use R_MAX_LOG2 + NSPC_LOG2 bits.
localparam int FLAT_IDX_W = R_MAX_LOG2 + NSPC_LOG2;
typedef cic_utils#(
.SPC (SPC),
.SAMP_W(SAMP_W)
) util_c;
typedef util_c::sample_t sample_t;// Single sample packed logic type (SAMP_W bits).
typedef util_c::word_t word_t; // Word type, packed array of SPC sample_t.
//---------------------------------------------------------------------------
// Synthesis-time checks
//---------------------------------------------------------------------------
// Check that SPC is a power of 2 and at least 1
if (SPC < 1 || (SPC & (SPC - 1)) != 0) begin
$error(
{"cic_decimator: SPC must be a power of 2 and at least 1.",
" Got SPC=%0d"},
SPC
);
end
// Only support R_MAX up to 255 to keep internal registers reasonably sized (R_MAX_LOG2 <= 8).
if (R_MAX > 255 || R_MAX < 1) begin
$error(
{"cic_decimator: This module only supports maximum decimation factors between 1 and 255,",
" but R_MAX=%0d was specified."},
R_MAX
);
end
//----------------------------------------------------------------------------
// Internal signal declarations
//----------------------------------------------------------------------------
// Output word register: assembled incrementally as input words arrive.
// No input buffer needed: each output slot grabs its sample directly from
// the current input word when that word's position matches the slot's need.
word_t output_word;
// Word counter: counts input words received in the current decimation group.
logic [R_MAX_LOG2-1:0] word_count;
// Control signals
logic compute_output_pre; // Combinational: same cycle as the Rth input_xfer.
logic compute_output; // Registered: 1 cycle delayed assembled output_word.
// tlast accumulation across the decimation group
logic tlast_accum;
// Output FIFO signals
logic [SPC*SAMP_W-1:0] packed_output;
// Input handshake
logic input_xfer;
assign input_xfer = data_in.tvalid & data_in.tready;
//----------------------------------------------------------------------------
// Internal logic
//----------------------------------------------------------------------------
// Unpack input tdata into a word_t
word_t input_word;
for (genvar i = 0; i < SPC; i++) begin : gen_unpack_input
assign input_word[i] = data_in.tdata[SAMP_W*i+:SAMP_W];
end
//----------------------------------------------------------------------------
// Phase 1: Word counter
//----------------------------------------------------------------------------
// Word counter: counts from 0 to decimation_factor-1
always_ff @(posedge clk) begin
if (rst || clr) begin
word_count <= '0;
end else if (input_xfer) begin
if (word_count == decim_factor - 1) begin
word_count <= '0;
end else begin
word_count <= word_count + 1;
end
end
end
// compute_output_pre indicates when word_count is at the last word of the current
// decimation group. Combined with data_in.tvalid/tready, this drives compute_output
// (1-cycle delayed) once the last word is accepted.
assign compute_output_pre = (word_count == decim_factor - R_MAX_LOG2'(1));
always_ff @(posedge clk) begin
if (rst || clr) begin
compute_output <= 1'b0;
end else if (data_in.tready) begin
compute_output <= compute_output_pre & data_in.tvalid;
end
end
//----------------------------------------------------------------------------
// Phase 2: Output selection logic
//----------------------------------------------------------------------------
// SPC parallel processing lanes, one per sample, each containing the logic
// to select the correct sample from the input stream for that output slot.
// Each lane calculates which input word contains the sample it needs, and at
// what position within that word the sample is located.
// When that word arrives, the sample is copied directly from the input
// into the output register.
//
// For output sample i (0..SPC-1):
// flat_idx = i * decimation_factor (flat sample index in the group)
// needed_word = flat_idx / SPC (which word in the group holds it)
// needed_samp = flat_idx % SPC (which sample within that word)
//
// When word_count == needed_word and input_xfer, grab input_word[needed_samp].
//
// Example (SPC=4, R=3):
// output[0]: flat=0, needed_word=0, needed_samp=0 -> grab from 1st word
// output[1]: flat=3, needed_word=0, needed_samp=3 -> grab from 1st word
// output[2]: flat=6, needed_word=1, needed_samp=2 -> grab from 2nd word
// output[3]: flat=9, needed_word=2, needed_samp=1 -> grab from 3rd word
//
// This logic allows each output slot to grab its needed sample directly from
// the input stream with no additional buffering.
for (genvar i = 0; i < SPC; i++) begin : gen_output_select
// Index computation (combinational)
logic [FLAT_IDX_W-1:0] flat_idx = '0;
logic [R_MAX_LOG2-1:0] needed_word;
logic [ NSPC_LOG2-1:0] needed_samp;
assign needed_word = flat_idx[NSPC_LOG2+:R_MAX_LOG2];
assign needed_samp = flat_idx[NSPC_LOG2-1:0];
// Grab the sample from the current input word when its position matches
always_ff @(posedge clk) begin
if (input_xfer && (word_count == needed_word)) begin
output_word[i] <= input_word[needed_samp];
end
// Register flat_idx to ease timing. Decimation factor expected to be stable
// for the duration of sample processing, and is expected to only change when
// module is idle, so this should not cause issues.
flat_idx <= FLAT_IDX_W'(i) * FLAT_IDX_W'(decim_factor);
end
end
//----------------------------------------------------------------------------
// Phase 3: tlast accumulation
//----------------------------------------------------------------------------
// OR-accumulate tlast across the decimation group.
// At the compute_output cycle (1 cycle after last input_xfer), tlast_accum
// holds the OR of all data_in.tlast values seen during the group. The FIFO
// captures this value directly alongside packed_output on compute_output.
//
// Pipeline: input_xfer (accumulate) -> compute_output (FIFO captures tlast_accum)
always_ff @(posedge clk) begin
if (rst || clr) begin
tlast_accum <= 1'b0;
end else if (input_xfer) begin
if (word_count == 0) begin
tlast_accum <= data_in.tlast;
end else begin
tlast_accum <= tlast_accum | data_in.tlast;
end
end
end
//----------------------------------------------------------------------------
// Phase 4: Pack output and output FIFO
//----------------------------------------------------------------------------
assign packed_output = output_word;
// Output FIFO: buffers decimated output for AXI-Stream backpressure handling.
// compute_output is used directly as FIFO valid, at this point output_word
// and tlast_accum both contain the correct values for the completed group.
wire [ SPC*SAMP_W:0] fifo_output;
axi_fifo #(
.WIDTH(SPC * SAMP_W + 1),
.SIZE (1)
) output_buffer (
.clk (clk),
.reset (rst),
.clear (clr),
.i_tdata ({tlast_accum, packed_output}),
.i_tvalid(compute_output),
.i_tready(data_in.tready),
.o_tdata (fifo_output),
.o_tvalid(data_out.tvalid),
.o_tready(data_out.tready),
.space (),
.occupied()
);
assign {data_out.tlast, data_out.tdata} = fifo_output;
endmodule
`default_nettype wire

View File

@ -0,0 +1,86 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_destaircase_pipeline
//
// Description:
//
// Reverses the stagger introduced by cic_staircase_pipeline so that all
// lanes are re-aligned to the same clock cycle.
//
// cic_staircase_pipeline delays lane k by k cycles.
// This module delays lane k by (SPC-1-k) cycles, giving every lane a
// uniform total latency of (SPC-1) clock-enabled cycles.
//
// out_data[0] = in_data[0] delayed SPC-1 cycles
// out_data[k] = in_data[k] delayed SPC-1-k cycles
// out_data[SPC-1] = in_data[SPC-1] (wire, 0 FFs)
//
// valid_in and valid_out are delayed according to lane 0. This is effectively
// a delay by SPC-1 cycles.
//
// Parameters:
// DATA_W : Bit width of each element.
// SPC : Number of parallel lanes (samples per clock).
//
module cic_destaircase_pipeline #(
parameter int DATA_W = 16,
parameter int SPC = 8
) (
input logic clk,
input logic rst,
input logic en,
input logic valid_in,
input logic [SPC-1:0][DATA_W-1:0] in_data,
output logic valid_out,
output logic [SPC-1:0][DATA_W-1:0] out_data
);
// --------------------------------------------------------------------
// Valid pipeline
// --------------------------------------------------------------------
// valid_pipeline[d] = valid_in delayed by d clock-enabled cycles.
// valid_pipeline[0] is a wire (no register).
logic valid_pipeline [SPC];
assign valid_pipeline[0] = valid_in;
for (genvar d = 1; d < SPC; d++) begin : gen_valid_pipeline
always_ff @(posedge clk) begin : valid_ff
if (rst) begin
valid_pipeline[d] <= 1'b0;
end else if (en) begin
valid_pipeline[d] <= valid_pipeline[d-1];
end
end : valid_ff
end : gen_valid_pipeline
assign valid_out = valid_pipeline[SPC-1];
// --------------------------------------------------------------------
// Data pipeline
// --------------------------------------------------------------------
// pipeline[k][d] = in_data[k] delayed by d clock-enabled cycles.
// pipeline[k][0] is a wire (no register).
logic [DATA_W-1:0] pipeline [SPC][SPC];
for (genvar k = 0; k < SPC; k++) begin : gen_pipeline_in
assign pipeline[k][0] = in_data[k];
end : gen_pipeline_in
// Lane k needs (SPC-1-k) registers; lane SPC-1 needs 0 (wire only).
for (genvar k = 0; k < SPC-1; k++) begin : gen_pipeline_lane
for (genvar d = 1; d <= SPC-1-k; d++) begin : gen_pipeline_depth
always_ff @(posedge clk) begin : pipeline_ff
if (en) begin
pipeline[k][d] <= pipeline[k][d-1];
end
end : pipeline_ff
end : gen_pipeline_depth
end : gen_pipeline_lane
// Output: lane k taps the d = SPC-1-k entry.
for (genvar k = 0; k < SPC; k++) begin : gen_out
assign out_data[k] = pipeline[k][SPC-1-k];
end : gen_out
endmodule : cic_destaircase_pipeline

View File

@ -0,0 +1,318 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_filter_decim
//
// Description:
//
// CIC decimation filter for SPC complex samples per clock cycle. The datapath
// implements the classic Hogenauer topology and adds rate-dependent gain
// normalization and an output FIFO:
//
// data_in
// (SPC x SAMP_W)
// │ tready
// v<─────────────────────────────────┐
// ┌───────────────────────────────┐ │
// │ I/Q sign extension │ │
// │ SAMP_W -> ACCUM_W │ │
// └──────────────┬────────────────┘ │
// │ │
// ┌──────────────┼────────────────┐ │
// │ ┌────────────────────────┐ │ │
// │ │ Integrator 0 │ │ │
// │ └───────────┬────────────┘ │ │
// │ │ │ ORDER stages │
// │ ... │ (full input rate)│
// │ │ │ │
// │ ┌────────────────────────┐ │ │
// │ │ Integrator ORDER-1 │ │ │
// │ └───────────┬────────────┘ │ │
// └──────────────┼────────────────┘ │
// │ │
// ┌────────────────────────┐ │
// decim_factor ─┤ Decimator (÷R) │ │
// └───────────┬────────────┘ │
// │ │
// ┌──────────────┼────────────────┐ │
// │ ┌────────────────────────┐ │ │
// │ │ Comb 0 │ │ │
// │ └───────────┬────────────┘ │ │
// │ │ │ ORDER stages │
// │ ... │ (decimated rate) │
// │ │ │ │
// │ ┌────────────────────────┐ │ │
// │ │ Comb ORDER-1 │ │ │
// │ └───────────┬────────────┘ │ │
// └──────────────┼────────────────┘ │
// │ │
// ┌───────────────────────────────┐ │
// │ Normalize and round │ │
// │ ACCUM_W -> SAMP_W │ │
// └──────────────┬────────────────┘ │
// │ │
// ┌───────────────────────────────┐ fifo_space │
// │ Output FIFO │──────────────────┘
// └──────────────┬────────────────┘
// │
// v
// data_out
// (SPC x SAMP_W)
//
// Each input word contains SPC packed complex samples. The module sign-extends
// each I and Q component to ACCUM_W, which includes the maximum CIC bit growth.
// The ORDER integrator stages process every input sample. For decimation factor
// R, the decimator consumes R input words and constructs one SPC-sample output
// word by selecting every R-th integrated sample. The ORDER comb stages then
// apply y[n] = x[n] - x[n-1] at the decimated rate.
//
// The barrel shifter normalizes the CIC gain by right-shifting each accumulator
// component by ceil(log2(R**ORDER)). It rounds each result to SAMP_W/2 bits
// before packing the SPC output samples.
//
// Downstream backpressure stops at the output FIFO. A registered credit gate
// admits input words only while the FIFO has room for every possible output
// already in flight, using the worst-case R=1 rate. Once accepted, a word can
// traverse the datapath without stalling on data_out.tready. If the reserved
// capacity runs low, data_in.tready deasserts until FIFO space becomes available.
//
// The decimation factor is driven directly on decim_factor. It must be valid
// before reset is released and remain stable while data is actively
// processed.
//
// NOTE: This module is expected to run within axi_rate_change, which ensures
// that each packet contains a multiple of R input words and that output tlast
// signals are correctly aligned.
//
// Parameters:
//
// SPC : Number of samples per clock cycle (must be power of 2, >= 1).
// SAMP_W : External sample width (I + Q packed, each SAMP_W/2 bits).
// MAX_DECIM : Maximum decimation factor supported. Sizes internal registers.
// ORDER : CIC filter order (number of integrator and comb stages).
//
// Signals:
// decim_factor : Runtime-configurable decimation ratio (1..MAX_DECIM).
//
`default_nettype none
module cic_filter_decim #(
parameter int SPC = 4,
parameter int SAMP_W = 32,
parameter int MAX_DECIM = 255,
parameter int ORDER = 4
)(
input wire clk,
input wire rst,
input wire clear,
input wire [$clog2(MAX_DECIM+1)-1:0] decim_factor,
// Data in (SPC * SAMP_W wide)
AxiStreamIf.slave data_in,
// Data out (SPC * SAMP_W wide)
AxiStreamIf.master data_out
);
//----------------------------------------------------------------------------
// Local parameters
//----------------------------------------------------------------------------
localparam int MAX_DELAY = 1; // Maximum comb delay in samples
// Accumulator width to accommodate max CIC growth without overflow, based on the formula:
localparam int ACCUM_W = 2 * (SAMP_W/2 + $clog2(MAX_DECIM*MAX_DELAY)*ORDER);
localparam int COMP_W = ACCUM_W/2; // Width of each I/Q component in the accumulator
import cic_utils_pkg::*;
// Reserve for the worst-case R=1 path, where every input word produces an
// output word, plus one cycle each for the registered FIFO-space decision
// and input_blocked state.
localparam int DATAPATH_LATENCY = integrator_latency(SPC, ORDER)
+ decimator_latency()
+ comb_filter_latency(ORDER)
+ barrel_shift_latency(ACCUM_W, SAMP_W)
+ 2; // Additional cycles for fifo_ready and input_blocked
// Set the output FIFO size to twice the datapath latency to allow continuous
// downstream traffic.
localparam int OUTPUT_FIFO_SIZE = $clog2(2 * DATAPATH_LATENCY);
localparam int OUTPUT_FIFO_DEPTH = 1 << OUTPUT_FIFO_SIZE;
//---------------------------------------------------------------------------
// Internal AXI-Stream buses
//---------------------------------------------------------------------------
// Integrator chain: 2 buses (input [0] and output [1] of cic_integrator).
AxiStreamIf #(SPC * ACCUM_W) integ_bus [2] (.clk(clk), .rst(rst));
// integ_bus[i] for i > 0 carries the output of integrator stage i-1.
// Comb chain: ORDER+1 buses connecting comb filter inputs/outputs.
// comb_bus[0] is driven by the decimator output.
// comb_bus[i] for i > 0 carries the output of comb stage i-1.
AxiStreamIf #(SPC * ACCUM_W) comb_bus [ORDER+1] (.clk(clk), .rst(rst));
// The output of the barrel shift stage after the bus width is reduced back
// to SAMP_W bits from ACCUM_W bits.
AxiStreamIf #(SPC * SAMP_W) barrel_shift_out (.clk(clk), .rst(rst));
//--------------------------------------------------------------------------
// Sign extension to ACCUM_W bits for the input samples
//
// Each sample in data_in is SAMP_W bits wide ({I[SAMP_W/2-1:0], Q[SAMP_W/2-1:0]}).
// Sign-extend each I and Q component independently from SAMP_W/2 to COMP_W
// bits, then pack into integ_bus[0].tdata as ACCUM_W-wide samples.
//--------------------------------------------------------------------------
logic fifo_ready;
logic input_blocked;
// Hold the input blocked until FIFO credit is restored.
always_ff @(posedge clk) begin
if (rst || clear) begin
input_blocked <= 1'b0;
end else begin
if (fifo_ready) begin
input_blocked <= 1'b0;
end else if (integ_bus[0].tvalid && integ_bus[0].tready) begin
input_blocked <= 1'b1;
end
end
end
always_comb begin
integ_bus[0].tvalid = data_in.tvalid & ~input_blocked;
integ_bus[0].tlast = data_in.tlast;
data_in.tready = integ_bus[0].tready & ~input_blocked;
end
for (genvar samp_idx = 0; samp_idx < SPC; samp_idx++) begin : gen_sign_ext
// Q component (lower half of each sample)
sign_extend #(
.bits_in (SAMP_W/2),
.bits_out(COMP_W)
) sign_ext_q (
.in (data_in.tdata[SAMP_W*samp_idx +: SAMP_W/2]),
.out(integ_bus[0].tdata[ACCUM_W*samp_idx +: COMP_W])
);
// I component (upper half of each sample)
sign_extend #(
.bits_in (SAMP_W/2),
.bits_out(COMP_W)
) sign_ext_i (
.in (data_in.tdata[SAMP_W*samp_idx + SAMP_W/2 +: SAMP_W/2]),
.out(integ_bus[0].tdata[ACCUM_W*samp_idx + COMP_W +: COMP_W])
);
end : gen_sign_ext
//---------------------------------------------------------------------------
// Integrator chain: N-th order, ORDER stages at full input rate
//---------------------------------------------------------------------------
cic_integrator #(
.SAMP_W(ACCUM_W),
.SPC (SPC),
.ORDER (ORDER)
) cic_integrator_x (
.clk (clk),
.rst (rst),
.clr (clear),
.data_in (integ_bus[0]),
.data_out(integ_bus[1])
);
//---------------------------------------------------------------------------
// Decimator: reduces sample rate by decim_factor
//---------------------------------------------------------------------------
cic_decimator #(
.SAMP_W (ACCUM_W),
.SPC (SPC),
.R_MAX (MAX_DECIM)
) cic_decimator_i (
.clk (clk),
.rst (rst),
.clr (clear),
.data_in (integ_bus[1]),
.data_out (comb_bus[0]),
.decim_factor (decim_factor)
);
//---------------------------------------------------------------------------
// Comb chain: ORDER stages at decimated rate
//---------------------------------------------------------------------------
for (genvar stage = 0; stage < ORDER; stage++) begin : gen_combs
cic_comb_filter #(
.ACCUM_W (ACCUM_W),
.SPC (SPC),
.FIFO_SIZE((stage == ORDER-1)? 1 : 0)
) cic_comb_filter_i (
.clk (clk),
.rst (rst),
.clr (clear),
.data_in (comb_bus[stage]),
.data_out(comb_bus[stage+1])
);
end
// Barrel-shift each I/Q component and extract SAMP_W/2 output bits.
cic_barrel_shift #(
.SPC (SPC),
.MAX_RATE (MAX_DECIM),
.ORDER (ORDER)
) cic_barrel_shift_i (
.clk (clk),
.rst (rst),
.clear (clear),
.rate_factor (decim_factor),
.data_in (comb_bus[ORDER]),
.data_out (barrel_shift_out)
);
// Downstream backpressure terminates at the output FIFO. The credit gate at
// data_in reserves enough room for every word already in the datapath.
assign barrel_shift_out.tready = 1'b1;
localparam FIFO_WIDTH = data_out.DATA_WIDTH + 1;
logic [15:0] fifo_space;
logic output_fifo_i_tready;
logic [FIFO_WIDTH-1:0] fifo_data_in;
logic [FIFO_WIDTH-1:0] fifo_data_out;
assign fifo_data_in = {barrel_shift_out.tlast, barrel_shift_out.tdata};
assign {data_out.tlast, data_out.tdata} = fifo_data_out;
always_ff @(posedge clk) begin
if (rst || clear) begin
fifo_ready <= 1'b0;
end else begin
fifo_ready <= (fifo_space > DATAPATH_LATENCY);
end
end
axi_fifo #(
.WIDTH(FIFO_WIDTH),
.SIZE (OUTPUT_FIFO_SIZE)
) output_fifo (
.clk (clk),
.reset (rst),
.clear (clear),
.i_tdata (fifo_data_in),
.i_tvalid (barrel_shift_out.tvalid),
.i_tready (output_fifo_i_tready),
.o_tdata (fifo_data_out),
.o_tvalid (data_out.tvalid),
.o_tready (data_out.tready),
.space (fifo_space),
.occupied ()
);
// synthesis translate_off
assert property (
@(posedge clk) disable iff (rst || clear)
barrel_shift_out.tvalid |-> output_fifo_i_tready
) else
$error("cic_filter_decim: output FIFO not ready for valid data");
// synthesis translate_on
endmodule : cic_filter_decim
`default_nettype wire

View File

@ -0,0 +1,357 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_filter_interp
//
// Description:
//
// CIC interpolation filter for multiple samples per clock cycle,
// implementing the classic Hogenauer (CIC) interpolation topology:
//
// data_in
// │
// v
// ┌───────────────────────────────┐
// │ ┌────────────────────────┐ │
// │ │ Comb 0 │ │
// │ └───────────┬────────────┘ │ ORDER stages
// │ │ │ (input rate)
// │ ... │
// │ │ │
// │ ┌────────────────────────┐ │
// │ │ Comb ORDER-1 │ │
// │ └───────────┬────────────┘ │
// └──────────────┼────────────────┘
// │
// ┌────────────────────────┐ tready
// interp_factor─┤ Interpolator (×R) │<──────────────┐
// └───────────┬────────────┘ │
// │ │
// ┌──────────────┼────────────────┐ │
// │ ┌────────────────────────┐ │ │
// │ │ Integrator 0 │ │ │
// │ └────────────────────────┘ │ │
// │ │ │ ORDER │
// │ ··· │ stages │
// │ │ │ │
// │ ┌────────────────────────┐ │ │
// │ │ Integrator ORDER-1 │ │ │
// │ └────────────────────────┘ │ │
// └──────────────┼────────────────┘ │
// │ │
// v │
// ┌───────────────────────────────┐ │
// │ Barrel shift │ │
// └──────────────┬────────────────┘ │
// │ │
// v │
// ┌───────────────────────────────┐ │
// │ Output FIFO │───────────┘
// └───────────────────────────────┘
// │
// v
// data_out
//
// ORDER comb stages (y[n] = x[n] - x[n-D]) operate at the full input rate.
// The single interpolator then produces R output words for each input word.
// Finally, ORDER integrator stages accumulate partial sums across SPC
// parallel sample lanes at the full output rate.
//
// Comb and interpolator stages use COMB_W bits. COMB_W provides ORDER bits
// of growth per I/Q component for the comb cascade. Integrator stages use
// ACCUM_W bits, automatically computed to provide
// ORDER*ceil(log2(MAX_INTERP*MAX_DELAY)) bits of growth per component.
// Internal stages do not clip or round. The output is normalized back to
// SAMP_W with signed round-to-nearest quantization.
//
// The interpolation factor is driven directly and must remain stable while
// the module is actively processing data.
//
// Parameters:
//
// SPC : Number of samples per clock cycle (must be power of 2, >= 1).
// SAMP_W : Bit width of each sample (I + Q packed, each SAMP_W/2 bits).
// MAX_INTERP : Maximum interpolation factor supported. Sizes internal registers.
// ORDER : CIC filter order (number of comb and integrator stages).
//
// Signals:
//
// clk : Clock input.
// rst : Synchronous reset input.
// clear : State clear input.
// interp_factor : Runtime-configurable interpolation ratio (1..MAX_INTERP).
// data_in : Input AXI-Stream bus carrying SPC packed SAMP_W-bit
// samples per word.
// data_out : Output AXI-Stream bus carrying SPC packed SAMP_W-bit
// samples per word.
//
`default_nettype none
module cic_filter_interp #(
parameter int SPC = 4,
parameter int SAMP_W = 32,
parameter int MAX_INTERP = 255,
parameter int ORDER = 4,
localparam int FACTOR_W = $clog2(MAX_INTERP+1)
)(
input wire clk,
input wire rst,
input wire clear,
input wire [FACTOR_W-1:0] interp_factor,
AxiStreamIf.slave data_in,
AxiStreamIf.master data_out
);
`include "../axi4s_sv/axi4s.vh"
//--------------------------------------------------------------------------
// Local parameters
//--------------------------------------------------------------------------
localparam int MAX_DELAY = 1; // Maximum comb delay in samples
// Accumulator width for the maximum integrator growth without overflow.
localparam int ACCUM_W = 2 * (SAMP_W/2 + $clog2(MAX_INTERP*MAX_DELAY)*ORDER);
// A cascade of ORDER comb stages has coefficients whose absolute sum is
// 2**ORDER, so each component needs ORDER bits of growth. The comb stages
// precede the interpolator and can therefore use less width than the full
// accumulation path.
localparam int COMB_W = 2 * (SAMP_W/2 + ORDER);
//---------------------------------------------------------------------------
// Internal AXI-Stream buses
//---------------------------------------------------------------------------
// Comb chain: ORDER+1 buses connecting comb filter inputs/outputs.
// comb_bus[0] is driven by the sign-extended input.
// comb_bus[i] for i > 0 carries the output of comb stage i-1.
AxiStreamIf #(SPC * COMB_W) comb_bus [ORDER+1] (.clk(clk), .rst(rst));
// The interpolator operates at COMB_W bits. Its output is sign-extended to
// ACCUM_W bits before entering the integrator chain.
AxiStreamIf #(SPC * COMB_W) interp_bus (.clk(clk), .rst(rst));
AxiStreamIf #(SPC * ACCUM_W) interp_bus_extended (.clk(clk), .rst(rst));
// Integrator path: two buses connecting the interpolator to the integrator
// chain and carrying the chain output.
// integ_bus[0] is driven by the interpolator.
// integ_bus[1] carries the output of the ORDER-stage integrator chain.
AxiStreamIf #(SPC * ACCUM_W) integ_bus [2] (.clk(clk), .rst(rst));
// Barrel Shift output bus
AxiStreamIf #(data_out.DATA_WIDTH) barrel_shift_out (.clk(clk), .rst(rst));
AxiStreamIf #(data_out.DATA_WIDTH) output_fifo_in (.clk(clk), .rst(rst));
//--------------------------------------------------------------------------
// Sign extension to COMB_W bits for the input samples
//
// Each sample in data_in is SAMP_W bits wide ({I, Q}). Sign-extend each I
// and Q component independently from SAMP_W/2 to COMB_W/2 bits, then pack
// into comb_bus[0].tdata as COMB_W-wide samples.
//--------------------------------------------------------------------------
assign comb_bus[0].tvalid = data_in.tvalid;
assign comb_bus[0].tlast = data_in.tlast;
assign data_in.tready = comb_bus[0].tready;
for (genvar samp_idx = 0; samp_idx < SPC; samp_idx++) begin : gen_sign_ext
// Q component (lower half of each sample)
sign_extend #(
.bits_in (SAMP_W/2),
.bits_out(COMB_W/2)
) sign_ext_q (
.in (data_in.tdata[SAMP_W*samp_idx +: SAMP_W/2]),
.out(comb_bus[0].tdata[COMB_W*samp_idx +: COMB_W/2])
);
// I component (upper half of each sample)
sign_extend #(
.bits_in (SAMP_W/2),
.bits_out(COMB_W/2)
) sign_ext_i (
.in (data_in.tdata[SAMP_W*samp_idx + SAMP_W/2 +: SAMP_W/2]),
.out(comb_bus[0].tdata[COMB_W*samp_idx + COMB_W/2 +: COMB_W/2])
);
end : gen_sign_ext
//---------------------------------------------------------------------------
// Comb chain: ORDER stages at input rate
//---------------------------------------------------------------------------
for (genvar stage = 0; stage < ORDER; stage++) begin : gen_combs
cic_comb_filter #(
.ACCUM_W (COMB_W),
.SPC (SPC),
.FIFO_SIZE((stage == ORDER-1)? 1 : 0)
) cic_comb_filter_i (
.clk (clk),
.rst (rst),
.clr (clear),
.data_in (comb_bus[stage]),
.data_out(comb_bus[stage+1])
);
end : gen_combs
//---------------------------------------------------------------------------
// Interpolator: increases sample rate by interp_factor
//---------------------------------------------------------------------------
cic_interpolator #(
.SAMP_W (COMB_W),
.SPC (SPC),
.R_MAX (MAX_INTERP)
) cic_interpolator_i (
.clk (clk),
.rst (rst || clear),
.data_in (comb_bus[ORDER]),
.data_out (interp_bus),
.interp_factor (interp_factor)
);
//--------------------------------------------------------------------------
// Sign extension from COMB_W to ACCUM_W bits.
//
// The interpolator does not increase the numeric width. The integrator
// stages require ACCUM_W bits to accommodate their accumulated growth.
//--------------------------------------------------------------------------
assign interp_bus_extended.tvalid = interp_bus.tvalid;
assign interp_bus_extended.tlast = interp_bus.tlast;
assign interp_bus.tready = interp_bus_extended.tready;
for (genvar samp_idx = 0; samp_idx < SPC; samp_idx++) begin : gen_interp_ext
sign_extend #(
.bits_in (COMB_W/2),
.bits_out(ACCUM_W/2)
) sign_ext_q (
.in (interp_bus.tdata[COMB_W*samp_idx +: COMB_W/2]),
.out(interp_bus_extended.tdata[ACCUM_W*samp_idx +: ACCUM_W/2])
);
sign_extend #(
.bits_in (COMB_W/2),
.bits_out(ACCUM_W/2)
) sign_ext_i (
.in (interp_bus.tdata[COMB_W*samp_idx + COMB_W/2 +: COMB_W/2]),
.out(interp_bus_extended.tdata[ACCUM_W*samp_idx + ACCUM_W/2 +: ACCUM_W/2])
);
end : gen_interp_ext
//---------------------------------------------------------------------------
// Integrator chain: ORDER stages at full output rate
//---------------------------------------------------------------------------
// Starting here until the output FIFO the data flow is controlled by the
// output FIFO backpressure.
logic fifo_ready;
logic input_blocked;
// Align input locking with AXI transfer.
always_ff @(posedge clk) begin
if (rst || clear) begin
input_blocked <= 1'b0;
end else begin
if (fifo_ready) begin
input_blocked <= 1'b0;
end else if (interp_bus_extended.tvalid && interp_bus_extended.tready) begin
input_blocked <= 1'b1;
end
end
end
always_comb begin
`AXI4S_ASSIGN(integ_bus[0], interp_bus_extended)
// overwrite handshake signal to block input when FIFO is full
interp_bus_extended.tready = integ_bus[0].tready & ~input_blocked;
integ_bus[0].tvalid = interp_bus_extended.tvalid & ~input_blocked;
end
cic_integrator #(
.SAMP_W(ACCUM_W),
.SPC (SPC),
.ORDER (ORDER)
) cic_integrator_i (
.clk (clk),
.rst (rst),
.clr (clear),
.data_in (integ_bus[0]),
.data_out(integ_bus[1])
);
// Barrel-shift each I/Q component and extract SAMP_W/2 output bits.
cic_barrel_shift #(
.SPC (SPC),
.MAX_RATE (MAX_INTERP),
.ORDER (ORDER-1) // An Nth order CIC interpolation filter has a Gain of
// R^(N-1) at DC, so we shift by ceil(log2(R^(N-1))) bits.
) cic_barrel_shift_i (
.clk (clk),
.rst (rst),
.clear (clear),
.rate_factor (interp_factor),
.data_in (integ_bus[1]),
.data_out (barrel_shift_out)
);
//---------------------------------------------------------------------------
// Output FIFO to absorb latency of bypassed modules
//---------------------------------------------------------------------------
import cic_utils_pkg::*;
localparam int LATENCY = integrator_latency(SPC, ORDER)
+ barrel_shift_latency(ACCUM_W, SAMP_W)
+ 1 // fifo_ready register
+ 1 // input_blocked register
+ 1; // output FIFO output register offset for space calculation
// assign the barrel shift output to the FIFO input, and always ready to
// accept data. The FIFO will backpressure the integrator chain when it is full.
always_comb begin
`AXI4S_ASSIGN(output_fifo_in, barrel_shift_out)
barrel_shift_out.tready = 1'b1;
end
// Allow the integrator chain to continue processing as long as the FIFO has
// enough space for the entire latency of the bypassed modules.
logic [15:0] fifo_space;
always_ff @(posedge clk) begin
if (rst || clear) begin
fifo_ready <= 1'b0;
end else begin
fifo_ready <= (fifo_space > LATENCY);
end
end
// FIFO depth is the next power of two at or above twice the calculated
// latency, ensuring sufficient buffering for constant data flow downstream
// after stalls.
localparam int FIFO_WIDTH = data_out.DATA_WIDTH + 1;
logic [FIFO_WIDTH-1:0] fifo_i_tdata;
logic [FIFO_WIDTH-1:0] fifo_o_tdata;
assign fifo_i_tdata = {output_fifo_in.tlast, output_fifo_in.tdata};
assign {data_out.tlast, data_out.tdata} = fifo_o_tdata;
axi_fifo #(
.WIDTH(FIFO_WIDTH),
.SIZE ($clog2(2*LATENCY))
) output_fifo (
.clk (clk),
.reset (rst | clear),
.clear (1'b0),
.i_tdata (fifo_i_tdata),
.i_tvalid (output_fifo_in.tvalid),
.i_tready (output_fifo_in.tready),
.o_tdata (fifo_o_tdata),
.o_tvalid (data_out.tvalid),
.o_tready (data_out.tready),
.space (fifo_space),
.occupied ()
);
endmodule : cic_filter_interp
`default_nettype wire

View File

@ -0,0 +1,211 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_integrator
//
// Description:
//
// N-th order CIC integrator for SPC parallel complex IQ samples per clock.
// Accepts an AXI-Stream of SPC packed SAMP_W-bit samples, splits them into
// independent I and Q components (each SAMP_W/2 bits wide), integrates each
// through two parallel cic_integrator_core pipelines, then reassembles the
// packed output word.
//
// Signal chain:
//
// data_in (SPC × SAMP_W)
// │
// ├──────────────────────────────┐
// ▼ ▼
// [I component] [Q component]
// cic_integrator_core cic_integrator_core
// (ORDER stages) (ORDER stages)
// │ │
// └──────────────┬───────────────┘
// ▼
// output FIFO
// │
// ▼
// data_out (SPC × SAMP_W)
//
// Each cic_integrator_core implements ORDER integration stages using a
// staircase input pipeline, ORDER stages of dsp48_prefix_accum adder
// chains with running accumulators, and a destaircase output pipeline.
//
// AXI-Stream handshake and pipeline flush:
//
// The pipeline CE (en) is tied to fifo_in_ready so the entire data path
// stalls in lock-step when the output FIFO is full. valid_in is passed
// separately to cic_integrator_core so that accumulators are frozen during
// flush cycles (en=1, valid_in=0), preventing inter-packet corruption.
//
// Three cases for (fifo_in_ready, data_in.tvalid):
// (0, x) — FIFO full: pipeline frozen; data_in.tready=0 stalls upstream.
// (1, 1) — Live data: pipeline and accumulators both advance.
// (1, 0) — Flush/bubble: pipeline advances to drain in-flight data;
// accumulators frozen; after CIC_LATENCY cycles the last word
// and its tlast have traversed the pipeline.
//
// Parameters:
//
// SAMP_W : Bit width of each sample (I+Q packed, each SAMP_W/2 bits). Set to
// a value that ensures that the integrator stages do not overflow.
// Must be a multiple of 2.
// SPC : Number of samples per clock cycle (>= 1).
// ORDER : CIC filter order (number of integrator stages).
//
`default_nettype none
module cic_integrator #(
parameter int SAMP_W = 32,
parameter int SPC = 4,
parameter int ORDER = 4
) (
input wire clk,
input wire rst,
input wire clr,
AxiStreamIf.slave data_in,
AxiStreamIf.master data_out
);
import cic_utils_pkg::*;
// compile time check
if (SAMP_W % 2 != 0) begin
$error("cic_integrator: SAMP_W must be a multiple of 2");
end
localparam int COMP_W = SAMP_W / 2;
// CIC_LATENCY: pipeline depth of cic_integrator_max from data_in to data_out:
// staircase : 0 cycles (valid is a wire)
// ORDER × (prefix_accum + spc_delay): ORDER × (2 + SPC-1) cycles
// final prefix_accum : 2 cycles
// de-staircase : SPC-1 cycles
// Total = ORDER*(SPC+1) + SPC + 1 cycles.
localparam int CIC_LATENCY = integrator_dsp_latency(SPC, ORDER);
// OUT_FIFO_SIZE: The pipeline is gated by fifo_in_ready so it stalls in
// lock-step with data_in.tready; the FIFO only needs to absorb the one
// word in transit between the pipeline output register and the FIFO input.
localparam int OUT_FIFO_SIZE = 1;
//--------------------------------------------------------------------------
// I/Q split
//
// lane k word: data_in.tdata[SAMP_W*k +: SAMP_W]
// Q component: bits [COMP_W-1:0]
// I component: bits [SAMP_W-1:COMP_W]
//--------------------------------------------------------------------------
logic [SPC-1:0][COMP_W-1:0] in_i;
logic [SPC-1:0][COMP_W-1:0] in_q;
for (genvar k = 0; k < SPC; k++) begin : gen_iq_split
assign in_q[k] = data_in.tdata[SAMP_W*k +: COMP_W];
assign in_i[k] = data_in.tdata[SAMP_W*k+COMP_W +: COMP_W];
end : gen_iq_split
//--------------------------------------------------------------------------
// Parallel I and Q integrator branches (cic_integrator_max)
//
// en = fifo_in_ready — pipeline CE; keeps draining on flush cycles
// so tlast is pushed through to the FIFO.
// valid_in = data_in.tvalid — gates accumulator updates internally;
// flush cycles (en=1, valid_in=0) leave accumulators frozen.
// valid_out = pipeline-fill indicator; drives FIFO i_tvalid.
//--------------------------------------------------------------------------
logic [SPC-1:0][COMP_W-1:0] out_i;
logic [SPC-1:0][COMP_W-1:0] out_q;
logic fifo_in_ready; // pipeline CE and FIFO write-side ready
logic valid_out_i; // CIC pipeline valid output (I branch)
cic_integrator_core #(
.DATA_W (COMP_W),
.SPC (SPC),
.ORDER (ORDER)
) branch_i (
.clk (clk),
.rst (rst || clr),
.en (fifo_in_ready),
.valid_in (data_in.tvalid),
.in_data (in_i),
.valid_out(valid_out_i),
.out_data (out_i)
);
cic_integrator_core #(
.DATA_W (COMP_W),
.SPC (SPC),
.ORDER (ORDER)
) branch_q (
.clk (clk),
.rst (rst || clr),
.en (fifo_in_ready),
.valid_in (data_in.tvalid),
.in_data (in_q),
.valid_out(),
.out_data (out_q)
);
//--------------------------------------------------------------------------
// tlast delay pipeline — CIC_LATENCY deep so tlast arrives at the FIFO in
// the same cycle as the last output word.
//
// CE = fifo_in_ready — identical to the data pipeline CE.
//--------------------------------------------------------------------------
logic [CIC_LATENCY-1:0] last_pipe;
always_ff @(posedge clk) begin
if (rst || clr) begin
last_pipe <= '0;
end else if (fifo_in_ready) begin
last_pipe <= {last_pipe[CIC_LATENCY-2:0], data_in.tlast & data_in.tvalid};
end
end
//--------------------------------------------------------------------------
// Output pack: repack I/Q components into per-lane tdata words
//--------------------------------------------------------------------------
logic [SAMP_W*SPC-1:0] out_tdata;
for (genvar k = 0; k < SPC; k++) begin : gen_output_pack
assign out_tdata[SAMP_W*k +: COMP_W] = out_q[k];
assign out_tdata[SAMP_W*k+COMP_W +: COMP_W] = out_i[k];
end : gen_output_pack
//--------------------------------------------------------------------------
// Output FIFO
//
// Decouples data_in.tready from data_out.tready.
//--------------------------------------------------------------------------
logic [SAMP_W*SPC:0] fifo_out_packed;
axi_fifo #(
.WIDTH(SAMP_W * SPC + 1),
.SIZE (OUT_FIFO_SIZE)
) output_fifo (
.clk (clk),
.reset (rst || clr),
.clear (rst || clr),
.i_tdata ({last_pipe[CIC_LATENCY-1], out_tdata}),
.i_tvalid (valid_out_i),
.i_tready (fifo_in_ready),
.o_tdata (fifo_out_packed),
.o_tvalid (data_out.tvalid),
.o_tready (data_out.tready),
.space (),
.occupied ()
);
assign data_out.tdata = fifo_out_packed[SAMP_W*SPC-1:0];
assign data_out.tlast = fifo_out_packed[SAMP_W*SPC];
//--------------------------------------------------------------------------
// Input ready: accept when the FIFO has space. The pipeline stalls in
// lock-step so there is no in-flight overshoot to worry about.
//--------------------------------------------------------------------------
assign data_in.tready = fifo_in_ready;
endmodule : cic_integrator
`default_nettype wire

View File

@ -0,0 +1,201 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_integrator_core
//
// Description:
//
// N-th order CIC integrator core for SPC parallel real-valued samples per
// clock. Used by cic_integrator as the per-component (I or Q) integration
// engine.
//
// Signal chain:
//
// in_data[SPC]
// │
// ▼
// cic_staircase_pipeline
// Lane k is delayed by k clock cycles to stagger the prefix-sum input.
// │
// ▼ dsp_chain[0]
// ┌──────────────────────────────────────────────────────────────────────┐
// │ For i = 0 .. ORDER-1: │
// │ │
// │ dsp48_prefix_accum (USE_PCIN=1) │
// │ Computes the i-th order running prefix sum across all SPC lanes │
// │ using a DSP48 PCOUT→PCIN cascade; simultaneously accumulates the │
// │ cross-lane total into accu_out[i]. accu_in = 0 for i=0, │
// │ accu_out[i-1] for i > 0. │
// │ │ │
// │ spc_delay (DELAY = SPC-1) │
// │ Delays the prefix-sum output by SPC-1 cycles so that accu_out[i] │
// │ settles before it is consumed as accu_in by the next stage. │
// │ │ │
// └──────────────────────────────────────────────────────────────────────┘
// │
// ▼ dsp_chain[2*ORDER]
// dsp48_prefix_accum (USE_PCIN=0, final stage)
// Computes the ORDER-th order prefix sum; accumulator output unused.
// │
// ▼ dsp_chain[2*ORDER+1]
// cic_destaircase_pipeline
// Re-aligns all SPC lanes to the same clock cycle.
// │
// ▼ out_data[SPC]
//
// All lanes are processed at the full input clock rate. Inputs are
// sign-extended to a fixed internal precision of INTERNAL_W=48 bits;
// outputs are truncated back to DATA_W bits.
//
// Parameters:
//
// DATA_W : Bit width of each input/output lane element.
// SPC : Number of parallel lanes (samples per clock, power of 2 >= 1).
// ORDER : CIC filter order (number of integration stages).
//
module cic_integrator_core #(
int DATA_W = 16,
int SPC = 8,
int ORDER = 4
) (
input logic clk,
input logic rst,
input logic en,
input logic valid_in,
input logic [SPC-1:0][DATA_W-1:0] in_data,
output logic valid_out,
output logic [SPC-1:0][DATA_W-1:0] out_data
);
// Internal pipeline width: always 48 bits (full DSP48 precision).
// Inputs are sign-extended to INTERNAL_W; outputs are truncated back to DATA_W.
localparam int INTERNAL_W = 48;
if (DATA_W > INTERNAL_W) begin
$error("cic_integrator_core: DATA_W (%0d) must be <= INTERNAL_W (%0d)", DATA_W, INTERNAL_W);
end
//--------------------------------------------------------------------------
// Input sign-extension: widen each lane from DATA_W to INTERNAL_W bits
//--------------------------------------------------------------------------
logic [SPC-1:0][INTERNAL_W-1:0] in_data_ext;
for (genvar k = 0; k < SPC; k++) begin : gen_sign_ext
assign in_data_ext[k] = INTERNAL_W'(signed'(in_data[k]));
end : gen_sign_ext
//--------------------------------------------------------------------------
// Staircase pipeline: lane k carries in_data[k] delayed by k cycles
//--------------------------------------------------------------------------
logic [SPC-1:0][INTERNAL_W-1:0] dsp_chain [2*ORDER+2];
logic [2*ORDER+1:0] valid_chain;
cic_staircase_pipeline #(
.DATA_W (INTERNAL_W),
.SPC (SPC)
) staircase_i (
.clk (clk),
.en (en),
.valid_in (valid_in),
.in_data (in_data_ext),
.valid_out(valid_chain[0]),
.out_data (dsp_chain[0])
);
//--------------------------------------------------------------------------
// Prefix-sum adder chain + accumulator.
// dsp_chain[2*i] → data_in port of prefix_accum stage i
// PCOUT→PCIN cascade wired inside dsp48_prefix_accum across all SPC lanes
// accu_out[i] → running cross-lane accumulator for order i
//--------------------------------------------------------------------------
logic [INTERNAL_W-1:0] accu_out [ORDER];
for (genvar i = 0; i < ORDER; i++) begin : gen_dsp_chain
dsp48_prefix_accum #(
.DATA_W (INTERNAL_W),
.SPC (SPC),
.USE_PCIN(1)
) prefix_accum_i (
.clk (clk),
.rst (rst),
.en (en),
.data_in (dsp_chain[2*i]),
.accu_in ((i == 0) ? '0 : accu_out[i-1]),
.valid_in (valid_chain[2*i]),
.data_out (dsp_chain[2*i+1]),
.accu_out (accu_out[i]),
.valid_out(valid_chain[2*i+1])
);
// Delay {data, valid} by SPC-1 cycles to allow accu_out[i] to settle
if (SPC > 1) begin : gen_delay
logic [SPC*INTERNAL_W:0] shift_reg [SPC-1];
always_ff @(posedge clk) begin
if (en) begin
shift_reg[0] <= {dsp_chain[2*i+1], valid_chain[2*i+1]};
for (int d = 1; d < SPC-1; d++) begin
shift_reg[d] <= shift_reg[d-1];
end
end
// reset valid chain only
if (rst) begin
for (int d = 0; d < SPC-1; d++) begin
shift_reg[d][0] <= '0;
end
end
end
assign {dsp_chain[2*i+2], valid_chain[2*i+2]} = shift_reg[SPC-2];
end else begin : gen_passthrough
assign dsp_chain[2*i+2] = dsp_chain[2*i+1];
assign valid_chain[2*i+2] = valid_chain[2*i+1];
end
end : gen_dsp_chain
// Final prefix-sum stage: ORDER-th order summation; accumulator output unused.
dsp48_prefix_accum #(
.DATA_W (INTERNAL_W),
.SPC (SPC),
.USE_PCIN(0)
) prefix_accum_i (
.clk (clk),
.rst (rst),
.en (en),
.data_in (dsp_chain[2*ORDER]),
.accu_in (accu_out[ORDER-1]),
.valid_in (valid_chain[2*ORDER]),
.data_out (dsp_chain[2*ORDER+1]),
.accu_out (),
.valid_out(valid_chain[2*ORDER+1])
);
//--------------------------------------------------------------------------
// De-staircase: re-align all lanes to the same clock cycle.
//--------------------------------------------------------------------------
logic [SPC-1:0][INTERNAL_W-1:0] out_data_int;
cic_destaircase_pipeline #(
.DATA_W (INTERNAL_W),
.SPC (SPC)
) destaircase_i (
.clk (clk),
.rst (rst),
.en (en),
.in_data (dsp_chain[2*ORDER+1]),
.valid_in (valid_chain[2*ORDER+1]),
.valid_out(valid_out),
.out_data (out_data_int)
);
//--------------------------------------------------------------------------
// Output truncation: keep the least-significant DATA_W bits of each lane
// (wrap-around / modulo arithmetic) and discard the upper
// (INTERNAL_W-DATA_W) bits.
//--------------------------------------------------------------------------
for (genvar k = 0; k < SPC; k++) begin : gen_trunc
assign out_data[k] = out_data_int[k][DATA_W-1:0];
end : gen_trunc
endmodule : cic_integrator_core

View File

@ -0,0 +1,321 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_interpolator
//
// Description:
//
// A 1-to-N CIC interpolator stage that produces N output words for every
// input word. This interpolation factor can be changed at runtime.
//
// The interpolation factor can be safely updated between packets while the
// module is idle, or after reset is asserted and before data arrives.
// Updating the interpolation factor takes a few clock cycles to complete
// internally and no data should be input during that time. Updating the
// interpolation factor while the module is processing data results in
// undefined output.
//
// The interpolation factor is driven directly on interp_factor. It must be
// valid before reset is released and remain stable while data is processed.
//
// Parameters:
//
// SAMP_W : Bit width of each input/output sample.
// SPC : Number of samples processed per clock cycle.
// R_MAX : Maximum interpolation factor supported. Used to size internal
// registers.
//
// Signals:
//
// clk : Clock
// rst : Reset
// data_in : AXI-Stream data input
// data_out : AXI-Stream data output
// interp_factor : Interpolation factor to use.
//
`default_nettype none
module cic_interpolator #(
parameter int SAMP_W = 32,
parameter int SPC = 4,
parameter int R_MAX = 255,
localparam int FACTOR_W = $clog2(R_MAX + 1)
) (
input wire clk,
input wire rst,
AxiStreamIf.slave data_in,
AxiStreamIf.master data_out,
input wire [FACTOR_W-1:0] interp_factor
);
`include "usrp_utils.svh"
localparam int WORD_IDX_W = `MAX(1, $clog2(R_MAX));
localparam int SAMP_IDX_W = `MAX(1, $clog2(SPC));
//---------------------------------------------------------------------------
// Assertions
//---------------------------------------------------------------------------
if (SPC < 1 || SPC != 2**$clog2(SPC)) begin : gen_spc_check
$error(
"cic_interpolator: SPC must be a power of 2 and at least 1. Got SPC=%0d",
SPC
);
end
if (R_MAX <= 1) begin : gen_rate_check
$error("cic_interpolator: R_MAX must be at least 2. Got R_MAX=%0d", R_MAX);
end
//---------------------------------------------------------------------------
// Signal Declarations
//---------------------------------------------------------------------------
// Create a version of the output bus we can index
logic gen_tvalid;
logic gen_tready;
logic [SPC-1:0][SAMP_W-1:0] gen_tdata;
logic gen_tlast;
// Input register
logic input_busy = 1'b0;
logic input_last;
logic [SPC-1:0][SAMP_W-1:0] input_samps;
//---------------------------------------------------------------------------
// Interpolation Factor
//---------------------------------------------------------------------------
logic [FACTOR_W-1:0] factor_m2 = 0; // Current factor minus 2
logic factor_is_one = 1; // High when factor == 1
always_ff @(posedge clk) begin
factor_m2 <= interp_factor - 2;
factor_is_one <= (interp_factor == 1);
end
//---------------------------------------------------------------------------
// Sample and Word Indices
//---------------------------------------------------------------------------
//
// Determine the output word and sample indices into which each input sample
// go after interpolation.
//
//---------------------------------------------------------------------------
localparam int ACCUM_W = WORD_IDX_W + SAMP_IDX_W;
// An array of indices that stores the mapping from input sample to output
// sample.
logic [ACCUM_W-1:0] accum [SPC];
// This array contains the output word index for each input sample
logic [WORD_IDX_W-1:0] word_idx [SPC];
// This array contains the output sample index for each input sample
logic [SAMP_IDX_W-1:0] samp_idx [SPC];
always_ff @(posedge clk) begin
for (int idx = 0; idx < SPC; idx++) begin
accum[idx] <= idx * interp_factor;
end
end
// Extract from accumulator the word and sample indices.
always_comb begin
for (int idx = 0; idx < SPC; idx++) begin
word_idx[idx] = accum[idx][SAMP_IDX_W +: WORD_IDX_W];
samp_idx[idx] = accum[idx][ 0 +: SAMP_IDX_W];
end
end
//---------------------------------------------------------------------------
// Word Counter
//---------------------------------------------------------------------------
//
// For each one input word we output factor words. This counter tracks the
// number of output words that have been output for each input word.
//
//---------------------------------------------------------------------------
logic [WORD_IDX_W-1:0] word_cnt;
logic word_cnt_last; // Assert on last word of output group
always_ff @(posedge clk) begin
if (rst) begin
word_cnt <= '0;
word_cnt_last <= 1'b1;
end else if (!input_busy) begin
word_cnt <= '0;
word_cnt_last <= factor_is_one;
end else if (gen_tvalid && gen_tready) begin
if (word_cnt_last) begin
word_cnt <= '0;
word_cnt_last <= factor_is_one;
end else begin
word_cnt <= word_cnt + 1;
word_cnt_last <= (word_cnt == factor_m2);
end
end
end
//---------------------------------------------------------------------------
// Input Word Logic
//---------------------------------------------------------------------------
//
// Captures the input word when accepted and holds it until all corresponding
// output words are generated.
//
// The input_busy flag tracks when we're holding a loaded input word. It
// starts low after reset so the first word is accepted immediately. It sets
// when a word is accepted and clears on the last output transfer for the
// current input word.
//
//---------------------------------------------------------------------------
// Assert data_in.tready when not busy, or combinationally on the cycle the
// last output word of the current interpolation group transfers.
assign data_in.tready = !input_busy ||
(gen_tvalid && gen_tready && word_cnt_last);
// Output is valid whenever we have a loaded input word to work from.
assign gen_tvalid = input_busy;
// Assert tlast on the last output word of the group when the input was last.
assign gen_tlast = input_last && word_cnt_last;
always_ff @(posedge clk) begin
if (rst) begin
input_busy <= 1'b0;
end else begin
if (data_in.tvalid && data_in.tready) begin
// Accepted a new input word; busy until end of interpolation group.
input_busy <= 1'b1;
input_last <= data_in.tlast;
input_samps <= data_in.tdata;
end else if (gen_tvalid && gen_tready && word_cnt_last) begin
// Last output word transferred with no new input; no longer busy.
input_busy <= 1'b0;
end
end
end
//---------------------------------------------------------------------------
// Output Word Generation
//---------------------------------------------------------------------------
//
// For each output word, we look at the current word_cnt and use that to
// determine which input samples, if any, should be output for each sample
// position. We pull the sample values from the stored input_samps register.
// We decide where those input samples go on the output by looking up the
// word_idx and samp_idx for that sample. All other sample positions are set
// to 0.
//
//---------------------------------------------------------------------------
// Intermediate results to pipeline the array indexing
// Stage 1: Determine which input samples match the current word count
logic [SPC-1:0] word_idx_match;
always_ff @(posedge clk) begin
if (gen_tready) begin
for (int src_samp = 0; src_samp < SPC; src_samp++) begin
word_idx_match[src_samp] <= (word_idx[src_samp] == word_cnt);
end
end
end
// Stage 2: Generate the sample index for each output sample position
logic [SAMP_IDX_W-1:0] out_mux_select [SPC];
logic [SPC-1:0] word_idx_en;
always_ff @(posedge clk) begin
automatic logic [SAMP_IDX_W-1:0] next_out_mux_select [SPC] = '{default: '0};
automatic logic [SPC-1:0] next_word_idx_en = '0;
for (int src_samp = 0; src_samp < SPC; src_samp++) begin
if (word_idx_match[src_samp]) begin
next_out_mux_select[samp_idx[src_samp]] |= SAMP_IDX_W'(src_samp);
next_word_idx_en[samp_idx[src_samp]] = '1;
end
end
if (gen_tready) begin
out_mux_select <= next_out_mux_select;
word_idx_en <= next_word_idx_en;
end
end
// create shift register for tdata, tvalid and tlast to pipeline them to the output
// FIFO
logic [1:0] tvalid_pipe;
logic [1:0] tlast_pipe;
logic [1:0][SPC-1:0][SAMP_W-1:0] tdata_pipe;
always_ff @(posedge clk) begin
if (gen_tready) begin
tlast_pipe <= {tlast_pipe[0], gen_tlast};
tdata_pipe <= {tdata_pipe[0], input_samps};
end
end
always_ff @(posedge clk) begin
if (rst) begin
tvalid_pipe <= 2'b00;
end else if (gen_tready) begin
tvalid_pipe <= {tvalid_pipe[0], gen_tvalid};
end
end
always_comb begin
// All samples are set to zero by default
gen_tdata = '0;
// Iterate over each sample and see if the word count and sample position
// match what we precomputed in samp_idx and word_idx.
for (int out_samp = 0; out_samp < SPC; out_samp++) begin
if (word_idx_en[out_samp]) begin
gen_tdata[out_samp] = tdata_pipe[1][out_mux_select[out_samp]];
end
end
end
//---------------------------------------------------------------------------
// Output Register
//---------------------------------------------------------------------------
wire [SPC*SAMP_W:0] fifo_out;
axi_fifo #(
.WIDTH (SPC*SAMP_W + 1 ),
.SIZE (1 )
) axi_fifo_i (
.clk (clk ),
.reset (rst ),
.clear (1'b0 ),
.i_tdata ({tlast_pipe[1], gen_tdata}),
.i_tvalid (tvalid_pipe[1] ),
.i_tready (gen_tready ),
.o_tdata (fifo_out ),
.o_tvalid (data_out.tvalid ),
.o_tready (data_out.tready ),
.space ( ),
.occupied ( )
);
assign {data_out.tlast, data_out.tdata} = fifo_out;
endmodule : cic_interpolator
`default_nettype wire

View File

@ -0,0 +1,65 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: cic_staircase_pipeline
//
// Description:
//
// Produces a staircase-delayed version of the SPC-wide input data vector.
//
// Lane k of out_data carries in_data[k] delayed by exactly k clock-enabled
// cycles:
// out_data[0] = in_data[0] (wire, 0 FFs)
// out_data[1] = in_data[1] delayed 1 cycle (1 FF)
// out_data[k] = in_data[k] delayed k cycles (k FFs)
//
// This stagger is required by the prefix-sum column stage so that c_in[k]
// arrives in the same cycle that p_reg[k-1] holds the previous lane's
// registered result — ensuring each column adds samples from the same batch.
//
// Parameters:
// DATA_W : Bit width of each element.
// SPC : Number of parallel lanes (samples per clock).
//
module cic_staircase_pipeline #(
parameter int DATA_W = 16,
parameter int SPC = 8
) (
input logic clk,
input logic en,
input logic [SPC-1:0][DATA_W-1:0] in_data,
input logic valid_in,
output logic [SPC-1:0][DATA_W-1:0] out_data,
output logic valid_out
);
// Internal shift-register array.
// pipeline[k][d] = in_data[k] delayed by d clock-enabled cycles.
// pipeline[k][0] is a wire (no register).
logic [DATA_W-1:0] pipeline [SPC][SPC];
for (genvar k = 0; k < SPC; k++) begin : gen_pipeline_in
assign pipeline[k][0] = in_data[k];
end : gen_pipeline_in
for (genvar k = 1; k < SPC; k++) begin : gen_pipeline_lane
for (genvar d = 1; d <= k; d++) begin : gen_pipeline_depth
always_ff @(posedge clk) begin : pipeline_ff
if (en) pipeline[k][d] <= pipeline[k][d-1];
end : pipeline_ff
end : gen_pipeline_depth
end : gen_pipeline_lane
// Output: lane k taps the d=k entry
for (genvar k = 0; k < SPC; k++) begin : gen_out
assign out_data[k] = pipeline[k][k];
end : gen_out
// lane 0 is a wire, so valid_out is just a copy of valid_in
assign valid_out = valid_in;
endmodule : cic_staircase_pipeline

View File

@ -134,6 +134,10 @@ module complex_multiply_iq #(
// width is the same as the product. No rounding or clipping is needed in this
// case.
if (CLIP_BITS == 0 && MULT_OUTPUT_WIDTH == WIDTH_PRODUCT) begin
// Questa Workaround for illegal output connection (vsim-3053)
logic [product.DATA_WIDTH:0] out_flop_tdata;
assign {product.tdata, product.tlast} = out_flop_tdata;
axi_fifo_flop2 #(
.WIDTH(product.DATA_WIDTH + 1)
) out_flop (
@ -143,7 +147,7 @@ module complex_multiply_iq #(
.i_tdata ({product_full.tdata, product_full.tlast}),
.i_tvalid(product_full.tvalid),
.i_tready(product_full.tready),
.o_tdata ({product.tdata, product.tlast}),
.o_tdata (out_flop_tdata),
.o_tvalid(product.tvalid),
.o_tready(product.tready),
.space (),

Some files were not shown because too many files have changed in this diff Show More